Removing item from observablearray in Knockoutjs - javascript

I am trying to learn Knockoutjs and I am having some issues with adding and removing objects to an observablearray.
I have the following viewmodel in which I fetch some data from a webservice and populate some html. This works fine. But what does not work is removing items from the observablearray since it seems the click-event does not call removeEmployee.
function EmployeeViewModel(){
var self=this;
self.employees=ko.observableArray();
self.removeEmployee = function(item) {
self.employees.remove(item);
};
}
function success(data) {
EmployeeViewModel.employees=ko.mapping.fromJS(data);
ko.applyBindings(EmployeeViewModel);
};
ApiCall({
data: {
[get data]
},
onSuccess: function(data){success(data.result)}
});
and the following html:
<div data-bind="foreach: employees">
<h2>Hello, <span data-bind="text: full_name"> </span>!</h2>
<button data-bind="click: $parent.removeEmployee">Remove</button>
</div>
I have tried setting up a jsfiddle here: http://jsfiddle.net/8yX5M/ in which removing items does work. The difference is, that in the jsfiddle the items are not fetched from an external source and that I use removeEmployee rather than $parent.removeEmployee.
Any ideas why the non-jsfiddle version is not working ?
thanks
Thomas

Because your success function isn't setting the value of the observableArray, it is resetting the object's definition -
function success(data) {
EmployeeViewModel.employees(ko.mapping.fromJS(data));
ko.applyBindings(EmployeeViewModel);
};
Use the setter function on EmployeeViewModel.employees by using the () and passing in a value.

Turned out it was because I did not instantiate EmployeeViewModel to a global variable before mapping the data.
The working code is
'use strict';
var employeeViewModel=new EmployeeModel();
function EmployeeModel(){
var self=this;
self.employees=ko.observableArray();
self.removeEmployee = function(item) {
self.employees.remove(item);
};
}
function getEmployeesSuccess(data,controlIds) {
employeeViewModel.employees=ko.mapping.fromJS(data);
var _i=0;
for (var _total=controlIds.length; _i < _total; _i++) {
ko.applyBindings(employeeViewModel,$("#"+controlIds[_i])[0]);
}
};
/* Databinds employeedata to an array of controls */
/* controlIds=Array of controls*/
function DataBindEmployees(controlIds)
{
ApiCall({
data: {
[get data]
},
onSuccess: function(data){getEmployeesSuccess(data.result, controlIds)} });
};

Related

AngularJs promise in forEach

I have method for getting data from server.
And I use it in foreach, and after it need bind him to $scope variable.
Like this:
var qualityMix = [];
var engagementMix = [];
angular.forEach(versions, function (versions) {
qualityMix.push(qualityScoreByTimeWithAppVerPromise(versions.version));
engagementMix.push(engagementByTimeWithAppVerPromise(versions.version));
});
$scope.qualityScoreByTimeMix = function () {
return $timeout(function () {
return $q.all(qualityMix).then(function (data) {
return {series: data};
});
});
};
$scope.engagementTimeMix = function () {
return $q.all(engagementMix).then(function (data) {
return {series: data};
});
};
qualityScoreByTimeWithAppVerPromise and engagementByTimeWithAppVerPromise it is functions for getting data from server. Then $scope.engagementTimeMix and $scope.qualityScoreByTimeMix need return functions with promise (is okay).
This code working but not always, some times I catch exceptions $scope.xxx is not a function.
I don't know how to fix it. Help me please. Thanks a lot!
UPD
It is code for build charts.
<div class="section">
<highchart id="mix_quality_score_by_time" type="area" data="qualityScoreByTimeMix"
chart-style="qualityScoreMixChartStyle"></highchart>
</div>
And my directive I invoke in other page, like this:
<compare-versions id="compare_versions_panel" start-day="timeFilter.startDay()"></compare-versions>
$scope.xxx is not a function
I mean what I catch message in chrome console what
$scope.engagementTimeMix and $scope.qualityScoreByTimeMix it not a function
You can use ng-if and draw graph when you already have value
example:
<div class="section" ng-if="qualityScoreByTimeMix">
<highchart id="mix_quality_score_by_time" type="area" data="qualityScoreByTimeMix" chart-style="qualityScoreMixChartStyle"></highchart>
</div>

