Knockout foreach binding, update value - javascript

I got ko model:
var Model = function(notes) {
this.note = ko.observableArray(notes);
};
var model = new Model([{"post":"a"},{"post":"b"}]);
ko.applyBindings(model);
And html div:
<div data-bind='foreach: note'>
<p><span data-bind='text: post'>
<p><input data-bind='value: post'>
</div>
Here's fiddle: http://jsfiddle.net/DE9bE/
I want to change my span value when new text is typed in input, like in that fiddle: http://jsfiddle.net/paulprogrammer/vwhqU/2/
But it didnt updates. How can i do that in foreach bindings?

The properties of the objects need to be made into observables.
You could do this yourself manually (i've used the plain js array map method here, if you need IE8 support you can use ko.utils.arrayMap for the same purpose):
var Model = function(notes) {
this.note = ko.observableArray(notes.map(function(note){
note.post = ko.observable(note.post);
return note;
}));
};
var model = new Model([{"post":"a"},{"post":"b"}]);
ko.applyBindings(model);
Demo
Or you can use the mapping plugin (seperate js file that you need to include) which does this (recursively) for you.
var Model = function(notes) {
this.note = ko.mapping.fromJS(notes);
};
var model = new Model([{"post":"a"},{"post":"b"}]);
ko.applyBindings(model);
Demo
If you get the whole data object from the server you could also feed it directly:
var model = ko.mapping.fromJS({
note: [{"post":"a"},{"post":"b"}]
});
ko.applyBindings(model);
Demo

What you want is an observable array with observables, or else your span will not be updated since you are changing a non-observable variable, see this post.
KnockoutJS - Observable Array of Observable objects

Try this:
HTML
<div data-bind='foreach: note'>
<p><span data-bind='text: post'></span></p>
<p><input data-bind='value: post'/></p>
</div>
JavaScript
var Model = function (notes) {
this.note = ko.observableArray(notes);
};
var Post = function (data) {
this.post = ko.observable(data);
};
var model = new Model([new Post("a"), new Post("a")]);
ko.applyBindings(model);
Demo

Related

How to subscribe to items in collection/foreach DOM rendering

I have a view that is used to create and also update an item. I have this problem when updating where a function is running twice due to a change event on a dropdown list. What was suggested in my previous post is that I use a subscription to the value and create logic accordingly within the subscription. I think this is a great idea, however, I have no idea how I will apply these subscriptions to my model when the bindings are being applied dynamically from a view model sent to the view from a back-end controller method.
The collection items get their functionality from data-bind values provided in a foreach element loop and using $data. It would be preferred to keep this format for the solution.
<tbody data-bind="foreach: koModel.ModelData.Lines">
<tr class="form-group">
<td>...</td>
<td>..other model properties..</td>
<td>...</td>
<!-- v v v This is the property with the change event v v v -->
<td>#Html.SearchPickerMvcKOMultipleFor(
m => m.ItemNumber,
"ItemPicker",
new { #class = "form-control" }.AddMore(
"data-bind",
"MyProgramSearchPicker: ItemNumber, value: $data.ItemNumber, event: { change: ItemNumericDetails($data) }"
))</td>
<!-- ^ ^ ^ ^ ^ -->
<td>etc</td>
</tr>
</tbody>
The model is converted to JSON and then to a JavaScript variable:
#{
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
string data = Newtonsoft.Json.JsonConvert.SerializeObject(Model);
#Html.Raw("var modelData = " + data + ";");
}
var koModel = new KOModel(modelData);
How can I apply a subscription to the ItemNumber property, either by executing an inline function from the element, or another method that can modify a dynamically rendered collection?
Create a class that represents a "line" of the koModel.ModelData.Lines
function Line(itemNumber) {
var self = this;
self.ItemNumber = ko.observable(itemNumber);
self.ItemNumber.subscribe(...);
};
koModel.ModelData.Lines.push(new Line(123));
Or, you can create a function in your view model:
function koModel() {
...
self.ItemNumericDetails = function (line) {
//do your job
};
};
and in the HTML:
<td>
#Html.SearchPickerMvcKOMultipleFor(
m => m.ItemNumber,
"ItemPicker",
new { #class = "form-control" }.AddMore(
"data-bind",
"MyProgramSearchPicker: ItemNumber, value: $data.ItemNumber, event: { change: $root.ItemNumericDetails }"
))
</td>

