We were unable to load Disqus. If you are a moderator please see our troubleshooting guide.

The_Linux_Lich • 15 years ago

This is neat, rpn! The only feature I think writable dependent observables are missing by default are the knockout array methods for when you declare a WDO with an array value(I've included them in my knockout.live plugin, for example), but they opened a lot of power for MVVM :)

Anonymous • 15 years ago

Absolutely fantastic example...

Anonymous • 15 years ago

Looks like just what we need, went down the first couple of routes too. Good work

Scott Anderson • 15 years ago

Thanks for this. I was just looking to do a modal dialog that a user could "Cancel" out of to prevent edits to the model, and this solves the problem perfectly. Cheers!

Damien • 15 years ago

Great stuff Ryan. Very slick!

Luis • 14 years ago

Wicked! I went route #1, temporary object as my VM main array was simple.
I will try this approach you suggest.

Steve Michelotti • 14 years ago

Very cool. Have you thought about a function that would convert every property into a protectedObservable() automatically? Sort of like the ko.mapping plugin - but instead of making every property observable(), it makes them protectedObservable().

Ryan Niemeyer • 14 years ago

Hi Steve-
I guess I hadn't thought too much about it. I have done it with mapping options in the mapping plugin. Might be nice to find a way to do it more automatically though.

Steve Michelotti • 14 years ago

Hmm, I though the mapping options in the mapping plugin force you to specify the property you're overriding (I could be mistaken on that). In this case, we'd want it to automatically apply to *all* properties like how the mapping plugin applies observable() to all properties. Maybe looping over the properties might be sufficient for simple cases.

PAEz • 14 years ago

That was great, as usual.
Great idea and a nice example of a simple list, which is just what I needed. I wanted a simple list with modal edit and this will do nicely.
Thought Id share my Fiddle in which I added a few functions I thought Id need....
http://jsfiddle.net/PAEz/D5...
...just incase any other copy and paste coders look at this ;P

Corey J Gaudin • 14 years ago

Is there any easy way to convert an already observable properties to protectedObservable. In other words, I have a JS model coming from a service that has a object graph of other complex objects and arrays. I use the ko.mapping plugin to convert the entire JS Model's properties and objects to observable. However, I would like on some cases to take one (not all) of its object properties and convert the observable to protectedObservable (because it will be used in a dialog with save/cancel-to edit).

Do I just unwrap each property I want protectedObservable and convert it to a protected Observable?

I added the following to the top of protectedObservable, but it just seems to much extra work:


var unwrap = ko.utils.unwrapObservable;

if (!ko.isObservable(initialValue)) {
_actualValue = ko.observable(initialValue);
_tempValue = initialValue;
}
else {
_actualValue = initialValue;
_tempValue = unwrap(initialValue);
}

Would ko.mapping.toJS(model) still work with protectedObservable?

Ryan Niemeyer • 14 years ago

@Corey - you could use the "create" callback in the mapping options (http://knockoutjs.com/docum... to control how your objects are created.

Otherwise, after using the mapping plugin and before calling applyBindings, you could redefine any variables that you want to be protected and just pass in the current value directly (unwrap it yourself).

Would be happy to help if you can't get it working.

Anonymous • 14 years ago

Ryan,

Nice demo, I have a similar need, however, I have to be able to add/delete Columns dynamically. This fiddle has all the behaviors I need except the Remove Column:

http://jsfiddle.net/rniemey...

can you help?

Ryan Niemeyer • 14 years ago

Here is a quick sample with the ability to remove a column: http://jsfiddle.net/rniemey.... Hope this helps.

Troy Ingram • 14 years ago

@Ryan thanks for the inspiration.

I put a spin on this and came up with a new concept. I created a 'protectObservable' function that takes in an existing observable and wraps it instead of creating a new observable to wrap. I also created a 'protectModel' function that takes in a model and wraps all of its writeable observables with the protected version and simply copies all of the other properties. This way, the new protected model can be bound to in the same way as the original model. This version only wraps the children of the model, not the entire tree.

Lets assume you have some observable 'selectedItem' that holds a model with observable properties and is data bound to some editable form. We can simply create a new observable 'protectedItem' set to 'ko.protectModel(selectedItem())' and change the binding to use 'protectedItem' instead of 'selectedItem'. Making 'protectedItem' a computedObservable would probably be a good choice here. The only other change required is to call 'protectedItem().commit()' whenever a save is performed. You can still save individual properties if you need to using 'protectedItem().myProp.commit();'.

This could probably use further refinement. In particular, I don't like that 'protectModel' adds functions to the cloned model, which would clobber any existing properties with those names.

You can find the code here: http://pastebin.com/Q1xSpyAM

Dam • 14 years ago

Is that possible that you can provide an example on how you init protectedItem and how you set it to ko.protectModel(selectedItem())?

Ryan Niemeyer • 14 years ago

@Troy - thanks for sharing. That is an interesting twist on the idea. protectModel looks quite useful.

Adam Levitt • 14 years ago

This was very helpful.  Any idea if this is getting put into the actual library?

Ryan Niemeyer • 14 years ago

@facebook-730122396  I don't think that this would get added to the actual library.  We are trying to keep it as lean as possible and encourage plugins to be created on top of it.  Glad that it was useful to you!

Ericj • 14 years ago

Hey Ryan, I am new to knockout and am really digging your blog posts. They have been REALLY helpful. One question about this approach is that I am not sure how it will apply to observable arrays. Would love an example. Thanks again!

Ryan Niemeyer • 14 years ago

@Ericj observableArrays are a little bit tricky with this method.  If you are dealing with primitive items or simple objects (no observables), then you can do something like: http://jsfiddle.net/rniemey... (primitives) or http://jsfiddle.net/rniemey... (simple objects).  So, the idea is that we make a copy of the object and either commit it as our cached value or reset back to our cached value.  
If you were dealing with complex items that contain observables, then you would have to let it know how to map the copy to your items like: http://jsfiddle.net/rniemey....

Ericj • 14 years ago

Ryan... thanks so much for your help. I took your example and applied it to my scenario. Here is the fiddle.. http://jsfiddle.net/digitzf...

I have two questions.

1) Is there a way to have it load and work properly without a blank ticket needed.
2) I cannot for the life of me figure out why my formattedPrice is not working.

