Future of Data Binding
A new revolution has come. There are few new APIs introduced in ECMAScript 5, but the most interesting functionality is added to Object property code. This new code gives you the ability to redefine the capabilities of an Object property. Capabilities like preventing Object manipulation, enumeration, deletion and addition of new property and even defining their getters and setters.
Three plus years back Rafael Weinstein, Erik Arvidsson and Adam Klein started implementing or we can say started developing the true way of data binding system of MDV (“model-driven views”). They got an agreement from the V8 team and started implementing Object.observe (“O.o”). Behind them Polymer team started building their framework system on top of O.o. As of now the world has changed in a variety of ways as other frameworks like Angular and Ember started their own. It will be interesting to watch how far they will manage to build their frameworks without them.
As O.o gets postponed from ECMAScript 5 and to be a part of ECMAScript 7 because not all browsers have implemented this functionality yet. A Synonym to that is Object.watch, which was a part of Firefox for testing purposes. Due to this Polymer rewrote their framework to support all browsers and released their first production version 1.0 without O.o.
Then comes Object.defineProperty which can create a watch over an Object property.
Example :-
[code language=”html”]
<script>
function _observe(properties, callback) {
var _obj = {};
Object.keys(properties).forEach(function(key) {
Object.defineProperty(_obj, key, {
get: function() {
console.log(‘get!’);
return properties[key];
},
set: function(value) {
callback(key, properties[key], value);
properties[key] = value;
}
});
});
return _obj;
}
var obj = { "firstname": "himanshu", "lastName": "adhikari" };
var scope = _observe(obj, function(key, oldValue, newValue) {
console.log(key, oldValue, newValue)
});
</script>
[/code]
Now change scope.firstName and observe console pane
scope.firstName = “Raman”;
Note: Please don’t try to replace _obj with properties in Object.defineProperty method as it will make it a recursive call to get function.
Getter and Setters: When Getter and Setters were not present in javascript we need to do a dirty check over an object as to identify which property gets affected after an event finishes it execution or an async call gets completed. This dirty checking or we can say the extra time we are taking in finding whether it’s an “update”, an “addition” or a “deletion” from an object can be overcome by setting Getters and Setters over object properties.
Enumeration: Now try to run below method, and you’ll find that nothing will appear on browser’s console.
[code language=”html”]
Object.keys(scope).forEach(function(key){
console.log(key)
});
[/code]
This is because by default enumeration is false and the object property will not show up during property enumeration (for…in loop or Object.keys method). To set it an example is given below:-
[code language=”html”]
function _observe(properties, callback) {
var _obj = {};
Object.keys(properties).forEach(function(key) {
Object.defineProperty(_obj, key, {
get: function() {
console.log(‘get!’);
return properties[key];
},
set: function(value) {
callback(key, properties[key], value);
properties[key] = value;
},
enumerable: true,
});
});
return _obj;
}
[/code]
Now run for loop over scope variable and this time logs will appear on browser’s console.
Configurable: Now try to run below code, and you’ll find that deleting a object property was never so tough before.
[code language=”html”]
delete scope.firstName;
[/code]
This is because by default configurable is set to false if not passed to Object.defineProperty. If you want to enable deletion on an object property then turn it to true.
Writable: If set to false then you cannot change the value of an object property, defaults to false. Writable cannot be used when getters and setters are present to Object.defineProperty.
[code language=”html”]
var o = {}; // Creates a new object
// Example of an object property added with defineProperty with a data property descriptor
Object.defineProperty(o, ‘a’, {
value: 37,
writable: true,
enumerable: true,
configurable: true
});
[/code]
Object.defineProperties:
There is one more method which can create a watch over an Object. This method defines new or modifies existing properties directly on an object, returning the object.
Syntax:
[code language=”html”]
Object.defineProperties(obj, properties)
[/code]
Example:
[code language=”html”]
var obj = {};
Object.defineProperties(obj, {
"firstName": {
value: "john",
writable: true
},
"lastName": {
value: "carter",
writable: false
}
});
[/code]
Object.defineProperty is used when you want to define a single property directly on an object but if you want to define multiple properties on an object with different configurations then you can use Object.defineProperties.
Till now we’ve learnt how to define and modify property/properties of an Object. Now let’s learn how we can react on DOM changes.
Mutation Observers: provides a way to react to changes in a DOM, changes like addition and removal of HTML node on DOM and even more detects changes in HTML node attributes.
Now lets create an example to observe the changes whenever a new attribute is added or being changed. Open your developer tools and go to console pane, now paste the below code.
[code language=”html”]
// select the target node
var target = document.querySelector(‘body’);
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation);
});
});
// configuration of the observer:
// set attributeOldValue to true if want to get old value of an attribute:
var config = { attributes: true, attributeOldValue: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
[/code]
Now go to element pane in developer tools and add any new attribute with value to body tag. Now press Esc and observe logs in console pane. You can see Mutation Records, as what is being changed on the target node. It will only tell us what was the old value of an attribute, you can get the new value from target node by
[code language=”html”]
target.getAttribute(MutationRecord.attributeName);
// later, you can stop observing
observer.disconnect();
[/code]
Now let’s observe the addition/deletion of nodes in DOM.
[code language=”html”]
<ol contenteditable oninput="">
<li>Press enter</li>
</ol>
<script>
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
var list = document.querySelector(‘ol’);
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === ‘childList’) {
var list_values = [].slice.call(list.children)
.map(function(node) {
return node.innerHTML;
})
.filter(function(s) {
if (s === ‘<br />’) {
return false;
} else {
return true;
}
});
}
});
});
observer.observe(list, {
attributes: true,
childList: true,
characterData: true
});
</script>
[/code]
Set childList to true if want to observe any addition/deletion to a particular child nodes.
Now my question is do we still need dirty checking frameworks if we have a way to observe and react whenever a change happens on DOM/Objects rather than checking every time as what was changed and if the code is not well written then you’ll find that in background something is still executing. As the project increases its size it becomes slow in browsers.
Till now we’ve learnt how to Observe the changes of an Object and how we can react to DOM manipulation. As the time is changing, there are several changes made to the web to make it smater. Now its time to make yourself smater and use only that much code which is required for making a website. I think every website is different from each other and each website requires their own framework’s according to their requirements. I request you to try implementing something from what we’ve discussed today.
Meet you guys soon with my next blog, till then HAPPY CODING!!!