Most modern browsers now support JavaScript's setter syntax for objects as well as CustomEvent dispatches. This opens the door to home-brew a simple observable pattern when, for whatever reason, a library offering "canned" observable patterns (like knockout.js or backbone.js) isn't available.
And why wouldn't an mvvm library be available? In my experience, this can happen because a client or other decision maker rationally or irrationally forbids certain .js libraries. Or maybe your page really is super-simple (they all start they way...) and doesn't warrant the overhead of full blown mvvm library. Or...or...you want an mvvm framework to work exactly as you want it to...so you build your own!
First, a simple example of setter syntax, which allows you to act whenever something is assigned to a particular property of your object. All we're going to do for now is log the occurrence:
var x = {
_b:5,
set b(myval) {
console.log("b was assigned something!");
this._b = myval;
},
get b() {
return this._b;
},
};
x.b = 7;
console.log("b is " + x.b);
console.log("_b is " + x._b);
Results:
b was assigned something!
b is 7
_b is 7
Now let's take it just a step further and raise and event at the document level for which we can listen and act:
var y = {_b:5,
set b(myval) {
this._b = myval;
document.dispatchEvent( new CustomEvent('b_set', { 'detail': this._b }));
},
get b() {return this._b;},
};
document.addEventListener('b_set',function(e) {
console.log("b_set event heard! b is : " + e.detail);
});
y.b=7;
Results:
b_set event heard! b is : 7
This basic pattern can be the foundation for an observable pattern that fits your particular situation. There are a lot of creative things you can do with event names, the event detail object, custom element attributes, etc. to make your own little (or not so little) custom mvvm framework.
Showing posts with label MVVM. Show all posts
Showing posts with label MVVM. Show all posts
Monday, April 27, 2015
Wednesday, April 8, 2015
All is not Equals in JavaScript Frameworks!
Love it or hate it, a common pattern utilized by JavaScript framework libraries is to set properties of observable objects using explicit functions. In some frameworks, using the equals sign (=) for assignment, although intuitive, will actually break stuff! This can be a common source of nasty bugs if you're not careful.
If I were inclined to create or extend a framework, I would probably try to take the approach Angular.js uses and funnel developers towards a pattern where using the equals sign for assignment behaves as expected, i.e. observers immediately see the change. I would offer a method whereby an unobserved change could intentionally be made. So by default you could do this:
myModel.value = "New value!";
...and observers of myModel would be immediately notified. If you wanted to hide the change for some reason, you could do:
myModel.suppressObservations("value","New value!");
...or something like that, but with a better method name ;-)
Here are some examples from frameworks I work with and how the equals sign can get you into trouble (note: JavaScript frameworks are constantly evolving. Examples below could become obsolete quickly!):
A. knockout.js
Observable objects in knockout.js have properties that are set by treating each property as a method:
var myViewModel = {
myString: ko.observable('Hello'),
}
To change the value of the myString property you must treat the property as a function:
myViewModel.myString('World!');
And something like this will actually "break" the observable because you'll be setting the property to a value instead of a function:
myViewModel.myString = 'World';
B. Kendo UI
Observable objects in Kendo are a little more robust than in knockout.js and work like this:
var myViewModel = new kendo.data.ObservableObject({ myString: "Hello" });
To change the value of myString in Kendo:
myViewModel.set("myString","World!");
It is safe to use the equals sign, but observers won't immediately be informed of the change:
myViewModel.myString = "World!";
There are two ways to view this approach. On the one hand, you can update a property value without triggering responses in observers, and maybe there are times you want to do that. But on the other hand, it is pretty easy to create a bug by mistakenly using the equals sign for assignment and accidentally hiding changes from observers.
C. backbone.js
Models in backbone don't expose properties for manipulation by the equals sign:
var myViewModel = new Backbone.Model({
myString: "Hello!";
});
To change the value in backbone:
myViewModel.set({myString: "World!"});
You can (mistakenly) do this:
myViewModel.myString = "World!";
I think this problem is a little more serious than Kendo UI's because myViewModel.myString is a completely different property than the one manipulated with myViewModel.set({myString: ... }) and myViewModel.get('myString'). Here it is not a matter of having the option of hiding a change from observers, and more likely creating a very subtle bug in your code.
D. Angular.js
Angular.js is a different beast altogether compare do the frameworks above. Typically, you define your model as a plain old JavaScript object and manipulate it inside a scoped controller. The most common pattern is to have the model expose its own methods for manipulation, and DOM elements are bound to the controller. In other words, you will usually manipulate the model using the equals sign for assignment.
However, in cases where you do want to manipulate the object from outside (which might be considered bad practice anyway...) you deal with the object within the context of what Angular.js calls a 'scope' which exposes the .$apply function:
myScope.$apply(function() {
myScope.myString ="World!";
});
Similar to Kendo UI, you can just do:
myScope.myString = "World!";
...but this goes unnoticed by observers and just like in Kendo UI you probably will rarely intend to do this. I suspect most applications built on angular try to avoid .$apply and try to do all property manipulation within the controller using standard assignment with the equals sign.
If I were inclined to create or extend a framework, I would probably try to take the approach Angular.js uses and funnel developers towards a pattern where using the equals sign for assignment behaves as expected, i.e. observers immediately see the change. I would offer a method whereby an unobserved change could intentionally be made. So by default you could do this:
myModel.value = "New value!";
...and observers of myModel would be immediately notified. If you wanted to hide the change for some reason, you could do:
myModel.suppressObservations("value","New value!");
...or something like that, but with a better method name ;-)
Here are some examples from frameworks I work with and how the equals sign can get you into trouble (note: JavaScript frameworks are constantly evolving. Examples below could become obsolete quickly!):
A. knockout.js
Observable objects in knockout.js have properties that are set by treating each property as a method:
var myViewModel = {
myString: ko.observable('Hello'),
}
To change the value of the myString property you must treat the property as a function:
myViewModel.myString('World!');
And something like this will actually "break" the observable because you'll be setting the property to a value instead of a function:
myViewModel.myString = 'World';
B. Kendo UI
Observable objects in Kendo are a little more robust than in knockout.js and work like this:
var myViewModel = new kendo.data.ObservableObject({ myString: "Hello" });
To change the value of myString in Kendo:
myViewModel.set("myString","World!");
It is safe to use the equals sign, but observers won't immediately be informed of the change:
myViewModel.myString = "World!";
There are two ways to view this approach. On the one hand, you can update a property value without triggering responses in observers, and maybe there are times you want to do that. But on the other hand, it is pretty easy to create a bug by mistakenly using the equals sign for assignment and accidentally hiding changes from observers.
C. backbone.js
Models in backbone don't expose properties for manipulation by the equals sign:
var myViewModel = new Backbone.Model({
myString: "Hello!";
});
To change the value in backbone:
myViewModel.set({myString: "World!"});
You can (mistakenly) do this:
myViewModel.myString = "World!";
I think this problem is a little more serious than Kendo UI's because myViewModel.myString is a completely different property than the one manipulated with myViewModel.set({myString: ... }) and myViewModel.get('myString'). Here it is not a matter of having the option of hiding a change from observers, and more likely creating a very subtle bug in your code.
D. Angular.js
Angular.js is a different beast altogether compare do the frameworks above. Typically, you define your model as a plain old JavaScript object and manipulate it inside a scoped controller. The most common pattern is to have the model expose its own methods for manipulation, and DOM elements are bound to the controller. In other words, you will usually manipulate the model using the equals sign for assignment.
However, in cases where you do want to manipulate the object from outside (which might be considered bad practice anyway...) you deal with the object within the context of what Angular.js calls a 'scope' which exposes the .$apply function:
myScope.$apply(function() {
myScope.myString ="World!";
});
Similar to Kendo UI, you can just do:
myScope.myString = "World!";
...but this goes unnoticed by observers and just like in Kendo UI you probably will rarely intend to do this. I suspect most applications built on angular try to avoid .$apply and try to do all property manipulation within the controller using standard assignment with the equals sign.
Subscribe to:
Posts (Atom)