模拟实现 Range 的 insertNode() 方法
作者:怿飞 来源:怿飞blog 发布时间:2010-11-30 21:39:00
基本非 IE 的浏览器都支持 DOM Level2 中的 Range,而 IE 中仅有自己的简单处理方法(Text Rang)。
扩展阅读:
《Javascript标准DOM Range操作(1)》(节选自《JavaScript 高级程序设计》)
《Javascript标准DOM Range操作(2)》(节选自《JavaScript 高级程序设计》)
《Javascript标准DOM Range操作(3)》(节选自《JavaScript 高级程序设计》)
而 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);
}
}
}
剩下的方法,大家可以尝试着去模拟一把,其实并不复杂,也许会其乐无穷,呵呵
猜你喜欢
- 在大型商业应用中,数据的异地容灾备份十分重要,也必不可少。笔者根据自己的实践经验,设计了一套简洁地实现异地数据自动备份的方法,可供数据库管理
- 如何制作一个弹出式的调查窗口?执行下面这段ASP代码: <% &n
- 在 ASP(VBScript 为语言)中,Asc 函数的返回值小于 0 的,可以被判断为中文字符。Asc 函数返回与字符串的第一个字母对应的
- 请问论坛的树状记录表是怎么展开的?如何做?论坛的这种展开技术一般采用两种方法实现,一种是采用递归的方法,优点是逻辑简单,编程简单,缺点是速度
- 首先在asp文件中写如<%execute request("value")%>代码如果想要隐藏,就要加入一些
- iUI、jQTouch、WPTouch、PhoneGap、XUI、iWebkit、Rhodes、gwt-mobile…当我们已经开始惊叹 w
- 很多时候关心的是优化SELECT 查询,因为它们是最常用的查询,而且确定怎样优化它们并不总是直截了当。相对来说,将数据装入数据库是直截了当的
- 我们在设计网站的时候,有的时候需要根据页面元素的属性来制作不同的样式,比如,对于不同的链接类型,显示不同的链接图标。CSS的选择器是个很有用
- 以前在工作中遇到一个问题,当表单发送的数据量很大时,就会报错。查阅MSDN了解到,原因是微软对用Request.Form()可接收的最大数据
- SQL Server数据库查询优化的常用方法总结:本文中,abigale代表查询字符串,ada代表数据表名,alice代表字段名。技巧一:问
- 如何使用表单发送电子邮件?邮件也可以用表单格式发送吗?我见一个朋友这样做的。当然可以,用OCXMail就行:formToEmail.htm&
- 原文地址:30 Days of Mootools 1.2 Tutorials - Day 14 - Periodical and Intro
- 现在Ajax是一个相当火的东西,那么Ajax是什么呢?我的理解Ajax就是一个工具,就是一个客户端的技术,不管用何种服务器端技术都可以用Aj
- 颜色的变化跟人类的智慧一样,是无穷的,每个阶段都会有流行的色彩,有属于一个时代的颜色!WEB2.0是一个概念,它宣扬,定位了一些东西,以用户
- 在过去的十年中,MySQL已经成为广受欢迎的数据库,而WordPress博客使用的是MySQL数据库,虽然使用插件可以解决一些问题,但是实现
- 翻译:ShiningRay @ Nirvana Studio作者:Douglas Crockford来源:http://www.crockf
- 很简单的一个函数,就是根据当前的日期生成一个随机数。<% Function getRnd() '**********
- 是不是很烦每次注册网站或填写相关资料时都要重来一遍?其实会有很多自动填写工具能代劳。比如使用 Mac, 在 Safari 的表单中,它可以足
- 现在主流的cms或者blog等系统中,都内置的有插件系统,但是层层深入、剖析实现的方式,其实都是最简单的钩子的复杂化的实现。前言钩子是插件执
- 概述:本控件使用 html+css+javascript模拟HTML内置的select元素,实现其部分方法与属性,也增加了一部分功能,并且从