澳门新葡萄京官网首页Firefox outerHTML实现代码

在线的 HTML
内容编辑器为用户提供文本的样式控制,例如文字的颜色、字体大小等。虽然现在网上有不少功能强大的编辑器(如:FCKEditor),但是在使用中需要许多复杂的配置,而且代码往往比较“臃肿”。本文的目的就是介绍如何开发一个
HTML
编辑器。应用本文介绍的方法,可以方便的开发出满足自己需求的,代码相对比较精简的
HTML 编辑器。以下是一个应用本文方法开发的 HTML
编辑器,实现了一些简单的功能:

最近发现各大类库都能利用div.innerHTML=HTML片断来生成节点元素,再把它们插入到目标元素的各个位置上。这东西实际上就是insertAdjacentHTML,但是IE可恶的innerHTML把这优势变成劣势。首先innerHTML会把里面的某些位置的空白去掉,见下面运行框的结果:

减少DOM数可以加快浏览器的在解析页面过程中DOM Tree和render
tree的构建,从而提高页面性能。为此我们可以把页面中那些首屏渲染不可见的部分HTML暂存在TextArea中,等完成渲染后再处理这部分HTML来达到这个目的。
要把TextArea
中暂存的HTML内容添加到页面中,使用元素的outerHTML属性是最简单方便的了,不过在DOM标准中并没有定义outerHTML,支持的浏览器有IE6+,safari,
operal和 Chrome,经测试FF4.0-
中还不支持。所以我们就来实现一个可以跨浏览器的outerHTML。
outerHTML 就是获取或设置包含元素标签本身在内的html。下面是实现代码:

代码下载

复制代码 代码如下:

复制代码 代码如下:

开发方法如下:

