is it possible to fire a function when observable array is modified? my goal here is to notify me if my observable array is modified to do some logics on my current application
this is my view model it has a observable array inside it
WT.BM.BarsViewModel = function () {
var self = this;
self.BarsDataHolder = ko.observableArray([]);
};
i just want to fire a function to notify me if self.BarsDataHolder has been modified
any ideas?
You can subscribe to any ko.observable or ko.observable array.
WT.BM.BarsViewModel.BarsDataHolder.subscribe(function(newArray) {
console.dir(newArray);
});
you can find this in the knockout documentation on the observables page
Related
I have a model defined as:
var TestModel = function () {
var self = this;
self.Item = ko.mapping.fromJS(new Item());
self.ItemList = ko.observableArray();
};
var Model = new TestModel();
ko.applyBindings(Model);
After an AJAX call I fill the array with:
ko.mapping.fromJS(data, {}, Model.ItemList);
The array is displayed in the screen and when the user selects an item, a modal dialog is opened to edit that item. The same modal dialog can be opened to add a new item. That modal has a data-bind="with: Item", so I can do something like this:
function add() {
ko.mapping.fromJS(new Item(), {}, Model.Commission); //Empty object
$('#dialog').dialog('open');
};
function saveAdd() {
//Clone the properties...
Model.ItemList.push(ko.mapping.fromJS(ko.mapping.toJS(Model.Item)));
}
function edit(i) {
//Clone properties!
ko.mapping.fromJS(ko.mapping.fromJS(ko.mapping.toJS(Model.ItemList()[i])), {}, Model.Item);
$('#dialog').dialog('open');
}
function saveEdit(i) {
//Clone properties!!!!
Model.ItemList()[i] = ko.mapping.fromJS(ko.mapping.toJS(Model.Item));
}
I want to avoid cloning the properties every time I need to assign the values of one observable to another one. I understand that if I manage to tell Knockout that the Model.Item is some Model.ItemList()[i], the objects references should make sure the changes in the UI reflect directly to my observableArray item, but I cannot just assing it as Model.Item = Model.ItemList()[i]; because that breaks the binding between the original Model.Item object and the view [*1].
Is there a way to assign an observable to another observable that's binded to the view, and maintain the references?
Failing to do that, is the a better way to do this? Basicaly a page where you store the list of items in an observableArray and then add/edit in another piece of HTML.
Edit: Explanation for [*1]
I cannot find the stackoverflow question where this was explained, but it was something like this:
When we do ko.applyBindings(Model);, the observable objects inside Model get binded to the view. At this point the object referenced by Model.Item is binded, so if I do Model.Item = Model.ItemList()[i]; the new object being referenced by Model.Item is not binded to anything. The original object (in memory) is still the one binded to the view, just that there are no references to it now.
If you make Model.Item an observable, with the mapped object inside of it, then you can set the observable's contents to the new item. The reference to the observable made by knockout remains intact because you're just updating the contents and not the property itself.
var TestModel = function () {
var self = this;
self.Item = ko.observable(ko.mapping.fromJS(new Item()));
self.ItemList = ko.observableArray();
};
...
Model.Item(Model.ItemList()[i]);
I have a list of reocrds fetching from the server, and converted to knockout observableArray by ko.mapping.fromJS.
self.items = ko.observableArray([]);
$.getJSON('/api/getdata', function(data) {
var mappedTasks = $.map(dataFromServer, function(cls) {
return ko.mapping.fromJS(cls);
});
self.items(mappedTasks);
}
In certain conditions I want some properties to be set as null and bind to the UI. Later upon users action I want to revert the old existing data instead of null. Is there way to attach a property oldValue to observable, so that I can preserve the old value and set it back when necessary. Can knockout extend be used for holding the data? Any help on this greatly appreciated,
George
When you're mapping the data from the server to an object with knockout observables, you can also define additional observables on the same object.
self.items = ko.observableArray([]);
$.getJSON('/api/getdata', function(data) {
var mappedTasks = $.map(dataFromServer, function(cls) {
var item = ko.mapping.fromJS(cls);
item.AdditionalValue1 = ko.observable(0);
item.AdditionalValue2 = ko.observable('test');
return item;
});
self.items(mappedTasks);
}
If you would like to change the way the object that you're binding to is structured, it may be easier to build mapping from the server side data to your client side view model.
The code can be found on http://jsfiddle.net/6kMWM/10/.
In the FilterViewModel I am creating an observable object.
var FilterViewModel= ko.observable({
Name: ko.observable("test"),
Code: ko.observable("test"),
Number: ko.observable("test")
});
Then in the BankViewModel I am running a computed method which when any of the input boxes change it should fire.
var BankViewModel = function(){
var self = this;
self.Collection = ko.observableArray([]),
self.filteredCollection = ko.computed(function () {
var filter = FilterViewModel();
alert("invoked");
}),
self.add = function (bankObject) {
self.Collection.push(bankObject);
},
self.isSelected = function (data) {
$('.bank').css('background-color', 'white');
$('.bank p').css('color', '#333');
$('#bank-row-' + data.Code()).css('background-color', 'blue');
$('#bank-row-' + data.Code()+" p").css('color', 'white');
}
};
For some reason it is not being fired. Can any one help me out please.
Thank-you in advanced
There are several problems with your fiddle:
You bind to values instead of observables. When you write <input
type="text" data-bind="value: global.filterViewModel().Name()"
placeholder="Filter by Name"/> ko uses the value of global.filterViewModel().Name not the observable. Thus there is no real binding (updating ko will not update the interface, updating the interface will not update ko). You need to remove the last parenthesis: global.filterViewModel().Name
You put Name instead of Code and vice versa in the binding
You subscribed to FilterViewModel's changes, but not it's child observable changes. To do this, include the evaluation of the child observables in your computed observable:
-
self.filteredCollection = ko.computed(function () {
var filter = FilterViewModel();
if (filter.Name() != 'testname')
alert("name");
if (filter.Code() != 'testcode')
alert("code");
if (filter.Number() != 'testnumber')
alert("number");
}),
You can test here http://jsfiddle.net/b37tu/1/
You need to instantiate your view model with a statement like this:
var model = new BankViewModel();
When the model is instantiated, its computed methods are evaluated initially. This is where your alert will fire.
But, i assume you want your computed method to subscribe to Name, Code and Number properties. In this case, you need to read these properties at least once in your computed method.
This is how dependency tracking works in KO. It records all the observables that you mention in your computed function and logs them. And your computed is evaluated again when one of those observables are updated.
For your code up there, your computed will subscribe to FilterViewModel but not to its individual properties Name, Code and Number. So if you need to subscribe for the changes in these individual properties, you have to mention them individually in your computed function. Well, it wouldn't make sense to have your computed to subscribe to them if they don't affect your computed function anyway.
If you want to learn how the process works, please take a look at its documentation:
http://knockoutjs.com/documentation/computedObservables.html
I am wondering if there are any pointers on the best way of "fetching" and then binding a collection of data to a view within Backbone.js.
I'm populating my collection with the async fetch operation and on success binding the results to a template to display on the page. As the async fetch operation executes off the main thread, I one loses reference to the backbone view object (SectionsView in this case). As this is the case I cannot reference the $el to append results. I am forced to create another DOM reference on the page to inject results. This works but I'm not happy with the fact that
I've lost reference to my view when async fetch is executed, is there a cleaner way of implementing this ? I feel that I'm missing something...Any pointers would be appreciated.
SectionItem = Backbone.Model.extend({ Title: '' });
SectionList = Backbone.Collection.extend({
model: SectionItem,
url: 'http://xxx/api/Sections',
parse: function (response) {
_(response).each(function (dataItem) {
var section = new SectionItem();
section.set('Title', dataItem);
this.push(section);
}, this);
return this.models;
}
});
//Views---
var SectionsView = Backbone.View.extend(
{
tagName : 'ul',
initialize: function () {
_.bindAll(this, 'fetchSuccess');
},
template: _.template($('#sections-template').html()),
render: function () {
var sections = new SectionList();
sections.fetch({ success: function () { this.SectionsView.prototype.fetchSuccess(sections); } }); //<----NOT SURE IF THIS IS THE BEST WAY OF CALLING fetchSuccess?
return this;
},
fetchSuccess: function (sections) {
console.log('sections ' + JSON.stringify(sections));
var data = this.template({
sections: sections.toJSON()
});
console.log(this.$el); //<-- this returns undefined ???
$('#section-links').append(data); //<--reference independent DOM div element to append results
}
}
);
darthal, why did you re-implement parse? It seems to do exactly what Backbone does by default (receive an array of models from the AJAX call and create the models + add them to the collection).
Now on to your question... you are supposed to use the reset event of the Collection to do the rendering. You also have an add and remove when single instances are added or deleted, but a fetch will reset the collection (remove all then add all) and will only trigger one event, reset, not many delete/add.
So in your initialize:
this.collection.on("reset", this.fetchSuccess, this);
If you are wondering where the this.collection is coming from, it's a param you need to give to your view when you create it, you can pass either a model or a collection or both and they will automatically be added to the object (the view)'s context. The value of this param should be an instance of SectionList.
You'll also have to update fetchSuccess to rely on this.collection instead of some parameters. Backbone collections provide the .each method if you need to iterate over all the models to do stuff like appending HTML to the DOM.
In most cases you don't need a fetchSuccess, you should just use your render: when the collection is ready (on 'reset'), render the DOM based on the collection.
So to summarize the most general pattern:
The collection should be independent from the view: you give the collection as a param to the view creation, the collection shouldn't be created from a specific view.
You bind the View to the collection's reset event (+add, remove if you need) to run a render()
this.collection.on("reset", this.render, this);
You do a fetch on the collection, anytime (probably when you init your app).
A typical code to start the app would look something like this:
var sections = new SectionList();
var sectionsView = new SectionsView({collection: sections});
sections.fetch();
Because you bound the reset event in the view's initialize, you don't need to worry about anything, the view's render() will run after the fetch.
I am using the click event on a button to set the value of an item that was generated using a foreach.
<table>
<tbody data-bind="foreach: Employees">
<a data-bind="click:$parent.delete()">
..
in my delete function I am setting the value but it doesn't update the screen
Delete :(emp) {
emp.active=false;
}
When I create I am setting all the individual properties as observable but seems like they are not when in the foreach loop.
Update
Employees is filtered.computed
var Employees=ko.computed(function() {
return ko.utils.arrayFilter(AllEmployees(), function (empid) {
return empid.ID == filter();
});
When you get/set observables you need to call them like this:
var val = obj.prop(); //Getter
obj.prop(false); //Setter
One other issue you have is that you are using parenthesis in your click binding. Remember that Knockout bindings are just javascript, so it will actually execute that expression when it binds.
You need to get rid of those parenthesis or emp will be undefined initially.
UPDATE:
I've updated this jsFiddle to include three filtered lists similar to what you have shown above. You can see that using a filtered list via a computed has no bearing on how knockout handles the bindings, and the UI updates seamlessly.
http://jsfiddle.net/jwcarroll/ceRPK/
To set an observable, you have to call it (since observables are implemented as functions):
emp.active(false);
Your method simply overwrites the observable.
Knockout subscribes to the observable array, but not to each observable within that array. If you want to subscribe to individual properties you need to subscribe manually by using myObservable.subscribe()
Knockout subscribes to the observable array, but not to each observable within that array. If you want to subscribe to individual properties you need to subscribe manually using myObservable.subscribe()
Edit
If you are trying to have your computed keep track of what should be in your computed you can do so like this -
var allEmployees = ko.observableArray([my data goes here]);
var Employees=ko.computed(function() {
return ko.utils.arrayFilter(allEmployees(), function (emp) {
return emp.active === true;
});
});
That works if active is not an observable property of each allEmployees(). If it is an observable just change that to -
var allEmployees = ko.observableArray([my data goes here]);
var Employees=ko.computed(function() {
return ko.utils.arrayFilter(allEmployees(), function (emp) {
return emp.active();
});
});