I am wondering how I can make certain fields observables in knockout.js that I get from an ajax call without having to define the whole object in my viewmodel. Is this possible? Here is what I have so far:
var viewModel = {
lines: new ko.observableArray([])
};
function refreshList(ionum) {
var data = {};
data['IONum'] = ionum;
$.ajax({
url: 'handlers/getlines.ashx',
data: data,
cache: false,
dataType: 'json',
success: function(msg) {
viewModel.lines(msg);
//here is where I am attempting to make the email address field observable
/*for (var i = 0; i < msg.length; i++) {
viewModel.lines()[i].emailAddress = new ko.observable(msg[i].emailAddress);
}*/
//alert(viewModel.lines[0].emailAddress);
//ko.applyBindings(viewModel);
}
});
}
With the mapping
plugin for Knockout you can define your view model from a plain Javascript object, as returned from an Ajax call:
var viewModel = ko.mapping.fromJS(data);
// Every time data is received from the server:
ko.mapping.updateFromJS(viewModel, data);
I had an almost similar situation, where my objects are instances of Javascript classes. The classes are defined without Knockout in mind, and I wanted to modify them to use observables instead.
I created a small helper to convert regular objects to observable objects. You can either specify the observable fields, or set them in the object (prototype). (I thought of doing this automatically, but I could not determine which field was safe to convert and which one was not.)
(function() {
ko.observableObject = function(object, ko_fields) {
ko_fields = ko_fields || object._ko_fields;
if (!ko_fields) {
return object;
}
for (var f_idx = 0; f_idx < ko_fields.length; f_idx++) {
var field_name = ko_fields[f_idx];
if (object[field_name] && object[field_name].__ko_proto__ !== undefined) {
continue;
}
if (object[field_name] instanceof Array) {
var field_array = object[field_name];
for (var a_idx = 0; a_idx < field_array.length; a_idx++) {
field_array[a_idx] = ko.observableObject(field_array[a_idx]);
}
object[field_name] = ko.observableArray(field_array);
} else {
object[field_name] = ko.observable(object[field_name]);
}
}
return object;
};
})();
You can use it with classes or with plain objects.
// With classes. We define the classes without Knockout-observables
// User.subscriptions is an array of Subscription objects
User = function(id, name) {
this.id = id;
this.name = name;
this.subscriptions = [];
};
Subscription = function(type, comment) {
this.type = type;
this.comment = comment;
});
// Create some objects
var jan = new User(74619, "Jan Fabry");
jan.subscriptions.push(new Subscription("Stack Overflow", "The start"));
jan.subscriptions.push(new Subscription("Wordpress Stack Exchange", "Blog knowledge"));
var chris = new User(16891, "Chris Westbrook");
// We would like to convert fields in our objects to observables
// Either define the fields directly:
ko.observableObject(jan, ['id', 'name', 'subscriptions']);
ko.observableObject(chris, ['id', 'name', 'subscriptions']);
// This will only convert the User objects, not the embedded subscriptions
// (since there is no mapping)
// If you define it in the class prototype, it will work for embedded objects too
User.prototype._ko_fields = ['id', 'name', 'subscriptions'];
Subscription.prototype._ko_fields = ['type', 'comment'];
ko.observableObject(jan);
ko.observableObject(chris);
// It also works with objects that are not created from a class, like your Ajax example
var observable = ko.observableObject({'id': 74619, 'name':'Jan'}, ['id', 'name']);
I fixed this issue, found out it was better to declare the observable fields on my object before setting it to my view, like
for (var i=0;i<msg.lenth;i++){
msg[i].emailAddress=ko.observable(msg[i].emailAddress);
}
viewModel.lines(msg);
Here is my CoffeeScript RequireJS module for converting what I call "view data" (server-derived JSON values) to KO ViewModels:
define "infrastructure/viewModels", [], (viewModels) ->
exports = {}
isDate = (x) ->
typeof x is "string" and
x.startsWith "/Date("
deserializeDate = (dateString) ->
new Date(parseInt(dateString.substr(6)))
isScalar = (x) ->
x is null or
typeof x is "string" or
typeof x is "number" or
typeof x is "boolean"
exports.fromViewData = (viewData) ->
if isDate viewData
return ko.observable deserializeDate viewData
if isScalar viewData
return ko.observable viewData
viewModel = {}
for own key, value of viewData
if key is "id" then continue
viewModel[key] =
if Array.isArray value
ko.observableArray (exports.fromViewData el for el in value)
else
exports.fromViewData value
return viewModel
return exports
Sample usage:
require ["infrastructure/viewModels"], (viewModels) ->
$.getJSON "/url/to/data", (viewData) ->
viewModel = viewModels.fromViewData viewData
ko.applyBindings viewModel
Of course, if you don't do CoffeeScript, you can translate to JavaScript by clicking "Try CoffeeScript" on the linked site. And if you don't use RequireJS, then just take the relevant functions from my module without wrapping them in define
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With