Thanks again!

Ryan Niemeyer • 14 years ago

@Ericj - in your computed observable you will want to pass the second argument to make sure that your "this" is correct.  Also, since price is an observable, you will want to access its a function to get its value.  I added a + in front of it as well to ensure that it will start as 0.  Here is the updated fiddle: http://jsfiddle.net/rniemey....   Maybe you can explain the blank ticket issue further.  It looks like maybe you have that part sorted out?

Art • 14 years ago

This works great in most of the cases, but it doesn't seem to work well with knockout validation... I can't find a way to get the validation to work. Does anyone know how to make this protectedObservable works with knockout validation?

Art • 14 years ago

I think the issue could be:

the protectedObservable needs to commit the change first in order to trigger the validation logic. However, as we are still updating the content, commit the change does not seem to be a good way to do it. Without having the value get updated, the validation will not kick in.

Still try to find a good solution to resolve this issue... 

Art • 14 years ago

It seems like as long as the validation is relies on notification of the value update, the actual value will must get updated in order for the validation rule to work unless I write a validation control to handle during the commit stage... 

Ryan Niemeyer • 14 years ago

@Art - to work with validation, one option would be to alter how the protected observable works where you would always update the real observable and you can choose to revert back to a cached value.  Here is a sample that uses a "revertableObservable" with the validation plugin.  The first name has a minimum length or 2 and maxium length of 10.  
http://jsfiddle.net/rniemey....  Hope this helps.

Art • 14 years ago

Thanks! It works great! I combine your suggestion with Troy's protectModel, and everything works automatically now.

Miguel Andrade • 14 years ago

Brilliant. What about a pull request to the official Knockout? Or a plugin, maybe.

Thank you!

Ryan Niemeyer • 14 years ago

I might think about a plugin.  The core needs to stay lean and mean.   There are certainly lots of features that could be added on top of this basic sample.  

gleb Chermennov • 14 years ago

that's a great example of knockout usage

cblaze22 • 14 years ago

Any chance you know what the problem is here?
http://stackoverflow.com/qu...

Guest • 14 years ago
Ryan Niemeyer • 14 years ago

