Values not updating when using functions to assign templates - javascript

I have this HTML and am trying to use it to generate a table with the option to add more rows:
<thead>
<tr>
<th>Item</th>
<th>Cost</th>
<th>Amount</th>
<th>
<button class="btn btn-default" data-bind="click: addItem">Add Item</button>
</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="template: {name: $parent.dynTemplate, data: item }">
<td data-bind="template: {name: $parent.dynTemplate, data: cost() }"></td>
<td data-bind="template: {name: $parent.dynTemplate, data: amount() }">
<td></td>
</tr>
</tbody>
I'm using two different templates:
<script id="inpTmp" type="text/html">
<input data-bind="value: $data" />
</script>
<script id="dispTmp" type="text/html">
<p data-bind="text: $data"></p>
</script>
and am choosing which one to call based on the results of the dynTemplate function. The knockout that I have powering this is very simple:
function ItemAdd(name, icost, iamount) {
var self = this;
self.item = name;
self.cost = ko.observable(icost);
self.amount = ko.observable(iamount);
}
function TestModel() {
var self= this;
self.items= ko.observableArray([
new ItemAdd("a", 5, 10),
new ItemAdd("b", 6, 4)
]);
self.addItem= function() {
self.items.push(new ItemAdd("", 0, 0));
};
self.dynTemplate= function(init, s) {
if(init=== 0 || init=== '') {
return 'inpTmp';
}
return 'dispTmp';
};
}
ko.applyBindings(new TestModel());
The problem that I am running into is that when I enter values into newly created rows, the values in items do not change. They initialize properly, but when I run a function to log the values in items they stay as their defaults. If I use knockout if statements, then everything updates properly. However, using 6 sets of if statements didn't seem very effective so I wanted to see if I could pull it out into a function and then send back the proper template. I'm trying to have inputs there when the value is "" or 0, and then change them to <p> when something is entered.
I've tried changing how the data is passed into the template, and I've tried to assign context using with, but to no avail. Calling dynTemplate does not work unless prefixed by $root or $parent. If that is changing the context, is there a way to reset it?
Is this a problem of context, and if so, is there a way to assign context with the dynTemplate function? Or are the newly created elements from the template not properly binding? I've searched quite a bit, and have found templates within foreach loops, but have not seen functions being used to apply them. If there is a better way to do this, please let me know.
Thank you for the help

Your current sample doesn't work because ko dependency tacker doesn't see that your model field is changed. It happens because 'init' is unwrapped value (not an observable).
This fiddle shows how to make it work with single 'item' field.
http://jsfiddle.net/tabalinas/VXXqr/
In this changed version of dynTemplate we get the value of observable, and thus dependency tracker can see that value changed. Of course, we need to change the template.
self.dynTemplate= function(item, s) {
var val = item.item();
if(val=== 0 || val=== '') {
return 'inpTmp';
}
return 'dispTmp';
};
<script id="inpTmp" type="text/html">
<input data-bind="value: $data.item" />
</script>
<script id="dispTmp" type="text/html">
<p data-bind="text: $data.item"></p>
</script>
For your case, where you need universal template for all fields you can do the following: pass as data the name of the field. The template will pick up data from $parent. dynTemplate func is changed accordingly.
<tbody data-bind="foreach: items">
<tr>
<td data-bind="template: {name: $parent.dynTemplate, data: 'item' }">
</td>
<td data-bind="template: {name: $parent.dynTemplate, data: 'cost' }">
</td>
<td data-bind="template: {name: $parent.dynTemplate, data: 'amount' }">
</td>
</tr>
</tbody>
<script id="inpTmp" type="text/html">
<input data-bind="value: $parent[$data]" />
</script>
<script id="dispTmp" type="text/html">
<p data-bind="text: $parent[$data]"></p>
</script>
self.dynTemplate= function(field, context) {
var value = context.$parent[field]();
if(value=== 0 || value=== '') {
return 'inpTmp';
}
return 'dispTmp';
};
See fiddle http://jsfiddle.net/tabalinas/VXXqr/5/

Related

How to bind a ko.obersavableArray that is nested in an object

