1

I'm using a combination of Knockout.js + the Knockout mapping plugin + jQuery in a rich web client that consumes JSON from a RESTful API.

I need some guidance on how to deal with null values in my view model. Here's my scenario and the problem I'm running into:

Most of the data members returned by the REST API are nullable. To account for this, I'm passing to the mapping plugin a sample of JSON with null values:

um.jsonMaps.campaign = {
    "priority": null,
    "recipientListId": null,
    "autoPrepare": null,
    "timeToSend": null
}

I do the initial binding like this:

this.model = ko.mapping.fromJS(um.jsonMaps.campaign);

Here's some data from an API call:

var data= {
    "priority": 95,
    "recipientListId": "a2aac72a-59f6-45da-a636-a48cc2b20137",
    "autoPrepare": false,
    "timeToSend": null
}

...which is bound like this:

ko.mapping.fromJS(data, this.viewModel.model);

The problem is that if users modify or touch any of the UI elements bound to this data, they implicitly turn the data member in the model into a quoted string literal. So, the integer 95 becomes "95" if the user adds some text and deletes it. And if a value that was null from the API is touched in the UI, it becomes "" (e.g. the empty string).

I need ints and nulls to remain as ints and nulls after editing.

Armchair Bronco
  • 2,367
  • 4
  • 31
  • 44
  • 1
    If you want to continue using the mapping plugin and don't want to provide extensive mapping options, then you could look at using a custom binding that wraps the value binding as described in this answer: http://stackoverflow.com/questions/7395946/knockout-js-json-has-numeric-but-knockout-changes-it-to-string-any-suggestions. – RP Niemeyer Jun 15 '12 at 00:52
  • This is another good link. Reviewing the info now. – Armchair Bronco Jun 15 '12 at 17:47
  • I reviewed this information, but felt that a custom binding introduced a level of complexity that I didn't really need. The answer that I ended up accepting did the trick while letting me continue using native "text" and "value" bindings. For both custom binding and KO.computed observables, I had to write a non-trivial amount of code anyway because our API requires special treatment for null values (in strings, numbers, dates, and even bools). Using computed observables actually resulted in less code for me. – Armchair Bronco Jun 15 '12 at 21:45

2 Answers2

3

In my project I ended up with creating view model over data from the server. I added new property with getter and setter so that it writes numeric value when required:

function DataViewModel(dataModel) {
    var self = this;
    self.priority = ko.observable(dataModel.priority);
    self.recipientListId = ko.observable(dataModel.recipientListId);
    self.autoPrepare = ko.observable(dataModel.autoPrepare);
    self.timeToSend = ko.observable(dataModel.timeToSend);



    self.priorityKo = ko.computed({
        read: function () {
            return self.priority().toFixed(2);
        },
        write: function (value) {
            value = parseFloat(value.replace(/[^\.\d]/g, ""));
            if (isNaN(value)) {
                self.priority(-1); // to fire 'changed' event
                self.priority(0); // final value in case of incorrect user input
            } else {
                self.priority(value); // real numeric value
            }
        },
        owner: this
    });

your mapping will look like:

this.model = ko.mapping.fromJS(new DataViewModel(um.jsonMaps.campaign));
Pavel Morshenyuk
  • 10,891
  • 4
  • 32
  • 38
  • OK, this looks like it might do the trick. It means I'll have to give up using the mapping plugin, but I like the ability to fine-tune different computed data members. And for null values that get transformed into empty strings (which is what is currently killing me when I call our update API), I'll get to turn these values back into null. – Armchair Bronco Jun 15 '12 at 00:12
  • I'll verify whether this works for me tomorrow and if so will accept this answer. Thanks! – Armchair Bronco Jun 15 '12 at 00:12
  • no problem, in my project i also implemented an method to create server object from view model (to send it to the server), but this can be archived with mapping plugin i think – Pavel Morshenyuk Jun 15 '12 at 00:18
  • Right. What I meant is that I have to give up using the mapping plugin as a sort of "black box" that just works its magic without any other intervention on my part -- that's the best part of the plugin. Ideally, the plugin would support this when I pass in the initial dataModel along with the datatype I want it to keep using: string, bool, int, float, or null. – Armchair Bronco Jun 15 '12 at 00:22
  • I've accepted this as the answer. I looked at the custom binding option as well, but for my use case, this implementation was cleaner. I ended up making a few minor modifications (which are hard to communicate in this UI [one of my small frustrations with the S.O. model for accepting an answer but not being able to show the code changes necessary to make it work for my scenario). In my version, I do this: self.priority_KO = ko.observable(dataModel.priority) and then: self.priority = ko.computed(...) This means I can bind to "priority" in my markup. – Armchair Bronco Jun 15 '12 at 21:39
  • I also had to write some utility methods that return JS object versions of the model that have all *_KO members removed. – Armchair Bronco Jun 15 '12 at 21:40
  • so you are extending server json objects with KO properties and then remove them before sending back to server? sounds great for me. Could you provide some examples? want to try it on my project :) – Pavel Morshenyuk Jun 17 '12 at 09:36
  • Yep,that's exactly what I'm doing. I'll post another answer to this question with code sample that show how I deal with integers and strings that can also be null. I'll need some time to finish up my live code (gotta keep "The Man" off my back!), then I'll massage my implementation for this thread. – Armchair Bronco Jun 18 '12 at 17:02
  • Well, I'm having some "issues" with my implementation when dealing with Boolean values that can also be null. Want to get to the bottom of it before I post any code. Sorry for the delay. – Armchair Bronco Jun 18 '12 at 23:59
1

You could use Ryan's Smart Dirty Flag extension as a basis of how to handle it.

Or you could manually accomplish the same sort of thing, but his article is still a good starting point for what it sounds like you are dealing with.

Tetsujin no Oni
  • 7,300
  • 2
  • 29
  • 46
  • Thanks for the link. I'll take a look. I've seen references to this article elsewhere. Guess it's time to print it out and finally read it! – Armchair Bronco Jun 15 '12 at 00:13