how can i bind my json object with knockoutjs, here is my json :
"{\"Sport\":[{\"Name\":\"nana\",\"Description\":\"lmkmsdqd\",\"EndDate\":\"2012-07-22T00:00:00\"},
{\"Name\":\"sfqsdffqf\",\"Description\":\"lkqjskdlqsd\",\"EndDate\":\"2012-07-22T00:00:00\"}],
\"Music\":[{\,\"Name\":\"nana\",\"Description\":\"lmkmsdqd\",\"EndDate\":\"2012-07-22T00:00:00\"},
{\"Name\":\"sfqsdffqf\",\"Description\":\"lkqjskdlqsd\",\"EndDate\":\"2012-07-22T00:00:00\"}]}"
please suggest how can i bind it !!
Alright, so I put together a crude fiddle to demonstrate some binding concepts, as well as ViewModel construction. I had to clean up your JSON to do it. It demonstrates template, foreach, and text binding. If you haven't already done so, I highly recommend going through the tutorials on the knockout site.
Here are the HTML bindings:
Sports
<ul data-bind="template: { name: 'listingTemplate', foreach: sports}"></ul>
</br>
Music
<ul data-bind="template: { name: 'listingTemplate', foreach: music}"></ul>
<script type="text/html" id="listingTemplate">
<li>
<span data-bind="text: name"></span></br>
<span data-bind="text: description"></span></br>
<span data-bind="text: endDate"></span></br></br>
</li>
</script>
and the viewmodels:
var Listing = function(data) {
this.name = ko.observable(data.Name || '');
this.description = ko.observable(data.Description|| '');
this.endDate = ko.observable(data.EndDate|| '');
};
var ViewModel = function(data) {
this.sports = ko.observableArray(
ko.utils.arrayMap(data.Sport, function(i) {return new Listing(i);})
);
this.music = ko.observableArray(
ko.utils.arrayMap(data.Music, function(i) {return new Listing(i);})
);
};
Related
I am new to KnockoutJS.
I have an app that works in a following way:
when page is loaded a complex data structure is passed to frontend
this datastructure is splitted into smaller chunks and these chunks of data are passed to components
user interacts with components to edit chunks of data
upon clicking a button updated complex data structure should be passed to backend
I have troubles with a fourth step. I've been reading throught documentation and yet I couldn't figure out how I am supposed to get updated data.
Here is a JSFiddle: https://jsfiddle.net/vrggyf45
Here is the same code in snippet. See the bottom of the question for what I've tried.
ko.components.register('firstComponent', {
viewModel: function(params) {
var self = this;
self.firstComponentValue = ko.observable(params.firstComponentValue);
},
template: '<div><pre data-bind="text: JSON.stringify(ko.toJS($data), null, 2)"></pre><input data-bind="value: firstComponentValue, valueUpdate: \'afterkeydown\'"></div>'
});
ko.components.register('secondComponent', {
viewModel: function(params) {
var self = this;
self.secondComponentValue = ko.observable(params.secondComponentValue);
},
template: '<div><pre data-bind="text: JSON.stringify(ko.toJS($data), null, 2)"></pre><input data-bind="value: secondComponentValue, valueUpdate: \'afterkeydown\'"></div>'
});
var json = '{"items":[{"componentName":"firstComponent","firstComponentValue":"somevalue"},{"componentName":"secondComponent","secondComponentValue":"someothervalue"}]}';
var data = JSON.parse(json);
var mainVM = {};
mainVM.items = ko.observableArray(
ko.utils.arrayMap(data.items,
function(item) {
return ko.observable(item);
}));
ko.applyBindings(mainVM);
$('input[type=button]').click(function() {
var updatedData = ko.dataFor(document.getElementById('main'));
//get updated json somehow?
console.log(data);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="main">
<pre data-bind="text: JSON.stringify(ko.toJS($data), null, 2)"></pre>
<div data-bind="foreach: {data: items, as: 'item'}">
<hr>
<div data-bind="component: {name:componentName, params: item}">
</div>
</div>
<hr>
<input type="button" value="post data">
</div>
If I had a single view model I could have just used ko.dataFor($("#rootElement")) and send it to backend. However I intend to use components and they have their own viewmodels, which are not connected to the root viewmodel. I could have find them all with jQuery and use ko.dataFor but it looks like a big hack to me.
I also could have define all the viewmodels, including the components in the main viewmodel, but it makes components kind of useless.
Also I tried to change components viewmodels constructors so they would mutate input data and override incomming values with observables, but it seems like a hack to me as well.
Is there a function like ko.components or something that could give me all living viewmodels?
You can pass observables to the components so that their edits are immediately reflected in the parent object. ko.mapping is handy for converting plain JS objects to observables. Then when you want the data back, ko.toJS() it.
ko.components.register('firstComponent', {
viewModel: function(params) {
var self = this;
self.firstComponentValue = params.firstComponentValue;
},
template: '<div><pre data-bind="text: JSON.stringify(ko.toJS($data), null, 2)"></pre><input data-bind="value: firstComponentValue, valueUpdate: \'afterkeydown\'"></div>'
});
ko.components.register('secondComponent', {
viewModel: function(params) {
var self = this;
self.secondComponentValue = params.secondComponentValue;
},
template: '<div><pre data-bind="text: JSON.stringify(ko.toJS($data), null, 2)"></pre><input data-bind="value: secondComponentValue, valueUpdate: \'afterkeydown\'"></div>'
});
var json = '{"items":[{"componentName":"firstComponent","firstComponentValue":"somevalue"},{"componentName":"secondComponent","secondComponentValue":"someothervalue"}]}';
var data = JSON.parse(json);
var mainVM = {};
mainVM.items = ko.mapping.fromJS(data.items);
ko.applyBindings(mainVM);
$('input[type=button]').click(function() {
var updatedData = ko.toJS(ko.dataFor(document.getElementById('main')));
//Or, more simply: updatedData = ko.toJS(mainVM);
console.log(updatedData);
});
<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>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="main">
<pre data-bind="text: JSON.stringify(ko.toJS($data), null, 2)"></pre>
<div data-bind="foreach: {data: items, as: 'item'}">
<hr>
<div data-bind="component: {name:componentName, params: item}">
</div>
</div>
<hr>
<input type="button" value="post data">
</div>
I have a list as follows:
<ul id="blogList" data-bind="foreach: Data">
<li>
<span data-bind="text: Title"> </span>
View
</li>
</ul>
And knockout view model as below:
var ViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
this.currentSelected = ko.observable();
self.viewEntry = function () {
currentSelected = this;
}
};
Setting the currentSelected to this doesn't seem to work because when I try to bind to currentSelected somewhere else I get nothing happening:
<h2 data-bind="text: currentSelected.Title"></h2>
Is that the correct way to bind to currentSelected? The list is working fine but setting the currentSelected and binding to it isn't working.
First let's have a look at your viewEntry method.
self.viewEntry = function () {
currentSelected = this;
}
currentSelected is undefined it should be self.currentSelected additionally since this is an observable you should not set this as var with equal sign but rather treat this as function so it should be self.currentSelected(this)
the other thing is binding inside this part:
<h2 data-bind="text: currentSelected.Title"></h2>
again this is an object so to access its properties you need to use currentSelected as function so it should be
<h2 data-bind="text: currentSelected().Title"></h2>
Here you can find a working sample: https://jsfiddle.net/jz9t5vbo/
im trying to databind a observable that has a single json object. I can do this with a ko.observablearray no problem with the foreach but not sure how to do it with a observale
this is my view model
var ViewModel = function () {
var self = this;
self.job = ko.observable();
}
self.job is has been populated with a json object from a api call and this works ok but im not sure how to databind to the html. i tried the foreach
<p data-bind="foreach: job">
Name: <span data-bind="text: jobslist.Name"> </span>
Description: <span data-bind="text: jobslist.Description"> </span>
</p>
It doesn't give any error just blank
{
"JobID":1,
"JobsListID":1,
"BookingID":2,
"TimeAllowed":20,
"TimeTaken":22,
"Comments":"Some comments",
"Status":"complete",
"Notes":null,
"TimeStarted":"2014-11-04T09:00:00",
"Difficulty":1,
"CompleteDate":"2014-11-04T09:22:00",
"booking":null,
"jobs_mechanics":[],
"jobslist": {
"JobsListID":1,
"JobCategoryID":1,
"Description":"Change Tyres ",
"Name":"Tyres",
"jobs":[],
"jobscategory":null,
"model_jobslist":[]
},
"timessheets":[]
}
You should be able to get the results using with binding. Here is the code assuming you use local variable instead of web service call:
var data = {
"JobID":1,
"JobsListID":1,
"BookingID":2,
"TimeAllowed":20,
"TimeTaken":22,
"Comments":"Some comments",
"Status":"complete",
"Notes":null,
"TimeStarted":"2014-11-04T09:00:00",
"Difficulty":1,
"CompleteDate":"2014-11-04T09:22:00",
"booking":null,
"jobs_mechanics":[],
"jobslist": {
"JobsListID":1,
"JobCategoryID":1,
"Description":"Change Tyres ",
"Name":"Tyres",
"jobs":[],
"jobscategory":null,
"model_jobslist":[]
},
"timessheets":[]
};
var ViewModel = function () {
var self = this;
self.job = ko.observable(data);
}
var vm = new ViewModel();
ko.applyBindings(vm);
And HTML:
<p data-bind="with: job().jobslist">
Name: <span data-bind="text: Name"> </span>
Description: <span data-bind="text: Description"> </span>
</p>
Here is a jsFiddle
For a navigation menu, I have two groups of links, each group and link showing up or not dependent on a user's role. The roles are looked up when the link structure is being built and the list of links is built accordingly. The returned JSON gets parsed, put into observable arrays with no problem, but when I actually try and apply the bindings, the binding fails because the observables are blank. Here is the HTML...
<ul id="user-menu" class="menu" data-bind="foreach: areas">
<li>
<a data-bind="attr: { href: areaLink }">
<img data-bind="attr: { src: iconUri }" />
<span data-bind="text: areaName"></span>
</a>
</li>
</ul>
<ul id="admin-menu" class="menu" data-bind="foreach: adminAreas">
<li>
<a data-bind="attr: { href: areaLink }">
<img data-bind="attr: { src: iconUri }" />
<span data-bind="text: areaName"></span>
</a>
</li>
</ul>
Knockout view model in the background...
var navigation = (function() {
function Area() {
var self = this;
self.areaName = ko.observable();
self.areaLink = ko.observable();
self.iconUri = ko.observable();
self.sequenceNo = ko.observable();
self.isAdmin = ko.observable();
self.loadFromVM = function (vm) {
self.areaName(vm.name || '');
self.areaLink(vm.link || '');
self.iconUri(vm.iconUri || '');
self.sequenceNo(vm.sequenceNo || '');
self.isAdmin(vm.isAdmin);
}
}
function viewModel() {
var self = this;
self.areas = ko.observableArray([]);
self.adminAreas = ko.observableArray([]);
self.setup = function () {
var data = {}; // population with basic session data
$.getJSON('....php', { JSON.stringify(data) }, function (results) {
for (var i = 0; i < results.length; i++) {
var area = new Area();
area.loadFromVM(results[i]);
if (area.isAdmin()) {
self.adminAreas().push(area);
} else {
self.areas().push(area);
}
}
});
};
}
var vmInstance;
return {
setup: function () {
vmInstance = new viewModel();
vmInstance.setup();
ko.applyBindings(vmInstance, $('#user-menu')[0]);
ko.applyBindings(vmInstance, $('#admin-menu')[0]);
}
};
})();
And then I bring it together with this in the navigation view file...
navigation.setup();
So after I get my JSON back, parse it, and organize it when I loop through the success function of the $.getJSON method, putting a watch on self.areas() and self.adminAreas() does show that the arrays have the exact information I want them to. But by the time they have to be applied, calling vmInstance.areas().length or vmInstance.adminAreas().length returns zero. Even more oddly, putting in an alert with the length of the arrays right after the $.getJSON call but within the setup() function will cause the alert to fire first, show zeroes, then goes through the get, populates the array, then fires zeroes again.
Not exactly sure what's going on here, but I can't remember seeing this kind of behavior in another project so I'm not quite sure what I'm doing wrong here. Any ideas or advice would be greatly appreciated.
EDIT: Nevermind on the Fiddle. It doesn't really capture my actual error.
adminarea object is not initialized.you made the adminArea variable but instead of this you have used same area variable to set values.
var adminArea = new Area();
adminArea.areaName('test admin area');
adminArea.areaLink('#');
adminArea.iconUri('http://evernote.com/media/img/getting_started/skitch/windows8/win8-checkmark_icon.png');
adminArea.sequenceNo(1);
adminArea.isAdmin(true);
Fiddle Demo
features:[
"attributes": {"CityName": "asdfsad", "Population":"125005"}
]
features:[
"attributes": {"FirstName": "xyz"}
]
....
I am getting features in javascript object and adding it in a
queryResult =ko.observableArray()
But as you can see the attributes are not an array. so I can not bind it with foreach binding. features object attributes properties are different one another.
<div data-bind="foreach: queryResult">
<span data-bind="text: $data"></span>
</div>
This writes [object Object] in span element
<div data-bind="foreach: queryResult">
<span data-bind="text: $data.CityName"></span>
</div>
This writes asdfsad in span element, but other feature attributes doesnot have property named CityName
Basically knockout foreach binding is for using with homogenous data. Also, it's not a good design if a web API returns inhomogenous collections. But if you still need this, you can hack with custom bindings, for example:
ko.bindingHandlers.objectText = {
update: function (element, valueAccessor, allBindingsAccessor, data) {
var myobj = ko.utils.unwrapObservable(valueAccessor());
if (!myobj) return;
// dynamically extract data from the object
var myproperty = "";
if (myobj.CityName)
myproperty = myobj.CityName;
else if (myobj.FirstName)
myproperty = myobj.FirstName;
// if there is data, "redirect" to normal text binding
if (myproperty)
ko.bindingHandlers.text.update(element, function() { return myproperty; }, allBindingsAccessor, data);
}
};
And then write your binding like:
<div data-bind="foreach: queryResult">
<span data-bind="objectText: $data"></span>
</div>
This is quite ugly, but you can get the idea I hope. This way you can convert any object to any kind of text representation, for example you could find the first non-empty string property, or concatenate all non-empty properties with their names, etc.
Assume you don't care about IE<=8, try http://jsfiddle.net/7y48q/
If you want to support IE6,7,8, load underscore.js and replace Object.keys(result) with _.keys(result)
<div data-bind="foreach: { data: queryResult, as: 'result'}">
<hr/>
<!-- ko foreach: { data: Object.keys(result), as: 'k' } -->
<span data-bind="text: k"></span>: <span data-bind="text: result[k]"></span><br/>
<!-- /ko -->
</div>
Not sure what your question is, but if you're trying to expand all the objects try the following:
HTML:
<div id="bindingRoot">
<div data-bind="foreach: queryResult">
<div data-bind="crack: $data">
<span data-bind="text: name"></span>: <span data-bind="text: value"></span><br />
</div>
</div>
</div>
JavaScript:
var model = {
queryResult: ko.observableArray()
};
model.queryResult.push({ CityName: 'asdfsad', Population: 125005 });
model.queryResult.push({ FirstName: 'xyz' });
ko.bindingHandlers.crack = {
init: function () {
return ko.bindingHandlers['foreach'].init.apply(this, arguments);
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var value = ko.unwrap(valueAccessor()),
cracked = [];
for (var k in value) {
cracked.push({ name: k, value: value[k] });
}
return ko.bindingHandlers['foreach'].update.apply(this, [element, function () { return cracked; }, allBindings, viewModel, bindingContext]);
}
};
ko.applyBindings(model, document.getElementById('bindingRoot'));
Result:
CityName: asdfsad
Population: 125005
FirstName: xyz
Demo here: http://jsfiddle.net/Fng27/