模拟实现 Range 的 insertNode() 方法

作者:怿飞 来源:怿飞blog 时间:2010-11-30 21:39:00 

最近对 RangeSelection 比较感兴趣。

基本非 IE 的浏览器都支持 DOM Level2 中的 Range,而 IE 中仅有自己的简单处理方法(Text Rang)。

扩展阅读:

而 IE 下的 Text Rang 主要用来处理文本,并非 DOM 节点,那如何在 IE 下模拟 DOM Level2 中的 Range 呢?

根据规范的 API,我们需要模拟下述属性和方法:

function zRange() {
    // Inital states
    this.startContainer = document;
    this.startOffset = 0;
    this.endContainer = document;
    this.endOffset = 0;
    this.commonAncestorContainer = document;
    this.collapsed = true;

    // Range constants
    this.START_TO_START = 0;
    this.START_TO_END = 1;
    this.END_TO_END = 2;
    this.END_TO_START = 3;
}

zRange.prototype = {
    // Public methods
    setStart : function(node, offset){},
    setEnd : function(node, offset){},
    setStartBefore : function(node){},
    setStartAfter : function(node){},
    setEndBefore : function(node){},
    setEndAfter : function(node){},
    collapse : function(toStart) {},
    selectNode : function(node) {},
    selectNodeContents : function(node){},
    deleteContents : function() {},
    extractContents : function(){},
    cloneContents : function() {},
    surroundContents : function () {},
    insertNode : function(node) {},
    cloneRange : function() {},
    detach : function() {},
    compareBoundaryPoints : function (how, sourceRange) {},
    constructor : zRange
}

我们还可以看一组使用 Range 方法和属性的统计数据,对于 2/8 原则的实践或许有帮助:

/**
 * Resource reference : http://kb.operachina.com/node/147
 *
 * collapse             51,435
 * setStartBefore       43,138
 * setStartAfter        40,270
 * selectNodeContents   37,027
 * collapsed            12,862
 * selectNode            4,636
 * deleteContents        3,935
 * setStart              3,171
 * startOffset           3,150
 * setEnd                3,086
 * detach                2,732
 * startContainer        2,659
 * endOffset             2,647
 * insertNode            2,321
 * cloneContents         2,261
 * endContainer          2,236
 * cloneRange            1,993
 * setEndAfter           1,911
 *
 */

下面我们简单讨论一下 Range 的 insertNode() 方法的模拟实现:

The insertNode() method inserts the specified node into the Range’s context tree. The node is inserted at the start boundary-point of the Range, without modifying it.

If the start boundary point of the Range is in a Text node, the insertNode operation splits the Text node at the boundary point. If the node to be inserted is also a Text node, the resulting adjacent Text nodes are not normalized automatically; this operation is left to the application.

The Node passed into this method can be a DocumentFragment. In that case, the contents of the DocumentFragment are inserted at the start boundary-point of the Range, but the DocumentFragment itself is not. Note that if the Node represents the root of a sub-tree, the entire sub-tree is inserted.

从上面的引用得知 insertNode() 方法用来在选区的开头插入节点,固我们先获取 Range 对象的 startContainer(Range 是从那个节点中开始的,即选区中第一个节点的父节点) 和 startOffset(在 startContainer 中 Range 开始的偏移位置) 属性。

var sc = this.startContainer,
    so = this.startOffset;

注: 如果 startContainer 是文本节点、注释节点或者是 CData 节点,startOffset 是指 Range 开始前的字符数,否则,偏移是 Range 中的第一个节点在其父节点中的索引。

接下来,我们需将情况分为下面两种:

第一种情形:startContainer 节点为文本节点、注释节点或者是 CData 节点。“startOffset 是指 Range 开始前的字符数”。

  • 如果 startOffset 等于 0,则表示 Range 是从 startContainer 起始位置开始,应将 node 插入到 startContainer 节点之前 sc.parentNode.insertBefore(node, sc);

  • 如果 startOffset 大于等于 startContainer 本身的节点长度,则表示 Range 是从 startContainer 末尾位置开始,应将 node 插入到 startContainer 节点之后,即如果存在下一节点,则插入到下一之前 sc.parentNode.insertBefore(node, sc.nextSibling);,如果不存在下一节点,则加入到 startContainer父节点最后 sc.parentNode.appendChild(node);

  • 如果 startOffset 在 startContainer 本身的节点长度之内,我们通过oSplitNode = object.splitText( [iIndex])将节点在startOffset 一分为二 nn = sc.splitText(so);,分为两个节点,则应将 node 插入到新生成的第二个节点之前 sc.parentNode.insertBefore(node, nn);

    The text node that invokes the splitText method has a nodeValue equal to the substring of the value, from zero to iIndex. The new text node has a nodeValue of the substring of the original value, from the specified index to the value length. Text node integrity is not preserved when the document is saved or persisted.

