I am trying to bind a computed observable which internally uses a observable array.
The "read" method does get called while loading.
But the "write" method does not get called when the values in the table are changed and the focus is moved.
Note that for simple computed observables , which do not wrap a array but a simple string, the "write" method works. However for this scenario it does not work.
I looked on the Knockout api documentation and in online forums but could not find anything about this. Can someone please advice?
Following is the HTML code
<thead>
<tr><th>People Upper Case Name</th></tr>
</thead>
<tbody data-bind="foreach: uppercasepeople">
<tr>
<td ><input type="text" data-bind="value: name"/></td>
</tr>
</tbody>
</table>
Following is the Java Script code
<script type="text/javascript">
var MyViewModel = function () {
var self = this;
var self = this;
//Original observable array is in lower case
self.lowercasepeople = ko.observableArray([
{ name: "bert" },
{ name: "charles" },
{ name: "denise" }
]);
//coputed array is upper case which is data-bound to the ui
this.uppercasepeople = ko.computed({
read: function () {
alert('read does get called :)...yaaaa!!!');
return self.lowercasepeople().map(function (element) {
var o = { name: element.name.toUpperCase() };
return o;
});
},
write: function (value) {
alert('Write does not get called :(... why?????');
//do something with the value
self.lowercasepeople(value.map(function (element) { return element.toLowerCase(); }));
},
owner: self
});
}
ko.applyBindings(new MyViewModel());
I have put the code similar to an example shown on the Knockout API documentation so people easily relate.
The computed observable that you have only deals with the array itself. The write function would only get called if you tried to set the value of uppercasepeople directly.
In this case, you would likely want to use a writeable computed on the person object itself and make the name observable. The writeable computed would then convert the name to upper-case and when written would populate the name observable with the lower-case value.
var MyViewModel = function () {
var self = this;
//Original observable array is in lower case
self.lowercasepeople = ko.observableArray([
{ name: "bert" },
{ name: "charles" },
{ name: "denise" }
]);
self.recententlyChangedValue = ko.observable();
//coputed array is upper case which is data-bound to the ui
this.uppercasepeople = ko.computed({
read: function () {
alert('read does get called :)...yaaaa!!!');
return self.lowercasepeople().map(function (element) {
var o = { name: element.name.toUpperCase() };
return o;
});
},
write: function (value) {
alert('It will be called only when you change the value of uppercasepeople field');
//do something with the value
self.recententlyChangedValue(value);
},
owner: self
});
}
Related
Here goes my View model, which helps to load the items to drop down. Items are getting loaded but when I inspect the element "value" attribute is empty. How can I get selected value?
$(function () {
tss.Department = function (selectedItem) {
var self = this;
self.id = ko.observable();
self.description = ko.observable();
self.isSelected = ko.computed(function () {
return selectedItem() === self;
});
self.stateHasChanged = ko.observable(false);
};
tss.vm = (function () {
var metadata = {
pageTitle: "My App"
},
selectedDepartment = ko.observable(),
departments = ko.observableArray([]),
sortFunction = function (a, b) {
return a.description().toLowerCase() > b.description().toLowerCase() ? 1 : -1;
},
selectDepartment = function (p) {
selectedDepartment(p);
},
loadDepartments = function () {
tss.departmentDataService.getDepartments(tss.vm.loadDepartmentsCallback);
},
loadDepartmentsCallback = function (json) {
$.each(json, function (i, p) {
departments.push(new tss.Department(selectedDepartment)
.id(p.DepartmentId)
.description(p.Description)
);
});
departments.sort(sortFunction);
};
return {
metadata: metadata,
departments: departments,
selectDepartment: selectDepartment,
loadDepartmentsCallback: loadDepartmentsCallback,
loadDepartments: loadDepartments,
choices: choices,
selectedChoice: selectedChoice
};
})();
tss.vm.loadDepartments();
ko.applyBindings(tss.vm);
});
Here is my HTML
<select data-bind="options:departments, value:selectDepartment,
optionsText: 'description', optionsCaption:'Select a product ...'">
</select>
Also sorting is not happening. departmentDataService used to call external data. which has both "id" and "description"
I also tried setting value as 'Id', but did not work.
You should not use an additional function selectDepartment to pass the value to the observable, but instead directly bind the observable to the value property of the select-box:
<select data-bind="options:departments, value:selectedDepartment, ...
(remember to export the selectedDepartment observable)
The value property is not only used to communicate the current value from view to viewmodel, but also vice versa: to set the selected option. Binding to a function that provides only "write" functionality is therefore not sufficient.
If you need to react to changes of the selected department, you can subscribe to the observable (this is explained in the official docs).
I have the following:
// Child Array is Cards, trying to add computed observable for each child
var CardViewModel = function (data) {
ko.mapping.fromJS(data, {}, this);
this.editing = ko.observable(false);
};
var mapping = {
'cards': { // This never gets hit, UNLESS I remove the 'create' method below
create: function (options) {
debugger;
return new CardViewModel(options.data);
}
},
create: function(options) {
var innerModel = ko.mapping.fromJS(options.data);
innerModel.cardCount = ko.computed(function () {
return innerModel.cards().length;
});
return innerModel;
}
};
var SetViewModel = ko.mapping.fromJS(setData, mapping);
debugger;
ko.applyBindings(SetViewModel);
However I can't get the 'cards' binding to work - that code isn't reached unless I remove the 'create' method. I'm trying to follow the example from the knockout site:
http://knockoutjs.com/documentation/plugins-mapping.html
They do this for the child object definition:
var mapping = {
'children': {
create: function(options) {
return new myChildModel(options.data);
}
}
}
var viewModel = ko.mapping.fromJS(data, mapping);
With the ChildModel defined like this:
var myChildModel = function(data) {
ko.mapping.fromJS(data, {}, this);
this.nameLength = ko.computed(function() {
return this.name().length;
}, this);
}
I've spent the past day on this and cannot for the life of me figure out why this isn't working. Any tips would be awesome.
EDIT: Here's a fiddle of what I'm working with. It's only showing SIDE 1 in the result because "editing" isn't recognized here:
<div data-bind="visible: !$parent.editing()" class="span5 side-study-box">
http://jsfiddle.net/PTSkR/1/
This is the error I get in chrome when I run it:
Uncaught Error: Unable to parse bindings. Message: TypeError: Object
has no method 'editing'; Bindings value: visible: !$parent.editing()
You have overridden the create behavior for your view model. The mapping plugin will not call any of the other handlers for the properties for you. Since you're mapping from within the create method, move your cards handler in there.
var mapping = {
create: function(options) {
var innerModel = ko.mapping.fromJS(options.data, {
'cards': {
create: function (options) {
debugger;
return new CardViewModel(options.data);
}
}
});
innerModel.cardCount = ko.computed(function () {
return innerModel.cards().length;
});
return innerModel;
}
};
updated fiddle
you didnt needed to have parenthesis. I just changed from
!$parent.editing()
to
!$parent.editing
See the updated fiddle here
I have binded my json array to knockout by using knockout-mapping plugin
JSON
{
"info":[
{
"Name":"Noob Here",
"Major":"Language",
"Sex":"Male",
"English":"15",
"Japanese":"5",
"Calculus":"0",
"Geometry":"20"
},
{
"Name":"Noob Here",
"Major":"Calculus",
"Sex":"Female",
"English":"0.5",
"Japanese":"40",
"Calculus":"20",
"Geometry":"05"
}
]
}
Binded using knockout-mapping plugin
var data = [];
$.each(data1.info, function (index, element) {
data.push({
English: element.English,
Japanese: element.Japanese,
Calculus: element.Calculus,
Geometry: element.Geometry,
name: element.Name,
major: element.Major,
sex: element.Sex
});
});
dataFunction.prototype = function () {
var getAllItems = function () {
var self = this;
ko.mapping.fromJS(data, {}, self.Items);
};
Now I want to alert the value of English.
I tried alert(this.English()); inside dataFunction.prototype and it doesn't work.
How to alert that value?
JS-Bin code: http://jsbin.com/ipeseq/4/edit
You need to define a proper view model and work from that in your mark-up.
I put together a view model with a custom view model mapping where I map your data into objects I called 'Student' that you can use in your markup. This object I extended with a ko.computed that calculates the total (It is in this object you can read and manipulate your observables).
var Student = function(data) {
var self = this;
ko.mapping.fromJS(data, { }, self);
self.total = ko.computed(function() { // Calculate total here
return self.English() + self.Japanese() + self.Calculus() + self.Geometry();
});
};
var viewModelMapping = { // Map all objects in 'info' to Student objects
'info': {
create: function(options) {
return new Student(options.data);
}
}
};
var ViewModel = function(data) { // Create a view model using the mapping
var self = this;
ko.mapping.fromJS(data,viewModelMapping,self);
}
$(document).ready(function () {
vm = new ViewModel(data);
ko.applyBindings(vm);
});
You can see the resulting JSBin code here
You can read more in the Customizing object construction using “create” and Customizing object updating using “update” sections here
This question is a follow-up to my last question: JavaScript Serialization and Methods. While related, I believe this is different which is why I started this thread. Regardless...
I have a complex tree of objects that I need to pass around between pages. Because of this, I'm attempting to serialize and deserialize my objects. One of my objects in particular has several collections of child objects. Each of those child object types has a function on it, that I'm trying to call. Unfortunately, I am not having any luck. I setup a test project in an attempt to isolate the problem and get to the bottom of it. Currently, I have my JavaScript Objects defined in a seperate file called objects.js. That file looks like this:
objects.js
function MyChild() { this.init(); }
MyChild.prototype = {
data: {
id: 0,
fullName: "",
},
init: function () {
},
save: function (key) {
},
load: function (key) {
},
test: function () {
alert("Testing Child functions");
}
}
function MyParent() { this.init(); }
MyParent.prototype = {
data: {
id: "",
children: null
},
init: function () {
this.data.children = [];
},
save: function (key) {
window.localStorage.setItem(key, JSON.stringify(this.data));
},
load: function (key) {
var temp = window.localStorage.getItem(key);
if (temp != null) {
this.data = JSON.parse(temp);
$.each(this.data.children, function (i, r) {
});
}
},
test: function () {
alert("Testing Parent functions");
}
}
I am creating, serializing, deserializing, and attempting to interact with these objects in an .html file. That .html file is shown here:
test.html
<div>
<input id="button1" type="button" value="Create Object Tree" onclick="button1Click();" /><br />
<input id="button2" type="button" value="Retrieve and Execute" onclick="button2Click();" /><br />
</div>
<script type="text/javascript">
function button1Click() {
var child = new MyChild();
child.data.id = 1;
var p = new MyParent();
p.data.id = "A";
p.data.children.push(child);
p.save("A");
}
function button2Click() {
var storedParent = new MyParent();
storedParent.load("A");
storedParent.test(); // This works
alert(storedParent.data.children.length); // This displays "1" like I would expect
alert(storedParent.data.children[0].data.id); // This does NOT work.
storedParent.data.children[0].test(); // This does NOT work.
}
</script>
I'm not sure what I'm doing wrong. Can someone please help me understand this? Can somone please help me fix my example. I have a hunch that I'm not serializing MyChild objects properly. But I don't understand how I should be serializing / deserializing them in relation to MyParent.
Thank you!
You need to store your data within each object, not within its prototype.
Data stored in in the prototype of an object is shared between all instances and won't be serialised by JSON.stringify, so your object data never ends up in the local storage.
To fix, add data to this within the this.init() function:
MyChild.prototype = {
init: function() {
this.data = {
id: 0,
fullName: ""
};
},
...
}
MyParent.prototype = {
init: function() {
this.data = {
id: "",
children: []
}
},
...
}
Working sample at http://jsfiddle.net/alnitak/fdwVB/
Note that attaching the functions in MyChild to the retrieved data is tricky. The code below appears to work:
load: function(key) {
var temp = window.localStorage.getItem(key);
if (temp != null) {
this.data = JSON.parse(temp);
var children = this.data.children;
for (var i = 0; i < children.length; ++i) {
children[i] = $.extend(new MyChild(), children[i]);
}
}
},
But note that it works by constructing a new "default" child for each retrieved entry, and then overwriting that child's data with the serialised values.
This could be problematic if the constructor has "side effects".
A better approach might be to allow the MyChild() constructor to take an optional complete object of initial values to use instead of the default values.
I'm binding data to a page using KnockoutJS, the ViewModel is being populated by an JSON response from an AJAX call using the mapping plugin, like this:
$(function () {
$.getJSON("#Url.Action("Get")",
function(allData) {
viewModel = ko.mapping.fromJS(allData);
viewModel.Brokers.Url = ko.computed(function()
{
return 'BASEURLHERE/' + this.BrokerNum();
});
ko.applyBindings(viewModel);
});
});
The middle part there doesn't work (it works fine without that computed property). "Brokers" is an observable array, and I want to add a computed value to every element in the array called URL. I'm binding that Brokers array to a foreach, and I'd like to use that URL as the href attribute of an anchor. Any ideas?
I've been working through very similar issues and I've found that you can intercept the creation of the Broker objects and insert your own fields using the mapping options parameter:
var data = { "Brokers":[{"BrokerNum": "2"},{"BrokerNum": "10"}] };
var mappingOptions = {
'Brokers': {
create: function(options) {
return (new (function() {
this.Url = ko.computed(function() {
return 'http://BASEURLHERE/' + this.BrokerNum();
}, this);
ko.mapping.fromJS(options.data, {}, this); // continue the std mapping
})());
}
}
};
viewModel = ko.mapping.fromJS(data, mappingOptions);
ko.applyBindings(viewModel);
Here is a fiddle to demonstrate this: http://jsfiddle.net/pwiles/ZP2pg/
Well, if you want Url in each broker, you have to add it to each broker:
$.each(viewModel.Brokers(), function(index, broker){
broker.Url = ko.computed(function(){return 'BASEURLHERE/' + broker.BrokerNum();});
});
I guess BrokerNum is not going to change, so you might as well just calculate Url once:
$.each(viewModel.Brokers(), function(index, broker){
broker.Url = 'BASEURLHERE/' + broker.BrokerNum();
});
You can also add Url property during mapping by providing "create" callback to ko.mapping.fromJS function. See mapping plugin docs for details.
If you only need url to bind to href, just bind the expression in html (within foreach binding):
<a data-bind="attr: {href: 'BASEURLHERE/' + BrokerNum()}">link to broker details</a>
Thanks to Peter Wiles i have very similar solution:
var ViewModel = function (data, ranges) {
var self = this;
this.productList = ko.observableArray();
var productListMapping = {
create: function (options) {
return (new (function () {
//this row above i don't understand...
this.len = ko.computed(function () {
//just test function returning lenght of object name
// and one property of this model
return this.name().length + ' ' + self.cons_slider_1();
}, this);
ko.mapping.fromJS(options.data, {}, this); // continue the std mapping
})());
}
}
this.cons_slider_1 = ko.observable(100);
ko.mapping.fromJS(data, productListMapping, this.productList);
};
Some differences:
I am not mapping to self, but on this.product.
The input json has not parent name like 'Brokers' in above example:
var products = [
{ "id": "pp1", "name": "Blue windy" },
{ "id": "pp1", "name": "Blue windy" }];
So in productMapping i'm typing just 'create:'
But, what i do not understand is the structure of create function. Could somebody explain me why the function returns new function, which has property. Couldn't it be simplified somehow?