<!doctype html>
<html dir=”ltr” lang=”zh-CN”>
    <head>
        <meta charset=”utf-8″ />
        <title>
            IE的innerHTML By 司徒正美
        </title>
        <script type=”text/javascript”>
            window.onload = function() {
                var div = document.createElement(“div”);
                div.innerHTML = ”   <td>   
<b>司徒</b>正美         </td>        “
                alert(“|” + div.innerHTML + “|”);
                var c = div.childNodes;
                alert(“生成的节点个数  ” + 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>

if(typeof HTMLElement !== “undefined” && !(“outerHTML” in
HTMLElement.prototype)) {
//console.log(“defined outerHTML”);
HTMLElement.prototype.__defineSetter__(“outerHTML”,function(str){
var fragment = document.createDocumentFragment();
var div = document.createElement(“div”);
div.innerHTML = str;
for(var i=0, n = div.childNodes.length; i<n; i++){
fragment.appendChild(div.childNodes[i]);
}
this.parentNode.replaceChild(fragment, this);
});
//
HTMLElement.prototype.__defineGetter__(“outerHTML”,function(){
var tag = this.tagName;
var attributes = this.attributes;
var attr = [];
//for(var name in attributes){//遍历原型链上成员
for(var i=0,n = attributes.length; i<n; i++){//n指定的属性个数
if(attributes[i].specified){
attr.push(attributes[i].name + ‘=”‘ + attributes[i].value + ‘”‘);
}
}
return ((!!this.innerHTML) ?
‘<‘ + tag + ‘ ‘ + attr.join(‘
‘)+’>’+this.innerHTML+'</’+tag+’>’ :
‘<‘ + tag + ‘ ‘ +attr.join(‘ ‘)+’/>’);
});
}

  1. 添加一个可编辑的 iframe

    <body>
        <p id=”p”>
        </p>
    </body>

代码说明:
1
代码中首先条件判断来监测浏览器是否支持outerHTML以避免覆盖浏览器原生的实现。
2 “__defineSetter__”,”__defineGetter__”
是firefox浏览器私有方面。分别定义当设置属性值和获取属性要执行的操作。
3 在”__defineSetter__”
“outerHTML”中为了避免插入页面中元素过多导致频繁发生reflow影响性能。使用了文档碎片对象fragment来暂存需要插入页面中DOM元素。
4 在”__defineGetter__” “outerHTML”
中使用元素attributes属性来遍历给元素指定的属性。结合innerHTML返回了包含原属本身在内的html字符串。
测试代码:

实现 HTML 编辑器的第 1 步就是在网页中放置一个可编辑的 iframe
用来输入文本,使 iframe 可编辑方法相当简单,只需要将 iframe 的
designMode 设置为 on 即可,具体步骤如下:

</html>

复制代码 代码如下:

var editor = document.getElementById(“IFRAME的ID”);
 
var editorDoc = editor.contentWindow.document;
var editorWindow = editor.contentWindow;
 
editorDoc.designMode = “on”;
 
editorDoc.open();
editorDoc.write(“<html><head></head><body
style=’margin:0px; padding: 0px;’></body></html>”);
editorDoc.close();

另一个可恶的地方是,在IE中以下元素的innerHTML是只读的:col、
colgroup、frameset、html、 head、style、table、tbody、 tfoot、
thead、title 与
tr。为了收拾它们,Ext特意弄了个insertIntoTable。insertIntoTable就是利用DOM的insertBefore与appendChild来添加,情况基本同jQuery。不过jQuery是完全依赖这两个方法,Ext还使用了insertAdjacentHTML。为了提高效率,所有类库都不约而同地使用了文档碎片。基本流程都是通过div.innerHTML提取出节点,然后转移到文档碎片上,然后用insertBefore与appendChild插入节点。对于火狐,Ext还使用了createContextualFragment解析文本,直接插入其目标位置上。显然,Ext的比jQuery是快许多的。不过jQuery的插入的不单是HTML片断,还有各种节点与jQuery对象。下面重温一下jQuery的工作流程吧。

<!DOCTYPE html>
<html>
<head>
<meta charset=”utf-8″ />
<title>outerHTML</title>
</head>
<body>
<div id=”content” class=”test”>
<p>This is <strong>paragraph</strong> with a list
following it</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</ul>
</div>
<script>
if(typeof HTMLElement !== “undefined” && !(“outerHTML” in
HTMLElement.prototype)) {
console.log(“defined outerHTML”);
HTMLElement.prototype.__defineSetter__(“outerHTML”,function(str){
var fragment = document.createDocumentFragment();
var div = document.createElement(“div”);
div.innerHTML = str;
for(var i=0, n = div.childNodes.length; i<n; i++){
fragment.appendChild(div.childNodes[i]);
}
this.parentNode.replaceChild(fragment, this);
});
//
HTMLElement.prototype.__defineGetter__(“outerHTML”,function(){
var tag = this.tagName;
var attributes = this.attributes;
var attr = [];
//for(var name in attributes){//遍历原型链上成员
for(var i=0,n = attributes.length; i<n; i++){//n指定的属性个数
if(attributes[i].specified){
attr.push(attributes[i].name + ‘=”‘ + attributes[i].value + ‘”‘);
}
}
return ((!!this.innerHTML) ?
‘<‘ + tag + ‘ ‘ + attr.join(‘
‘)+’>’+this.innerHTML+'</’+tag+’>’ :
‘<‘ + tag + ‘ ‘ +attr.join(‘ ‘)+’/>’);
});
}
var content = document.getElementById(“content”);
alert(content.outerHTML)
</script>
</body>
</html>

  1. 设置选中文本的样式

复制代码 代码如下:

假设要获取 <p id=”outerID”>sdfdsdfsd</p> 的 P的outerHTML
代码:

设置选中文本样式的方法最简单的方式就是使用 document.execCommand, 但是
execCommand 功能比较局限,有时不能满足需求,例如:execCommand
设置字体大小只能是 1-7,
不能使用像素大小,而且如果你在点击工具栏按钮到调用 execCommand
的过程中点击了其他的 DIV,iframe 的选中内容会消失,这时调用 execCommand
是无效的。因此本文介绍另一种方法,基本思路如下:

append: function() {
  //传入arguments对象,true为要对表格进行特殊处理,回调函数
  return this.domManip(arguments, true, function(elem){
    if (this.nodeType == 1)
      this.appendChild( elem );
  });
},
domManip: function( args, table, callback ) {
  if ( this[0] ) {//如果存在元素节点
    var fragment = (this[0].ownerDocument ||
this[0]).createDocumentFragment(),
    //注意这里是传入三个参数
    scripts = jQuery.clean( args, (this[0].ownerDocument ||
this[0]), fragment ),
    first = fragment.firstChild;

复制代码 代码如下:

(1) 获取选中的 HTML;

    if ( first )
      for ( var i = 0, l = this.length; i < l; i++ )
        callback.call( root(this[i], first), this.length > 1 || i
> 0 ?
      fragment.cloneNode(true) : fragment );

var _p = document.getElementById(‘outerID’);
_P = _P.cloneNode();
var _DIV = document.createElement();
_DIV.appendChild(_P);
alert(_DIV.innerHTML); 就是P的outerHTML;

(2) 修改 HTML 的样式;

    if ( scripts )
      jQuery.each( scripts, evalScript );
  }

firefox没有outerHTML用以下方法解决

(3) 用修改后的 HTML 替换选中的 HTML。

  return this;

复制代码 代码如下:

2.1 获取选中的 HTML

  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为arguments对象,context为document对象,fragment为空的文档碎片
clean: function( elems, context, fragment ) {
  context = context || document;

/**
* 兼容firefox的 outerHTML
使用以下代码后,firefox可以使用element.outerHTML
**/
if(window.HTMLElement) {
HTMLElement.prototype.__defineSetter__(“outerHTML”,function(sHTML){
var r=this.ownerDocument.createRange();
r.setStartBefore(this);
var df=r.createContextualFragment(sHTML);
this.parentNode.replaceChild(df,this);
return sHTML;
});
HTMLElement.prototype.__defineGetter__(“outerHTML”,function(){
var attr;
var attrs=this.attributes;
var str=”<“+this.tagName.toLowerCase();
for(var i=0;i<attrs.length;i++){
attr=attrs[i];
if(attr.specified)
str+=” “+attr.name+’=”‘+attr.value+'”‘;
}
if(!this.canHaveChildren)
return str+”>”;
return
str+”>”+this.innerHTML+”</”+this.tagName.toLowerCase()+”>”;
});
HTMLElement.prototype.__defineGetter__(“canHaveChildren”,function(){
switch(this.tagName.toLowerCase()){
case “area”:
case “base”:
case “basefont”:
case “col”:
case “frame”:
case “hr”:
case “img”:
case “br”:
case “input”:
case “isindex”:
case “link”:
case “meta”:
case “param”:
return false;
}
return true;
});
}

在不同的浏览器中获取选中的 HTML 的方法是不同的,在 IE 中可以使用

  // !context.createElement fails in IE with an error but returns typeof
‘object’
  if ( typeof context.createElement === “undefined” )
  //确保context为文档对象
    context = context.ownerDocument || context[0] &&
context[0].ownerDocument || document;

测试有效.
关于insertAdjacentHTML兼容的解新决办法

var range = document.selection.createRange()

  // If a single string is passed in and it’s a single tag
  // just do a createElement and skip the rest
  //如果文档对象里面只有一个标签,如<div>
  //我们大概可能是在外面这样调用它$(this).append(“<div>”)
 
//这时就直接把它里面的元素名取出来,用document.createElement(“div”)创建后放进数组返回
  if ( !fragment && elems.length === 1 && typeof elems[0] === “string”
) {
    var match = /^<(w+)s*/?>$/.exec(elems[0]);
    if ( match )
      return [ context.createElement( match[1] ) ];
  }
  //利用一个div的innerHTML创建众节点
  var ret = [], scripts = [], div = context.createElement(“div”);
 
//如果我们是在外面这样添加$(this).append(“<td>表格1</td>”,”<td>表格1</td>”,”<td>表格1</td>”)
 
//jQuery.each按它的第四种支分方式(没有参数,有length)遍历aguments对象,callback.call(
value, i, value )
  jQuery.each(elems, function(i,
elem){//i为索引,elem为arguments对象里的元素
    if ( typeof elem === “number” )
      elem += ”;

复制代码 代码如下:

在 Firefox,Chrome 中则使用

    if ( !elem )
      return;

//—在组件最后插入html代码
function InsertHtm(op,code,isStart){
if(Dvbbs_IsIE5)
op.insertAdjacentHTML(isStart ? “afterbegin” : “afterEnd”,code);
else{
var range=op.ownerDocument.createRange();
range.setStartBefore(op);
var fragment = range.createContextualFragment(code);
if(isStart)
op.insertBefore(fragment,op.firstChild);
else
op.appendChild(fragment);
}
}

var range = window.getSelection().getRangeAt(0);

    // 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 + “>”;
      });

关于inner/outerHTML在NC6中的参考
DOM level 1 has no methods to allow for insertion of unparsed HTML into
the document tree (as IE allows with insertAdjacentHTML or assignment to
inner/outerHTML).NN6 (currently in beta as NN6PR3) know supports the
.innerHTMLproperty of HTMLElements so that you can read or write the
innerHTML of a page element like in IE4+.NN6 also provides a DOM level 2
compliant Range object to which a createContextualFragment(‘html source
string’)was added to spare DOM scripters the task of parsing html and
creating DOM elements.You create a Range with var range =
document.createRange();Then you should set its start point to the
element where you want to insert the html for instance var someElement =
document.getElementById(‘elementID’);
range.setStartAfter(someElement);Then you create a document fragment
from the html source to insert for example var docFrag =
range.createContextualFragment(‘<P>Kibology for
all.</P>’);and insert it with DOM methods
someElement.appendChild(docFrag);The Netscape JavaScript 1.5 version
even provides so called setters for properties which together with the
ability to prototype the DOM elements allows to emulate setting of
outerHMTL for NN6:<SCRIPT LANGUAGE=”JavaScript1.5″>if
(navigator.appName == ‘Netscape’) { HTMLElement.prototype.outerHTML
setter = function (html) { this.outerHTMLInput = html; var range =
this.ownerDocument.createRange(); range.setStartBefore(this); var
docFrag = range.createContextualFragment(html);
this.parentNode.replaceChild(docFrag, this); }}</SCRIPT> If you
insert that script block you can then write cross browser code assigning
to .innerHTML .outerHTMLfor instance document.body.innerHTML =
‘<P>Scriptology for all</P>’;which works with both IE4/5 and
NN6.The following provides getter functions for .outerHTMLto allow to
read those properties in NN6 in a IE4/5 compatible way. Note that while
the scheme of traversing the document tree should point you in the right
direction the code example might not satisfy your needs as there are
subtle difficulties when trying to reproduce the html source from the
document tree. See for yourself whether you like the result and improve
it as needed to cover other exceptions than those handled (for the empty
elements and the textarea
element).<HTML><HEAD><STYLE></STYLE><SCRIPT
LANGUAGE=”JavaScript1.5″>var emptyElements = { HR: true, BR: true,
IMG: true, INPUT: true};var specialElements = { TEXTAREA:
true};HTMLElement.prototype.outerHTML getter = function () { return
getOuterHTML (this);}function getOuterHTML (node) { var html = ”;
switch (node.nodeType) { case Node.ELEMENT_NODE: html += ‘<‘; html
+= node.nodeName; if (!specialElements[node.nodeName]) { for (var a =
0; a < node.attributes.length; a++) html += ‘ ‘ +
node.attributes[a].nodeName.toUpperCase() + ‘=”‘ +
node.attributes[a].nodeValue + ‘”‘; html += ‘>’; if
(!emptyElements[node.nodeName]) { html += node.innerHTML; html +=
‘</’ + node.nodeName + ‘>’; } } else switch (node.nodeName) {
case ‘TEXTAREA’: for (var a = 0; a < node.attributes.length; a++) if
(node.attributes[a].nodeName.toLowerCase() != ‘value’) html += ‘ ‘ +
node.attributes[a].nodeName.toUpperCase() + ‘=”‘ +
node.attributes[a].nodeValue + ‘”‘; else var content =
node.attributes[a].nodeValue; html += ‘>’; html += content; html +=
‘</’ + node.nodeName + ‘>’; break; } break; case
Node.TEXT_NODE: html += node.nodeValue; break; case Node.COMMENT_NODE:
html += ‘<!’ + ‘–‘ + node.nodeValue + ‘–‘ + ‘>’; break; } return
html;}</SCRIPT></HEAD><BODY><A HREF=”javascript:
alert(document.documentElement.outerHTML); void 0″>show
document.documentElement.outerHTML</A>|<A HREF=”javascript:
alert(document.body.outerHTML); void 0″>show
document.body.outerHTML</A>|<A HREF=”javascript:
alert(document.documentElement.innerHTML); void 0″>show
document.documentElement.innerHTML</A>|<A HREF=”javascript:
alert(document.body.innerHTML); void 0″>show
document.body.innerHTML</A><FORM
NAME=”formName”><TEXTAREA NAME=”aTextArea” ROWS=”5″
COLS=”20″>JavaScript.FAQTs.comKibology for
all.</TEXTAREA></FORM><DIV><P>JavaScript.FAQTs.com</P><BLOCKQUOTE>Kibology
for all.<BR>All for
Kibology.</BLOCKQUOTE></DIV></BODY></HTML>Note
that the getter/setter feature is experimental and its syntax is subject
to change.
HTMLElement.prototype.innerHTML setter = function (str) { var r =
this.ownerDocument.createRange(); r.selectNodeContents(this);
r.deleteContents(); var df = r.createContextualFragment(str);
this.appendChild(df); return str;}HTMLElement.prototype.outerHTML setter
= function (str) { var r = this.ownerDocument.createRange();
r.setStartBefore(this); var df = r.createContextualFragment(str);
this.parentNode.replaceChild(df, this); return str;}
HTMLElement.prototype.innerHTML getter = function () { return
getInnerHTML(this);}
function getInnerHTML(node) { var str = “”; for (var i=0;
i<node.childNodes.length; i++) str +=
getOuterHTML(node.childNodes.item(i)); return str;}
HTMLElement.prototype.outerHTML getter = function () { return
getOuterHTML(this)}
function getOuterHTML(node) { var str = “”; switch (node.nodeType) {
case 1: // ELEMENT_NODE str += “<” + node.nodeName; for (var i=0;
i<node.attributes.length; i++) { if
(node.attributes.item(i).nodeValue != null) { str += ” ” str +=
node.attributes.item(i).nodeName; str += “=””; str +=
node.attributes.item(i).nodeValue; str += “””; } }
if (node.childNodes.length == 0 && leafElems[node.nodeName]) str +=
“>”; else { str += “>”; str += getInnerHTML(node); str += “<” +
node.nodeName + “>” } break; case 3: //TEXT_NODE str +=
node.nodeValue; break; case 4: // CDATA_SECTION_NODE str +=
“<![CDATA[” + node.nodeValue + “]]>”; break; case 5: //
ENTITY_REFERENCE_NODE str += “&” + node.nodeName + “;” break;
case 8: // COMMENT_NODE str += “<!–” + node.nodeValue + “–>”
break; }
return str;}
var _leafElems = [“IMG”, “HR”, “BR”, “INPUT”];var leafElems = {};for
(var i=0; i<_leafElems.length; i++) leafElems[_leafElems[i]] =
true;
然后我们可以封成JS引用
if (/Mozilla/5.0/.test(navigator.userAgent))
document.write(‘<script type=”text/javascript”
src=”mozInnerHTML.js”></sc’ + ‘ript>’);

2.2 替换选中的 HTML

      // Trim whitespace, otherwise indexOf won’t work as expected
      var tags = elem.replace(/^s+/, “”).substring(0,
10).toLowerCase();

复制代码 代码如下:

通过 2.1
的方法获取了表示选中内容的对象后,就可以调用其方法来替换掉选中的内容。在不同的浏览器中替换选中的
HTML 的方法有所差异,在 IE 中可以只需调用 range.pasteHTML 就行了,在
Firefox,Chrome 中则使用 range.deleteContents 和 range.insertNode
两个方法来实现

      var wrap =
        // option or optgroup
        !tags.indexOf(“<opt”) &&
        [ 1, “<select multiple=’multiple’>”, “</select>” ]
||

<script language=”JavaScript” type=”Text/JavaScript”>
<!–
var emptyElements = { HR: true, BR: true, IMG: true, INPUT: true }; var
specialElements = { TEXTAREA: true };
HTMLElement.prototype.outerHTML getter = function() {
return getOuterHTML(this);
}
function getOuterHTML(node) {
var html = ”;
switch (node.nodeType) {
case Node.ELEMENT_NODE: html += ‘<‘; html += node.nodeName; if
(!specialElements[node.nodeName]) {
for (var a = 0; a < node.attributes.length; a++)
html += ‘ ‘ + node.attributes[a].nodeName.toUpperCase() + ‘=”‘ +
node.attributes[a].nodeValue + ‘”‘;
html += ‘>’;
if (!emptyElements[node.nodeName]) {
html += node.innerHTML;
html += ‘</’ + node.nodeName + ‘>’;
}
} else
switch (node.nodeName) {
case ‘TEXTAREA’: for (var a = 0; a < node.attributes.length; a++)
if (node.attributes[a].nodeName.toLowerCase() != ‘value’)
html
+= ‘ ‘ + node.attributes[a].nodeName.toUpperCase() + ‘=”‘ +
node.attributes[a].nodeValue

2.3 封装一个操作选中 HTML 的类

        !tags.indexOf(“<leg”) &&
        [ 1, “<fieldset>”, “</fieldset>” ] ||

  • ‘”‘;
    else
    var content = node.attributes[a].nodeValue;
    html += ‘>’; html += content; html += ‘</’ + node.nodeName +
    ‘>’; break;
    } break;
    case Node.TEXT_NODE: html += node.nodeValue; break;
    case Node.COMMENT_NODE: html += ‘<!’ + ‘–‘ + node.nodeValue + ‘–‘
  • ‘>’; break;
    }
    return html;
    }
    //–>
    </script>

由于 2.1 中获取的 range
对象的方法在不同浏览器中差异很大,因此,为了方便实现 2.1 和 2.2
提到的两个操作,封装了一个操作选中 HTML 的类
SelectionRange,该类有两个方法,GetSelectedHtml 和 Replace,分别用于获取
HTML 和替换 HTML。其代码如下:

        tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
        [ 1, “<table>”, “</table>” ] ||

Tree和render
tree的构建,从而提高页面性能。为此我们可以把页面中那些首屏渲染不可见的部…

//用于记录浏览器的类型
var browser = {};
 
var ua = navigator.userAgent.toLowerCase();
 
browser.msie = (/msie ([d.]+)/).test(ua);
browser.firefox = (/firefox/([d.]+)/).test(ua);
browser.chrome = (/chrome/([d.]+)/).test(ua);
 
//获取多个节点的HTML
function GetInnerHTML(nodes)
{
    var builder = [];
    for (var i = 0; i < nodes.length; i++)
    {
        if (nodes[i].nodeValue != undefined)
        {
            builder.push(nodes[i].innerHTML);
        }
        else
        {
            if (nodes[i].textContent)
builder.push(nodes[i].textContent.replace(/</ig, function() {
return “<“; }));
            else if (nodes[i].nodeValue)
builder.push(nodes[i].nodeValue.replace(/</ig, function() {
return “<“; }));
        }
    }
    return builder.join(“”);
}
 
function SelectionRange(doc, range)
{
    //获取选中的内容的HTML
    this.GetSelectedHtml = function()
    {
        if (range == null) return “”;
       
        if (browser.msie)
        {
            if (range.htmlText != undefined) return range.htmlText;
            else return “”;
        }
        else if (browser.firefox || browser.chrome)
        {
            return GetInnerHTML(range.cloneContents().childNodes);
        }
        else
        {
            return “”;
        }
    }
   
    //用html替换选中的内容的HTML
    this.Replace = function(html)
    {
        if (range != null)
        {
            if (browser.msie)
            {
                if (range.pasteHTML != undefined)
                {
                   
//当前选中html可能以为某种原因(例如点击了另一个DIV)而丢失,重新选中
                    range.select();
                    range.pasteHTML(html);
                    return true;
                }
            }
            else if (browser.firefox || browser.chrome)
            {
                if (range.deleteContents != undefined &&
range.insertNode != undefined)
                {
                    //将文本html转换成DOM对象
                    var temp = doc.createElement(“DIV”);
                    temp.innerHTML = html;
                   
                    var elems = [];
                    for (var i = 0; i < temp.childNodes.length;
i++)
                    {
                        elems.push(temp.childNodes[i]);
                    }
                   
                    //删除选中的节点
                    range.deleteContents();
                   
                   
//将html对应的节点(即temp的所有子节点)逐个插入到range中,并从temp中删除
                    for (var i in elems)
                    {
                        temp.removeChild(elems[i]);
                        range.insertNode(elems[i]);
                    }
                     return true;
                }
            }
        }
        return false;
    }
}

        !tags.indexOf(“<tr”) &&
        [ 2, “<table><tbody>”,
“</tbody></table>” ] ||

与此同时,还实现了一个函数 GetSelectionRange 用于获取当前选中文本对应的
SelectionRange 对象,

        // <thead> matched above
      (!tags.indexOf(“<td”) || !tags.indexOf(“<th”)) &&
        [ 3, “<table><tbody><tr>”,
“</tr></tbody></table>” ] ||

function GetSelectionRange(win)
{
    var range = null;
   
    if (browser.msie)
    {
        range = win.document.selection.createRange();
        if (range.parentElement().document != win.document)
        {
            range = null;
        }
    }
    else if (browser.firefox || browser.chrome)
    {
        var sel = win.getSelection();
        if (sel.rangeCount > 0) range = sel.getRangeAt(0); else range
= null;
    }
   
    return new SelectionRange(win.document, range);
}

        !tags.indexOf(“<col”) &&
        [ 2,
“<table><tbody></tbody><colgroup>”,
“</colgroup></table>” ] ||

2.4 修改选中的 HTML 的样式

        // IE can’t serialize <link> and <script> tags
normally
        !jQuery.support.htmlSerialize &&//用于创建link元素
      [ 1, “div<div>”, “</div>” ] ||

修改选中的 HTML 的样式方法并不复杂,只需要将 HTML 转成 DOM
对象,然后递归的设置每一个节点对应的样式的值即可 , 具体代码如下:

        [ 0, “”, “” ];

function SetNodeStyle(doc, node, name, value)
{
    if (node.innerHTML == undefined)
    {
        return node;
    }
    else
    {
        node.style[name] = value;
       
        for (var i = 0; i < node.childNodes.length; i++)
        {
            var cn = node.childNodes[i];
            if (node.innerHTML != undefined)
            {
                SetNodeStyle(doc, cn, name, value);
            }
        }
       
        return node;
    }
}
 
function SetStyle(doc, html, name, value)
{
    var dom = doc.createElement(“DIV”);
    dom.innerHTML = html;
   
    for (var i = 0; i < dom.childNodes.length; i++)
    {
        var node = dom.childNodes[i];
       
        if (node.innerHTML == undefined)
        {
            //如果是文本节点,则转换转换成span
            var span = doc.createElement(“SPAN”);
            span.style[name] = value;
            if (node.nodeValue != undefined) span.innerHTML =
node.nodeValue.replace(/</ig, function() { return “<“; });
            else if (node.textContent != undefined) span.innetHTML =
node.textContent.replace(/</ig, function() { return “<“; });
            //替换掉文本节点
            dom.replaceChild(span, node);
        }
        else
        {
            SetNodeStyle(doc, node, name, value);
        }
    }
   
    return dom.innerHTML;
}

      // Go to html and back, then peel off extra wrappers
      div.innerHTML = wrap[1] + elem +
wrap[2];//比如”<table><tbody><tr>”
+<td>表格1</td>+”</tr></tbody></table>”

2.5 示例

      // Move to the right depth
      while ( wrap[0]– )
        div = div.lastChild;

使用以上的代码,就可以相当方便的实现一个 HTML
编辑器,例如,以下代码实现将选中文本的字体大小设置为 32px:

     
//处理IE自动插入tbody,如我们使用$(‘<thead></thead>’)创建HTML片断,它应该返回
     
//'<thead></thead>’,而IE会返回'<thead></thead><tbody></tbody>’
      if ( !jQuery.support.tbody ) {

var range = GetSelectionRange(editorWindow);
var html = SetStyle(editorDoc, range.GetSelectedHtml(), “fontSize”,
“32px”);
range.Replace(html);

        // 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 :
          [];

  1. 总结

        for ( var j = tbody.length – 1; j >= 0 ; –j )
        //如果是自动插入的里面肯定没有内容
          if ( jQuery.nodeName( tbody[ j ], “tbody” ) && !tbody[ j
].childNodes.length )
            tbody[ j ].parentNode.removeChild( tbody[ j ] );

本文给出的代码兼容 IE,Firefox 和 Chrome,如果有其它问题可以通过 EMail
或者 WebIM 与我联系。

      }

作者:卢春城

      // 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 );
     //把所有节点做成纯数组
      elem = jQuery.makeArray( div.childNodes );
    }

E-mail:mrlucc@126.com

    if ( elem.nodeType )
      ret.push( elem );
    else
    //全并两个数组,merge方法会处理IE下object元素下消失了的param元素
      ret = jQuery.merge( ret, elem );

WebIM:

  });

