Deep understanding of JavaScript dynamic insertion techniques _javascript techniques

Source: Internet
Author: User
Tags return tag

It has recently been found that each large class library can use div.innerhtml=html fragments to generate node elements and then insert them into various locations of the target elements. This thing is actually insertadjacenthtml, but IE hateful innerhtml turn this advantage into a disadvantage. First of all, innerHTML will remove some of the blanks in the inside, see the results of the following run box:

Copy Code code as follows:






<!doctype html>


<html dir= "ltr" lang= "ZH-CN" >


<head>


<meta charset= "Utf-8"/>


<title>


IE innerHTML by Masaki


</title>


<script type= "Text/javascript" >


Window.onload = function () {


var div = document.createelement ("div");


div.innerhtml = "<td> <b> situ </b> positive Beauty </td>"


Alert ("|" + div.innerhtml + "|");


var c = div.childnodes;


Alert ("Number of nodes generated" + c.length);


for (Var i=0,n=c.length;i<n;i++) {


alert (C[i].nodetype);


if (C[i].nodetype = = 1) {


Alert ("::" +c[i].childnodes.length);


}


}


}


</script>


</head>





<body>


<p id= "P" >


</p>


</body>

Another hateful place is that the innerhtml of the following elements in IE are read-only: col, Colgroup, frameset, HTML, head, style, table, Tbody, TFOOT, THEAD, title, and tr. In order to pack them, ext deliberately made a insertintotable. Insertintotable is the use of Dom InsertBefore and appendchild to add, the situation is basically the same as jquery. But jquery is totally dependent on both methods, and Ext also uses insertadjacenthtml. In order to improve efficiency, all class libraries invariably use document fragmentation. The basic process is extracted from the node by div.innerhtml, then transferred to the document fragment, and then inserted into the node with InsertBefore and AppendChild. For Firefox, Ext also uses the Createcontextualfragment parsing text and inserts it directly into its target location. Obviously, ext is much faster than jquery. But jquery inserts not only HTML snippets, but also a variety of nodes and jquery objects. Now let's revisit the jquery workflow.

Copy Code code as follows:



Append:function () {


Incoming arguments object, true to special handling of table, callback function


Return This.dommanip (arguments, True, function (Elem) {


if (This.nodetype = 1)


This.appendchild (Elem);


});


},


