Knockout tutorial "Loading and saving data (from server)"

Ajax calls were modified to use jsFiddle's "echo" system.

by jasonkylefrank

HTML

<script src="http://knockoutjs.com/downloads/knockout-2.3.0.debug.js"></script>
<script src="http://documentcloud.github.io/underscore/underscore-min.js"></script>
<link rel="stylesheet" href="http://learn.knockoutjs.com/Content/App/coderunner.css">
<link rel="stylesheet" href="http://learn.knockoutjs.com//Content/TutorialSpecific/loadingsaving.css">
<h3>Tasks</h3>

<form data-bind="submit: addTask">
    Add task: <input data-bind="value: newTaskText" placeholder="What needs to be done?" />
    <button type="submit">Add</button>
</form>

<ul data-bind="foreach: tasks, visible: tasks().length > 0">
    <li>

        <input type="checkbox" data-bind="checked: isDone" />
        <input data-bind="value: title, disable: isDone" />
        
        <!-- JF: trying to fix binding errors: 
        <input type="checkbox" data-bind="checked: $data.isDone" />
        <input data-bind="value: $data.title, disable: $data.isDone" /> -->
        <a href="#" data-bind="click: $parent.removeTask">Delete</a> 
    </li> 
</ul>

You have <b data-bind="text: incompleteTasks().length">&nbsp;</b> incomplete task(s)
<span data-bind="visible: incompleteTasks().length == 0"> - it's beer time!</span>

<button data-bind="click: reloadFromServer">Add tasks from server again</button>
<!--
<form action="/tasks/saveform" method="post">
    <input type="hidden" name="tasks" data-bind="value: ko.toJSON(tasks)" />
    <button type="submit">Save</button>
</form> -->

<button data-bind="click: save">Save</button>

JavaScript

function Task(data) {
    this.title = ko.observable(data.title);
    this.isDone = ko.observable(data.isDone);
}

function TaskListViewModel() {
    
    // Data that normally would come from the server 
    //  (we need it here for the jsFiddle 'echo' system to simulate returned server data)
    var fakeServerData = [
        { "title": "Wire the money to Panama", "isDone": true },
        { "title": "Get hair dye, beard trimmer, dark glasses and \"passport\"",
          "isDone": false},
        { "title": "Book taxi to airport",   "isDone": false },
        { "title": "Arrange for someone to look after the cat", "isDone": false }
    ];
    // Data
    var self = this;
    self.tasks = ko.observableArray([]);
    self.newTaskText = ko.observable();
    self.incompleteTasks = ko.computed(function() {
        return ko.utils.arrayFilter(self.tasks(), function(task) { return !task.isDone() && !task._destroy; });
    });
    
    var loadCount = 0;

    // Operations
    self.addTask = function() {
        self.tasks.push(new Task({ title: this.newTaskText() }));
        //self.tasks.push(new Task({ title: "task from VM", isDone: true }));
        self.newTaskText("");
    };
    self.removeTask = function(task) { 
        //self.tasks.remove(task) 

        // This way adds a _destroy property to the task but does not actually remove it from the array. 
        // The server will use that info to remove its entry from the database.
        self.tasks.destroy(task); 
    };
    
    var loadFromServer = function() {

        $.ajax("/echo/json/", {
            data: { json: ko.toJSON(fakeServerData) },
            type: "POST", dataType: 'json',
            success: handleLoadFromServerResponse
        });
    };
    // ajax success callback
    function handleLoadFromServerResponse(allData) {
        var mappedTasks = $.map(allData, function(item) { return new Task(item); });
        debugger;
        if(loadCount === 0)
            self.tasks(mappedTasks);
    ...