Saturday, 25 January 2014

Mutation Events and Mutation Observer

Mutation Events

Before we start to understand Mutation, just note the meaning of Mutation, Mutation means changing of the structure, so mutation means change, so as the name and definition suggests Mutation Events are those events which are fired when any DOM element, its attribute or in short DOM structure is changed.

Mutation Observer needed where DOM is changed dynamically using GUI, like blogger designing, CMS designing web page from GUI, hence Mutation  Observer plays vital role in OpenERP CMS also.

Mutation events provide a mechanism for a web page or an extension to get notified about changes made to the DOM. Use Mutation Observers instead if possible.

Events                        Type            Attribute      Description                                    Bubbles   Cancelable

Mutation    
DOMSubtree             Modified                           (none)     Fires when the subtree is modified                                                                                                                                                        Yes           No
DOMNode                Inserted                             (none)     Fires when a node has been added as a child of another node                                                                                                            Yes        No
DOMNode                Removed                          (none)     Fires when a node has been removed from a DOM-tree                                                                                                                     Yes        No
DOMNode                RemovedFromDocument (none)     Fires when a node is being removed from a document                                                                                                                       No        No
DOMNode                InsertedIntoDocument      (none)     Fires when a node is being inserted into a document                                                                                                                       No        No
DOMAttr                  Modified                           (none)     Fires when an attribute has been modified                                                                                                                                             Yes        No
DOMCharacterData  Modified                           (none)     Fires when the character data has been modified                                                                                                                        Yes        No
DOMAttributeName Changed
DOMElementName  Changed



The mutation events have been marked as deprecated in the DOM Events specification, as the API's design is flawed (see details in the "DOM Mutation Events Replacement: The Story So Far / Existing Points of Consensus" post to public-webapps).

Mutation Observers are the proposed replacement for mutation events in DOM4. They are expected to be included in Firefox 14 and Chrome 18.

The practical reasons to avoid the mutation events are performance issues and cross-browser support.
Performance

Adding DOM mutation listeners to a document profoundly degrades the performance of further DOM modifications to that document (making them 1.5 - 7 times slower!). Moreover, removing the listeners does not reverse the damage.

Usage

You can register a listener for mutation events using element.addEventListener as follows:

element.addEventListener("DOMNodeInserted", function (ev) {

  // ...
}, false);

The event object is passed to the listener in a MutationEvent (see its definition in the specification) for most events, and MutationNameEvent for DOMAttributeNameChanged and DOMElementNameChanged.


Mutation Observer

MutationObserver provides developers a way to react to changes in a DOM. It is designed as a replacement for Mutation Events defined in the DOM3 Events specification.

Constructor
MutationObserver()

Constructor for instantiating new DOM mutation observers.

MutationObserver(
  function callback
);

Parameters

callback
   The function which will be called on each DOM mutation. The observer will call this function with two arguments. The first is an array of objects, each of type MutationRecord. The second is this MutationObserver instance.

Instance methods
void observe( Node target, MutationObserverInit options );
void disconnect();
Array takeRecords();
observe()

Registers the MutationObserver instance to receive notifications of DOM mutations on the specified node.

void observe(
  Node target,
  MutationObserverInit options
);

Parameters

target
   The Node on which to observe DOM mutations.
options
   A MutationObserverInit object, specifies which DOM mutations should be reported.

NOTE: Adding observer to an element is just like addEventListener, if you observe the element multiple times it does not make a difference. Meaning if you observe element twice, the observe callback does not fire twice, nor will you have to run disconnect() twice. In other words, once an element is observed, observing it again with the same will do nothing. However if the callback object is different it will of course add another observer to it.

disconnect()

Stops the MutationObserver instance from receiving notifications of DOM mutations. Until the observe() method is used again, observer's callback will not be invoked.

void disconnect();

takeRecords()

Empties the MutationObserver instance's record queue and returns what was in there.

Array takeRecords();

Return value

Returns an Array of MutationRecords.
MutationObserverInit

MutationObserverInit is an object which can specify the following properties:
NOTE: At the very least, childList, attributes, or characterData must be set to true. Otherwise, "An invalid or illegal string was specified" error is thrown.

