Search code examples
asp.net-mvcknockout.jsknockout-mapping-plugin

Automatically update viewModel after save to db


What I want to achieve is once the data got saved into database, when it goes back to client, it will automatically update the observable array. But somehow I couldn't make it happen.

This is my Server side code:

    [HttpGet]
    public JsonResult GetTasks()
    {
        var tasks = context.ToDoTasks.ToList();

        return Json(tasks.Select(c => new TaskViewModel(c)).ToList(), JsonRequestBehavior.AllowGet);
    }

    [HttpPost]
    public JsonResult AddTask(string text, string date)
    {
        var nTask = new ToDoTask()
        {
            Text = text,
            Date = DateTime.ParseExact(date, "MM/dd/yyyy", System.Globalization.CultureInfo.InvariantCulture),
            IsDone = false,
            Order = 1,
        };

        context.ToDoTasks.Add(nTask);
        context.SaveChanges();

        return Json(new TaskViewModel(nTask), JsonRequestBehavior.AllowGet); 
    }

This is my cshtml file code:

        <form>
            <div class="controls controls-row" style="margin-top:40px;">
                <input class="span7" type="text" placeholder="Task to do" style="margin-right:4px;" id="oText">
                <div id="task-date" class="input-append date">
                    <input data-format="MM/dd/yyyy" type="text" placeholder="MM/dd/yyyy" name="taskDate" id="oDate" />
                    <span class="add-on">
                        <i data-time-icon="icon-time" data-date-icon="icon-calendar">
                        </i>
                    </span>
                </div>
                <button class="btn" type="submit" style="margin-top:-10px;" data-bind="click: save">+</button>
            </div>
            <div class="controls">
                <label class="checkbox">
                    <input type="checkbox"> Mark all as complete
                </label>
            </div>
            <div id="task-section" style="margin-top:20px;">
                <ul data-bind="foreach: Tasks">
                    <!-- ko if: IsDone -->
                    <li>
                        <span><input type="checkbox" style="margin:-5px 5px 0px 0px;" data-bind="checked: IsDone" /></span>
                        <del><span data-bind="text: Text"></span></del>
                        <del><span class="task-date" data-bind="text: Date"></span></del>
                    </li>
                    <!-- /ko -->
                    <!-- ko ifnot: IsDone -->
                    <li>
                        <span><input type="checkbox" style="margin:-5px 5px 0px 0px;" data-bind="checked: IsDone" /></span>
                        <span data-bind="text: Text"></span>
                        <span class="task-date" data-bind="text: Date"></span>
                    </li>
                    <!-- /ko -->
                </ul>
            </div>
            <div class="clearfix" style="margin-top:30px;">
                <span class="pull-left" style="font-weight:bold;"><span data-bind="text: oItemLeft"></span> item left</span>
                <span class="pull-right badge" style="cursor:pointer;" data-bind="click: remove">Clear # completed item</span>
            </div>
        </form>

And finally my JS:

var ViewModel = function (data) {
    var self = this;
    self.Tasks = ko.mapping.fromJS(data, {}, self.Tasks);
    self.oItemLeft = ko.computed(function () {
        var i = 0;
        data.forEach(function (entry) {
            if (!entry.IsDone) i++;
        });

        return i;
    });

    self.save = function () {
        $.ajax({
            url: "Home/AddTask",
            type: "POST",
            data: { text: $('#oText').val(), date: $('#oDate').val() },
            success: function (response) {
                ko.mapping.fromJS(response, ViewModel);
            }
        });
    };
    self.remove = function () {
        alert('delete');
    }
}

$(function () {
    $.getJSON("/Home/GetTasks/", null, function (data) {
        ko.applyBindings(new ViewModel(data));
    });

    // for datepicker
    $('#task-date').datetimepicker({
        language: 'pt-BR',
        pickTime: false
    });
});

Solution

  • self.save = function () {
        $.ajax({
            url: "Home/AddTask",
            type: "POST",
            data: { text: $('#oText').val(), date: $('#oDate').val() },
            success: function (response) {
                var task = ko.mapping.fromJS(response);
                self.Tasks.push(task);
            }
        });
    };
    

    Also for oItemLeft you should be referring to self.Tasks instead of data:

    self.oItemLeft = ko.computed(function () {
        var i = 0;
        self.Tasks().forEach(function (entry) {
            if (!entry.IsDone) i++;
        });
    
        return i;
    });