Mike H.
Mike H.

Reputation: 1751

How to edit observableArray data when fetched with $.getJSON?

I'm pretty new to knockout.js but I love it so far! I'm writing in MVC4 and have run into a little snag. I've got kojs down with static data but I'm now using data passed from a controller via JSON and am not sure exactly how to do this.

Originally I had a "class" for my activities:

function Activity(context) {
    var self = this;
    self.type = context.type;
    self.name = context.name;
    self.time = ko.observable(context.time);
    self.product = context.product;
    self.item = context.item;
    self.itemAmount = context.itemAmount;

    self.formattedPrice = ko.computed(function () {
        var price = context.netPrice;
        return price ? "$" + price.toFixed(2) : "None";
    });
}

Populated with static data in my viewmodel:

self.activities = ko.observableArray([
        new Activity({ type: 1, name: "John Smith", time: "1 hour", itemAmount: "5", netPrice: 232.16 }),
        new Activity({ type: 1, name: "Jane Doe", time: "2 hours", itemAmount: "7", netPrice: 4812.30 }),
        new Activity({ type: 1, name: "Clark Kent", time: "4 hours", itemAmount: "5", netPrice: 19.09 }),
    ]);

Which is great and I can use the ko.computed methods to change my data around. Now that I am pulling my data, I've condensed my code to this:

function ActivityViewModel() {
    var self = this;
    self.activities = ko.observableArray();
    $.getJSON("Home/ActivityData", self.activities);
}

Which works fine and In my data-bind fields I've simply converted my text calls from their variable names to the database record names preceded by $data. -- very cool and easy.

Problem is I have a time field that I need to "humanize" via moment.js, so the question is...how can I access self.activities data post-JSON and edit a specific field?

Sorry if this is an easy one but I have had no luck finding help in the matter (I'm probably not looking in the right spots). Thanks in advance!

UPDATE


The data from the server getting JSON'd is from this LINQ query:

var Data = from m in dataContext.Activities
                   select new 
                   { 
                       Type = m.Type,
                       ClientName = m.ClientName,
                       UserID = m.UserID,
                       ProductsNo = m.ProductsNo,
                       ProductName = m.ProductName,
                       NetPrice = m.NetPrice,
                       Time = System.Data.Linq.SqlClient.SqlMethods.DateDiffSecond(m.RecordCreated, DateTime.Now)
                   };

What I need to do client side is take the Time variable and run a function against it in javascript. I assume it's done with ko.computed() functions, however I can't seem to figure out how to target the Time variable once it's pulled into self.activities.

Upvotes: 0

Views: 1240

Answers (1)

PW Kad
PW Kad

Reputation: 14995

Remember Knockout is based on the MVVM pattern (although it trickles into MV* in my opinion)

You need a class model. Generally any item that can change inside the model should be an observable. If type, name, product, etc... won't be changing then don't worry about making them observable, but if they are, consider updating them.

function activityModel(context) {
    var self = this;
    self.type = ko.observable(context.type);
    self.name = ko.observable(context.name);
    self.time = ko.observable(context.time);
    self.product = ko.observable(context.product);
    self.item = ko.observable(context.item);
    self.itemAmount = ko.observable(context.itemAmount);

    self.formattedPrice = ko.computed(function () {
        var price = context.netPrice;
        return price ? "$" + price.toFixed(2) : "None";
    });
}

Then in your view model, if you are not using a mapping library, you need to iterate through the results and create an object for each one, on the successful return of the AJAX call (remember $.getJSON is just shorthand AJAX) -

function activityViewModel() {
    var self = this;
    self.activities = ko.observableArray();
    $.getJSON("Home/ActivityData", function(data) {
      $.each( data, function( key, val ) {
        self.activities.push(new activityModel(data));
      });
    });
}

Last, you need a custom binding handler to show your dateTime in human readable. You can register this before your view model -

ko.bindingHandlers.DateTime = {
    update: function (element, valueAccessor) {
        var value = valueAccessor();
        var date = moment(value());
        var strDate = date.format('MMMM Do YYYY, h:mm:ss a');
        $(element).text(strDate);
    }
};

And then use it in your view -

<div data-bind="foreach: activities"> 
    <span data-bind="DateTime: time"></span>
</div>

Upvotes: 5

Related Questions