Property Description
childList Set to true if mutations to target's children are to be observed.
attributes Set to true if mutations to target's attributes are to be observed.
characterData Set to true if mutations to target's data are to be observed.
subtree Set to true if mutations to not just target, but also target's descendants are to be observed.
attributeOldValue Set to true if attributes is set to true and target's attribute value before the mutation needs to be recorded.
characterDataOldValue Set to true if characterData is set to true and target's data before the mutation needs to be recorded.
attributeFilter Set to an array of attribute local names (without namespace) if not all attribute mutations need to be observed.
MutationRecord


MutationRecord is the object that will be passed to the observer's callback. It has the following properties:

Property Type Description
type String Returns attributes if it was an attribute mutation. characterData if it was a mutation to a CharacterData node. And childList if it was a mutation to the tree of nodes.
target Node Returns the node the mutation affected, depending on the type. For attributes, it is the element whose attribute changed. For characterData, it is the CharacterData node. For childList, it is the node whose children changed.
addedNodes NodeList Return the nodes added, or null.
removedNodes NodeList Return the nodes removed, or null.
previousSibling Node Return the previous sibling of the added or removed nodes, or null.
nextSibling Node Return the next sibling of the added or removed nodes, or null.
attributeName String Returns the local name of the changed attribute, or null.
attributeNamespace String Returns the namespace of the changed attribute, or null.
oldValue String The return value depends on the type. For attributes, it is the value of the changed attribute before the change. For characterData, it is the data of the changed node before the change. For childList, it is null.
Example usage


The following example was taken from this blog post.

// select the target node
var target = document.querySelector('#some-id');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation.type);
  });    
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
// later, you can stop observing
observer.disconnect();



Why Mutation Observer needed to replace Mutation Events ?

As I already defined that Mutation events defined in DOM3 has been deprecated and replacement is Mutation Observer.

Simple reason of replacement is performance, here are few design issue with Mutation events design:

Problem: DOM Mutation events as currently specified and implemented
are widely viewed as fatally flawed because they are:

(1) Verbose (fire too often)
(2) Slow (because of event propagation -- and because they prevent
certain UA(User Agent) run-time optimizations)
(3) "Crashy" (have been the source of many crashers for UAs because
script can tear up the world in any way it wishes while a DOM
operation is underway).

Solution:

*Agreed upon design points:

Primarily because of a proposal made by Jonas Sicking in 2009, the
group has been largely in agreement about the following:

(1) The vocabulary of mutations should be more expressive and require
fewer "words" to adequately describe what happened . For instance, a
single innerHTML assignment which results in removing N nodes and
inserting M nodes should be expressed as a single mutation (e.g. {
mutation: "ChildlistChanged", added: [...], removed: [...] } ) -- not
a sequence of mutations, one for each node inserted or removed.

(2) Mutations should avoid the expense of event propagation (mainly
capture & bubble).

(3) Mutations should be delivered to observers after the DOM
operations which generated them complete -- removing the possibility
of having script interfere with their operation. For example, an
execCommand() operation is permitted to make any & all DOM operations
it needs *before* it has to notify any observers of what happened.

Through discussing Jonas's proposal, we observed that in a system
which allows multiple observers that can, themselves, make mutations,
observers will generally need to be tolerant of an arbitrary number of
mutations having occurred before being notified.

Further, there is strong performance motivation for observers to
respond to the net-effect of a set of mutations, rather than acting
immediately in response to each mutation.

Thus:

(4) Observers should be called with the *set* of mutations which has
occurred since they were last called (or since registration), rather
than being called once per mutation. I.e. Deliver mutations in batches
of "everything that has happened since the last time I called you -
up till now".

For reference one go through:
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events
https://developer.mozilla.org/en/docs/Web/API/MutationObserver
http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0779.html

Hope this post will help you to understand Mutation event and its replacement Mutation Observer and the reason why it is replaced.

In next post I will describe Mutation Observer plays vital roll in any web technology so in OpenERP(specifically in OpenERP CMS).

Thanks.