See JsFiddle here http://jsfiddle.net/WtgbV/2/
In words: I have some ajax call, and in the server's response I get some array of items (Items in knockout viewmodel)
I need to know that property name was changed in element with id==2 etc to save changes automatically on server (via POST request)
What is the simplest/easiest way to track changes in each element in Items array?
I co-wrote a component called DirtyFlag that detects changes in Knockout observables (or a set of them). You can grab in from my library called KoLite that you can grab off NuGet or GitHub.
https://github.com/CodeSeven/KoLite
https://nuget.org/packages/KoLite
dirtyFlag
// Your model
var Person = function () {
var self = this;
self.id = ko.observable();
self.firstName = ko.observable().extend({ required: true });
self.lastName = ko.observable().extend({ required: true });
self.dirtyFlag = new ko.DirtyFlag([self.firstName,self.lastName]);
return self;
};
Hook these into your viewmodel to detect if there were changes ...
//Property on your view model. myPerson is an instance of Person.
//Did it Change?
isDirty = ko.computed(function () {
return myPerson().dirtyFlag().isDirty();
}),
Then to resync the changes ...
//Resync Changes
dirtyFlag().reset();
Knockout has a built in PubSub system (used by their observables and other core elements).
You could make use of this system by extending each of your properties to publish an event on a certain topic after being edited.
You'd then need to have a subscription on this topic so you can track changes in the data.
Take a look at this excellent post
You can easily achieve this by providing your own mapping. The following is a very basic example, just to show you what the PubSub system could do for you. See example.
If I may give you a hint, it's might be a better idea to not save per property but to detect changes and do an autosave of the whole array after a certain period.
So each value will publish a 'change event' on the topic and each time you receive a message on a topic the timeOut will be reset. After timeout expires you can save changes in the BE.
Related
I have some ko.pureComputed properties that usually hold a big amount of data inside themselves.
When those ko.pureComputed properties go to sleeping state (noone is subscribe to them) I don't need that data anymore until they go back to listening state (someone is subscribe to them).
During that time while they are in the sleeping state I'd like the ko.pureComputed properties to clear their values so that the garbage collector can remove that computed data from memory, then when I need the computed data again, that is, when the ko.pureComputed go back into listening state, I'd like to reevalute the computed data.
Is that possible?
Further details about my use-case scenario:
My site is a Single Page Application, meaning a Javascript framework (Durandal) switches pages (HTML and JS) in display for the user.
Some pages have a need for computed properties which would store large amount of data. I'd like to use ko.pureComputed for that purpose, because it will stop updating itself once the user goes off its page, i.e. once the ko.pureComputed goes into sleep state because it has no more listeners.
(Durandal deattaches and reattaches the page's JS viewmodel from and into the HTML view when the user goes away or visits the page)
The problem is that the ko.pureComputed keeps its latest value cached.
In my case those values are large arrays of large objects, which take up a noticeable amount of memory. I'd like to dispose of that data once it's not needed anymore.
Is there a way to clear the cached value from the ko.pureComputed once it goes into the sleeping state (when the user leaves the page), and then later reinitialize it when the ko.pureComputed goes back to listening state (when the user revisits the page)?
Using a pure computed's state change events, we can tell the computed to clear its value while it's sleeping. Here's a wrapper function that sets it all up:
function computedValueOnlyWhenActive(readFunction) {
var isAwake = ko.observable(false),
theComputed = ko.pureComputed(function () {
if (isAwake()) {
return readFunction();
}
});
theComputed.subscribe(function() {
isAwake(true);
}, undefined, "awake");
theComputed.subscribe(function() {
isAwake(false);
theComputed.peek(); // force reevaluation
}, undefined, "asleep");
return theComputed;
}
Demo: https://jsfiddle.net/mbest/gttosLzc/
This isn't an answer to the specific question you asked, but it might be a more helpful answer depending on your situation.
In Durandal the router plugin navigates by asynchronously loading the specified module with a requireJS call. Once it retrieves the module it checks if the result is either an object or a function, and if it's a function it will instantiate a new object from the function. If it is an object it just uses the object.
RequireJS automatically caches the modules it retrieves in that it doesn't bother re-fetching a module from the server if it's already downloaded it. So if your module definition is a plain object then that same object will get displayed each time.
This module definition will save its state between navigations:
define(['durandal/app'], function (app) {
var title = 'myView';
var vm = {
title: title;
};
return vm;
});
This module definition will create a new object and will re-bind all knockout bindings resulting in a freshly loaded screen on each navigation.
define(['durandal/app'], function (app) {
var title = 'myView';
var vm = function(){
this.title = title;
};
return vm;
});
EDIT:
For a more granular durandal solution that also works with older versions of knockout (i.e. before pureComputed) you can combine the concept in michael best's answer of using an isAwake observable with durandal's view activation and deactivation lifecycle hooks.
function viewModel(){
var self = this;
this.isAwake = ko.observable(true);
this.theComputed = ko.computed(function () {
if (isAwake()) {
return myValue();
}
return "";
});
this.activate = function(){
self.isAwake(true);
}
this.deactivate = function(){
self.isAwake(false);
}
}
var vm = new viewModel();
return vm; //return the instance not the function
http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks.html
Given the following models:
(note: these are simplified for illustration purposes)
App.CustomerOrder = DS.Model.extend({
deliveries: DS.hasMany('delivery'),
total: DS.attr('number')
});
App.Delivery = DS.Model.extend({
orderlines: DS.hasMany('orderline')
});
App.OrderLine = DS.Model.extend({
productid: DS.attr('string'),
qtyordered: DS.attr('number')
});
When the app first loads I'm querying an API that sends me information about which dependencies should trigger an update. So for example it'll send me something like:
CustomerOrder: ["deliveries", "deliveries.orderlines", "deliveries.orderlines.qtyordered"...]
..means, if deliveries are added/deleted from a customerorder or if lines are added/deleted from a delivery attached to a customer order or if the qtyordered on an orderline on a delivery attached to a customer order, then what the API expects is for me to serialize CustomerOrder (along with the entire chain of relationships) and sent to an 'update' service (i.e. server/customerorder/updates type thing) that will run various routines and fill-in pieces of data and send the entire chain of objects back.
For illustration purposes I've put a simple example on here of an ordertotal (I realize this is easily calculated client-side but there's a bunch of other stuff that would be duplication of code from the server). So, if the qtyordered on an orderline changes, I need to send the customerorder instance to the server, where it will update my total field.
One of the challenges is that I can't hard code that dependency list by setting up observer functions with .observes() type stuff, it has to be done dynamically after that dependency data is loaded (presumably using addObserver). The other is that observers wont dig multiple layers deep like that.
I've tried using a mix-in to the models that overrides the init function and does exactly that.
clientchangeset: DS.attr('raw'),
init: function() {
this._super.apply(this, arguments);
var className = this.auth.camelizedModelString(this.constructor.toString());
var watchlist = this.auth.dependencies[className] || null;
var self = this;
watchlist.forEach(function(watch) {
if(watch.hasOwnProperty('attributeName') && watch.hasOwnProperty('collectionFlag')) {
// {attributeName: attributeName, collectionFlag: collectionFlag}
if(watch['collectionFlag']) {
console.log(className+'.addObserver('+watch['attributeName']+'.#each.clientchangeset)');
self.addObserver(watch['attributeName']+'.#each.clientchangeset', null, 'updateChangelist');
} else {
console.log(className+'.addObserver('+watch['attributeName']+')');
self.addObserver(watch['attributeName'], null, 'updateChangelist');
}
}
});
},
This appears to work, but only one layer deep. For completeness, heres the updateChangelist function:
updateChangelist: function(src, field, value) { //jshint ignore:line
if(this.get('pauseUpdates')) {
return;
}
var className = this.auth.camelizedModelString(this.constructor.toString());
var oldclientchangeset = this.get('clientchangeset') || [];
console.log('Before: '+className+'.[clientchangeset]= '+oldclientchangeset);
oldclientchangeset.pushObject(field);
this.set('clientchangeset', oldclientchangeset);
console.log('After: '+className+'.[clientchangeset]= '+oldclientchangeset);
}
So in general the way I got this to work was to create the observers as indicated, but the handlers simply update a property called '_needsUpdate' on each level of the relationships whenever they are triggered. '_needsUpdate' is just a date so when triggered I do:
this.set('_needsUpdate', +new Date());
Then when setting up observers at each level for that level's children, I just set up a single observer to look at child.#each._needsUpdate.
tl;dr
How to use backbone.stickit with a html form to change an existing model fetched from the server and only PATCH the changed attributes (changed by user input within the html form) to the server?
/tl;dr
I'm using backbone.stickit in a backbone.js application to bind a model to a HTML-form which is part of a backbone view. This works fine so far, but it becomes a little bit complicated if I'm going to save the bound model. This is because I want to use the PATCH-method and only send the changed attributes to the server. I try to illustrate what I've done so far:
Fetching the model from Server
user = new User(); //instatiate a new user-model
user.fetch(); //fetching the model from the server
console.log(user.changedAttributes()); // Returns ALL attributes, because model was empty
The last line indicates my problem, because I thought I can used the changedAtrributes() method later to get the attributes which need a patch on the server. So I tried this workaround which I found here
user.fetch({
success: function (model, response, options) {
model.set({});
}
});
user.changedAtrributes(); //Returns now "false"
Do stickit-bindings
Now I render my view and call the stickit() method on the view, to do the bindings:
//Bindings specified in the view:
[...]
bindings: {
"#username" : "username"
"#age" : "age"
}
[...]
//within the render method of the view
this.stickit();
The bindings work fine and my user model gets updated, but changedAttributes() remain empty all the time.
Save the model to the server
If the user has made all required changes, the model should be saved to the server. I want to use the PATCH method and only send the changed attributes to the server.
user.save(null, {patch:true}); //PATCH method is used but ALL attributes are sent to the server
OR
user.save(user.changedAttributes(),{patch : true});
With the second approach there are different outcomes:
if I didn't use the user.set({}) woraround, all attributes get PATCHED to the server
if I use the user.set({}) woraround the return value of changedAttributes() is "false" and all attributes are PUT to the server
if I call a user.set("age","123") before calling save(), then only the age attribute is PATCHED to the server
So outcome 3 is my desired behaviour, but there are 2 problems with this: First stickit doesn't seem to use the set() method on the model to update the attributes if they are changed within the html-form. And second, if you call set() with one attribute and afterwards with another, only the second attributes is returned by changedAttributes().
Maybe I just overseen something in the backbone or backbone.stickit docs, so I didn't get the desired behaviour working. Any ideas about that?
NOTE: As found out the problem wasn't directly related to backbone.stickit, more to backbone itself.
Solved this problem on my own, maybe this helps someone who may stumble upon this question:
Backbone only keep track of unchanged attributes, but not of unsaved attributes. So with
model.changedAttributes();
you will only get the attributes of the model, which was changed since the last
model.set("some_attribute","some_value")
Finally I stumbled upon backbone.trackit which is a backbone.js plugin maintained by the creator of backbone.stickit. With this plugin you can track unsaved attributes (all attributes which have changed since the last model.save()) and then use them in the save-method of a model. Example (my usecase):
Backbone.View.extend({
bindings: {
"#name" : "name",
"#age" : "age"
},
initialize: function () {
this.model = new User();
this.model.fetch({
success: function (model, response, options) {
//this tells backbone.stickit to track unsaved attributes
model.startTracking();
}
});
},
render: function () {
this.$el.html(tmpl);
this.stickit();
return this;
},
onSaveUserToServer: function () {
//first argument: only unsaved attributes, second argument: tell backbone to PATCH
this.model.save(this.model.unsavedAttributes(), { patch: true });
});
});
I'm trying to wrap a cookie in a computed observable (which I'll later turn into a protectedObservable) and I'm having some problems with the computed observable. I was under the opinion that changes to the computed observable would be broadcast to any UI elements that have been bound to it.
I've created the following fiddle
JavaScript:
var viewModel = {};
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
return {
'write' : function (val) { privateZipcode = val; },
'read': function () { return privateZipcode; }
}
}();
viewModel.zipcode = ko.computed({
read: function () {
return cookie.read();
},
write: function (value) {
cookie.write(value);
},
owner: viewModel
});
ko.applyBindings(viewModel);?
HTML:
zipcode:
<input type='text' data-bind="value: zipcode"> <br />
zipcode:
<span data-bind="text: zipcode"></span>?
I'm not using an observable to store privateZipcode since that's really just going to be in a cookie. I'm hoping that the ko.computed will provide the notifications and binding functionality that I need, though most of the examples I've seen with ko.computed end up using a ko.observable underneath the covers.
Shouldn't the act of writing the value to my computed observable signal the UI elements that are bound to its value? Shouldn't these just update?
Workaround
I've got a simple workaround where I just use a ko.observable along side of my cookie store and using that will trigger the required updates to my DOM elements but this seems completely unnecessary, unless ko.computed lacks the signaling / dependency type functionality that ko.observable has.
My workaround fiddle, you'll notice that the only thing that changes is that I added a seperateObservable that isn't used as a store, its only purpose is to signal to the UI that the underlying data has changed.
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
// extra observable that isnt really used as a store, just to trigger updates to the UI
var seperateObservable = ko.observable(privateZipcode);
return {
'write' : function (val) {
privateZipcode = val;
seperateObservable(val);
},
'read': function () {
seperateObservable();
return privateZipcode;
}
}
}();
This makes sense and works as I'd expect because viewModel.zipcode depends on seperateObservable and updates to that should (and does) signal the UI to update. What I don't understand, is why doesn't a call to the write function on my ko.computed signal the UI to update, since that element is bound to that ko.computed?
I suspected that I might have to use something in knockout to manually signal that my ko.computed has been updated, and I'm fine with that, that makes sense. I just haven't been able to find a way to accomplish that.
sigh, I found someone with my exact same problem
If dependentObservables don't notifySubscribers on write, why do they
even bother to do it on read? They get added to the observables list
and subscribed to, but then they never trigger on updates. So what is
the point of subscribing to them at all?
Ryan Niemeyer answers:
I think that for your scenario, dependentObservables may not be the
right tool for the job. dependentObservables are set up to detect
dependencies in the read function and re-evaluate/notify whenever any
of those dependencies change. In a writeable dependentObservable, the
write function is really just a place to intercept the write and allow
you to set any observables necessary, such that your read function
would return the proper value (write is typically the reverse of read
in most cases, unless you are transforming the value).
For your case, I would personally use an observable to represent the
value and then a manual subscription to that observable to update the
original value (the one that you may not have control over).
It would be like: http://jsfiddle.net/rniemeyer/Nn5TH/
So it looks like this fiddle would be a solution
var viewModel = {};
// simulating a cookie store, this part isnt as important
var cookie = function () {
// simulating a value stored in cookies
var privateZipcode = "12345";
return {
'write' : function (val) {
console.log("updated cookie value with: " + val);
privateZipcode = val;
},
'read': function () {
return privateZipcode;
}
}
}();
viewModel.zipcode = ko.observable(cookie.read());
// manually update the cookie when the observable changes
viewModel.zipcode.subscribe(function(newValue) {
cookie.write(newValue);
});
ko.applyBindings(viewModel);
That makes sense and its somewhat simpler to use. Overall I'm not sure how great of an idea it is to treat a cookie as an observable since the server could edit it in an ajax request, etc.
Try making your internal privatezipcode an observable. See here: http://jsfiddle.net/KodeKreachor/fAGes/9/
I've got 2 fields in my model that have a master/slave type relationship.
If the master updates the slave should take the update too.
If the slave updates the master is unaffected.
I've managed to implement this with a manual subscription - http://jsfiddle.net/ProggerPete/XNUPj/
But I'm wondering if I could achieve the same result without the manual binding. The reason I'm wanting it is I'd prefer not to have to unbind my manual subscriptions when i'm destroying my view.
Cheers,
Peter
Generally, I would say that the manual subscription is the most straightforward approach to your question.
However, it is pretty easy to create your own custom observable that encapsulates this functionality and handles updating both the master and slave in a writeable dependentObservable. It might look something like this:
function customObservable(initialValue) {
var _source = ko.observable(initialValue),
_local = ko.observable(initialValue),
result = ko.dependentObservable({
read: _source,
write: function(newValue) {
_source(newValue);
_local(newValue);
}
});
result.local = _local;
return result;
}
and you would use it like:
var viewModel = {
source: customObservable("sourceValue")
};
The customObservable (call it whatever you want) returns a writeable dependentObservable that will update both values that you can bind against as source. The local value is also exposed as source.local.
So, you would use this in your scenario like: http://jsfiddle.net/rniemeyer/67pDS/
I am not sure how you want to use this functionality though. If you are looking for the ability to accept/cancel edits to an observable, then you might want to look at this custom observable.
Snippet to show disposal in custom binding:
var subscription = oComboBoxModel.value.subscribe(updateBestMatchFromValue, oComboBoxModel);
//handle disposal (if ko.cleanNode is called on the element)
ko.utils.domNodeDisposal.addDisposeCallback(element, function(){
subscription.dispose();
});