Implement the Undo and rollback operations of the editor based on the new HTML5 Mutation Observer, html5mutation

Source: Internet
Author: User

Implement the Undo and rollback operations of the editor based on the new HTML5 Mutation Observer, html5mutation

Introduction to MutationObserver

MutationObserver provides developers with the ability to make appropriate responses when DOM trees change within a certain range. This API is designed to replace the Mutation events introduced in the DOM3 event specification.

Mutation Observer is an interface for monitoring DOM changes. Mutation Observer will be notified of any changes to the DOM object tree.

Mutation Observer has the following features:

• It runs only after all script tasks are completed, that is, it adopts the asynchronous mode.
• It encapsulates DOM change records into an array for processing, rather than individually processing DOM changes.
• It allows you to observe all the changes that occur on the DOM node, or to observe a certain type of changes.

MDN information: MutationObserver

MutationObserver is a constructor. Therefore, you must use new MutationObserver when creating the constructor;

A callback function is required to instantiate MutationObserver. This callback function is called when the specified DOM node (target node) changes,

During the call, the observer object will pass two parameters to the function:

1: The first parameter is an array containing several MutationRecord objects;

2: The second parameter is the observer object itself.

For example:


The Code is as follows:
Var observer = new MutationObserver (function (mutations ){
Mutations. forEach (function (mutation ){
Console. log (mutation. type );
});
});

Observer Method

The instance observer has three methods: 1: observe; 2: disconnect; 3: takeRecords;

Observe Method

Observe method: register the target node to be observed for the current observer object, and receive a notification when the DOM of the target node (which can also observe its child nodes at the same time) changes;

This method requires two parameters. The first parameter is the target node, and the second parameter is the type of change to be monitored. It is a json object and the example is as follows:


The Code is as follows:
Observer. observe (document. body ,{
'Childlist': true, // The child element of the element is added or deleted.
'Subtree': true, // all child elements of the element are added or deleted.
'Bubuckets': true, // listener attribute change
'Characterdata': true, // listen for text or comment changes
'Bubuteoldvalue ': true, // original Attribute Value
'Characterdataoldvalue ': true
});

Disconnect Method

The disconnect method will stop observing the attributes and node changes of the target node until the next call to the observe method again;

TakeRecords

Clears the record queue of the observer object and returns an array containing the Mutation event object;

MutationObserver is no longer suitable for implementing redo and undo of an editor, because any changes made within a specified node will be recorded. If you use the traditional keydown or keyup implementation, there will be some drawbacks, for example:

1: The rolling position is inaccurate because the rolling position is lost;

