knockout observable array is not updating view on removing elements from array - javascript

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

Related

orderBy not working with pagination and filters

<table>
<thead>
<tr>
<th class="col-md-3" ng-click="sortDirection = !sortDirection">Created At</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="food in foods | filter:foodFilter | itemsPerPage:pageSize | orderBy:'created_at_date'">
<td class="col-md-"> {{food.created_at_date}} </td>
</tbody>
</table>
<dir-pagination-controls
max-size= 7
boundary-links="true">
</dir-pagination-controls>
This is only a snippet of my code but its too large to put up. Everything is working except only some of the created_at_date is in order. When I click on a different filter to add in or remove data depending on that filter, only some of it is entered into the correct place. My main question is: is there someway to sort all of the dates properly while still allowing the everything else function as well? All help is welcome, Thanks
(function () {
"use strict";
App.controller('foodsController', ['$scope'],
function($scope) {
$scope.sortDirection = true;
In your controller you can add the method to order the array before you loop over them.
Assuming your foods array has an array of objects, each with a key of created_at_date and a value:
App.controller('foodsController', function($scope) {
$scope.foods = [{
created_at_date: 6791234
}, {
created_at_date: 9837245
}, {
created_at_date: 1234755
}];
// create a method exposed to the scope for your template.
$scope.orderBy = function(key, array) {
// now you've received the array, you can sort it on the key in question.
var sorted = array.sort(function(a, b) {
return a[key] - b[key];
});
return sorted;
}
});
Now on your template, you have a method available to sort your values for you:
<table>
<thead>
<tr>
<th class="col-md-3" ng-click="sortDirection = !sortDirection">Created At</th>
</tr>
</thead>
<tbody>
<tr dir-paginate="food in orderBy('created_at_date', foods) | filter:foodFilter | itemsPerPage:pageSize">
<td class="col-md-"> {{food.created_at_date}} </td>
</tr>
</tbody>
</table>
The orderBy method which we've created on your controller returns an array, but it's just sorted by the key that's sent in as the first argument of the function. The second argument is the original array you're trying to sort.
At least this way you can check if you remove all your other filters to see if it's ordered correctly, if then after you add them back in it changes it's because those filters are also changing the order.

Formatting data before render it

I am displaying some data in the view, but I need to formatted first, I was doing something like
val.toFixed(2) and that is OK, it works but the problem is that val sometimes comes with letters, and toFixed(2) is not taking that into account so is not displaying the letters.
So I need something that takes into account letters and numbers, the letters don't have to change, only the numbers which comes like 234235.345345435, and obviously I need it like this 234235.34.
Here is some of the code I am using
<table>
<tr>
<th ng-repeat='header in headers'>{{header.th}}</th>
</tr>
<tr>
<td ng-repeat='data in headers'>
<div ng-repeat='inner in data.td'>
<span ng-repeat='(prop, val) in inner'>{{val.toFixed(2)}}</span>
</div>
</td>
</tr>
</table>
and in the controller
$scope.LoadMyJson = function() {
for (var s in myJson){
$scope.data.push(s);
if ($scope.headers.length < 1)
for (var prop in myJson[s]){
prop.data = [];
$scope.headers.push({th:prop, td: []});
}
}
for (var s in $scope.data){
for (var prop in $scope.headers){
var header = $scope.headers[prop].th;
var data = myJson[$scope.data[s]][header];
$scope.headers[prop].td.push(data);
console.log($scope.headers[prop].td);
}
}
};
and I prepared this Fiddle
the way it is right now, is displaying the table properly, but as you see, the table is missing the name, it is because of the toFixed method.
So, what can I do ?
Create a custom filter to use on your template.
<table>
<tr>
<th ng-repeat='header in headers'>{{header.th}}</th>
</tr>
<tr>
<td ng-repeat='data in headers'>
<div ng-repeat='inner in data.td'>
<span ng-repeat='(prop, val) in inner'>{{val|formatValue}}</span>
</div>
</td>
</tr>
</table>
angular.module('whatever').filter('formatValue', function () {
return function (value) {
if (isNaN(parseFloat(value))) {
return value;
}
return parseFloat(value).toFixed(2);
}
});
You can try this :
That is a clean way to render formated data in view using angularjs as MVC
frontend framework :
Create a filter in your angular application.
Include your filter in your index.html.
use your filter like this : {{somedata | filterName}}
That is a simple angular filter to solve your problem, hope it will help you :
angular.module('app')
.filter('formatHeader', function() {
return function(data) {
if(angular.isNumber(data)) {
return data.toFixed(2);
}
return data;
}
});
And us it like this :
<table>
<tr>
<th ng-repeat='header in headers'>{{header.th}}</th>
</tr>
<tr>
<td ng-repeat='data in headers'>
<div ng-repeat='inner in data.td'>
<span ng-repeat='(prop, val) in inner'>{{val | formatHeader}}</span>
</div>
</td>
</tr>
You can take a look about these references :
angular functions
filter doc.
angular tutorials

Decrement a value after clicking a button in the same row - knockout

I have a table which I fill with some numbers. There is a button in each row. After clicking this button I would like to decrement a counter in this row. How to to this with knockout?
<div class="panel panel-default">
<div class=panel-heading>Title</div>
<table class=table>
<thead>
<tr>
<th>Counter</th>
<th>Increment</th>
</tr>
</thead>
<tbody data-bind="foreach: records">
<tr>
<td data-bind="text: counter"></td>
<td> <input type="button" value="increment" data-bind=??? ></td>
</tr>
</tbody>
</table>
</div>
<script>
function AppViewModel() {
var self = this;
self.records = ko.observableArray([]);
$.getJSON("/data", function(data) {
self.records(data);
})
//function to decrement
}
ko.applyBindings(new AppViewModel());
</script>
I would do it this way:
Process data you get from server, turn counter property into observable and add function to decrement counter property
Restructure you code a little so viewmodel will be created by the time of ajax request
Move applyBindings call to ajax callback so it would fire when everything has been loaded
So the code would look like:
<tr>
<td data-bind="text: counter"></td>
<td> <input type="button" value="decrement" data-bind="click: decrement"></td>
</tr>
function AppViewModel() {
var self = this;
self.records = ko.observableArray([]);
}
var vm = new AppViewModel();
// load data from server
$.getJSON("/data", function(data) {
data.forEach( function(item) {
// make counter observable
item.counter = ko.observable(item.counter);
// add function to decrement
item.decrement = function() {
this.counter( this.counter()-1 );
}
})
// load array into viewmodel
vm.records(data);
// apply bindings when all obervables have been declared
ko.applyBindings(vm);
})
Check demo: Fiddle
I prefer to initialize and bind my viewmodel right away, but agree with the other poster that you need an observable.
Here is a solution that continues to create and bind your viewmodel right away, as in your original example, but instead of an array of the raw records you receive back it converts them into their own little model objects that have an observable for the counter and an increment function that can be data bound too. This decouples your data load from the life of the viewmodel, so if you wanted to add a button to load fresh data to overwrite it or anything like that, it's just another call to getData().
<!-- ... -->
<tbody data-bind="foreach: records">
<tr>
<td data-bind="text: counter"></td>
<td> <input type="button" value="increment" data-bind="click: increment" ></td>
</tr>
</tbody>
<!-- ... -->
<script>
function AppViewModel() {
var self = this;
self.records = ko.observableArray([]);
self.getData = function(){ /* ... */ };
self.getFakeData = function(){
var data = [{ counter: 1 }, { counter: 2}, { counter: 3 }];
var freshData = data.map(function(record){
return new AppRecord(record);
});
self.records(freshData);
};
}
function AppRecord(rawRecord) {
var self = this;
self.counter = ko.observable(rawRecord.counter);
self.increment = function(){
self.counter(self.counter() + 1);
};
}
var vm = new AppViewModel();
vm.getFakeData(); // replace with getData()
ko.applyBindings(vm);
</script>
Fiddle, with a getFakeData with sample data: https://jsfiddle.net/4hxyarLa/1/
If you are going to have a lot of rows and are concerned abut memory, you could put the increment function in a prototype method for the AppRecord and access the record via a parameter on the function, or you could add the function to the AppViewModel and bind to $parent.increment to call it and access the record via parameter passed to that function to increment it's counter property.

Knockout.js: Updating objects loaded with mapping plugin

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

knockout observable array update not updating the UI

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/

Categories

Resources