How do I access my viewmodel variables in my knockout component template?

I am trying to create a list of instruction steps using Knockout components/templates.
The UL is the going to contain a list of steps (using a knockout-registered custom element sidebar-step) which is just a template of <li></li>. I have another value this.testVar part of my model that could contain an attribute of <li> such as the class, or maybe a "data-customAttribute".
My question is, how do I include my testVar value into the template? I want it so that it might output a line like:
<sidebar-step class=*testVar* params="vm: sidebarStepModel">Message 1</sidebar-step>
Fiddle: https://jsfiddle.net/uu4hzc41/1/
HTML:
<ul>
<sidebar-step params="vm: sidebarStepModel"></sidebar-step>
</ul>
JavaScript:
ko.components.register("sidebar-step", {
viewModel: function (params) {
this.vm = params.vm;
},
template: "<li data-bind=\'text: vm.message\'></li>"
});
var SidebarStepModel = function () {
this.message = ko.observable("step description");
this.testVar = ko.observable("some value");
};
var ViewModel = function () {
this.sidebarStepModel = new SidebarStepModel();
this.sidebarStepModel.message("Message 1");
this.sidebarStepModel.testVar("69");
};
ko.applyBindings(new ViewModel());
You can use data-bind on the custom tag. To set class,
<sidebar-step data-bind="css:sidebarStepModel.testVar" params="vm: sidebarStepModel"></sidebar-step>
https://jsfiddle.net/uu4hzc41/3/

edit update existing array in javascript