出处:

  if ( fragment ) {
    for ( var i = 0; ret[i]; i++ ) {
     
//如果第一层的childNodes就有script元素节点,就用scripts把它们收集起来,供后面用globalEval动态执行
      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 {
        //遍历各层节点,收集script元素节点
        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;//由于动态插入是传入三个参数,因此这里就返回了
  }

  return ret;
},

澳门新葡萄京官网首页 1

真是复杂的让人掉眼泪!不过jQuery的实现并不太高明,它把插入的东西统统用clean转换为节点集合,再把它们放到一个文档碎片中,然后用appendChild与insertBefore插入它们。在除了火狐外,其他浏览器都支持insertAdjactentXXX家族的今日,应该好好利用这些原生API。下面是Ext利用insertAdjactentHTML等方法实现的DomHelper方法,官网给出的数据:

澳门新葡萄京官网首页 2

这数据有点老了,而且最新3.03早就解决了在IE
table插入内容的诟病(table,tbody,tr等的innerHTML都是只读,insertAdjactentHTML,pasteHTML等方法都无法修改其内容,要用又慢又标准的DOM方法才行,Ext的早期版本就在这里遭遇滑铁卢了)。可以看出,结合insertAdjactentHTML与文档碎片后,IE6插入节点的速度也得到难以置信的提升,直逼火狐。基于它,Ext开发了四个分支方法insertBefore、insertAfter、insertFirst、append,分别对应jQuery的before、after、prepend与append。不过,jQuery还把这几个方法巧妙地调换了调用者与传入参数,衍生出insertBefore、insertAfter、prependTo与appendTo这几个方法。但不管怎么说,jQuery这样一刀切的做法实现令人不敢苛同。下面是在火狐中实现insertAdjactentXXX家族的一个版本:

复制代码 代码如下:

(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
})()

