KnockoutJS Assembling updated data from components - javascript

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>

Related

cant make an object I added to a knockout model observable

Here is the fiddle demonstrating the problem http://jsfiddle.net/LkqTU/31955/
I made a representation of my actual problem in the fiddle. I am loading an object via web api 2 and ajax and inserting it into my knockout model. however when I do this it appears the attributes are no longer observable. I'm not sure how to make them observable. in the example you will see that the text box and span load with the original value however updating the textbox does not update the value.
here is the javascript.
function model() {
var self = this;
this.emp = ko.observable('');
this.loademp = function() {
self.emp({
name: 'Bryan'
});
}
}
var mymodel = new model();
$(document).ready(function() {
ko.applyBindings(mymodel);
});
here is the html
<button data-bind="click: loademp">
load emp
</button>
<div data-bind="with: emp">
<input data-bind="value: name" />
<span data-bind="text: name"></span>
</div>
You need to make name property observable:
this.loademp = function(){
self.emp({name: ko.observable('Bryan')});
}

Knockout.js Bind a observable with single json object

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

bindings break when instantiating viewmodel for applyBindings in knockout.js

I must be missing something very simple, but I'm not seeing it.
I had a simple viewmodel:
var ViewModel = function() {
var self = this;
self.categories = ko.observableArray([{id:'one',display:'ONE'},{id:'two',display:'TWO'}]);
self.show_subcategories_for = function(category){
alert(category.id);
};
};
ko.applyBindings(ViewModel);
and html for it:
<div id="categories">
<!-- ko foreach: categories -->
<input type="radio" data-bind="attr: { id: id }, click: show_subcategories_for" name="category" />
<label data-bind="attr: { 'for': id }, text: display"></label>
<!-- /ko -->
</div>
working jsfiddle for it: http://jsfiddle.net/J8VNY/2/
Then I was trying to factor out categories array and wanted to pass it to the view model as a parameter, knockout would error out on "show_subcategories_for is not defined ", which is definitely there.
I changed the viewmodel to following:
var ViewModel = function(cats) {
var self = this;
self.categories = ko.observableArray(cats);
self.show_subcategories_for = function(category){
alert(category.id);
};
};
ko.applyBindings(new ViewModel(
[{id:'one',display:'ONE'},{id:'two',display:'TWO'}]
));
here is jsfiddle with the error: http://jsfiddle.net/J8VNY/1/
Everything appears to be correct yet for some reason instantiating viewmodel and passing it an array as a parameter causes knockout some confusion.
Any insight would be appreciated. Thank you
You have to use the $root keyword:
<input type="radio"
name="category"
data-bind="attr: { id: id }, click: $root.show_subcategories_for" />
See Documentation

How can I copy this knockout observable?

I am trying to add a "default" object to my Knockout observableArray. The way my code stands currently, I end up adding the exact same object to the array instead of a new instance of the object. I tried using jQuery's .extend(), but, that didn't work out so I am looking for input.
Here is a demonstration to show my problem: http://jsfiddle.net/2c8Fx/
HTML:
<div data-bind="foreach: People">
<input type="text" data-bind="value: Name" />
</div>
<button type="button" data-bind="click: AddPerson">Add Person</button>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
Script:
function ViewModel() {
var self = this;
self.EmptyPerson = ko.mapping.fromJS({Name: null, Age: null});
self.People = ko.observableArray([self.EmptyPerson]);
self.AddPerson = function() {
self.People.push(self.EmptyPerson);
};
}
ko.applyBindings(new ViewModel());
This doesn't work because the observableArray is actually holding a reference to the same object for each index.
What's the best way to create a new instance of my Knockout object?
function ViewModel() {
var self = this;
self.People = ko.observableArray();
self.AddPerson = function() {
self.People.push(ko.mapping.fromJS({Name: null, Age: null}));
};
}
ko.applyBindings(new ViewModel());
I did this and it works like you think it would. I hope this helps.
Check out a working example here

Bind JSON with table in knockoutjs

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);})
);
};

Categories

Resources