Dommanip:function (args, table, callback) {


if (This[0]) {//If an element node exists


var fragment = (This[0].ownerdocument | | this[0]). Createdocumentfragment (),


Note that this is a three-parameter pass.


Scripts = Jquery.clean (args, (this[0].ownerdocument | | this[0)), fragment),


i = Fragment.firstchild;





if (a)


for (var i = 0, L = this.length i < l; i++)


Callback.call (Root (This[i], a), This.length > 1 | | | i > 0?


Fragment.clonenode (TRUE): fragment);





if (scripts)


Jquery.each (scripts, evalscript);


}





return this;





function root (elem, cur) {


return table && jquery.nodename (elem, "table") && jquery.nodename (cur, "tr")?


(Elem.getelementsbytagname ("tbody") [0] | |


Elem.appendchild (Elem.ownerDocument.createElement ("Tbody")):


Elem


}


}


Elems is arguments object, context is document object, fragment is blank


Clean:function (Elems, context, fragment) {


Context = Context | | Document





!context.createelement fails in IE with an error but returns typeof ' object '


if (typeof context.createelement = = "undefined")


Make sure the context is a Document object


Context = Context.ownerdocument | | Context[0] && Context[0].ownerdocument | | Document





If A single string are passed in and it's a single tag


Just do a createelement and skip the rest


If there is only one label in the Document object, such as <div>


We probably were outside. Call it $ (this). Append ("<div>")


The element name inside it is then taken out, and the document.createelement ("div") is created and put into the array to return


if (!fragment && elems.length = = 1 && typeof elems[0] = = = "string") {


var match =/^< (\w+) \s*\/?>$/.exec (elems[0));


if (match)


return [Context.createelement (Match[1])];


}


Use a div innerhtml to create a public node


var ret = [], scripts = [], div = context.createelement ("div");


If we're outside this way add $ (this). Append ("<td> form 1</td>", "<td> form 1</td>", "<td> form 1</td>")


The Jquery.each traverses the Aguments object by its fourth branch (no parameter, length), Callback.call (value, I, value)


Jquery.each (Elems, function (i, elem) {//i as index, elem as element in arguments object


if (typeof Elem = = "Number")


Elem + = ';





if (!elem)


Return





Convert HTML string into DOM nodes


if (typeof Elem = = "string") {


Fix "XHTML"-style tags in all browsers


Elem = Elem.replace (< (\w+) [^>]*?) \/>/g, function (all, front, tag) {


Return Tag.match (/^ (abbr|br|col|img|input|link|meta|param|hr|area|embed) $/i)?


All:


Front + "></" + tag + ">";


});





Trim whitespace, otherwise indexOf won ' t work as expected


var tags = elem.replace (/^\s+/, ""). Substring (0). toLowerCase ();





var wrap =


option or Optgroup


!tags.indexof ("<opt") &&


[1, "<select multiple= ' multiple ' >", "</select>"] | |





!tags.indexof ("<leg") &&


[1, "<fieldset>", "</fieldset>"] | |





Tags.match (/^< (THEAD|TBODY|TFOOT|COLG|CAP)/) &&


[1, "<table>", "</table>"] | |





!tags.indexof ("<tr") &&


[2, "<table><tbody>", "</tbody></table>"] | |





<thead> matched above


(!tags.indexof ("<td") | | |!tags.indexof ("<th") &&


[3, "<table><tbody><tr>", "</tr></tbody></table>"] | |





!tags.indexof ("<col") &&


[2, "<table><tbody></tbody><colgroup>", "</colgroup></table>"] | |





IE can ' t serialize <link> and <script> tags normally


!jquery.support.htmlserialize &&//is used to create a LINK element


[1, "div<div>", "</div>"] | |





[0, "", ""];





Go to HTML and back, then peel off extra wrappers


div.innerhtml = wrap[1] + Elem + wrap[2];//such as "<table><tbody><tr>" +<td> form 1</td>+ "</" Tr></tbody></table> "





Move to the right depth


while (wrap[0]--)


div = div.lastchild;





Handle IE automatically insert tbody, such as we use $ (' <thead></thead> ') to create an HTML fragment, it should return


' <thead></thead> ', and IE will return ' <thead></thead><tbody></tbody> '


if (!jquery.support.tbody) {





String was a <table> *may* have spurious <tbody>


var hasbody =/<tbody/i.test (Elem),


Tbody =!tags.indexof ("<table") &&!hasbody?


Div.firstchild && Div.firstChild.childNodes:





String was a bare <thead> or <tfoot>


WRAP[1] = = "<table>" &&!hasbody?


Div.childnodes:


[];





for (var j = tbody.length-1 J >= 0;--j)


If it's automatically inserted, there's definitely no content.


if (Jquery.nodename (tbody[j], "Tbody") &&!tbody[J].childnodes.length)


tbody[J].parentnode.removechild (tbody[J]);





}





IE completely kills leading whitespace when InnerHTML is used


if (!jquery.support.leadingwhitespace &&/^\s/.test (elem))


Div.insertbefore (Context.createtextnode (Elem.match (/^\s*/) [0]), div.firstchild);


Make all nodes a pure array


Elem = Jquery.makearray (div.childnodes);


}





if (Elem.nodetype)


Ret.push (Elem);


Else


All two arrays, the merge method handles the PARAM elements that are missing under IE under the object element.


ret = Jquery.merge (ret, elem);





});





if (fragment) {


for (var i = 0; ret[i]; i++) {


If the childnodes on the first level has script element nodes, collect them with scripts for later use globaleval dynamic execution


if (Jquery.nodename (Ret[i], "script") && (!ret[i].type | | ret[i].type.tolowercase () = = "Text/javascript")) {


Scripts.push (Ret[i].parentnode ret[i].parentnode.removechild (Ret[i]): Ret[i]);


} else {


Iterate through the nodes of each layer to collect the script element node


if (Ret[i].nodetype = = 1)


Ret.splice.apply (ret, [i + 1, 0].concat (Jquery.makearray (Ret[i].getelementsbytagname ("script")));


Fragment.appendchild (Ret[i]);


}


}





Return scripts;//Because dynamic inserts are passed in three parameters, this returns the


}





return ret;


},

It's so complicated that people shed tears! But jquery's implementation is not so good, it transforms everything that is plugged into a collection of nodes, puts them in a document fragment, and inserts them with AppendChild and InsertBefore. In addition to Firefox, other browsers support the Insertadjactentxxx family today, you should make good use of these native APIs. The following is ext use insertadjactenthtml methods to achieve the Domhelper method, the official website gives the data:

The data is a bit old, and the latest 3.03 has already solved the table,tbody,tr of IE table Insert content (innerHTML is read-only, insertadjactenthtml,pastehtml and other methods can not modify its content, To use a slow and standard Dom method, the earlier version of Ext was hit by Waterloo. As can be seen, combined with insertadjactenthtml and document fragments, IE6 the speed of the insertion of the node has been incredibly elevated, almost to Firefox. Based on it, ext developed four branch methods InsertBefore, InsertAfter, Insertfirst, append, respectively corresponding to jquery before, after, Prepend and append. However, jquery has cleverly swapped the caller and incoming parameters to derive the InsertBefore, InsertAfter, Prependto and Appendto methods. But in any case, jquery's one-size-fits-all approach is not very onerous. Here is a version of the Insertadjactentxxx family that is implemented in Firefox:

Copy Code code as follows:



(function () {


if (' HtmlElement ' in this) {


if (' insertadjacenthtml ' in Htmlelement.prototype) {


Return


}


} else {


Return


}





function Insert (w, N) {


Switch (W.touppercase ()) {


Case ' BeforeEnd ':


This.appendchild (N)


Break


Case ' Beforebegin ':


This.parentNode.insertBefore (n, this)


Break


Case ' Afterbegin ':


This.insertbefore (n, This.childnodes[0])


Break


Case ' Afterend ':


This.parentNode.insertBefore (n, this.nextsibling)


Break


}


}





function insertAdjacentText (W, t) {


Insert.call (This, W, document.createTextNode) (T | | ''))


}





function insertAdjacentHTML (W, h) {


var r = Document.createrange ()


R.selectnode (This)


Insert.call (This, W, r.createcontextualfragment (h))


}





function Insertadjacentelement (w, N) {


Insert.call (This, W, N)


return n


}





HTMLElement.prototype.insertAdjacentText = insertAdjacentText


HTMLElement.prototype.insertAdjacentHTML = insertadjacenthtml


HTMLElement.prototype.insertAdjacentElement = Insertadjacentelement


})()

We can use it to design a faster and more reasonable dynamic insertion method. Here are some of my implementations:

Copy Code code as follows:



Four insertion methods, corresponding to the insertadjactenthtml four insertion positions, the name applies to jquery's


Stuff can be a string, a variety of nodes or DOM objects (a class array object, easy to chain operation!) )


Code is simpler than the implementation of jquery!


Append:function (stuff) {


Return Dom.batch (This,function (EL) {


Dom.insert (El,stuff, "BeforeEnd");


});


},


Prepend:function (stuff) {


Return Dom.batch (This,function (EL) {


Dom.insert (El,stuff, "Afterbegin");


});


},


Before:function (stuff) {


Return Dom.batch (This,function (EL) {


Dom.insert (El,stuff, "Beforebegin");


});


},


After:function (stuff) {


Return Dom.batch (This,function (EL) {


Dom.insert (El,stuff, "afterend");


});


}

All of them are called two static methods, batch and inserts. Because the DOM object is a class array object, I have implemented several important iterators for it, such as ForEach, map, and filter, as I did with jquery. A DOM object contains multiple DOM elements, and we can traverse them with a foreach and execute the callback method.

Copy Code code as follows:

Batch:function (Els,callback) {
Els.foreach (callback);
Return els;//chain operation
},

The Insert method performs the corresponding function of the Dommanip method of jquery (Dojo is the place method), but the Insert method processes one element node at a time, unlike jquery, which processes a set of ELEMENT nodes. Cluster processing has been separated by the above batch method.

Copy Code code as follows:



Insert:function (El,stuff,where) {


Defines two global things that provide an internal method call


var doc = El.ownerdocument | | Dom.doc,


fragment = Doc.createdocumentfragment ();


if (stuff.version) {//If it is a DOM object, move the element node inside it to the document fragment


Stuff.foreach (function (EL) {


Fragment.appendchild (EL);


})


stuff = fragment;


}


For Firefox and IE part of the element call


Dom._insertadjacentelement = function (el,node,where) {


Switch (where) {


Case ' Beforebegin ':


El.parentNode.insertBefore (Node,el)


Break


Case ' Afterbegin ':


El.insertbefore (Node,el.firstchild);


Break


Case ' BeforeEnd ':


El.appendchild (node);


Break


Case ' Afterend ':


if (el.nextsibling) El.parentNode.insertBefore (node,el.nextsibling);


else El.parentNode.appendChild (node);


Break


}


};


For Firefox calls


dom._insertadjacenthtml = function (el,htmlstr,where) {


var range = Doc.createrange ();


Switch (where) {


Case "Beforebegin"://before


Range.setstartbefore (EL);


Break


Case "Afterbegin"://after


Range.selectnodecontents (EL);


Range.collapse (TRUE);


Break


Case "BeforeEnd"://append


Range.selectnodecontents (EL);


Range.collapse (FALSE);


Break


Case "Afterend"://prepend


Range.setstartafter (EL);


Break


}


var parsedhtml = range.createcontextualfragment (HTMLSTR);


Dom._insertadjacentelement (El,parsedhtml,where);


};


The innerHTML of the following elements is read-only in IE, and an error occurs when you invoke insertadjacentelement to insert


Col, Colgroup, frameset, HTML, head, style, title,table, tbody, TFOOT, THEAD, and TR;


Dom._insertadjacentiefix = function (el,htmlstr,where) {


var parsedhtml = dom.parsehtml (htmlstr,fragment);


Dom._insertadjacentelement (El,parsedhtml,where)


};


If it is a node, duplicate a copy of the


Stuff = Stuff.nodetype? Stuff.clonenode (true): stuff;


if (el.insertadjacenthtml) {//ie,chrome,opera,safari has been implemented INSERTADJACTENTXXX family


try{//suitable for opera,safari,chrome and IE


el[' insertadjacent ' + (Stuff.nodetype? ' Element ': ' HTML '] (where,stuff);


}catch (e) {


Some elements of IE may have an error calling insertadjacentxxx, so use this patch


Dom._insertadjacentiefix (El,stuff,where);


}


}else{


Firefox Special


dom[' _insertadjacent ' + (Stuff.nodetype? ' Element ': ' HTML '] (el,stuff,where);


}


}

Insert method in the implementation of the Firefox insert operation, using a number of rare methods of the DOM Range object, specific to the Firefox website to view. The following implementation converts the string into a node, using innerHTML this great method. Prototype.js called _getcontentfromanonymouselement, but there are many problems that Dojo calls _todom, The clean mootools of the element.properties.html,jquery. Ext does not have this thing, it only supports the insertAdjacentHTML method of passing in the HTML fragment, and does not support the insertadjacentelement of the incoming element node. But sometimes we need to insert a text node (not wrapped in an element node), and then we need to make a container out of document fragments, and the Insert method is out.

Copy Code code as follows:



Parsehtml:function (Htmlstr, fragment) {


var div = dom.doc.createElement ("div"),


Resingletag =/^< (\w+) \s*\/?>$/;//matches a single label, such as <li>


Htmlstr + = ';


if (Resingletag.test (HTMLSTR)) {//If STR is a single label


return [Dom.doc.createElement (regexp.$1)]


}


var tagwrap = {


Option: [SELECT],


Optgroup: ["select"],


TBODY: ["Table"],


THEAD: ["Table"],


TFOOT: ["Table"],


TR: ["Table", "tbody"],


TD: ["Table", "Tbody", "tr"],


TH: ["Table", "Thead", "tr"],


Legend: ["FieldSet"],


Caption: ["table"],


Colgroup: ["Table"],


Col: ["Table", "Colgroup"],


Li: ["ul"],


link:["DIV"]


};


for (var param in tagwrap) {


var tw = Tagwrap[param];


Switch (param) {


Case "option": Tw.pre = ' <select multiple= ' multiple ' > '; Break


Case "link": Tw.pre = ' fixbug<div> '; Break


Default:tw.pre = "<" + Tw.join ("><") + ">";


}


Tw.post = "</" + tw.reverse (). Join ("></") + ">";


}


var remultitag =/<\s* ([\w\:]+)/,//matches a pair of labels or multiple labels, such as <li></li>,li


Match = Htmlstr.match (Remultitag),


Tag = match? Match[1].tolowercase (): "";/resolution to <li,li


if (Match && Tagwrap[tag]) {


var wrap = Tagwrap[tag];


div.innerhtml = wrap.pre + htmlstr + wrap.post;


n = wrap.length;


while (--n >= 0)//Return what we have added


div = div.lastchild;


}else{


div.innerhtml = Htmlstr;


}


Handle IE automatically insert tbody, such as we use dom.parsehtml (' <thead></thead> ') to convert HTML fragments, it should return


' <thead></thead> ', and IE will return ' <thead></thead><tbody></tbody> '


That is, in the standard browser return div.children.length returns 1,ie will return 2


if (Dom.feature.autoInsertTbody &&!!! Tagwrap[tag]) {


var Owninsert = Tagwrap[tag].join ('). IndexOf ("tbody")!== -1,//We inserted


Tbody = Div.getelementsbytagname ("Tbody"),


AutoInsert = tbody.length > 0;//ie inserted


if (!owninsert && autoinsert) {


for (Var i=0,n=tbody.length;i<n;i++) {


if (!tbody[i].childnodes.length)//If it is automatically inserted inside there must be no content


Tbody[i].parentnode.removechild (Tbody[i]);


}


}


}


if (Dom.feature.autoRemoveBlank &&/^\s/.test (HTMLSTR))


Div.insertbefore (Dom.doc.createTextNode (Htmlstr.match (/^\s*/) [0]), div.firstchild);


if (fragment) {


var firstchild;


while ((FirstChild = Div.firstchild)) {//Transfer the node on the div to the document fragment!


Fragment.appendchild (FirstChild);


}


return fragment;


}


return div.children;


}

Well, basically, it runs a lot faster than jquery, and the code implementation is pretty, at least not as mess as jquery. jquery also has four inversion methods. Here is the implementation of jquery:

Copy Code code as follows:



Jquery.each ({


Appendto: "Append",


Prependto: "Prepend",


InsertBefore: "Before",


InsertAfter: "After",


ReplaceAll: "ReplaceWith"


}, function (name, original) {


jquery.fn[Name] = function (selector) {//insert (HTML, element node, jquery object)


var ret = [], insert = jquery (selector);//Convert Insert to jquery object


for (var i = 0, L = insert.length i < l; i++) {


var elems = (i > 0 this.clone (True): this). get ();


jquery.fn[Original].apply (JQuery (insert[i), elems);//Call four implemented insert methods


ret = Ret.concat (elems);


}


Return This.pushstack (ret, name, selector);//due to the absence of a chain-operated code, it is necessary to implement the


};


});

My implementation:

Copy Code code as follows:

Dom.each ({
Appendto: ' Append ',
Prependto: ' Prepend ',
InsertBefore: ' Before ',
InsertAfter: ' After '
},function (method,name) {
Dom.prototype[name] = function (stuff) {
return dom (Stuff) [method] (this);
};
});

The general code is given, you can get everything you want.

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.