我们可以利用它设计出更快更合理的动态插入方法。下面是我的一些实现:

复制代码 代码如下:

//四个插入方法,对应insertAdjactentHTML的四个插入位置,名字就套用jQuery的
//stuff可以为字符串,各种节点或dom对象(一个类数组对象,便于链式操作!)
//代码比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”);
        });
    }

它们里面都是调用了两个静态方法,batch与insert。由于dom对象是类数组对象,我仿效jQuery那样为它实现了几个重要迭代器,forEach、map与filter等。一个dom对象包含复数个DOM元素,我们就可以用forEach遍历它们,执行其中的回调方法。

复制代码 代码如下:

batch:function(els,callback){
    els.forEach(callback);
    return els;//链式操作
},

insert方法执行jQuery的domManip方法相应的机能(dojo则为place方法),但insert方法每次处理一个元素节点,不像jQuery那样处理一组元素节点。群集处理已经由上面batch方法分离出去了。

复制代码 代码如下:

insert : function(el,stuff,where){
     //定义两个全局的东西,提供内部方法调用
     var doc = el.ownerDocument || dom.doc,
     fragment = doc.createDocumentFragment();
    
if(stuff.version){//如果是dom对象,则把它里面的元素节点移到文档碎片中
         stuff.forEach(function(el){
             fragment.appendChild(el);
         })
         stuff = fragment;
     }
     //供火狐与IE部分元素调用
     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;
         }
     };
      //供火狐调用
     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);
     };
    