I have my knockout page hub, and I need a ko.obeservableArray nested in a ko.observable object, this is where I define them:
function IncomeDeclarationHub() {
//data comes from a ajax call.
self.myIncomeDeclarationViewModel = ko.observable(new IncomeDeclarationViewModel(data));
}
function IncomeDeclarationViewModel(data) {
var self = this;
self.retentionAmount = ko.observable();
self.taxableMonth = ko.observable();
self.incDecDetGroViewModels = ko.observableArray();
if (data != null) {
var arrayLenght = data.IncDecDetGroViewModels.length;
for (var i = 0; i < arrayLenght; i++) {
var myObject = new IncomeDecDetGroViewModel(data.IncDecDetGroViewModels[i]);
self.incDecDetGroViewModels.push(myObject);
}
}
}
And this is my HTML code:
<span class="label">
Retention Amount
</span>
<input data-bind="value: myIncomeDeclarationViewModel.retentionAmount" />
<table>
<tbody data-bind="foreach: myIncomeDeclarationViewModel.incDecDetGroViewModels">
...
</tbody>
</table>
Ok so the thing is that incDecDetGroViewModels never gets populated, I used to have that ko.obersableArray outside the object, and it worked fine, now that I inserted it in my object myIncomeDeclarationViewModel is not populating the html table. Do I need to call it in a different way at the data-bind
myIncomeDeclarationViewModel is an observable, so you have to unwrap it to access it's properties. Add parenthesis to unwrap it (access the observable's underlying value) like this:
<span class="label">
Retention Amount
</span>
<input data-bind="value: myIncomeDeclarationViewModel().retentionAmount" />
<table>
<tbody data-bind="foreach: myIncomeDeclarationViewModel().incDecDetGroViewModels">
...
</tbody>
</table>
Here's a working jsFiddle based on your example
JsFiddle
well previously you can access just becoz it is in scope but right now you done some nesting so you just need to some looping in your view part to get that .
Something like this may be :
<table data-bind="foreach:myIncomeDeclarationViewModel">
<tbody data-bind="foreach:$data.incDecDetGroViewModels">
...
</tbody>
</table>
You can also ContainerLess foreach if you looking for something different like :
<!-- ko foreach:myIncomeDeclarationViewModel -->
//your table code
<!--/ko-->
I hope this solves the riddle .

Update viewmodel when value changes in dynamically added input in knockout

I have been working on KnockoutJS since two weeks and I am trying to add inline editing in a grid using KnockOutJS and jQuery. My html:
<table>
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Excerpts</th>
<th>Content</th>
</tr>
</thead>
<tbody data-bind="foreach: Articles">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: Excerpts, event: { dblclick: $root.editField }"></td>
<td data-bind="text: Excerpts, event: { dblclick: $root.editField }"></td>
<td data-bind="text: Content, event: { dblclick: $root.editField }"></td>
</tr>
</tbody>
My JS:
function Articles(Articles) {
this.id = ko.observable(Articles.id);
this.Title = ko.observable(Articles.Title);
this.Excerpts = ko.observable(Articles.Excerpts);
this.Content = ko.observable(Articles.Content);
}
var ViewModel = {
Articles: ko.observableArray
([new Articles(id = 1, Title = "Title1", Excerpts = "Excerpts1", Content = "Content1")]),
loadArticles: function () {
var self = this;
self.Articles(Articles);
},
editField: function (d, e) {
var currentEle = $(e.target);
var value = $(e.target).html();
$(currentEle).html('<input class="thVal" type="text" value="' + value + '" />');
$(currentEle).find('input').focus();
$(currentEle).find('input').keyup(function (event) {
if (event.keyCode == 13) {
$(currentEle).html($(currentEle).find('input').val().trim());
//CallAjaxWithData('/MTB_Articles/EditArticle', 'POST', ko.toJSON(d), null, null); // To update data in server
}
});
$(document).click(function () {
if ($(currentEle).find('input').val() != null) {
$(currentEle).html($(currentEle).find('input').val().trim());
//CallAjaxWithData('/MTB_Articles/EditArticle', 'POST', ko.toJSON(d), null, null); // To update data in server
}
});
}
}
ko.applyBindings(ViewModel);
ViewModel.loadArticles();
Whenever the user double clicks on any td in the grid, I am adding an input field dynamically using the editField function and binding the updated value to the td again when user presses enter key or clicks somewhere else on the page. The parameter d in the editField function gives the current viewmodel object. I have to update the corresponding value in the parameter d when user edits the value in a particular column, convert d to json format and send it to server via ajax call to be updated in the database. The changes made by the user should be reflected in the view model( the parameter d). So how can we update the view model using dynamically added controls?
JSFiddle for this
You can do it in a more 'ko-ish' way that will make it easier for you.
KO is mostly declarative, and you're mixing declarative and procedural (jQuery) code.
To make it declarative, and much easier to implement, do the following:
add an editing observable property to your Articles. Initialize it to false
inside the <td>'s show either the text, or a data-bound input, depending on the value of the editing observable property
use the double click event, to set editing to true
use the enter key press to do what you need (ajax) with the values in your model, and set the editing to false again
You can do it like this:
<td>
<!-- ko ifnot: editing, text: Excerpts --><!-- /ko -->
<!-- ko if: editing -->
<input class="thVal" type="text" data-bind="value: Excerpts" />
<!--- /ko -->
</td>
Or even shorter:
<td>
<!-- ko ifnot: editing, text: Excerpts --><!-- /ko -->
<input class="thVal" type="text" data-bind="value: Excerpts, if: editing" />
</td>

knockout binding for simple object not working

