Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Knockout.js - Update page every 5 seconds with new value

I am just looking into knockout.js with MVC-Web-API and I am trying to create a Hello World page that will update the time on the page every 5 seconds. It is making the call every 5 seconds, I can see this in my controller (breakpoint), but still nothing displays on screen.

UPDATE: I have still been working on this, and I have now established that I am getting the data back from the server, the call is being made to the controller every 5 seconds, and it is returning the JSON I need (alerts are showing this) however there is still nothing displaying on the span element on the page.

I realistically need to use the mapping function as I am developing a larger website, that has a model with over 50 properties and don't particularly want to go through and map them individually to in the viewmodel.

I have included my code below.

<span data-bind="text: TimeString"></span>

<script type="text/javascript">
    var viewModel;
var getUpdates = setInterval(function () {
    $.getJSON(
        "/Values/Get", {},
        function (model) {
            alert(model.TimeString);
            ko.mapping.fromJS(model, viewModel);
        });
}, 5000);

$(document).ready(
    function () {
        $.getJSON(
            "/Values/Get", {},
            function (model) {
                var viewModel = ko.mapping.fromJS(model);
                alert(model.TimeString);
                ko.applyBindings(viewModel);
            });
    });

function bindViewModel(model) {
    ko.applyBindings(model);
}

public class HelloWorldModel
{
    public DateTime TimeDT { get; set; }
    public String TimeString { get; set; }
}

    public class ValuesController : Controller
{
    public HelloWorldModel Model = new HelloWorldModel();

    [System.Web.Mvc.AcceptVerbs(HttpVerbs.Get)]
    public JsonResult Get()
    {
        Model.TimeDT = DateTime.Now;
        Model.TimeString = Model.TimeDT.ToString("HH:mm:ss");

        return Json(Model, JsonRequestBehavior.AllowGet);
    }

    // POST api/values
    public void Post([FromBody]string value)
    {
    }

    // PUT api/values/5
    public void Put(int id, [FromBody]string value)
    {
    }

    // DELETE api/values/5
    public void Delete(int id)
    {
    }
}
}
like image 538
Ben Sharpe Avatar asked Jul 04 '13 15:07

Ben Sharpe


3 Answers

If you follow the documentation, it shouldn't be too hard. In your first call to the server, do:

var viewModel = ko.mapping.fromJS(model);
ko.applyBindings(viewModel);

You are applying bindings with a JS object (getJSON returns a JS object, not a JSON string, if I'm correctly reading the documentation).

After that, in your repeated function, do:

ko.mapping.fromJS(model, viewModel);

From the documentation:

  • All properties of an object are converted into an observable. If an update would change the value, it will update the observable.
  • Arrays are converted into observable arrays. If an update would change the number of items, it will perform the appropriate add/remove actions. It will also try to keep the order the same as the original JavaScript array.
like image 189
Peter Avatar answered Nov 02 '22 05:11

Peter


You don't need replace the complete view-model and can instead update the properties returned from your Ajax request, like this:

$(function() {
    var vm = {
        TimeDT: ko.observable(),
        TimeString: ko.observable()
    };

    function updateValues() {    
        $.getJSON("/Values/Get").done(function(data) {
            vm.TimeDT(data.TimeDT);
            vm.TimeString(data.TimeString);
        });
    }

    ko.applyBindings(vm);

    updateValues();
    setInterval(updateValues, 5000);
});

You can see here a small example I made in JsFiddle.

like image 39
Meryovi Avatar answered Nov 02 '22 05:11

Meryovi


Disclaimer: I work with Ben.

There are a few issues with the code, the first is you are missing some javascript references, secondly the viewModel object is always null each time the timer loops through.

You need to download the knockout mapping Javascript file from here, name it knockout.mapping-latest.js and save it in the Scripts directory. Then make sure the references to jquery and knockout.js are added.

An updated Razor view is below:

<div id="body">

    <span data-bind='text: TimeString'></span>

    <script src="~/Scripts/jquery-1.8.2.js" type="text/javascript"></script>
    <script src="~/Scripts/knockout-2.2.0.debug.js" type="text/javascript"></script>
    <script src="~/Scripts/knockout.mapping-latest.js" type="text/javascript"></script>
    <script type="text/javascript">
        var viewModel;
        var getUpdates = setInterval(function () {
            $.getJSON(
                "/Values/Get", {},
                function (model) {
                    //alert(model.TimeString);
                    ko.mapping.fromJS(model, viewModel);
                });
        }, 5000);

        var viewModelSet = false;

        $(document).ready(
            function () {
                $.getJSON(
                    "/Values/Get", {},
                    function (model) {
                        viewModel = ko.mapping.fromJS(model);
                        ko.applyBindings(viewModel);
                    });
            });

        function bindViewModel(model) {
            ko.applyBindings(model);
        }
    </script>    

</div>

Just to add, public fields in a class are generally a no-go, in your ViewModel your HelloWorldModel instance doesn't get accessed from anywhere else in the code, just from within that class so it can be private. If it was accessed from elsewhere, the best practise would be to keep it private, and expose it through a property. More info here.

like image 31
JMK Avatar answered Nov 02 '22 03:11

JMK