Question

jsfiddle example. Like the title says I am trying to use a computed observable along with rniemeyer knockout sortable example. I keep getting

the write method needs to be implemented

This error is viewable in the developer console.

I have a write method implement on my ko.computed but it still errors out. What I am I doing wrong?

html and javascript below

<div id="main">
    <h3>Tasks</h3>
    <div class="container" data-bind="sortable: tasks">
        <div class="item">
            <span data-bind="visible: !$root.isTaskSelected($data)">
                <a href="#" data-bind="text: TestName"></a>
            </span>
            <span data-bind="visibleAndSelect: $root.isTaskSelected($data)">
                <input data-bind="value: name, event: { blur: $root.clearTask }" />
            </span>  
        </div>
    </div>
</div>

var Task = function(first,last) {
    var self = this;
    self.firstName = ko.observable(first);
    self.lastName = ko.observable(last);
    self.TestName = ko.computed({
        read: function (){
            return self.firstName() + " " +  self.lastName();
        },
        write: function (item) {
            console.log(item);
        }
    });

    return self;
}

var ViewModel = function() {
    var self = this;
    self.testTasks = ko.observableArray([
        new Task("test","one"),
        new Task("test","two"),
        new Task("test","three")
         ]);

    self.tasks = ko.computed({
        read: function() { return self.testTasks();},
        write: function(item) {console.log(item);}
    }); 



    self.selectedTask = ko.observable();
    self.clearTask = function(data, event) {
        if (data === self.selectedTask()) {
            self.selectedTask(null);                
        }

        if (data.name() === "") {
           self.tasks.remove(data);   
        }
    };
    self.addTask = function() {
        var task = new Task("new");
        self.selectedTask(task);
        self.tasks.push(task);
    };

    self.isTaskSelected = function(task) {
       return task === self.selectedTask();  
    };
};

//control visibility, give element focus, and select the contents (in order)
ko.bindingHandlers.visibleAndSelect = {
    update: function(element, valueAccessor) {
        ko.bindingHandlers.visible.update(element, valueAccessor);
        if (valueAccessor()) {
            setTimeout(function() {
                $(element).find("input").focus().select();
            }, 0); //new tasks are not in DOM yet
        }
    }
};

ko.applyBindings(new ViewModel());
Was it helpful?

Solution

As the very author of this plugin says here, you can't use a computed observable; the sortable plugin depends on an actual observable array.

Which makes sense when you think about it: the plugin is actually manipulating the various indexes of the array as you re-sort the elements.

OTHER TIPS

Here's a "writableComputedArray" if you want the best of both worlds. If you add/remove from the array, and a subsequent re-compute of the observable performs the same add/remove, subscribers will not get notified the second time. However, it's your responsibility to make sure that there are no discrepancies between the computation of the array and what actually gets added/removed. You can accomplish this by making the necessary changes in the sortable binding's afterMove event.

ko.writeableComputedArray = function (evaluatorFunction) {
    // We use this to get notified when the evaluator function recalculates the array.
    var computed = ko.computed(evaluatorFunction);

    // This is what gets returned to the caller and they can subscribe to
    var observableArray = ko.observableArray(computed());

    // When the computed changes, make the same changes to the observable array.
    computed.subscribe(function (newArray) {
        // Add any new values
        newArray.forEach(function (value) {
            var i = observableArray.indexOf(value);

            if (i == -1) {
                // It's a new value, push it
                observableArray.unshift(value);
            }
        });

        // Remove any old ones.  Loop backwards since we're removing items from it.
        for (var valueIndex = observableArray().length - 1; valueIndex >= 0; valueIndex--) {
            var value = observableArray()[valueIndex];

            var i = newArray.indexOf(value);

            if (i == -1) {
                // It's an old value, remove it
                observableArray.remove(value);
            }
        }
    });

    return observableArray;
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top