//以下元素的innerHTML在IE中是只读的,调用insertAdjacentElement进行插入就会出错
     // col, colgroup, frameset, html, head, style, title,table, tbody,
tfoot, thead, 与tr;
     dom._insertAdjacentIEFix = function(el,htmlStr,where){
         var parsedHTML = dom.parseHTML(htmlStr,fragment);
         dom._insertAdjacentElement(el,parsedHTML,where)
     };
     //如果是节点则复制一份
     stuff = stuff.nodeType ?  stuff.cloneNode(true) : stuff;
     if (el.insertAdjacentHTML)
{//ie,chrome,opera,safari都已实现insertAdjactentXXX家族
         try{//适合用于opera,safari,chrome与IE
             el[‘insertAdjacent’+ (stuff.nodeType ?
‘Element’:’HTML’)](where,stuff);
         }catch(e){
            
//IE的某些元素调用insertAdjacentXXX可能出错,因此使用此补丁
             dom._insertAdjacentIEFix(el,stuff,where);
         }     
     }else{
         //火狐专用
         dom[‘_insertAdjacent’+ (stuff.nodeType ?
‘Element’:’HTML’)](el,stuff,where);
     }
 }

insert方法在实现火狐插入操作中,使用了W3C DOM
Range对象的一些罕见方法,具体可到火狐官网查看。下面实现把字符串转换为节点,利用innerHTML这个伟大的方法。Prototype.js称之为_getContentFromAnonymousElement,但有许多问题,dojo称之为_toDom,mootools的Element.Properties.html,jQuery的clean。Ext没有这东西,它只支持传入HTML片断的insertAdjacentHTML方法,不支持传入元素节点的insertAdjacentElement。但有时,我们需要插入文本节点(并不包裹于元素节点之中),这时我们就需要用文档碎片做容器了,insert方法出场了。

