0
votes

I have a lot of (KnockOut) view models that get data from a rest service and then populate "item" view models that are pretty much simple and just contain the fields coming from the REST interface.

I was just wondering if there was a way to not having to define the item viewmodels but somehow just create them dynamic as objects (where each property is an observable).

So in the example below I would want to not have the "ItemViewModel" but just say within the AddItems function that it should create an object based on the data and make each entry an ko.observable. the passed "itemName" then contains "ItemViewModel1" (or in other call "ItemViewModel2" ...etc).

So e.g. if the Json Rest input has a field "LAST_NAME" it would add self.LAST_NAME = ko.observable()" filled with that value etc. (so I can still reference it in the views).

var ItemViewModel1 = function (data) {
    var self = this;
    self.PAR1 = ko.observable(data.PAR1) 
    self.PAR2 = ko.observable(data.PAR2) 
    self.PAR3 = ko.observable(data.PAR3) 
    self.PAR4 = ko.observable(data.PAR4) 
    // … etc
}
var MasterViewModel1 = function (data) {
    var self = this;
    ReportBaseViewModel.call(self)
}

var ReportBaseViewModel = function () {
    var self = this;

    /* commonly used vars */
    self.report = ko.observable();
    self.searchedCallBackFunction = ko.observable();
    self.items = ko.observableArray();
    self.selecteditem = ko.observable();
    self.selectedPerson = ko.observable();

    /* method: print */
    self.PrintEventHandler = function (data) { window.print(); };

    /* method: add items to array */
    self.AddItems = function (data) {
        var newitems = ko.utils.arrayMap(data, function (item) {
            c = new window[self.itemname](item);
            return c;
        });
        self.items(newitems);
    };

    /* eventhandler: select one item */
    self.SelectEventHandler = function (item) {
        selecteditem(item);
    };

    self.GetReport = function (selectedPerson, viewContainer, url, itemName) {
        self.selectedPerson(selectedPerson);
        self.itemname = itemName;
        var jqxhr = $.ajax({
            url: url,
            type: "GET"
        }).done(function (data, textStatus, jqXHR) {
            if (data != null) {
                self.AddItems(data);
                $('#' + viewContainer).show();
                document.getElementById(viewContainer).scrollIntoView();
            }
        }).fail(function (jqXHR, textStatus, errorThrown) {
            console.log('fail' + JSON.stringify(jqXHR));
            toastr.options = {
                "closeButton": true,
                "debug": false,
                "newestOnTop": false,
                "progressBar": false,
                "positionClass": "toast-top-right",
                "preventDuplicates": false,
                "onclick": null,
                "showDuration": "0",
                "hideDuration": "1000",
                "timeOut": "0",
                "extendedTimeOut": "0",
                "showEasing": "swing",
                "hideEasing": "linear",
                "showMethod": "fadeIn",
                "hideMethod": "fadeOut"
            };
            toastr["error"]("ERROR");
        }).always(function (jqXHR, textStatus, errorString) {
            if (typeof self.searchedCallBackFunction() === 'function') {
                self.searchedCallBackFunction();
            }
        });
    }
}
2

2 Answers

1
votes

There is. If your objects are simple and not nested, you can write the code to map them yourself:

var someJSON = '{ "firstName": "John", "lastName": "Doe" }';

var makeSimpleVM = function(obj) {
  // Return a new object with all property
  // values wrapped in an observable
  return Object
    .keys(obj)
    .reduce(function(vm, key) {
      vm[key] = ko.observable(obj[key]);
      return vm;
    }, {});
};

var myVM = makeSimpleVM(JSON.parse(someJSON));

console.log(ko.isObservable(myVM.firstName)); // true
console.log(myVM.firstName()); // John

myVM.firstName("Jane");
console.log(myVM.firstName()); // Jane
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

I think it's important to read through this naive implementation: it makes you understand why it's probably a better idea to use a ready-made plugin.

As soon as your server side code contains arrays, nested viewmodels or any properties that you don't want mapped, you'll run in to problems. The ko.mapping plugin has already solved these problems for you. It maps arrays to ko.observableArrays and lets you specify mapping strategies.

var someJSON = '{ "firstName": "John", "lastName": "Doe" }';

// Let's use the library this time
var myVM = ko.mapping.fromJS(JSON.parse(someJSON));

console.log(ko.isObservable(myVM.firstName)); // true
console.log(myVM.firstName()); // John

myVM.firstName("Jane");
console.log(myVM.firstName()); // Jane
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
1
votes

You could try using the mapping plugin or the Json functions, depending on what exactly you are looking for. I think what you are looking for is the mapping plugin:

http://knockoutjs.com/documentation/plugins-mapping.html

http://knockoutjs.com/documentation/json-data.html