I am making CRUD app for learning purpose. I need to update existing javascript array on click of edit button. However currently its not updating the existing array rather then its creating new record. Below is the JS code of controller
For Add screen below is the controller code
.controller('addController', ['$scope','$location','$rootScope', function(scope,location,rootScope){
scope.save = function (){
scope.personName = document.getElementById('name').value;
scope.personDesc = document.getElementById('desc').value;
scope.person = {'name' : scope.personName, 'desc' : scope.personDesc};
if(typeof rootScope.crew === 'undefined'){
rootScope.crew = [];
}
rootScope.crew.push(scope.person);
location.path("/");
}
}])
For Edit Screen, below is the code of controller :-
.controller('editController',['$scope','$location','$routeParams','$rootScope', function(scope,location,routeParams,rootScope){
var oldName = scope.crew[routeParams.id].name;
document.getElementById('name').value = scope.crew[routeParams.id].name;
document.getElementById('desc').value = scope.crew[routeParams.id].desc;
scope.editSave = function(){
scope.person = {
'name' : document.getElementById('name').value,
'desc' : document.getElementById('desc').value
}
rootScope.crew.push(scope.person);
location.path("/");
}
}])
Currently I am adding record in existing array rather updating.
Please suggest
The problem is you are pushing a new item to the array. You need to just update the existing person with the person in scope.
.controller('editController',['$scope','$location','$routeParams','$rootScope', function(scope,location,routeParams,rootScope){
var person = scope.crew[routeParams.id]
scope.person = {
name = person.name,
desc = person.desc
};
scope.editSave = function(){
scope.crew[routeParams.id] = scope.person;
location.path("/");
}
}])
In your edit view you would have this:
<input type="text" id="name" ng-model="person.name"/>
<input type="text" id="desc" ng-model="person.desc"/>
It's also worth mentioning that there is no need to have code such as document.getElementById as angular will handle the model binding for you so you don't have to interact with the dom using javascript.
Every object that you are pushing in array must be identified by some id.So assign one id attribute to the person object that you are pushing.
Now come to the edit.html
<tr ng-repeat="p in person">
{{p.name}}
//In button I am passing id which I used in editing the person object
<button ng-click="edit(p.id)"></button>
</tr>
//In controller
edit(id){
//firstly search for the person which is going to be updated
for(i=0;i<arrayInWhichYouArePushingData.length;i++)
{
if(arrayInWhichYouArePushingData[i].id==id)
{
arrayInWhichYouArePushingData[i]=UpdatedData;
break;
}
}
This is just an algorithm to solve this type of problem.You have to modify little bit.

Multiple Select List and KnockoutJS

I have a multi-select list that I've implemented following the instructions on the KO site. The important portions of my code currently look like this (removed unnecessary code):
function Attribute(data) {
var self = this;
self.Id = data.Id;
self.Name = data.Name;
}
// Individual Row in Table
function Criteria(data) {
var self = this;
self.Attributes = data.Attributes;
}
// Represent the ViewModel for attributes.
function CriteriaViewModel() {
var self = this;
// Catalog Data
self.availableAttributes = window.ko.observableArray([]);
$.getJSON(window.attributeListUrl, function(availableData) {
self.availableAttributes($.map(availableData.Attributes, function(item) { return new Attribute(item); }));
});
// Editable Data
self.criterion = window.ko.observableArray([]);
// Load initial state from server
$.getJSON(window.criteriaListUrl, function (availableData) {
self.criterion($.map(availableData.Criterion, function (item) { return new Criteria(item); }));
});
}
Then, in my HTML, I bind it all together (or, I at least try to):
<tbody data-bind="foreach: criterion">
<tr>
<td>
<select class="selectedAttributes"
data-bind="options: $root.availableAttributes, selectedOptions: Attributes, optionsText: 'Name', optionsValue: 'Id'"
multiple
size="6">
</select>
</td>
</tr>
</tbody>
The possible options display correctly. However, there is no apparent binding between the criteria's attributes against the possible options. From reading the guide, it seems as though KO should be able to bind objects directly. Can anybody provide guidance here?
I forgot to mention that everything works except the actual binding of the multi-select list. I am applying my bindings appropriately in general - just not with the multi-select list.
The attributes property on the Criteria object needs to be an observableArray. Here is a Jsfiddle demonstrating
function Criteria(data) {
var self = this;
self.Attributes = ko.observableArray(data.Attributes);
}
var x= $('#select1 option:selected');
if(x.length>0){
x.each(function(){
alert($(this).text());
self.selectedCategory.push(new categoryModel($(this).text()));
$('#select1 option:selected').remove();
});
}
refer http://jsfiddle.net/deepakpandey1234/wse4gdLq/

Passing values to ko.computed in Knockout JS

I've been working for a bit with MVC4 SPA, with knockoutJs,
My problem is I want to pass a value to a ko.computed. Here is my code.
<div data-bind="foreach: firms">
<fieldset>
<legend><span data-bind="text: Name"></span></legend>
<div data-bind="foreach: $parent.getClients">
<p>
<span data-bind="text: Name"></span>
</p>
</div>
</fieldset>
</div>
self.getClients = ko.computed(function (Id) {
var filter = Id;
return ko.utils.arrayFilter(self.Clients(), function (item) {
var fId = item.FirmId();
return (fId === filter);
});
});
I simply want to display the Firmname as a header, then show the clients below it.
The function is being called, but Id is undefined (I've tried with 'Firm' as well), if I change:
var filter = id; TO var filter = 1;
It works fine,
So... How do you pass a value to a ko.computed? It doesn't need to be the Id, it can also be the Firm object etc.
Each firm really should be containing a list of clients, but you could use a regular function I think and pass it the firm:
self.getClientsForFirm = function (firm) {
return ko.utils.arrayFilter(self.Clients(), function (item) {
var fId = item.FirmId();
return (fId === firm.Id());
});
});
Then in html, $data is the current model, in your case the firm:
<div data-bind="foreach: $root.getClientsForFirm($data)">
Knockout doesn't allow you pass anything to a computed function. That is not what it is for. You could instead just use a regular function there if you'd like.
Another option is to have the data already in the dataset on which you did the first foreach. This way, you don't use $parent.getClients, but more like $data.clients.

Categories