I can make knockout work for array or simple object but not for a simple json object.
Can someone provide pointers plz.
http://jsfiddle.net/aECM9/
html:
<table>
<tbody>
<tr><td>customer Number: </td><td data-bind="text: customerData().reference"></td></tr>
<tr><td>customer Name: </td><td data-bind="text: customerData().name"></td></tr>
<tr><td>customer Manager Code: </td><td data-bind="text: customerData().managerCode"></td></tr>
</tbody>
</table>
javascript:
var viewModel = {
customerData: ko.observable()
};
ko.applyBindings(viewModel);
viewModel.customerData({
reference: 123,
name: "bob",
managerCode: 321
});
Look at the console. When you call applyBindings it throws an error because it tries to access customerData().reference but customerData() is undefined at this point. Move your .applyBindings below where you assign the customerData value.
See: http://jsfiddle.net/aECM9/2/
Another alternative would be to use an if binding to stop knockout from trying to bind properties on customerData() before it has a value:
<tbody data-bind="if: customerData()">
Now nothing after this to the closing </tbody> tag will be rendered if customerData() is falsy.
http://jsfiddle.net/aECM9/3/
I used tbody, but you could use any appropriate parent tag or even a virtual tag:
<!-- ko if: customerData() -->

Accessing object fields in observable array of objects. KnockoutJS

I have a table, that is filled through data-binds with data from observable array of objects (persons). When i click a certain cell of table, index of a line, and index of a cell is written into variables "self.currentLine" and "self.currentCell", while input appears above with 100% width and 100% height, covering that data with itself.
Is there a possibility to get access to certain field of certain object in observable array, using only indexes of fields instead of using field names? (ex. not self.persons[0]'name', but self.persons[0][0])
Here is a code(JS):
function person(fullname, age, sex, married)
{
this.name = ko.observable(fullname); //string, only observable field, while i'm trying to get this working properly.
this.age = age; //Data
this.sex = sex; //string
this.married = married; //bool
};
function personViewModel()
{
var self = this;
self.currentLine = ko.observable();
self.currentCell = ko.observable();
self.columnNames = ko.observableArray([
'Name',
'Age',
'Sex',
'Married'
]);
self.persons = ko.observableArray([...]);
};
self.setLine = function(index)
{
self.currentLine(index);
};
self.setCell= function(cellIndex)
{
self.currentCell(cellIndex);
};
};
ko.applyBindings(new personViewModel());
And HTML code i use:
<table>
<thead data-bind="template: { name: 'tableHeader', data: columnNames }" />
<tbody data-bind="template: { name: 'tableContent', foreach: persons }" />
</table>
<script id="tableHeader" type="text/html">
<tr data-bind="foreach: $data">
<td data-bind="text: $data,
css: { 'active': $root.currentItem() == $data }">
</td>
</tr>
</script>
<script id="tableContent" type="text/html">
<tr data-bind="click: $root.setLine.bind($data, $index())">
<td data-bind="click: $root.setCell.bind($data, $element.cellIndex)">
<span data-bind="text: name"></span>
<input type="text" data-bind="visible: $root.currentCell() == 0 && $index() == $root.currentLine(),
value: name"/> <!--fixed-->
</td>
</tr>
</script>
In html i set input visible according to cell clicked in the table. So now i need to pass a value of a cell to an input, so i could edit this data.
UPDATE: as usual, i've forgot to put round brackets '()' after value: name() in input. But here comes second question. As i know value must be automaticly changed while input loses his focus. But mine doesn't change...
Use the input value binding to to pass a value of a cell:
AFAIK, there is no way to access a field with its supposed index, to read a field from an object in observableArray you may use this syntax :
persons()[index].fieldName(), given that the field is observable also.
hope it help.

KnockoutJS Bindings With Nested Templates

I'm having a problem with nested bindings with Knockout.JS
For example if I have the following in say an app.js file:
var UserModel = function() {
this.writeups = ko.observableArray([]);
}
var WriteupModel = function() {
this.type = 'some type';
}
var MyViewModel = function() {
this.newUser = new UserModel();
this.selectedUser = ko.observable(this.newUser);
this.selectedUser().writeups().push(new WriteupModel());
}
ko.applyBindings(new MyViewModel());
and the following for a view:
<div id="empReportView" data-bind="template: { name: 'empTmpl', data: selectedUser }"></div>
<script type="text/html" id="empTmpl">
<table>
<tbody data-bind="template: { name: 'empWuItem', foreach: $data.writeups } ">
</tbody>
</table>
</script>
<script type="text/html" id="empWuItem">
<tr>
<td data-bind="text: type"></td>
</tr>
</script>
Whenever another WriteupModel is pushed onto the writeups array belonging to the selectedUser the table doesn't update. This is a simplified version of what I'm trying to accomplish but it's to be assumed that when they create a writeup it should update the write-ups table based on the new information.
I'm new to Knockout so any help would be appreciated!
Thanks.
-=-= Edit 1 =-=-
One thing to note, if you reload the binding for the selectedUser it will spit out the empWuItem template for the added writeup. This just seems inefficient as the bindings should trigger when the WriteUp is added to the writeups observable array in the UserModel without have to "re-assign" the selectedUser property in the view model.
Push is a property of observable array:
this.selectedUser().writeups().push(new WriteupModel())
should be
this.selectedUser().writeups.push(new WriteupModel());

Categories

Resources