复制代码 代码如下:

parseHTML : function(htmlStr, fragment){
    var div = dom.doc.createElement(“div”),
    reSingleTag = 
/^<(w+)s*/?>$/;//匹配单个标签,如<li>
    htmlStr += ”;
    if(reSingleTag.test(htmlStr)){//如果str为单个标签
        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:]+)/,//匹配一对标签或多个标签,如<li></li>,li
    match = htmlStr.match(reMultiTag),
    tag = match ? match[1].toLowerCase() : “”;//解析为<li,li
    if(match && tagWrap[tag]){
        var wrap = tagWrap[tag];
        div.innerHTML = wrap.pre + htmlStr + wrap.post;
        n = wrap.length;
        while(–n >= 0)//返回我们已经添加的内容
            div = div.lastChild;
    }else{
        div.innerHTML = htmlStr;
    }
   
//处理IE自动插入tbody,如我们使用dom.parseHTML(‘<thead></thead>’)转换HTML片断,它应该返回
   
//'<thead></thead>’,而IE会返回'<thead></thead><tbody></tbody>’
    //亦即,在标准浏览器中return div.children.length会返回1,IE会返回2
    if(dom.feature.autoInsertTbody && !!tagWrap[tag]){
        var ownInsert = tagWrap[tag].join(”).indexOf(“tbody”) !==
-1,//我们插入的
        tbody = div.getElementsByTagName(“tbody”),
        autoInsert = tbody.length > 0;//IE插入的
        if(!ownInsert && autoInsert){
            for(var i=0,n=tbody.length;i<n;i++){
                if(!tbody[i].childNodes.length
)//如果是自动插入的里面肯定没有内容
                    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)){ //
将div上的节点转移到文档碎片上!
            fragment.appendChild(firstChild);
        }
        return fragment;
    }
    return div.children;
}

嘛,基本上就是这样,运行起来比jQuery快许多,代码实现也算优美,至少没有像jQuery那样乱成一团。jQuery还有四个反转方法。下面是jQuery的实现:

复制代码 代码如下:

jQuery.each({
    appendTo: “append”,
    prependTo: “prepend”,
    insertBefore: “before”,
    insertAfter: “after”,
    replaceAll: “replaceWith”
}, function(name, original){
    jQuery.fn[ name ] = function( selector )
{//插入物(html,元素节点,jQuery对象)
        var ret = [], insert = jQuery( selector
);//将插入转变为jQuery对象
        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
);//调用四个已实现的插入方法
            ret = ret.concat( elems );
        }
        return this.pushStack( ret, name, selector
);//由于没有把链式操作的代码分离出去,需要自行实现
    };
});

我的实现:

复制代码 代码如下:

dom.each({
    appendTo: ‘append’,
    prependTo: ‘prepend’,
    insertBefore: ‘before’,
    insertAfter: ‘after’
},function(method,name){
    dom.prototype[name] = function(stuff){
        return dom(stuff)[method](this);
    };
});

大致的代码都给出,大家可以各取所需。

发表评论

电子邮件地址不会被公开。 必填项已用*标注