When I update an observable element of Knockout the UI is not getting update
HTML
<tbody data-bind="foreach: students, visible: !students().isDeleted">
<tr>
<td data-bind="text: RollNo"></td>
<td data-bind="text: Name"></td>
<td data-bind="text: Phone"></td>
<td data-bind="text: Email"></td>
<td>
Edit
Delete
</td>
</tr>
</tbody>
Javascript
function StudentModel(student){
this.RollNo = ko.observable(student.RollNo);
this.Name = ko.observable(student.Name);
this.Phone = ko.observable(student.Phone);
this.Email = ko.observable(student.Email);
this.isDeleted = ko.observable(student.isDeleted);
this.isEdited = ko.observable(student.isEdited);
}
function StudentViewModel() {
//Array of students
this.students = ko.observableArray();
//Data retrived from the server
var listStudent= JSON.parse(#Html.Raw(ViewBag.StudentsList));;
var mappedStudents = $.map(listStudent, function(student) { return new StudentModel(student) });
//Map it to show the data
this.students(mappedStudents);
//Delete student
this.deleteStudent= function(student){
var stu = this.students()[this.students.indexOf(student)];
stu.isDeleted(true);
}.bind(this);
When I click on Delete the UI is not updated... When I try stu.isDeleted=true; still it does not works... Any help would be appreciated...
Fiddle
The problem is in the databinding.
visible: !students().isDeleted
This looks up the isDeleted property in the observable array. Which doesn't exist, so it is false and will always show all the elements.
If you want to hide the students the visible binding should be on the <tr>.
If you want to remove the student from the observable array you can just remove it.
http://jsfiddle.net/8fALs/2/
Related
I want to render a table containing a list of objects my server is sending me. I'm currently doing this:
<table>
<thead>
<tr>
<th>id</th>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody data-bind="foreach: services">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: name"></td>
<td data-bind="text: status"></td>
</tr>
</tbody>
</table>
And the Knockout.js binding part:
var mappedData = komapping.fromJSON('{{{ services }}}');
ko.applyBindings({services: mappedData});
services is a variable containing JSON data and the whole page is rendered with handlebars. So far so good. I'm able to render the data received in the table.
Now the problem: I'd like to receive a notification which tells me that the status of a service has changed, and update the corresponding object within mappedData. The problem is that mappedData seems pretty opaque and I'm unable to retrieve an object and update it given its id.
Help appreciated!
Your mappedData variable at this point will be a knockout array with a bunch of objects that contain knockout observables.
So all you have to do is change the status observable in the correct object from the array.
function updateServiceStatus(id, status) {
var service = mappedData().filter(function(e) { return e.id() == id; });
if (service.length) {
service[0].status(status);
}
}
To get the object, you can write a helper function that will retrieve for you a service object. You could do something like this (assuming mappedData is an observableArray and id observable) :
function get_service_by_id(service_id){
for(var i=0;i<mappedData().length;i++){
if (mappedData()[i].id() === service_id){
return mappedData()[i];
}
}
return false;
}
I'm trying to reimplement a student attendance example from the Udacity JavaScript Design Patterns course. So far I've managed to recreate the table and correctly populate it with some student data, however it appears when I change a checkbox value this isn't updated in the model.
For example, when I display
debugVM.studentList()[0].days();
in the console the output displays the initial attendance data instead of the current state of the checkboxes. A JSFiddle for this can be found here.
index.html
<table>
<thead>
<tr>
<th>Student</th>
<!-- ko foreach: attendance -->
<th data-bind="html: $data"></th>
<!-- /ko -->
</tr>
</thead>
<tbody data-bind="foreach: studentList">
<tr>
<td data-bind="html: name, click: $parent.debugStudent"></td>
<!-- ko foreach: days -->
<td>
<input type="checkbox" data-bind="value: $data, checkedValue: $data, checked: $data" />
</td>
<!-- /ko -->
</tr>
</tbody>
</table>
app.js
var Student = function(student){
this.name = ko.observable(student.name);
this.days = ko.observableArray(student.days);
};
var ViewModel = function() {
var self = this;
this.attendance = ko.observableArray([1,2,3,4,5,6,7,8,9,10,11,12]);
this.studentList = ko.observableArray([]);
students.forEach(function(student) {
self.studentList.push(new Student(student));
});
self.debugStudent = function() {
console.log(self.studentList()[0].days());
};
};
var debugVM = new ViewModel();
ko.applyBindings(debugVM);
From the knockout documentation
Key point: An observableArray tracks which objects are in the array,
not the state of those objects
In your case this means that days should be not observable array, but array of observables.
For example you can add new viewModel Day:
var Day = function(isChecked) {
this.isChecked = ko.observable(isChecked);
}
And set the days property like this
this.days = [];
for (var i = 0; i< student.days.length; i++) {
this.days.push(new Day(student.days[i] == 1));
}
See working fiddle
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>
Here is my view model code
var TopicsViewModel = function() {
var self = this;
var fakeTopicData =
[
];
self.createProfile = function () {
alert("came to create profile");
};
self.editProfile = function () {
alert("came to edit profile");
};
self.removeProfile = function (profile) {
alert("came to remove profile");
fakeTopicData.pop();
self.topicsArr(fakeTopicData);
};
var refresh = function() {
self.topicsArr = fakeTopicData;
};
self.topicsArr = ko.observableArray([]);
refresh();
};
ko.applyBindings(new TopicsViewModel());
Here is my html for the view:
<hr />
<hr />
<table class="table table-striped table-bordered table-condensed">
<tr >
<th>Area</th>
<th>Name</th>
<th>Link</th>
<th>Description</th>
<th>Why</th>
</tr>
<tbody data-bind="foreach : topicsArr">
<tr>
<td data-bind="text :area"> </td>
<td class=""><a data-bind="text:name, click:$parent.editProfile"></a></td>
<td data-bind="text:link"> </td>
<td data-bind="text:desc"> </td>
<td data-bind="text:why" ></td>
<td><button class="btn btn-mini btn-danger" data-bind="click:$parent.removeProfile">remove</button></td>
</tr>
</tbody>
</table>
<script src="~/Scripts/Topic.js"></script>
The view initially display all the Topics in my fakeData Array.
On clicking the remove Button, I am trying to remove an element from the array, and expected the view to refresh and not show the removed item any more. However the view still shows all the 3 topics.
Could someone please point to what I might be doing wrong.
I spend a long time researching the other similar queries on stackoverflow, but am still stuck. Thanks so much for any insight into this issue.
You are replacing your observable array called topicsarr with one which isn't observable in your refresh method...
Change
var refresh = function() {
self.topicsArr = fakeTopicData;
};
to
var refresh = function() {
self.topicsArr(fakeTopicData);
};
you have 2 issues in your code.
First, you are setting your observableArray topicsArr with non observableArray or normal array in refresh function. Instead use self.topicsArr(fakeTopicData)
Second, in function removeProfile you are using pop() to remove profile element. From KnockoutJS documentation:
myObservableArray.pop() removes the last value from the array and
returns it
So, it's better to use remove(item) and pass to it your profile element or loop through your array and remove that specific item
myObservableArray.remove(someItem) removes all values that equal
someItem and returns them as an array
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.