2

I am trying to implement inline editing using knockout. I found this thread Knockout Inline Edit Binding

and tweaked it a bit to have a "Select" for the edit mode. I managed to get it to work except that both edit and view modes are visible, which is not what I intended. Could someone please correct my mistake. Thanks!

HTML:

<div>
    <button id="InputSubmitBtn" type="submit" class="btn btn-custom" data-bind="click: addParam"><span class="glyphicon glyphicon-plus-sign"></span>&nbsp;Add</button>
</div>
<table class="table table-hover table-condensed">
    <thead>
        <tr>
            <th>Name</th>
            <th>Value</th>
            <th>Data type</th>
        </tr>
    </thead>
    <tbody data-bind="foreach: ParamData">
        <tr>
            <td data-bind="text: ParamKey" style="width: 20%"></td>
            <td data-bind="clickToEdit: ParamValue" style="width: 20%"></td>
            <td data-bind="text: DataType" style="width: 20%"></td>
        </tr>
    </tbody>
</table>

JS:

var format = function (str, col) {
    col = typeof col === 'object' ? col : Array.prototype.slice.call(arguments, 1);

    return str.replace(/\{\{|\}\}|\{(\w+)\}/g, function (m, n) {
        if (m == "{{") {
            return "{";
        }
        if (m == "}}") {
            return "}";
        }
        return col[n];
    });
};

var selectOptionsArr = ko.observableArray(['Blue', 'Green', 'Yellow', 'Red']);

var ParamConstr = function (key, value, dataType) {
    return {
        ParamKey: ko.observable(key),
        ParamValue: ko.observable(value),
        DataType: ko.observable(dataType)

    };
};
var my = my || {};

function Generator() {}

Generator.prototype.rand = Math.floor(Math.random() * 26) + Date.now();

Generator.prototype.getId = function () {
    return this.rand++;
};
var idGen = new Generator();
//var ParamData = ko.observableArray([]);
my.viewModel = {
    ParamData: ko.observableArray([]),
    addParam: function () {
        this.ParamData.push(new ParamConstr("$$" + "key1", "Green", "Varchar"));
    }
};

ko.bindingHandlers.hidden = {
    update: function (element, valueAccessor) {
        ko.bindingHandlers.visible.update(element, function () {
            return !ko.utils.unwrapObservable(valueAccessor());
        });
    }
};

ko.bindingHandlers.clickToEdit = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {

        var observable = valueAccessor(),
            link = document.createElement("a"),
            input = document.createElement("select");

        var id = idGen.getId();

        input.setAttribute("id", id);
        input.setAttribute("class", 'selectpicker');


        element.appendChild(link);
        element.appendChild(input);

        var unwrappedArray = ko.toJS(selectOptionsArr);

        for (var i = 0; i < unwrappedArray.length; i++) {
            var option = document.createElement("option");
            option.value = unwrappedArray[i];
            option.text = unwrappedArray[i];
            input.appendChild(option);
        }
        var arrayForSelect = [];

        for (var j = 0; j < unwrappedArray.length; j++) {
            arrayForSelect.push({ value: unwrappedArray[j], id: unwrappedArray[j] });
        }

        $('.selectpicker').val(allBindingsAccessor().selectedItem());
        $('.selectpicker').selectpicker('refresh');

        observable.editing = ko.observable(false);

        ko.applyBindingsToNode(link, {
            text: observable,
            hidden: observable.editing,
            click: observable.editing.bind(null, true)
        });

        ko.applyBindingsToNode(input, {
            selectedOptions: allBindingsAccessor().selectedItem(), options: arrayForSelect, optionsText: 'value', optionsValue: 'id', selectPicker: {},
            value: observable,
            visible: observable.editing,
            event: {
                change: function (data, event) {
                    observable.editing(false);
                    return false;
                }
            }
        });
    }
};

ko.bindingHandlers.selectPicker = {
    after: ['options'],   /* KO 3.0 feature to ensure binding execution order */
    init: function (element, valueAccessor, allBindingsAccessor) {
        var $element = $(element);
        $element.addClass('selectpicker').selectpicker();

        var doRefresh = function () {
            $element.selectpicker('refresh');
        }, subscriptions = [];

        // KO 3 requires subscriptions instead of relying on this binding's update
        // function firing when any other binding on the element is updated.

        // Add them to a subscription array so we can remove them when KO
        // tears down the element.  Otherwise you will have a resource leak.
        var addSubscription = function (bindingKey) {
            var targetObs = allBindingsAccessor.get(bindingKey);

            if (targetObs && ko.isObservable(targetObs)) {
                subscriptions.push(targetObs.subscribe(doRefresh));
            }
        };

        addSubscription('options');
        addSubscription('value');           // Single
        addSubscription('selectedOptions'); // Multiple

        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            while (subscriptions.length) {
                subscriptions.pop().dispose();
            }
        });
    },
    update: function (element, valueAccessor, allBindingsAccessor) {
    }
};

ko.applyBindings(my.viewModel);

$(document).ready(function () {
    $(".selectpicker").selectpicker();
});

JSFiddle: http://jsfiddle.net/sourabhtewari/nkkt88v2/9/

Community
  • 1
  • 1
Navyseal
  • 891
  • 1
  • 13
  • 36

1 Answers1

3

The selectpicker is not the input node, so bindings on the input node don't control it. Instead, you need to subscribe to the editing observable. I didn't give it a way to escape from editing; you have to pick a new value to get out of editing mode.

    observable.subscribe(function (newValue) {
        observable.editing(false);
    });

    observable.editing.subscribe(function (newValue) {
        $(input).selectpicker(newValue ? 'show' : 'hide');
    });
    observable.editing.notifySubscribers();

Updated fiddle: http://jsfiddle.net/nkkt88v2/7/

Roy J
  • 42,522
  • 10
  • 78
  • 102
  • an updated fiddle which selects the value on edit http://jsfiddle.net/sourabhtewari/nkkt88v2/12/ Now only if someone can suggest how to go back to view mode when clicking outside the select. – Navyseal Aug 01 '15 at 17:56