Knockout core is trying to stay lean, focused, and maintainable. We have added extensibility points that help with these scenarios (like the ability to extend observables with something like the "editable" function that I showed in the video from here: http://www.knockmeout.net/2.... There is room for plugins to be built on top of KO for functionality that is outside the core. For example, here is a library that does things like this: https://github.com/romanych....

Facio • 14 years ago

Based on Art's protectModel, I came up with this little method to simplify the commit() while using Ryan's protectedObservable:

ko.protectedCommit = function (m) { for (var key in m) { var prop = m[key]; if (ko.isWriteableObservable(prop)) { prop.commit(); } }};

ares • 14 years ago

I add one function to return the tempvalue for data validation before commit:
result.current = function () { return _tempValue; }

Steve Rasch • 14 years ago

Hi,
is there an easy way to collect all protectedObservables from my ViewModel? This way i don't have to enumerate all properties by hand...

Ryan Niemeyer • 14 years ago

I usually do something like:
http://jsfiddle.net/rniemey.... Look at acceptItemEdit and cancelItemEdit.

dpineda • 13 years ago

Im using a different approach to handle Arrays is this good?

var OriginalExperience;
var Experience = function (currentJobs, pastJobs, practices) {

var self = this;
self.CurrentJobs = ko.observableArray(currentJobs);
self.PastJobs = ko.observableArray(pastJobs);
self.Practices = ko.observableArray(practices);
self.InEdition = ko.observable(false);

self.ToggleEdition = function () {
self.InEdition(!self.InEdition());
if (self.InEdition()) {
OriginalExperience = ko.mapping.toJSON(self);
}

};

self.CancelEdition = function () {
ko.mapping.fromJSON(OriginalExperience, {}, self);
self.InEdition(false);
};

//Add, Remove.....

}

Ryan Niemeyer • 13 years ago

@dpineda - I don't see a problem with that technique. I actually do something somewhat similar, but without the mapping plugin in much of my code.

Sereban • 13 years ago

But how to use it in save method.

Usually I do like this:
$.ajax("api/values/" + item.id(), {
data: ko.toJSON(item),
type: "pop",
contentType: "application/json",
success: function (responce) {},
error: function (xhr, textStatus, errorThrown) {}
});

item was something like this:
function Friend(id, name) {
var self = this;
self.id = id;
self.name = name;
}

But now item is like this:
function Friend(id, name) {
var self = this;
self.id = ko.protectedObservable(id);
self.name = ko.protectedObservable(name);
}

and
data: ko.toJSON(item),

is not correct now.

What to do?

Ryan Niemeyer • 13 years ago

@Sereban - you will still retrieve the currently committed values of the protectedObservables when you do ko.toJSON on them. Do you need to get at the temporary values?

Navneet • 13 years ago

Hi,

I am using the protectedobservable in my project from Accept/Cancel scenario. It works great. But now, I need to subscribe when a particular property changes. Since, all the properties of the selecteditem are private, I am unable to do so.

Any suggestions how can I accomplish this task.

Ryan Niemeyer • 13 years ago

@Navneet - you can still subscribe to the protectedObservable itself and it will notify you when the value changes, as it is reading the underlying private observable. If you need to get at the temp value, then you can choose to expose it by attaching it to "result" (result.tempValue = _tempValue). Then, you can access it like myProtectedObservable.tempValue. Hope that helps!

Marco • 13 years ago

result.tempValue = ko.observable(_tempValue);

isn't it?

Ryan Niemeyer • 13 years ago

@Marco - I guess that I had meant doing var _tempValue = ko.observable(initialValue); at the beginning and then finally doing result.tempValue = _tempValue. Same thing really, but you are right that the tempValue has to be observable. Thanks!

Guest • 13 years ago
Ryan Niemeyer • 13 years ago

@norbiu - you are running into the fact that all bindings on an element fire together (until KO 3.0). In this case changing isLoading triggers all of the bindings on that element and ends up resetting the value back to the original. Here is a slightly different pattern that uses a real "temp" observable attached to the original. So, you can bind against "editValue" as well as "editValue.temp". In the fiddle, I made the save function commit the value after 1 second, as if the request to the server returned successfully. Hope this helps: http://jsfiddle.net/rniemey...

fancyoung • 12 years ago

Thanks a lot, this helps. PS: before I saw this comment, I spend 1 day to find out it's the problem of knockout version.

Paul Aldred-Bann • 13 years ago

Amazing article, really helped me out - couldn't appreciate this more!!