Knockoutjs components callback after loaded all items in foreach

I have a component in knockoutjs for creating list of albums from Facebook using javascript graph api.
How can I call function after render all items?
I have tried foreach: {data: items, afterRender: afterItemsLoaded} - method afterItemsLoaded is called for every row not after render all items.
I have tried to use following code:
HTML:
<h1>List: </h1>
<list></list>
Javascript:
var countOfItemLoaded = 0;
ko.components.register('list', {
viewModel: function() {
},
template: '<div data-bind="foreach: {data: items, afterRender: afterItemsLoaded}"><div data-bind="text: $data"></div></div>'
});
var viewModel = function() {
var self = this;
self.items = ko.observableArray([]);
$.getJSON("https://graph.facebook.com/410564482402231/albums?fields=photos,name,photos.name", function (result) {
$.each(result.data,function(key, item) {
self.items.push(item.name);
});
});
self.afterItemsLoaded = function() {
countOfItemLoaded = countOfItemLoaded + 1;
alert("Loaded item " + countOfItemLoaded);
}
};
ko.applyBindings(viewModel);
Demo in jsFiddle:
jsFiddle link
I don't think support has been added for this yet.
See: https://github.com/knockout/knockout/pull/339
Ryan Niemeyer who works on Knockout did offer this solution to someone else almost 2 years ago
knockoutJS execute callback after foreach finishes rendering

Prevent digest of parent scopes in Angular

I am investigating Angular as a potential framework to use for an upcoming project. The test app I am making consists of an unordered list of which list items can be added to via an add link. Each list item contains a number of checkboxes. The number of checkboxes for a particular list item can be increased or decreased with plus and minus links next to each list item. Observe:
Hopefully that makes sense. Each checkbox has an ng-model directive binding the value of the checkbox to a property in an object. When the application is in the state above clicking any of the checkboxes fires six checks (one for each checkbox) -- the entire $scope of the root controller is checked for changes. Ideally, only the $scope of the relevant list item would be checked for changes. How can I accomplish this? I've attached my test code for reference. I've tried adding ng-click="$event.stopPropagation()" to the input node as well as to the li node but this appears to increase (double) the number of checks in the digest.
HTML:
<div ng-app ng-controller="App">
<ul>
<li ng-repeat="line in lines" ng-controller="LineController">
<input type="checkbox" ng-repeat="box in line.boxes" ng-model="box.on" />
<a ng-show="line.boxes.length > 1" ng-click="removeBox()">-</a>
<a ng-click="addBox()">+</a>
</li>
</ul>
<a ng-click="addLine()">Add</a>
</div>
JavaScript:
function App($scope) {
$scope.lines = [];
$scope.addLine = function () {
$scope.lines.push({
boxes: []
});
};
}
function LineController($scope) {
$scope.addBox = function () {
var box = {};
Object.defineProperty(box, 'on', {
enmerable: true,
get: function () {
console.log('Get!');
return this._on;
},
set: function (on) {
this._on = on;
}
});
$scope.line.boxes.push(box);
};
$scope.removeBox = function () {
$scope.line.boxes.pop();
};
}
If your concern is that AnguarJS dirty checking is going to be too slow for your needs, your question really need to be "is AngularJS going to be to slow to build X?" If X is a 3D game with lots of constant rendering then the answer is probably yes, AngularJS is not what you want. If X is "a scalable business/consumer oriented single page application", then the dirty checking algorithm is not going to be your bottle neck.
This SO answer has a good explanation of how data binding works and talks a bit about performance concerns.
What about to use $watch. We can invoke watch only for specific row. That means if you have 4x4 matrix (4 rows , 4 columns) on any checkbox state change we call watch 4 times
var webApp = angular.module('myModule', []);
webApp.controller('App', function ($scope) {
$scope.lines = [];
$scope.addLine = function () {
console.log("addLine");
$scope.lines.push({
boxes: []
});
};
});
webApp.controller('LineController', function ($scope) {
$scope.addBox = function () {
var box = {};
/* Object.defineProperty(box, 'on', {
enmerable: true,
get: function () {
console.log('Get!');
return this._on;
},
set: function (on) {
this._on = on;
}
});*/
$scope.line.boxes.push(box);
$scope.$watch(function () {
return $scope.line.boxes;
},
function (newValue, oldValue) {
if(newValue == oldValue) return;
console.log('Get new checkbox!');
}, true);
};
$scope.removeBox = function () {
$scope.line.boxes.pop();
};
});
Demo Fiddle