if (so===0) {
    // At the start of text
    sc.parentNode.insertBefore(node, sc);
} else if (so >= sc.nodeValue.length) {
    // At the end of text
    if (ns) {
        sc.parentNode.insertBefore(node, sc.nextSibling);
    } else {
        sc.parentNode.appendChild(node);
    }
} else {
    // Middle, need to split
    // http://msdn.microsoft.com/zh-cn/library/ms536764.aspx
    nn = sc.splitText(so);
    sc.parentNode.insertBefore(node, nn);
}

第二种情形:startContainer 节点为非 第一种情形中的节点。“偏移是 Range 中的第一个节点在其父节点中的索引”。获取 startContainer 中子节点偏移量为 startOffset 的节点 cn = sc.childNodes[so];,如果存在,则按照 insertNode 方法的定义,应将 node 插入到该节点之前 sc.insertBefore(node, cn);,如果不存在,即 startOffset 大于等于 sc.childNodes.length,则应将 node 插入到 startContainer 的子节点最后sc.appendChild(node);

if (sc.childNodes.length > 0) {
    cn = sc.childNodes[so];
}

if (cn) {
    sc.insertBefore(node, cn);
} else {
    sc.appendChild(node);
}

详细代码实现如下:

zRange.prototype.insertNode = function(node) {
    var sc = this.startContainer,
        so = this.startOffset,
        p = sc.parentNode,
        ns = sc.nextSibling,
        nn, cn;

    // 如果节点是 TEXT_NODE 或者 CDATA_SECTION_NODE
    if ((sc.nodeType === 3 || sc.nodeType === 4 || sc.nodeType === 8 ) && sc.nodeValue) {
        if (so === 0) {
            // At the start of text
            p.insertBefore(node, sc);
        } else if (so >= sc.nodeValue.length) {
            // At the end of text
            if (ns) {
                p.insertBefore(node, ns);
            } else {
                p.appendChild(node);
            }
        } else {
            // Middle, need to split
            // http://msdn.microsoft.com/zh-cn/library/ms536764.aspx
            nn = sc.splitText(so);
            p.insertBefore(node, nn);
        }
    } else {
        if (sc.childNodes.length > 0) {
            cn = sc.childNodes[so];
        }

        if (cn) {
            sc.insertBefore(node, cn);
        } else {
            sc.appendChild(node);
        }
    }
}

剩下的方法,大家可以尝试着去模拟一把,其实并不复杂,也许会其乐无穷,呵呵

标签:range,insertnode
0
投稿

猜你喜欢

  • 网页栅格系统研究:960的秘密

    2008-10-24 17:03:00
  • 了解WEB页面工具语言XML(二)定义

    2008-09-05 17:18:00
  • 教你用FrontPage2003轻松布局网页

    2008-02-24 17:05:00
  • 精细讲述SQL Server数据库备份多种方法

    2009-01-13 13:33:00
  • mysql int范围与最大值分析

    2012-03-26 18:24:00
  • 如何巧妙利用SQL Server的EXISTS结构

    2009-02-19 17:36:00
  • 实现一个获取元素样式的函数getStyle

    2009-02-10 10:37:00
  • asp如何对数组显示和排序?

    2009-11-20 18:30:00
  • asp脚本延时 自定义的delay函数

    2008-04-07 12:59:00
  • SQL存储过程初探

    2009-09-09 14:22:00
  • chr(9)、chr(10)、chr(13)、chr(32)与特殊空格

    2009-07-03 15:26:00
  • 网页代码中键盘操作相关标签教程

    2010-03-18 15:56:00
  • 为自己的网站添加RSS功能

    2007-11-05 19:18:00
  • sqlserver中如何查询出连续日期记录的代码

    2011-09-30 11:16:56
  • Oracle 创建用户及数据表的方法

    2009-09-26 18:25:00
  • 正则表达式学习笔记

    2008-04-15 07:44:00
  • 为你的有序列表添加个性样式

    2009-02-23 13:12:00
  • MySQL字段类型详解

    2009-01-05 09:23:00
  • 学习 YUI3 中的沙箱机制

    2010-04-12 12:52:00
  • 仿天涯底部固定漂浮导航,无JS纯CSS定义

    2009-07-06 12:44:00
  • asp之家 网络编程 m.aspxhome.com