2: The focus is lost;
....
It took several hours to write a MutationObserver-implemented undo and redo (undo rollback Management) Management plug-in MutationJS, which can be introduced as a separate plug-in :( http://files.cnblogs.com/files/diligenceday/MutationJS.js ):


The Code is as follows:
/**
* @ Desc MutationJs: use the new DOM3 event MutationObserve. Listen to the specified node element, listen to changes to the internal dom attribute or dom node, and execute the corresponding callback;
**/
Window. nono = window. nono || {};
/**
* @ Desc
**/
Nono. MutationJs = function (dom ){
// Unified compatibility
Var MutationObserver = this. MutationObserver = window. MutationObserver |
Window. WebKitMutationObserver |
Window. MozMutationObserver;
// Determine whether the browser supports MutationObserver;
This. mutationObserverSupport = !! MutationObserver;
// Listen to the sub-element by default, attributes of the sub-element, and attribute value changes;
This. options = {
'Chillist': true,
'Subtree': true,
'Bubuckets': true,
'Characterdata': true,
'Bubuteoldvalue ': true,
'Characterdataoldvalue ': true
};
// This instance saves MutationObserve;
This. muta = {};
// The list variable stores user operations;
This. list = [];
// The index currently rolled back
This. index = 0;
// If there is no dom, the system listens to the body by default;
This. dom = dom | document.doc umentElement. body | document. getElementsByTagName ("body") [0];
// Start listening immediately;
This. observe ();
};
$. Extend (nono. MutationJs. prototype ,{
// When a node changes, redo and undo should be saved to the list;
"Callback": function (records, instance ){
// Clear the index;
This. list. splice (this. index + 1 );
Var _ this = this;
Records. map (function (record ){
Var target = record.tar get;
Console. log (record );
// Delete or add an element;
If (record. type = "childList "){
// Delete an element;
If (record. removedNodes. length! = 0 ){
// Obtain the relative index of an element;
Var indexs = _ this. getIndexs (target. children, record. removedNodes );
_ This. list. push ({
"Undo": function (){
_ This. disconnect ();
_ This. addChildren (target, record. removedNodes, indexs );
_ This. reObserve ();
},
"Redo": function (){
_ This. disconnect ();
_ This. removeChildren (target, record. removedNodes );
_ This. reObserve ();
}
});
// Add an element;
};
If (record. addedNodes. length! = 0 ){
// Obtain the relative index of an element;
Var indexs = _ this. getIndexs (target. children, record. addedNodes );
_ This. list. push ({
"Undo": function (){
_ This. disconnect ();
_ This. removeChildren (target, record. addedNodes );
_ This. reObserve ();
},
"Redo": function (){
_ This. disconnect ();
_ This. addChildren (target, record. addedNodes, indexs );
_ This. reObserve ();
}
});
};
// @ Desc characterData is a ghost;
// Ref: http://baike.baidu.com/link? Url = Z3Xr2y7zIF50bjXDFpSlQ0PiaUPVZhQJO7SaMCJXWHxD6loRcf_TVx1vsG74WUSZ_0-7wq4_oq0Ci-8ghUAG8a
} Else if (record. type = "characterData "){
Var oldValue = record. oldValue;
Var newValue = record.tar get. textContent // | record.tar get. innerText, not compatible with IE789, so innerText is not needed;
_ This. list. push ({
"Undo": function (){
_ This. disconnect ();
Target. textContent = oldValue;
_ This. reObserve ();
},
"Redo": function (){
_ This. disconnect ();
Target. textContent = newValue;
_ This. reObserve ();
}
});
// If the attribute changes, style, dataset, and attribute all belong to attributes and can be processed in a unified manner;
} Else if (record. type = "attributes "){
Var oldValue = record. oldValue;
Var newValue = record.tar get. getAttribute (record. attributeName );
Var attributeName = record. attributeName;
_ This. list. push ({
"Undo": function (){
_ This. disconnect ();
Target. setAttribute (attributeName, oldValue );
_ This. reObserve ();
},
"Redo": function (){
_ This. disconnect ();
Target. setAttribute (attributeName, newValue );
_ This. reObserve ();
}
});
};
});
// Reset the index;
This. index = this. list. length-1;
},
"RemoveChildren": function (target, nodes ){
For (var I = 0, len = nodes. length; I <len; I ++ ){
Target. removeChild (nodes [I]);
};
},
"AddChildren": function (target, nodes, indexs ){
For (var I = 0, len = nodes. length; I <len; I ++ ){
If (target. children [indexs [I]) {
Target. insertBefore (nodes [I], target. children [indexs [I]);
} Else {
Target. appendChild (nodes [I]);
};
};
},
// Shortcut, used to determine which node of the parent element the child belongs;
"IndexOf": function (target, obj ){
Return Array. prototype. indexOf. call (target, obj)
},
"GetIndexs": function (target, objs ){
Var result = [];
For (var I = 0; I <objs. length; I ++ ){
Result. push (this. indexOf (target, objs [I]);
};
Return result;
},
/**
* @ Desc specifies the listener object
**/
"Observe": function (){
If (this. dom. nodeType! = 1) return alert ("the parameter is incorrect. The first parameter should be a dom node ");
This. muta = new this. MutationObserver (this. callback. bind (this ));
// Start listening immediately;
This. muta. observe (this. dom, this. options );
},
/**
* @ Desc start listening again;
**/
"ReObserve": function (){
This. muta. observe (this. dom, this. options );
},
/**
* @ Desc does not record dom operations. All operations in this function are not recorded in the undo and redo lists;
**/
"Without": function (fn ){
This. disconnect ();
Fn & fn ();
This. reObserve ();
},
/**
* @ Desc cancel the listener;
**/
"Disconnect": function (){
Return this. muta. disconnect ();
},
/**
* @ Desc Save the Mutation operation to the list;
**/
"Save": function (obj ){
If (! Obj. undo) return alert ("the first parameter passed in must have the undo method ");
If (! Obj. redo) return alert ("the first parameter passed in must have the redo method ");
This. list. push (obj );
},
/**
* @ Desc;
**/
"Reset": function (){
// Clear the array;
This. list = [];
This. index = 0;
},
/**
* @ Desc Delete the operation after the specified index;
**/
"Splice": function (index ){
This. list. splice (index );
},
/**
* @ Desc go back and cancel the rollback
**/
"Undo": function (){
If (this. canUndo ()){
This. list [this. index]. undo ();
This. index --;
};
},
/**
* @ Desc go forward and re-operate
**/
"Redo": function (){
If (this. canRedo ()){
This. index ++;
This. list [this. index]. redo ();
};
},
/**
* @ Desc determine whether the operation can be undone
**/
"CanUndo": function (){
Return this. index! =-1;
},
/**
* @ Desc: determines whether operations can be performed again;
**/
"CanRedo": function (){
Return this. list. length-1! = This. index;
}
});

How to Use MutationJS

How can this MutationJS be used?


The Code is as follows:
// This is to instantiate a MutationJS object. If no parameter is input, the system listens for changes to the body element by default;
Mu = new nono. MutationJs ();
// You can specify an element, for example;
Mu = new nono. MutationJS (document. getElementById ("div0 "));
// All element changes under this element will be recorded by the plug-in;

There are several methods for Mutation instance mu:

1: mu. undo () operation rollback;

2: mu. redo () undo rollback;

3: Can mu. canUndo () perform rollback? the return value is true or false;

4: Can mu. canRedo () be undone or rolled back? The return value is true or false;

5: mu. reset () clears all undo lists and releases space;

6: mu. without () transmits a function parameter. All dom operations within the function are not recorded;

MutationJS provides a simple undoManager for reference. It runs completely normally on Firefox, chrome, Google browser, and IE11:


The Code is as follows:
<! DOCTYPE html>
<Html>
<Head lang = "en">
<Meta charset = "UTF-8">
<Title> </title>
<Script src = "http://cdn.bootcss.com/jquery/1.9.0/jquery.js"> </script>
<Script src = "http://files.cnblogs.com/files/diligenceday/MutationJS.js"> </script>
</Head>
<Body>
<Div>
<P>
MutationObserver is used to replace a series of Events of the original Mutation Events. The browser monitors the addition, deletion, and replacement of all elements under the specified Element;
</P>
<Div style = "padding: 20px; border: 1px solid # f00">
<Input type = "button" value = "Undo operation" id = "prev">;
<Input type = "button" value = "Undo operation rollback" id = "next">;
</Div>
<Input type = "button" value = "add node" id = "b0">;
<Input value = "text" id = "value">
<Div id = "div"> </div>
</Div>
<Script>
Window. onload = function (){
Window. mu = new nono. MutationJs ();
// Cancel the listener
Mu. disconnect ();
// Listen again
Mu. reObserve ();
Document. getElementById ("b0"). addEventListener ("click", function (ev ){
Div = document. createElement ("div ");
Div. innerHTML = document. getElementById ("value"). value;
Document. getElementById ("div"). appendChild (div );
});
Document. getElementById ("prev"). addEventListener ("click", function (ev ){
Mu. undo ();
});
Document. getElementById ("next"). addEventListener ("click", function (ev ){
Mu. redo ();
});
};
</Script>
</Body>
</Html>

DEMO in IE:

MutatoinObserver browser compatibility:

Feature Chrome Firefox (Gecko) Internet Explorer Opera Safari
Basic support 18

Webkit

26

14 (14) 11 15 6.0 WebKit

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.