Durandal widget destory method or something equivalent

I am setting some setInterval values on my widget's controller code as follows:
define(['durandal/widget'],function (widget) {
var count = 0;
var intervals = [],
ctor = function (element, settings) {
this.settings = settings;
};
ctor.prototype.updateCount = function( ){
var interval = setInterval(function () {
count = count + 1;
return count;
}, 1000);
intervals.push(interval);
}
return ctor;
}
The above code is being run inside a forEach loop inside the view like:
<div data-bind="foreach: {data: settings.items}">
<span class="count" data-bind="text:$parent.updateCount()"></span>
</div>
What I would like to do is call the clearInterval method on all the items in the intervals array when the widget is destroyed or essentially removed from the dom. I know I could do this using the deactivate on a viewModel but from a reusability point of view, I would like the widget itself to handle the clearing of interval. Is there any way I could achieve this with the widget module in Durandal.
For anyone else looking into the same issue, there's a knockout way of achieving the same. Have a look at the following links https://github.com/BlueSpire/Durandal/issues/139 and https://groups.google.com/forum/#!topic/durandaljs/NqUkY-9us2g . The suggestion is to use:
ko.utils.domNodeDisposal.addDisposeCallback(element, callback)
As long as the widget is removed with JQuery's "remove" function, adding a custom event handler on this "remove" function should go like this:
var self = this;
var self.count = 0;
var self.intervals = [];
self.ctor = function (element, settings) {
$(element).on("remove", function () {
$.each(self.intervals, function(index, ival) {
clearInterval(ival);
});
});
this.settings = settings;
};
The problem is that if the widget is removed without JQuery, simply by manipulating the DOM, the event will not be fired. You could then implement the code for the DOMNodeRemoved event, but it's not gonna work for IE...
Edit: if you're using JQuery pre-1.9.1, you might want to check out the other answers to this question.

Knockout.js: Click binding only working first time I click?

Having some issues with a function in Knockout.js. Basically it is a menu where the first menu item "Översikt" should fetch a JSON array and populate the view.
The knockout code:
self.ongoingAuctions = ko.observableArray([]);
self.getOngoingAuctions = function(data) {
$.getJSON("assets/json/auctions.json", function(data) {
self.ongoingAuctions(data);
});
}
My click binding:
The problem is that this only works the first time I click on the menu item. The JSON doesn't get fetched the second, third, n:th time.
What am I doing wrong? Or have I misunderstood something?
Thanks in advance!
I have shared this fiddle for you that shows something else is wrong in your code that you havent not specified in the question:
It makes the call to the non existing json (in my case) every time you click
JS Fiddle to working code
var viewModel = function(){
var self = this;
self.ongoingAuctions = ko.observableArray([]);
self.getOngoingAuctions = function(data) {
$.getJSON("assets/json/auctions.json", function(data) {
self.ongoingAuctions(data);
});
}
self.setHeadline = function(){
console.log('set headline')
}
self.headline = function(){
console.log('headline');
}
}
var myVm = new viewModel();
ko.applyBindings(myVm);

Categories