Range.js 67 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982
  1. /**
  2. * Range封装
  3. * @file
  4. * @module UE.dom
  5. * @class Range
  6. * @since 1.2.6.1
  7. */
  8. /**
  9. * dom操作封装
  10. * @unfile
  11. * @module UE.dom
  12. */
  13. /**
  14. * Range实现类,本类是UEditor底层核心类,封装不同浏览器之间的Range操作。
  15. * @unfile
  16. * @module UE.dom
  17. * @class Range
  18. */
  19. (function() {
  20. var guid = 0,
  21. fillChar = domUtils.fillChar,
  22. fillData;
  23. /**
  24. * 更新range的collapse状态
  25. * @param {Range} range range对象
  26. */
  27. function updateCollapse(range) {
  28. range.collapsed =
  29. range.startContainer &&
  30. range.endContainer &&
  31. range.startContainer === range.endContainer &&
  32. range.startOffset == range.endOffset;
  33. }
  34. function selectOneNode(rng) {
  35. return (
  36. !rng.collapsed &&
  37. rng.startContainer.nodeType == 1 &&
  38. rng.startContainer === rng.endContainer &&
  39. rng.endOffset - rng.startOffset == 1
  40. );
  41. }
  42. function setEndPoint(toStart, node, offset, range) {
  43. //如果node是自闭合标签要处理
  44. if (
  45. node.nodeType == 1 &&
  46. (dtd.$empty[node.tagName] || dtd.$nonChild[node.tagName])
  47. ) {
  48. offset = domUtils.getNodeIndex(node) + (toStart ? 0 : 1);
  49. node = node.parentNode;
  50. }
  51. if (toStart) {
  52. range.startContainer = node;
  53. range.startOffset = offset;
  54. if (!range.endContainer) {
  55. range.collapse(true);
  56. }
  57. } else {
  58. range.endContainer = node;
  59. range.endOffset = offset;
  60. if (!range.startContainer) {
  61. range.collapse(false);
  62. }
  63. }
  64. updateCollapse(range);
  65. return range;
  66. }
  67. function execContentsAction(range, action) {
  68. //调整边界
  69. //range.includeBookmark();
  70. var start = range.startContainer,
  71. end = range.endContainer,
  72. startOffset = range.startOffset,
  73. endOffset = range.endOffset,
  74. doc = range.document,
  75. frag = doc.createDocumentFragment(),
  76. tmpStart,
  77. tmpEnd;
  78. if (start.nodeType == 1) {
  79. start =
  80. start.childNodes[startOffset] ||
  81. (tmpStart = start.appendChild(doc.createTextNode("")));
  82. }
  83. if (end.nodeType == 1) {
  84. end =
  85. end.childNodes[endOffset] ||
  86. (tmpEnd = end.appendChild(doc.createTextNode("")));
  87. }
  88. if (start === end && start.nodeType == 3) {
  89. frag.appendChild(
  90. doc.createTextNode(
  91. start.substringData(startOffset, endOffset - startOffset)
  92. )
  93. );
  94. //is not clone
  95. if (action) {
  96. start.deleteData(startOffset, endOffset - startOffset);
  97. range.collapse(true);
  98. }
  99. return frag;
  100. }
  101. var current,
  102. currentLevel,
  103. clone = frag,
  104. startParents = domUtils.findParents(start, true),
  105. endParents = domUtils.findParents(end, true);
  106. for (var i = 0; startParents[i] == endParents[i]; ) {
  107. i++;
  108. }
  109. for (var j = i, si; (si = startParents[j]); j++) {
  110. current = si.nextSibling;
  111. if (si == start) {
  112. if (!tmpStart) {
  113. if (range.startContainer.nodeType == 3) {
  114. clone.appendChild(
  115. doc.createTextNode(start.nodeValue.slice(startOffset))
  116. );
  117. //is not clone
  118. if (action) {
  119. start.deleteData(
  120. startOffset,
  121. start.nodeValue.length - startOffset
  122. );
  123. }
  124. } else {
  125. clone.appendChild(!action ? start.cloneNode(true) : start);
  126. }
  127. }
  128. } else {
  129. currentLevel = si.cloneNode(false);
  130. clone.appendChild(currentLevel);
  131. }
  132. while (current) {
  133. if (current === end || current === endParents[j]) {
  134. break;
  135. }
  136. si = current.nextSibling;
  137. clone.appendChild(!action ? current.cloneNode(true) : current);
  138. current = si;
  139. }
  140. clone = currentLevel;
  141. }
  142. clone = frag;
  143. if (!startParents[i]) {
  144. clone.appendChild(startParents[i - 1].cloneNode(false));
  145. clone = clone.firstChild;
  146. }
  147. for (var j = i, ei; (ei = endParents[j]); j++) {
  148. current = ei.previousSibling;
  149. if (ei == end) {
  150. if (!tmpEnd && range.endContainer.nodeType == 3) {
  151. clone.appendChild(
  152. doc.createTextNode(end.substringData(0, endOffset))
  153. );
  154. //is not clone
  155. if (action) {
  156. end.deleteData(0, endOffset);
  157. }
  158. }
  159. } else {
  160. currentLevel = ei.cloneNode(false);
  161. clone.appendChild(currentLevel);
  162. }
  163. //如果两端同级,右边第一次已经被开始做了
  164. if (j != i || !startParents[i]) {
  165. while (current) {
  166. if (current === start) {
  167. break;
  168. }
  169. ei = current.previousSibling;
  170. clone.insertBefore(
  171. !action ? current.cloneNode(true) : current,
  172. clone.firstChild
  173. );
  174. current = ei;
  175. }
  176. }
  177. clone = currentLevel;
  178. }
  179. if (action) {
  180. range
  181. .setStartBefore(
  182. !endParents[i]
  183. ? endParents[i - 1]
  184. : !startParents[i] ? startParents[i - 1] : endParents[i]
  185. )
  186. .collapse(true);
  187. }
  188. tmpStart && domUtils.remove(tmpStart);
  189. tmpEnd && domUtils.remove(tmpEnd);
  190. return frag;
  191. }
  192. /**
  193. * 创建一个跟document绑定的空的Range实例
  194. * @constructor
  195. * @param { Document } document 新建的选区所属的文档对象
  196. */
  197. /**
  198. * @property { Node } startContainer 当前Range的开始边界的容器节点, 可以是一个元素节点或者是文本节点
  199. */
  200. /**
  201. * @property { Node } startOffset 当前Range的开始边界容器节点的偏移量, 如果是元素节点,
  202. * 该值就是childNodes中的第几个节点, 如果是文本节点就是文本内容的第几个字符
  203. */
  204. /**
  205. * @property { Node } endContainer 当前Range的结束边界的容器节点, 可以是一个元素节点或者是文本节点
  206. */
  207. /**
  208. * @property { Node } endOffset 当前Range的结束边界容器节点的偏移量, 如果是元素节点,
  209. * 该值就是childNodes中的第几个节点, 如果是文本节点就是文本内容的第几个字符
  210. */
  211. /**
  212. * @property { Boolean } collapsed 当前Range是否闭合
  213. * @default true
  214. * @remind Range是闭合的时候, startContainer === endContainer && startOffset === endOffset
  215. */
  216. /**
  217. * @property { Document } document 当前Range所属的Document对象
  218. * @remind 不同range的的document属性可以是不同的
  219. */
  220. var Range = (dom.Range = function(document) {
  221. var me = this;
  222. me.startContainer = me.startOffset = me.endContainer = me.endOffset = null;
  223. me.document = document;
  224. me.collapsed = true;
  225. });
  226. /**
  227. * 删除fillData
  228. * @param doc
  229. * @param excludeNode
  230. */
  231. function removeFillData(doc, excludeNode) {
  232. try {
  233. if (fillData && domUtils.inDoc(fillData, doc)) {
  234. if (!fillData.nodeValue.replace(fillCharReg, "").length) {
  235. var tmpNode = fillData.parentNode;
  236. domUtils.remove(fillData);
  237. while (
  238. tmpNode &&
  239. domUtils.isEmptyInlineElement(tmpNode) &&
  240. //safari的contains有bug
  241. (browser.safari
  242. ? !(
  243. domUtils.getPosition(tmpNode, excludeNode) &
  244. domUtils.POSITION_CONTAINS
  245. )
  246. : !tmpNode.contains(excludeNode))
  247. ) {
  248. fillData = tmpNode.parentNode;
  249. domUtils.remove(tmpNode);
  250. tmpNode = fillData;
  251. }
  252. } else {
  253. fillData.nodeValue = fillData.nodeValue.replace(fillCharReg, "");
  254. }
  255. }
  256. } catch (e) {}
  257. }
  258. /**
  259. * @param node
  260. * @param dir
  261. */
  262. function mergeSibling(node, dir) {
  263. var tmpNode;
  264. node = node[dir];
  265. while (node && domUtils.isFillChar(node)) {
  266. tmpNode = node[dir];
  267. domUtils.remove(node);
  268. node = tmpNode;
  269. }
  270. }
  271. Range.prototype = {
  272. /**
  273. * 克隆选区的内容到一个DocumentFragment里
  274. * @method cloneContents
  275. * @return { DocumentFragment | NULL } 如果选区是闭合的将返回null, 否则, 返回包含所clone内容的DocumentFragment元素
  276. * @example
  277. * ```html
  278. * <body>
  279. * <!-- 中括号表示选区 -->
  280. * <b>x<i>x[x</i>xx]x</b>
  281. *
  282. * <script>
  283. * //range是已选中的选区
  284. * var fragment = range.cloneContents(),
  285. * node = document.createElement("div");
  286. *
  287. * node.appendChild( fragment );
  288. *
  289. * //output: <i>x</i>xx
  290. * console.log( node.innerHTML );
  291. *
  292. * </script>
  293. * </body>
  294. * ```
  295. */
  296. cloneContents: function() {
  297. return this.collapsed ? null : execContentsAction(this, 0);
  298. },
  299. /**
  300. * 删除当前选区范围中的所有内容
  301. * @method deleteContents
  302. * @remind 执行完该操作后, 当前Range对象变成了闭合状态
  303. * @return { UE.dom.Range } 当前操作的Range对象
  304. * @example
  305. * ```html
  306. * <body>
  307. * <!-- 中括号表示选区 -->
  308. * <b>x<i>x[x</i>xx]x</b>
  309. *
  310. * <script>
  311. * //range是已选中的选区
  312. * range.deleteContents();
  313. *
  314. * //竖线表示闭合后的选区位置
  315. * //output: <b>x<i>x</i>|x</b>
  316. * console.log( document.body.innerHTML );
  317. *
  318. * //此时, range的各项属性为
  319. * //output: B
  320. * console.log( range.startContainer.tagName );
  321. * //output: 2
  322. * console.log( range.startOffset );
  323. * //output: B
  324. * console.log( range.endContainer.tagName );
  325. * //output: 2
  326. * console.log( range.endOffset );
  327. * //output: true
  328. * console.log( range.collapsed );
  329. *
  330. * </script>
  331. * </body>
  332. * ```
  333. */
  334. deleteContents: function() {
  335. var txt;
  336. if (!this.collapsed) {
  337. execContentsAction(this, 1);
  338. }
  339. if (browser.webkit) {
  340. txt = this.startContainer;
  341. if (txt.nodeType == 3 && !txt.nodeValue.length) {
  342. this.setStartBefore(txt).collapse(true);
  343. domUtils.remove(txt);
  344. }
  345. }
  346. return this;
  347. },
  348. /**
  349. * 将当前选区的内容提取到一个DocumentFragment里
  350. * @method extractContents
  351. * @remind 执行该操作后, 选区将变成闭合状态
  352. * @warning 执行该操作后, 原来选区所选中的内容将从dom树上剥离出来
  353. * @return { DocumentFragment } 返回包含所提取内容的DocumentFragment对象
  354. * @example
  355. * ```html
  356. * <body>
  357. * <!-- 中括号表示选区 -->
  358. * <b>x<i>x[x</i>xx]x</b>
  359. *
  360. * <script>
  361. * //range是已选中的选区
  362. * var fragment = range.extractContents(),
  363. * node = document.createElement( "div" );
  364. *
  365. * node.appendChild( fragment );
  366. *
  367. * //竖线表示闭合后的选区位置
  368. *
  369. * //output: <b>x<i>x</i>|x</b>
  370. * console.log( document.body.innerHTML );
  371. * //output: <i>x</i>xx
  372. * console.log( node.innerHTML );
  373. *
  374. * //此时, range的各项属性为
  375. * //output: B
  376. * console.log( range.startContainer.tagName );
  377. * //output: 2
  378. * console.log( range.startOffset );
  379. * //output: B
  380. * console.log( range.endContainer.tagName );
  381. * //output: 2
  382. * console.log( range.endOffset );
  383. * //output: true
  384. * console.log( range.collapsed );
  385. *
  386. * </script>
  387. * </body>
  388. */
  389. extractContents: function() {
  390. return this.collapsed ? null : execContentsAction(this, 2);
  391. },
  392. /**
  393. * 设置Range的开始容器节点和偏移量
  394. * @method setStart
  395. * @remind 如果给定的节点是元素节点,那么offset指的是其子元素中索引为offset的元素,
  396. * 如果是文本节点,那么offset指的是其文本内容的第offset个字符
  397. * @remind 如果提供的容器节点是一个不能包含子元素的节点, 则该选区的开始容器将被设置
  398. * 为该节点的父节点, 此时, 其距离开始容器的偏移量也变成了该节点在其父节点
  399. * 中的索引
  400. * @param { Node } node 将被设为当前选区开始边界容器的节点对象
  401. * @param { int } offset 选区的开始位置偏移量
  402. * @return { UE.dom.Range } 当前range对象
  403. * @example
  404. * ```html
  405. * <!-- 选区 -->
  406. * <b>xxx<i>x<span>xx</span>xx<em>xx</em>xxx</i>[xxx]</b>
  407. *
  408. * <script>
  409. *
  410. * //执行操作
  411. * range.setStart( document.getElementsByTagName("i")[0], 1 );
  412. *
  413. * //此时, 选区变成了
  414. * //<b>xxx<i>x[<span>xx</span>xx<em>xx</em>xxx</i>xxx]</b>
  415. *
  416. * </script>
  417. * ```
  418. * @example
  419. * ```html
  420. * <!-- 选区 -->
  421. * <b>xxx<img>[xx]x</b>
  422. *
  423. * <script>
  424. *
  425. * //执行操作
  426. * range.setStart( document.getElementsByTagName("img")[0], 3 );
  427. *
  428. * //此时, 选区变成了
  429. * //<b>xxx[<img>xx]x</b>
  430. *
  431. * </script>
  432. * ```
  433. */
  434. setStart: function(node, offset) {
  435. return setEndPoint(true, node, offset, this);
  436. },
  437. /**
  438. * 设置Range的结束容器和偏移量
  439. * @method setEnd
  440. * @param { Node } node 作为当前选区结束边界容器的节点对象
  441. * @param { int } offset 结束边界的偏移量
  442. * @see UE.dom.Range:setStart(Node,int)
  443. * @return { UE.dom.Range } 当前range对象
  444. */
  445. setEnd: function(node, offset) {
  446. return setEndPoint(false, node, offset, this);
  447. },
  448. /**
  449. * 将Range开始位置设置到node节点之后
  450. * @method setStartAfter
  451. * @remind 该操作将会把给定节点的父节点作为range的开始容器, 且偏移量是该节点在其父节点中的位置索引+1
  452. * @param { Node } node 选区的开始边界将紧接着该节点之后
  453. * @return { UE.dom.Range } 当前range对象
  454. * @example
  455. * ```html
  456. * <!-- 选区示例 -->
  457. * <b>xx<i>xxx</i><span>xx[x</span>xxx]</b>
  458. *
  459. * <script>
  460. *
  461. * //执行操作
  462. * range.setStartAfter( document.getElementsByTagName("i")[0] );
  463. *
  464. * //结果选区
  465. * //<b>xx<i>xxx</i>[<span>xxx</span>xxx]</b>
  466. *
  467. * </script>
  468. * ```
  469. */
  470. setStartAfter: function(node) {
  471. return this.setStart(node.parentNode, domUtils.getNodeIndex(node) + 1);
  472. },
  473. /**
  474. * 将Range开始位置设置到node节点之前
  475. * @method setStartBefore
  476. * @remind 该操作将会把给定节点的父节点作为range的开始容器, 且偏移量是该节点在其父节点中的位置索引
  477. * @param { Node } node 新的选区开始位置在该节点之前
  478. * @see UE.dom.Range:setStartAfter(Node)
  479. * @return { UE.dom.Range } 当前range对象
  480. */
  481. setStartBefore: function(node) {
  482. return this.setStart(node.parentNode, domUtils.getNodeIndex(node));
  483. },
  484. /**
  485. * 将Range结束位置设置到node节点之后
  486. * @method setEndAfter
  487. * @remind 该操作将会把给定节点的父节点作为range的结束容器, 且偏移量是该节点在其父节点中的位置索引+1
  488. * @param { Node } node 目标节点
  489. * @see UE.dom.Range:setStartAfter(Node)
  490. * @return { UE.dom.Range } 当前range对象
  491. * @example
  492. * ```html
  493. * <!-- 选区示例 -->
  494. * <b>[xx<i>xxx</i><span>xx]x</span>xxx</b>
  495. *
  496. * <script>
  497. *
  498. * //执行操作
  499. * range.setStartAfter( document.getElementsByTagName("span")[0] );
  500. *
  501. * //结果选区
  502. * //<b>[xx<i>xxx</i><span>xxx</span>]xxx</b>
  503. *
  504. * </script>
  505. * ```
  506. */
  507. setEndAfter: function(node) {
  508. return this.setEnd(node.parentNode, domUtils.getNodeIndex(node) + 1);
  509. },
  510. /**
  511. * 将Range结束位置设置到node节点之前
  512. * @method setEndBefore
  513. * @remind 该操作将会把给定节点的父节点作为range的结束容器, 且偏移量是该节点在其父节点中的位置索引
  514. * @param { Node } node 目标节点
  515. * @see UE.dom.Range:setEndAfter(Node)
  516. * @return { UE.dom.Range } 当前range对象
  517. */
  518. setEndBefore: function(node) {
  519. return this.setEnd(node.parentNode, domUtils.getNodeIndex(node));
  520. },
  521. /**
  522. * 设置Range的开始位置到node节点内的第一个子节点之前
  523. * @method setStartAtFirst
  524. * @remind 选区的开始容器将变成给定的节点, 且偏移量为0
  525. * @remind 如果给定的节点是元素节点, 则该节点必须是允许包含子节点的元素。
  526. * @param { Node } node 目标节点
  527. * @see UE.dom.Range:setStartBefore(Node)
  528. * @return { UE.dom.Range } 当前range对象
  529. * @example
  530. * ```html
  531. * <!-- 选区示例 -->
  532. * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
  533. *
  534. * <script>
  535. *
  536. * //执行操作
  537. * range.setStartAtFirst( document.getElementsByTagName("i")[0] );
  538. *
  539. * //结果选区
  540. * //<b>xx<i>[xxx</i><span>xx]x</span>xxx</b>
  541. *
  542. * </script>
  543. * ```
  544. */
  545. setStartAtFirst: function(node) {
  546. return this.setStart(node, 0);
  547. },
  548. /**
  549. * 设置Range的开始位置到node节点内的最后一个节点之后
  550. * @method setStartAtLast
  551. * @remind 选区的开始容器将变成给定的节点, 且偏移量为该节点的子节点数
  552. * @remind 如果给定的节点是元素节点, 则该节点必须是允许包含子节点的元素。
  553. * @param { Node } node 目标节点
  554. * @see UE.dom.Range:setStartAtFirst(Node)
  555. * @return { UE.dom.Range } 当前range对象
  556. */
  557. setStartAtLast: function(node) {
  558. return this.setStart(
  559. node,
  560. node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length
  561. );
  562. },
  563. /**
  564. * 设置Range的结束位置到node节点内的第一个节点之前
  565. * @method setEndAtFirst
  566. * @param { Node } node 目标节点
  567. * @remind 选区的结束容器将变成给定的节点, 且偏移量为0
  568. * @remind node必须是一个元素节点, 且必须是允许包含子节点的元素。
  569. * @see UE.dom.Range:setStartAtFirst(Node)
  570. * @return { UE.dom.Range } 当前range对象
  571. */
  572. setEndAtFirst: function(node) {
  573. return this.setEnd(node, 0);
  574. },
  575. /**
  576. * 设置Range的结束位置到node节点内的最后一个节点之后
  577. * @method setEndAtLast
  578. * @param { Node } node 目标节点
  579. * @remind 选区的结束容器将变成给定的节点, 且偏移量为该节点的子节点数量
  580. * @remind node必须是一个元素节点, 且必须是允许包含子节点的元素。
  581. * @see UE.dom.Range:setStartAtFirst(Node)
  582. * @return { UE.dom.Range } 当前range对象
  583. */
  584. setEndAtLast: function(node) {
  585. return this.setEnd(
  586. node,
  587. node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length
  588. );
  589. },
  590. /**
  591. * 选中给定节点
  592. * @method selectNode
  593. * @remind 此时, 选区的开始容器和结束容器都是该节点的父节点, 其startOffset是该节点在父节点中的位置索引,
  594. * 而endOffset为startOffset+1
  595. * @param { Node } node 需要选中的节点
  596. * @return { UE.dom.Range } 当前range对象,此时的range仅包含当前给定的节点对象
  597. * @example
  598. * ```html
  599. * <!-- 选区示例 -->
  600. * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
  601. *
  602. * <script>
  603. *
  604. * //执行操作
  605. * range.selectNode( document.getElementsByTagName("i")[0] );
  606. *
  607. * //结果选区
  608. * //<b>xx[<i>xxx</i>]<span>xxx</span>xxx</b>
  609. *
  610. * </script>
  611. * ```
  612. */
  613. selectNode: function(node) {
  614. return this.setStartBefore(node).setEndAfter(node);
  615. },
  616. /**
  617. * 选中给定节点内部的所有节点
  618. * @method selectNodeContents
  619. * @remind 此时, 选区的开始容器和结束容器都是该节点, 其startOffset为0,
  620. * 而endOffset是该节点的子节点数。
  621. * @param { Node } node 目标节点, 当前range将包含该节点内的所有节点
  622. * @return { UE.dom.Range } 当前range对象, 此时range仅包含给定节点的所有子节点
  623. * @example
  624. * ```html
  625. * <!-- 选区示例 -->
  626. * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
  627. *
  628. * <script>
  629. *
  630. * //执行操作
  631. * range.selectNode( document.getElementsByTagName("b")[0] );
  632. *
  633. * //结果选区
  634. * //<b>[xx<i>xxx</i><span>xxx</span>xxx]</b>
  635. *
  636. * </script>
  637. * ```
  638. */
  639. selectNodeContents: function(node) {
  640. return this.setStart(node, 0).setEndAtLast(node);
  641. },
  642. /**
  643. * clone当前Range对象
  644. * @method cloneRange
  645. * @remind 返回的range是一个全新的range对象, 其内部所有属性与当前被clone的range相同。
  646. * @return { UE.dom.Range } 当前range对象的一个副本
  647. */
  648. cloneRange: function() {
  649. var me = this;
  650. return new Range(me.document)
  651. .setStart(me.startContainer, me.startOffset)
  652. .setEnd(me.endContainer, me.endOffset);
  653. },
  654. /**
  655. * 向当前选区的结束处闭合选区
  656. * @method collapse
  657. * @return { UE.dom.Range } 当前range对象
  658. * @example
  659. * ```html
  660. * <!-- 选区示例 -->
  661. * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
  662. *
  663. * <script>
  664. *
  665. * //执行操作
  666. * range.collapse();
  667. *
  668. * //结果选区
  669. * //“|”表示选区已闭合
  670. * //<b>xx<i>xxx</i><span>xx|x</span>xxx</b>
  671. *
  672. * </script>
  673. * ```
  674. */
  675. /**
  676. * 闭合当前选区,根据给定的toStart参数项决定是向当前选区开始处闭合还是向结束处闭合,
  677. * 如果toStart的值为true,则向开始位置闭合, 反之,向结束位置闭合。
  678. * @method collapse
  679. * @param { Boolean } toStart 是否向选区开始处闭合
  680. * @return { UE.dom.Range } 当前range对象,此时range对象处于闭合状态
  681. * @see UE.dom.Range:collapse()
  682. * @example
  683. * ```html
  684. * <!-- 选区示例 -->
  685. * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>
  686. *
  687. * <script>
  688. *
  689. * //执行操作
  690. * range.collapse( true );
  691. *
  692. * //结果选区
  693. * //“|”表示选区已闭合
  694. * //<b>xx<i>xxx</i><span>|xxx</span>xxx</b>
  695. *
  696. * </script>
  697. * ```
  698. */
  699. collapse: function(toStart) {
  700. var me = this;
  701. if (toStart) {
  702. me.endContainer = me.startContainer;
  703. me.endOffset = me.startOffset;
  704. } else {
  705. me.startContainer = me.endContainer;
  706. me.startOffset = me.endOffset;
  707. }
  708. me.collapsed = true;
  709. return me;
  710. },
  711. /**
  712. * 调整range的开始位置和结束位置,使其"收缩"到最小的位置
  713. * @method shrinkBoundary
  714. * @return { UE.dom.Range } 当前range对象
  715. * @example
  716. * ```html
  717. * <span>xx<b>xx[</b>xxxxx]</span> => <span>xx<b>xx</b>[xxxxx]</span>
  718. * ```
  719. *
  720. * @example
  721. * ```html
  722. * <!-- 选区示例 -->
  723. * <b>x[xx</b><i>]xxx</i>
  724. *
  725. * <script>
  726. *
  727. * //执行收缩
  728. * range.shrinkBoundary();
  729. *
  730. * //结果选区
  731. * //<b>x[xx]</b><i>xxx</i>
  732. * </script>
  733. * ```
  734. *
  735. * @example
  736. * ```html
  737. * [<b><i>xxxx</i>xxxxxxx</b>] => <b><i>[xxxx</i>xxxxxxx]</b>
  738. * ```
  739. */
  740. /**
  741. * 调整range的开始位置和结束位置,使其"收缩"到最小的位置,
  742. * 如果ignoreEnd的值为true,则忽略对结束位置的调整
  743. * @method shrinkBoundary
  744. * @param { Boolean } ignoreEnd 是否忽略对结束位置的调整
  745. * @return { UE.dom.Range } 当前range对象
  746. * @see UE.dom.domUtils.Range:shrinkBoundary()
  747. */
  748. shrinkBoundary: function(ignoreEnd) {
  749. var me = this,
  750. child,
  751. collapsed = me.collapsed;
  752. function check(node) {
  753. return (
  754. node.nodeType == 1 &&
  755. !domUtils.isBookmarkNode(node) &&
  756. !dtd.$empty[node.tagName] &&
  757. !dtd.$nonChild[node.tagName]
  758. );
  759. }
  760. while (
  761. me.startContainer.nodeType == 1 && //是element
  762. (child = me.startContainer.childNodes[me.startOffset]) && //子节点也是element
  763. check(child)
  764. ) {
  765. me.setStart(child, 0);
  766. }
  767. if (collapsed) {
  768. return me.collapse(true);
  769. }
  770. if (!ignoreEnd) {
  771. while (
  772. me.endContainer.nodeType == 1 && //是element
  773. me.endOffset > 0 && //如果是空元素就退出 endOffset=0那么endOffst-1为负值,childNodes[endOffset]报错
  774. (child = me.endContainer.childNodes[me.endOffset - 1]) && //子节点也是element
  775. check(child)
  776. ) {
  777. me.setEnd(child, child.childNodes.length);
  778. }
  779. }
  780. return me;
  781. },
  782. /**
  783. * 获取离当前选区内包含的所有节点最近的公共祖先节点,
  784. * @method getCommonAncestor
  785. * @remind 返回的公共祖先节点一定不是range自身的容器节点, 但有可能是一个文本节点
  786. * @return { Node } 当前range对象内所有节点的公共祖先节点
  787. * @example
  788. * ```html
  789. * //选区示例
  790. * <span>xxx<b>x[x<em>xx]x</em>xxx</b>xx</span>
  791. * <script>
  792. *
  793. * var node = range.getCommonAncestor();
  794. *
  795. * //公共祖先节点是: b节点
  796. * //输出: B
  797. * console.log(node.tagName);
  798. *
  799. * </script>
  800. * ```
  801. */
  802. /**
  803. * 获取当前选区所包含的所有节点的公共祖先节点, 可以根据给定的参数 includeSelf 决定获取到
  804. * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点, 如果 includeSelf
  805. * 的取值为true, 则返回的节点可以是自身的容器节点, 否则, 则不能是容器节点
  806. * @method getCommonAncestor
  807. * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
  808. * @return { Node } 当前range对象内所有节点的公共祖先节点
  809. * @see UE.dom.Range:getCommonAncestor()
  810. * @example
  811. * ```html
  812. * <body>
  813. *
  814. * <!-- 选区示例 -->
  815. * <b>xxx<i>xxxx<span>xx[x</span>xx]x</i>xxxxxxx</b>
  816. *
  817. * <script>
  818. *
  819. * var node = range.getCommonAncestor( false );
  820. *
  821. * //这里的公共祖先节点是B而不是I, 是因为参数限制了获取到的节点不能是容器节点
  822. * //output: B
  823. * console.log( node.tagName );
  824. *
  825. * </script>
  826. *
  827. * </body>
  828. * ```
  829. */
  830. /**
  831. * 获取当前选区所包含的所有节点的公共祖先节点, 可以根据给定的参数 includeSelf 决定获取到
  832. * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点, 如果 includeSelf
  833. * 的取值为true, 则返回的节点可以是自身的容器节点, 否则, 则不能是容器节点; 同时可以根据
  834. * ignoreTextNode 参数的取值决定是否忽略类型为文本节点的祖先节点。
  835. * @method getCommonAncestor
  836. * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点
  837. * @param { Boolean } ignoreTextNode 获取祖先节点的过程中是否忽略类型为文本节点的祖先节点
  838. * @return { Node } 当前range对象内所有节点的公共祖先节点
  839. * @see UE.dom.Range:getCommonAncestor()
  840. * @see UE.dom.Range:getCommonAncestor(Boolean)
  841. * @example
  842. * ```html
  843. * <body>
  844. *
  845. * <!-- 选区示例 -->
  846. * <b>xxx<i>xxxx<span>x[x]x</span>xxx</i>xxxxxxx</b>
  847. *
  848. * <script>
  849. *
  850. * var node = range.getCommonAncestor( true, false );
  851. *
  852. * //output: SPAN
  853. * console.log( node.tagName );
  854. *
  855. * </script>
  856. *
  857. * </body>
  858. * ```
  859. */
  860. getCommonAncestor: function(includeSelf, ignoreTextNode) {
  861. var me = this,
  862. start = me.startContainer,
  863. end = me.endContainer;
  864. if (start === end) {
  865. if (includeSelf && selectOneNode(this)) {
  866. start = start.childNodes[me.startOffset];
  867. if (start.nodeType == 1) return start;
  868. }
  869. //只有在上来就相等的情况下才会出现是文本的情况
  870. return ignoreTextNode && start.nodeType == 3 ? start.parentNode : start;
  871. }
  872. return domUtils.getCommonAncestor(start, end);
  873. },
  874. /**
  875. * 调整当前Range的开始和结束边界容器,如果是容器节点是文本节点,就调整到包含该文本节点的父节点上
  876. * @method trimBoundary
  877. * @remind 该操作有可能会引起文本节点被切开
  878. * @return { UE.dom.Range } 当前range对象
  879. * @example
  880. * ```html
  881. *
  882. * //选区示例
  883. * <b>xxx<i>[xxxxx]</i>xxx</b>
  884. *
  885. * <script>
  886. * //未调整前, 选区的开始容器和结束都是文本节点
  887. * //执行调整
  888. * range.trimBoundary();
  889. *
  890. * //调整之后, 容器节点变成了i节点
  891. * //<b>xxx[<i>xxxxx</i>]xxx</b>
  892. * </script>
  893. * ```
  894. */
  895. /**
  896. * 调整当前Range的开始和结束边界容器,如果是容器节点是文本节点,就调整到包含该文本节点的父节点上,
  897. * 可以根据 ignoreEnd 参数的值决定是否调整对结束边界的调整
  898. * @method trimBoundary
  899. * @param { Boolean } ignoreEnd 是否忽略对结束边界的调整
  900. * @return { UE.dom.Range } 当前range对象
  901. * @example
  902. * ```html
  903. *
  904. * //选区示例
  905. * <b>xxx<i>[xxxxx]</i>xxx</b>
  906. *
  907. * <script>
  908. * //未调整前, 选区的开始容器和结束都是文本节点
  909. * //执行调整
  910. * range.trimBoundary( true );
  911. *
  912. * //调整之后, 开始容器节点变成了i节点
  913. * //但是, 结束容器没有发生变化
  914. * //<b>xxx[<i>xxxxx]</i>xxx</b>
  915. * </script>
  916. * ```
  917. */
  918. trimBoundary: function(ignoreEnd) {
  919. this.txtToElmBoundary();
  920. var start = this.startContainer,
  921. offset = this.startOffset,
  922. collapsed = this.collapsed,
  923. end = this.endContainer;
  924. if (start.nodeType == 3) {
  925. if (offset == 0) {
  926. this.setStartBefore(start);
  927. } else {
  928. if (offset >= start.nodeValue.length) {
  929. this.setStartAfter(start);
  930. } else {
  931. var textNode = domUtils.split(start, offset);
  932. //跟新结束边界
  933. if (start === end) {
  934. this.setEnd(textNode, this.endOffset - offset);
  935. } else if (start.parentNode === end) {
  936. this.endOffset += 1;
  937. }
  938. this.setStartBefore(textNode);
  939. }
  940. }
  941. if (collapsed) {
  942. return this.collapse(true);
  943. }
  944. }
  945. if (!ignoreEnd) {
  946. offset = this.endOffset;
  947. end = this.endContainer;
  948. if (end.nodeType == 3) {
  949. if (offset == 0) {
  950. this.setEndBefore(end);
  951. } else {
  952. offset < end.nodeValue.length && domUtils.split(end, offset);
  953. this.setEndAfter(end);
  954. }
  955. }
  956. }
  957. return this;
  958. },
  959. /**
  960. * 如果选区在文本的边界上,就扩展选区到文本的父节点上, 如果当前选区是闭合的, 则什么也不做
  961. * @method txtToElmBoundary
  962. * @remind 该操作不会修改dom节点
  963. * @return { UE.dom.Range } 当前range对象
  964. */
  965. /**
  966. * 如果选区在文本的边界上,就扩展选区到文本的父节点上, 如果当前选区是闭合的, 则根据参数项
  967. * ignoreCollapsed 的值决定是否执行该调整
  968. * @method txtToElmBoundary
  969. * @param { Boolean } ignoreCollapsed 是否忽略选区的闭合状态, 如果该参数取值为true, 则
  970. * 不论选区是否闭合, 都会执行该操作, 反之, 则不会对闭合的选区执行该操作
  971. * @return { UE.dom.Range } 当前range对象
  972. */
  973. txtToElmBoundary: function(ignoreCollapsed) {
  974. function adjust(r, c) {
  975. var container = r[c + "Container"],
  976. offset = r[c + "Offset"];
  977. if (container.nodeType == 3) {
  978. if (!offset) {
  979. r[
  980. "set" +
  981. c.replace(/(\w)/, function(a) {
  982. return a.toUpperCase();
  983. }) +
  984. "Before"
  985. ](container);
  986. } else if (offset >= container.nodeValue.length) {
  987. r[
  988. "set" +
  989. c.replace(/(\w)/, function(a) {
  990. return a.toUpperCase();
  991. }) +
  992. "After"
  993. ](container);
  994. }
  995. }
  996. }
  997. if (ignoreCollapsed || !this.collapsed) {
  998. adjust(this, "start");
  999. adjust(this, "end");
  1000. }
  1001. return this;
  1002. },
  1003. /**
  1004. * 在当前选区的开始位置前插入节点,新插入的节点会被该range包含
  1005. * @method insertNode
  1006. * @param { Node } node 需要插入的节点
  1007. * @remind 插入的节点可以是一个DocumentFragment依次插入多个节点
  1008. * @return { UE.dom.Range } 当前range对象
  1009. */
  1010. insertNode: function(node) {
  1011. var first = node,
  1012. length = 1;
  1013. if (node.nodeType == 11) {
  1014. first = node.firstChild;
  1015. length = node.childNodes.length;
  1016. }
  1017. this.trimBoundary(true);
  1018. var start = this.startContainer,
  1019. offset = this.startOffset;
  1020. var nextNode = start.childNodes[offset];
  1021. if (nextNode) {
  1022. start.insertBefore(node, nextNode);
  1023. } else {
  1024. start.appendChild(node);
  1025. }
  1026. if (first.parentNode === this.endContainer) {
  1027. this.endOffset = this.endOffset + length;
  1028. }
  1029. return this.setStartBefore(first);
  1030. },
  1031. /**
  1032. * 闭合选区到当前选区的开始位置, 并且定位光标到闭合后的位置
  1033. * @method setCursor
  1034. * @return { UE.dom.Range } 当前range对象
  1035. * @see UE.dom.Range:collapse()
  1036. */
  1037. /**
  1038. * 闭合选区,可以根据参数toEnd的值控制选区是向前闭合还是向后闭合, 并且定位光标到闭合后的位置。
  1039. * @method setCursor
  1040. * @param { Boolean } toEnd 是否向后闭合, 如果为true, 则闭合选区时, 将向结束容器方向闭合,
  1041. * 反之,则向开始容器方向闭合
  1042. * @return { UE.dom.Range } 当前range对象
  1043. * @see UE.dom.Range:collapse(Boolean)
  1044. */
  1045. setCursor: function(toEnd, noFillData) {
  1046. return this.collapse(!toEnd).select(noFillData);
  1047. },
  1048. /**
  1049. * 创建当前range的一个书签,记录下当前range的位置,方便当dom树改变时,还能找回原来的选区位置
  1050. * @method createBookmark
  1051. * @param { Boolean } serialize 控制返回的标记位置是对当前位置的引用还是ID,如果该值为true,则
  1052. * 返回标记位置的ID, 反之则返回标记位置节点的引用
  1053. * @return { Object } 返回一个书签记录键值对, 其包含的key有: start => 开始标记的ID或者引用,
  1054. * end => 结束标记的ID或引用, id => 当前标记的类型, 如果为true,则表示
  1055. * 返回的记录的类型为ID, 反之则为引用
  1056. */
  1057. createBookmark: function(serialize, same) {
  1058. var endNode,
  1059. startNode = this.document.createElement("span");
  1060. startNode.style.cssText = "display:none;line-height:0px;";
  1061. startNode.appendChild(this.document.createTextNode("\u200D"));
  1062. startNode.id = "_baidu_bookmark_start_" + (same ? "" : guid++);
  1063. if (!this.collapsed) {
  1064. endNode = startNode.cloneNode(true);
  1065. endNode.id = "_baidu_bookmark_end_" + (same ? "" : guid++);
  1066. }
  1067. this.insertNode(startNode);
  1068. if (endNode) {
  1069. this.collapse().insertNode(endNode).setEndBefore(endNode);
  1070. }
  1071. this.setStartAfter(startNode);
  1072. return {
  1073. start: serialize ? startNode.id : startNode,
  1074. end: endNode ? (serialize ? endNode.id : endNode) : null,
  1075. id: serialize
  1076. };
  1077. },
  1078. /**
  1079. * 调整当前range的边界到书签位置,并删除该书签对象所标记的位置内的节点
  1080. * @method moveToBookmark
  1081. * @param { BookMark } bookmark createBookmark所创建的标签对象
  1082. * @return { UE.dom.Range } 当前range对象
  1083. * @see UE.dom.Range:createBookmark(Boolean)
  1084. */
  1085. moveToBookmark: function(bookmark) {
  1086. var start = bookmark.id
  1087. ? this.document.getElementById(bookmark.start)
  1088. : bookmark.start,
  1089. end = bookmark.end && bookmark.id
  1090. ? this.document.getElementById(bookmark.end)
  1091. : bookmark.end;
  1092. this.setStartBefore(start);
  1093. domUtils.remove(start);
  1094. if (end) {
  1095. this.setEndBefore(end);
  1096. domUtils.remove(end);
  1097. } else {
  1098. this.collapse(true);
  1099. }
  1100. return this;
  1101. },
  1102. /**
  1103. * 调整range的边界,使其"放大"到最近的父节点
  1104. * @method enlarge
  1105. * @remind 会引起选区的变化
  1106. * @return { UE.dom.Range } 当前range对象
  1107. */
  1108. /**
  1109. * 调整range的边界,使其"放大"到最近的父节点,根据参数 toBlock 的取值, 可以
  1110. * 要求扩大之后的父节点是block节点
  1111. * @method enlarge
  1112. * @param { Boolean } toBlock 是否要求扩大之后的父节点必须是block节点
  1113. * @return { UE.dom.Range } 当前range对象
  1114. */
  1115. enlarge: function(toBlock, stopFn) {
  1116. var isBody = domUtils.isBody,
  1117. pre,
  1118. node,
  1119. tmp = this.document.createTextNode("");
  1120. if (toBlock) {
  1121. node = this.startContainer;
  1122. if (node.nodeType == 1) {
  1123. if (node.childNodes[this.startOffset]) {
  1124. pre = node = node.childNodes[this.startOffset];
  1125. } else {
  1126. node.appendChild(tmp);
  1127. pre = node = tmp;
  1128. }
  1129. } else {
  1130. pre = node;
  1131. }
  1132. while (1) {
  1133. if (domUtils.isBlockElm(node)) {
  1134. node = pre;
  1135. while ((pre = node.previousSibling) && !domUtils.isBlockElm(pre)) {
  1136. node = pre;
  1137. }
  1138. this.setStartBefore(node);
  1139. break;
  1140. }
  1141. pre = node;
  1142. node = node.parentNode;
  1143. }
  1144. node = this.endContainer;
  1145. if (node.nodeType == 1) {
  1146. if ((pre = node.childNodes[this.endOffset])) {
  1147. node.insertBefore(tmp, pre);
  1148. } else {
  1149. node.appendChild(tmp);
  1150. }
  1151. pre = node = tmp;
  1152. } else {
  1153. pre = node;
  1154. }
  1155. while (1) {
  1156. if (domUtils.isBlockElm(node)) {
  1157. node = pre;
  1158. while ((pre = node.nextSibling) && !domUtils.isBlockElm(pre)) {
  1159. node = pre;
  1160. }
  1161. this.setEndAfter(node);
  1162. break;
  1163. }
  1164. pre = node;
  1165. node = node.parentNode;
  1166. }
  1167. if (tmp.parentNode === this.endContainer) {
  1168. this.endOffset--;
  1169. }
  1170. domUtils.remove(tmp);
  1171. }
  1172. // 扩展边界到最大
  1173. if (!this.collapsed) {
  1174. while (this.startOffset == 0) {
  1175. if (stopFn && stopFn(this.startContainer)) {
  1176. break;
  1177. }
  1178. if (isBody(this.startContainer)) {
  1179. break;
  1180. }
  1181. this.setStartBefore(this.startContainer);
  1182. }
  1183. while (
  1184. this.endOffset ==
  1185. (this.endContainer.nodeType == 1
  1186. ? this.endContainer.childNodes.length
  1187. : this.endContainer.nodeValue.length)
  1188. ) {
  1189. if (stopFn && stopFn(this.endContainer)) {
  1190. break;
  1191. }
  1192. if (isBody(this.endContainer)) {
  1193. break;
  1194. }
  1195. this.setEndAfter(this.endContainer);
  1196. }
  1197. }
  1198. return this;
  1199. },
  1200. enlargeToBlockElm: function(ignoreEnd) {
  1201. while (!domUtils.isBlockElm(this.startContainer)) {
  1202. this.setStartBefore(this.startContainer);
  1203. }
  1204. if (!ignoreEnd) {
  1205. while (!domUtils.isBlockElm(this.endContainer)) {
  1206. this.setEndAfter(this.endContainer);
  1207. }
  1208. }
  1209. return this;
  1210. },
  1211. /**
  1212. * 调整Range的边界,使其"缩小"到最合适的位置
  1213. * @method adjustmentBoundary
  1214. * @return { UE.dom.Range } 当前range对象
  1215. * @see UE.dom.Range:shrinkBoundary()
  1216. */
  1217. adjustmentBoundary: function() {
  1218. if (!this.collapsed) {
  1219. while (
  1220. !domUtils.isBody(this.startContainer) &&
  1221. this.startOffset ==
  1222. this.startContainer[
  1223. this.startContainer.nodeType == 3 ? "nodeValue" : "childNodes"
  1224. ].length &&
  1225. this.startContainer[
  1226. this.startContainer.nodeType == 3 ? "nodeValue" : "childNodes"
  1227. ].length
  1228. ) {
  1229. this.setStartAfter(this.startContainer);
  1230. }
  1231. while (
  1232. !domUtils.isBody(this.endContainer) &&
  1233. !this.endOffset &&
  1234. this.endContainer[
  1235. this.endContainer.nodeType == 3 ? "nodeValue" : "childNodes"
  1236. ].length
  1237. ) {
  1238. this.setEndBefore(this.endContainer);
  1239. }
  1240. }
  1241. return this;
  1242. },
  1243. /**
  1244. * 给range选区中的内容添加给定的inline标签
  1245. * @method applyInlineStyle
  1246. * @param { String } tagName 需要添加的标签名
  1247. * @example
  1248. * ```html
  1249. * <p>xxxx[xxxx]x</p> ==> range.applyInlineStyle("strong") ==> <p>xxxx[<strong>xxxx</strong>]x</p>
  1250. * ```
  1251. */
  1252. /**
  1253. * 给range选区中的内容添加给定的inline标签, 并且为标签附加上一些初始化属性。
  1254. * @method applyInlineStyle
  1255. * @param { String } tagName 需要添加的标签名
  1256. * @param { Object } attrs 跟随新添加的标签的属性
  1257. * @return { UE.dom.Range } 当前选区
  1258. * @example
  1259. * ```html
  1260. * <p>xxxx[xxxx]x</p>
  1261. *
  1262. * ==>
  1263. *
  1264. * <!-- 执行操作 -->
  1265. * range.applyInlineStyle("strong",{"style":"font-size:12px"})
  1266. *
  1267. * ==>
  1268. *
  1269. * <p>xxxx[<strong style="font-size:12px">xxxx</strong>]x</p>
  1270. * ```
  1271. */
  1272. applyInlineStyle: function(tagName, attrs, list) {
  1273. if (this.collapsed) return this;
  1274. this.trimBoundary()
  1275. .enlarge(false, function(node) {
  1276. return node.nodeType == 1 && domUtils.isBlockElm(node);
  1277. })
  1278. .adjustmentBoundary();
  1279. var bookmark = this.createBookmark(),
  1280. end = bookmark.end,
  1281. filterFn = function(node) {
  1282. return node.nodeType == 1
  1283. ? node.tagName.toLowerCase() != "br"
  1284. : !domUtils.isWhitespace(node);
  1285. },
  1286. current = domUtils.getNextDomNode(bookmark.start, false, filterFn),
  1287. node,
  1288. pre,
  1289. range = this.cloneRange();
  1290. while (
  1291. current &&
  1292. domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING
  1293. ) {
  1294. if (current.nodeType == 3 || dtd[tagName][current.tagName]) {
  1295. range.setStartBefore(current);
  1296. node = current;
  1297. while (
  1298. node &&
  1299. (node.nodeType == 3 || dtd[tagName][node.tagName]) &&
  1300. node !== end
  1301. ) {
  1302. pre = node;
  1303. node = domUtils.getNextDomNode(
  1304. node,
  1305. node.nodeType == 1,
  1306. null,
  1307. function(parent) {
  1308. return dtd[tagName][parent.tagName];
  1309. }
  1310. );
  1311. }
  1312. var frag = range.setEndAfter(pre).extractContents(),
  1313. elm;
  1314. if (list && list.length > 0) {
  1315. var level, top;
  1316. top = level = list[0].cloneNode(false);
  1317. for (var i = 1, ci; (ci = list[i++]); ) {
  1318. level.appendChild(ci.cloneNode(false));
  1319. level = level.firstChild;
  1320. }
  1321. elm = level;
  1322. } else {
  1323. elm = range.document.createElement(tagName);
  1324. }
  1325. if (attrs) {
  1326. domUtils.setAttributes(elm, attrs);
  1327. }
  1328. elm.appendChild(frag);
  1329. //针对嵌套span的全局样式指定,做容错处理
  1330. if (elm.tagName == "SPAN" && attrs && attrs.style) {
  1331. utils.each(elm.getElementsByTagName("span"), function(s) {
  1332. s.style.cssText = s.style.cssText + ";" + attrs.style;
  1333. });
  1334. }
  1335. range.insertNode(list ? top : elm);
  1336. //处理下滑线在a上的情况
  1337. var aNode;
  1338. if (
  1339. tagName == "span" &&
  1340. attrs.style &&
  1341. /text\-decoration/.test(attrs.style) &&
  1342. (aNode = domUtils.findParentByTagName(elm, "a", true))
  1343. ) {
  1344. domUtils.setAttributes(aNode, attrs);
  1345. domUtils.remove(elm, true);
  1346. elm = aNode;
  1347. } else {
  1348. domUtils.mergeSibling(elm);
  1349. domUtils.clearEmptySibling(elm);
  1350. }
  1351. //去除子节点相同的
  1352. domUtils.mergeChild(elm, attrs);
  1353. current = domUtils.getNextDomNode(elm, false, filterFn);
  1354. domUtils.mergeToParent(elm);
  1355. if (node === end) {
  1356. break;
  1357. }
  1358. } else {
  1359. current = domUtils.getNextDomNode(current, true, filterFn);
  1360. }
  1361. }
  1362. return this.moveToBookmark(bookmark);
  1363. },
  1364. /**
  1365. * 移除当前选区内指定的inline标签,但保留其中的内容
  1366. * @method removeInlineStyle
  1367. * @param { String } tagName 需要移除的标签名
  1368. * @return { UE.dom.Range } 当前的range对象
  1369. * @example
  1370. * ```html
  1371. * xx[x<span>xxx<em>yyy</em>zz]z</span> => range.removeInlineStyle(["em"]) => xx[x<span>xxxyyyzz]z</span>
  1372. * ```
  1373. */
  1374. /**
  1375. * 移除当前选区内指定的一组inline标签,但保留其中的内容
  1376. * @method removeInlineStyle
  1377. * @param { Array } tagNameArr 需要移除的标签名的数组
  1378. * @return { UE.dom.Range } 当前的range对象
  1379. * @see UE.dom.Range:removeInlineStyle(String)
  1380. */
  1381. removeInlineStyle: function(tagNames) {
  1382. if (this.collapsed) return this;
  1383. tagNames = utils.isArray(tagNames) ? tagNames : [tagNames];
  1384. this.shrinkBoundary().adjustmentBoundary();
  1385. var start = this.startContainer,
  1386. end = this.endContainer;
  1387. while (1) {
  1388. if (start.nodeType == 1) {
  1389. if (utils.indexOf(tagNames, start.tagName.toLowerCase()) > -1) {
  1390. break;
  1391. }
  1392. if (start.tagName.toLowerCase() == "body") {
  1393. start = null;
  1394. break;
  1395. }
  1396. }
  1397. start = start.parentNode;
  1398. }
  1399. while (1) {
  1400. if (end.nodeType == 1) {
  1401. if (utils.indexOf(tagNames, end.tagName.toLowerCase()) > -1) {
  1402. break;
  1403. }
  1404. if (end.tagName.toLowerCase() == "body") {
  1405. end = null;
  1406. break;
  1407. }
  1408. }
  1409. end = end.parentNode;
  1410. }
  1411. var bookmark = this.createBookmark(),
  1412. frag,
  1413. tmpRange;
  1414. if (start) {
  1415. tmpRange = this.cloneRange()
  1416. .setEndBefore(bookmark.start)
  1417. .setStartBefore(start);
  1418. frag = tmpRange.extractContents();
  1419. tmpRange.insertNode(frag);
  1420. domUtils.clearEmptySibling(start, true);
  1421. start.parentNode.insertBefore(bookmark.start, start);
  1422. }
  1423. if (end) {
  1424. tmpRange = this.cloneRange()
  1425. .setStartAfter(bookmark.end)
  1426. .setEndAfter(end);
  1427. frag = tmpRange.extractContents();
  1428. tmpRange.insertNode(frag);
  1429. domUtils.clearEmptySibling(end, false, true);
  1430. end.parentNode.insertBefore(bookmark.end, end.nextSibling);
  1431. }
  1432. var current = domUtils.getNextDomNode(bookmark.start, false, function(
  1433. node
  1434. ) {
  1435. return node.nodeType == 1;
  1436. }),
  1437. next;
  1438. while (current && current !== bookmark.end) {
  1439. next = domUtils.getNextDomNode(current, true, function(node) {
  1440. return node.nodeType == 1;
  1441. });
  1442. if (utils.indexOf(tagNames, current.tagName.toLowerCase()) > -1) {
  1443. domUtils.remove(current, true);
  1444. }
  1445. current = next;
  1446. }
  1447. return this.moveToBookmark(bookmark);
  1448. },
  1449. /**
  1450. * 获取当前选中的自闭合的节点
  1451. * @method getClosedNode
  1452. * @return { Node | NULL } 如果当前选中的是自闭合节点, 则返回该节点, 否则返回NULL
  1453. */
  1454. getClosedNode: function() {
  1455. var node;
  1456. if (!this.collapsed) {
  1457. var range = this.cloneRange().adjustmentBoundary().shrinkBoundary();
  1458. if (selectOneNode(range)) {
  1459. var child = range.startContainer.childNodes[range.startOffset];
  1460. if (
  1461. child &&
  1462. child.nodeType == 1 &&
  1463. (dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName])
  1464. ) {
  1465. node = child;
  1466. }
  1467. }
  1468. }
  1469. return node;
  1470. },
  1471. /**
  1472. * 在页面上高亮range所表示的选区
  1473. * @method select
  1474. * @return { UE.dom.Range } 返回当前Range对象
  1475. */
  1476. //这里不区分ie9以上,trace:3824
  1477. select: browser.ie
  1478. ? function(noFillData, textRange) {
  1479. var nativeRange;
  1480. if (!this.collapsed) this.shrinkBoundary();
  1481. var node = this.getClosedNode();
  1482. if (node && !textRange) {
  1483. try {
  1484. nativeRange = this.document.body.createControlRange();
  1485. nativeRange.addElement(node);
  1486. nativeRange.select();
  1487. } catch (e) {}
  1488. return this;
  1489. }
  1490. var bookmark = this.createBookmark(),
  1491. start = bookmark.start,
  1492. end;
  1493. nativeRange = this.document.body.createTextRange();
  1494. nativeRange.moveToElementText(start);
  1495. nativeRange.moveStart("character", 1);
  1496. if (!this.collapsed) {
  1497. var nativeRangeEnd = this.document.body.createTextRange();
  1498. end = bookmark.end;
  1499. nativeRangeEnd.moveToElementText(end);
  1500. nativeRange.setEndPoint("EndToEnd", nativeRangeEnd);
  1501. } else {
  1502. if (!noFillData && this.startContainer.nodeType != 3) {
  1503. //使用<span>|x<span>固定住光标
  1504. var tmpText = this.document.createTextNode(fillChar),
  1505. tmp = this.document.createElement("span");
  1506. tmp.appendChild(this.document.createTextNode(fillChar));
  1507. start.parentNode.insertBefore(tmp, start);
  1508. start.parentNode.insertBefore(tmpText, start);
  1509. //当点b,i,u时,不能清除i上边的b
  1510. removeFillData(this.document, tmpText);
  1511. fillData = tmpText;
  1512. mergeSibling(tmp, "previousSibling");
  1513. mergeSibling(start, "nextSibling");
  1514. nativeRange.moveStart("character", -1);
  1515. nativeRange.collapse(true);
  1516. }
  1517. }
  1518. this.moveToBookmark(bookmark);
  1519. tmp && domUtils.remove(tmp);
  1520. //IE在隐藏状态下不支持range操作,catch一下
  1521. try {
  1522. nativeRange.select();
  1523. } catch (e) {}
  1524. return this;
  1525. }
  1526. : function(notInsertFillData) {
  1527. function checkOffset(rng) {
  1528. function check(node, offset, dir) {
  1529. if (node.nodeType == 3 && node.nodeValue.length < offset) {
  1530. rng[dir + "Offset"] = node.nodeValue.length;
  1531. }
  1532. }
  1533. check(rng.startContainer, rng.startOffset, "start");
  1534. check(rng.endContainer, rng.endOffset, "end");
  1535. }
  1536. var win = domUtils.getWindow(this.document),
  1537. sel = win.getSelection(),
  1538. txtNode;
  1539. //FF下关闭自动长高时滚动条在关闭dialog时会跳
  1540. //ff下如果不body.focus将不能定位闭合光标到编辑器内
  1541. browser.gecko ? this.document.body.focus() : win.focus();
  1542. if (sel) {
  1543. sel.removeAllRanges();
  1544. // trace:870 chrome/safari后边是br对于闭合得range不能定位 所以去掉了判断
  1545. // this.startContainer.nodeType != 3 &&! ((child = this.startContainer.childNodes[this.startOffset]) && child.nodeType == 1 && child.tagName == 'BR'
  1546. if (this.collapsed && !notInsertFillData) {
  1547. // //opear如果没有节点接着,原生的不能够定位,不能在body的第一级插入空白节点
  1548. // if (notInsertFillData && browser.opera && !domUtils.isBody(this.startContainer) && this.startContainer.nodeType == 1) {
  1549. // var tmp = this.document.createTextNode('');
  1550. // this.insertNode(tmp).setStart(tmp, 0).collapse(true);
  1551. // }
  1552. //
  1553. //处理光标落在文本节点的情况
  1554. //处理以下的情况
  1555. //<b>|xxxx</b>
  1556. //<b>xxxx</b>|xxxx
  1557. //xxxx<b>|</b>
  1558. var start = this.startContainer,
  1559. child = start;
  1560. if (start.nodeType == 1) {
  1561. child = start.childNodes[this.startOffset];
  1562. }
  1563. if (
  1564. !(start.nodeType == 3 && this.startOffset) &&
  1565. (child
  1566. ? !child.previousSibling ||
  1567. child.previousSibling.nodeType != 3
  1568. : !start.lastChild || start.lastChild.nodeType != 3)
  1569. ) {
  1570. txtNode = this.document.createTextNode(fillChar);
  1571. //跟着前边走
  1572. this.insertNode(txtNode);
  1573. removeFillData(this.document, txtNode);
  1574. mergeSibling(txtNode, "previousSibling");
  1575. mergeSibling(txtNode, "nextSibling");
  1576. fillData = txtNode;
  1577. this.setStart(txtNode, browser.webkit ? 1 : 0).collapse(true);
  1578. }
  1579. }
  1580. var nativeRange = this.document.createRange();
  1581. if (
  1582. this.collapsed &&
  1583. browser.opera &&
  1584. this.startContainer.nodeType == 1
  1585. ) {
  1586. var child = this.startContainer.childNodes[this.startOffset];
  1587. if (!child) {
  1588. //往前靠拢
  1589. child = this.startContainer.lastChild;
  1590. if (child && domUtils.isBr(child)) {
  1591. this.setStartBefore(child).collapse(true);
  1592. }
  1593. } else {
  1594. //向后靠拢
  1595. while (child && domUtils.isBlockElm(child)) {
  1596. if (child.nodeType == 1 && child.childNodes[0]) {
  1597. child = child.childNodes[0];
  1598. } else {
  1599. break;
  1600. }
  1601. }
  1602. child && this.setStartBefore(child).collapse(true);
  1603. }
  1604. }
  1605. //是createAddress最后一位算的不准,现在这里进行微调
  1606. checkOffset(this);
  1607. nativeRange.setStart(this.startContainer, this.startOffset);
  1608. nativeRange.setEnd(this.endContainer, this.endOffset);
  1609. sel.addRange(nativeRange);
  1610. }
  1611. return this;
  1612. },
  1613. /**
  1614. * 滚动到当前range开始的位置
  1615. * @method scrollToView
  1616. * @param { Window } win 当前range对象所属的window对象
  1617. * @return { UE.dom.Range } 当前Range对象
  1618. */
  1619. /**
  1620. * 滚动到距离当前range开始位置 offset 的位置处
  1621. * @method scrollToView
  1622. * @param { Window } win 当前range对象所属的window对象
  1623. * @param { Number } offset 距离range开始位置处的偏移量, 如果为正数, 则向下偏移, 反之, 则向上偏移
  1624. * @return { UE.dom.Range } 当前Range对象
  1625. */
  1626. scrollToView: function(win, offset) {
  1627. win = win ? window : domUtils.getWindow(this.document);
  1628. var me = this,
  1629. span = me.document.createElement("span");
  1630. //trace:717
  1631. span.innerHTML = "&nbsp;";
  1632. me.cloneRange().insertNode(span);
  1633. domUtils.scrollToView(span, win, offset);
  1634. domUtils.remove(span);
  1635. return me;
  1636. },
  1637. /**
  1638. * 判断当前选区内容是否占位符
  1639. * @private
  1640. * @method inFillChar
  1641. * @return { Boolean } 如果是占位符返回true,否则返回false
  1642. */
  1643. inFillChar: function() {
  1644. var start = this.startContainer;
  1645. if (
  1646. this.collapsed &&
  1647. start.nodeType == 3 &&
  1648. start.nodeValue.replace(new RegExp("^" + domUtils.fillChar), "")
  1649. .length +
  1650. 1 ==
  1651. start.nodeValue.length
  1652. ) {
  1653. return true;
  1654. }
  1655. return false;
  1656. },
  1657. /**
  1658. * 保存
  1659. * @method createAddress
  1660. * @private
  1661. * @return { Boolean } 返回开始和结束的位置
  1662. * @example
  1663. * ```html
  1664. * <body>
  1665. * <p>
  1666. * aaaa
  1667. * <em>
  1668. * <!-- 选区开始 -->
  1669. * bbbb
  1670. * <!-- 选区结束 -->
  1671. * </em>
  1672. * </p>
  1673. *
  1674. * <script>
  1675. * //output: {startAddress:[0,1,0,0],endAddress:[0,1,0,4]}
  1676. * console.log( range.createAddress() );
  1677. * </script>
  1678. * </body>
  1679. * ```
  1680. */
  1681. createAddress: function(ignoreEnd, ignoreTxt) {
  1682. var addr = {},
  1683. me = this;
  1684. function getAddress(isStart) {
  1685. var node = isStart ? me.startContainer : me.endContainer;
  1686. var parents = domUtils.findParents(node, true, function(node) {
  1687. return !domUtils.isBody(node);
  1688. }),
  1689. addrs = [];
  1690. for (var i = 0, ci; (ci = parents[i++]); ) {
  1691. addrs.push(domUtils.getNodeIndex(ci, ignoreTxt));
  1692. }
  1693. var firstIndex = 0;
  1694. if (ignoreTxt) {
  1695. if (node.nodeType == 3) {
  1696. var tmpNode = node.previousSibling;
  1697. while (tmpNode && tmpNode.nodeType == 3) {
  1698. firstIndex += tmpNode.nodeValue.replace(fillCharReg, "").length;
  1699. tmpNode = tmpNode.previousSibling;
  1700. }
  1701. firstIndex += isStart ? me.startOffset : me.endOffset; // - (fillCharReg.test(node.nodeValue) ? 1 : 0 )
  1702. } else {
  1703. node = node.childNodes[isStart ? me.startOffset : me.endOffset];
  1704. if (node) {
  1705. firstIndex = domUtils.getNodeIndex(node, ignoreTxt);
  1706. } else {
  1707. node = isStart ? me.startContainer : me.endContainer;
  1708. var first = node.firstChild;
  1709. while (first) {
  1710. if (domUtils.isFillChar(first)) {
  1711. first = first.nextSibling;
  1712. continue;
  1713. }
  1714. firstIndex++;
  1715. if (first.nodeType == 3) {
  1716. while (first && first.nodeType == 3) {
  1717. first = first.nextSibling;
  1718. }
  1719. } else {
  1720. first = first.nextSibling;
  1721. }
  1722. }
  1723. }
  1724. }
  1725. } else {
  1726. firstIndex = isStart
  1727. ? domUtils.isFillChar(node) ? 0 : me.startOffset
  1728. : me.endOffset;
  1729. }
  1730. if (firstIndex < 0) {
  1731. firstIndex = 0;
  1732. }
  1733. addrs.push(firstIndex);
  1734. return addrs;
  1735. }
  1736. addr.startAddress = getAddress(true);
  1737. if (!ignoreEnd) {
  1738. addr.endAddress = me.collapsed
  1739. ? [].concat(addr.startAddress)
  1740. : getAddress();
  1741. }
  1742. return addr;
  1743. },
  1744. /**
  1745. * 保存
  1746. * @method createAddress
  1747. * @private
  1748. * @return { Boolean } 返回开始和结束的位置
  1749. * @example
  1750. * ```html
  1751. * <body>
  1752. * <p>
  1753. * aaaa
  1754. * <em>
  1755. * <!-- 选区开始 -->
  1756. * bbbb
  1757. * <!-- 选区结束 -->
  1758. * </em>
  1759. * </p>
  1760. *
  1761. * <script>
  1762. * var range = editor.selection.getRange();
  1763. * range.moveToAddress({startAddress:[0,1,0,0],endAddress:[0,1,0,4]});
  1764. * range.select();
  1765. * //output: 'bbbb'
  1766. * console.log(editor.selection.getText());
  1767. * </script>
  1768. * </body>
  1769. * ```
  1770. */
  1771. moveToAddress: function(addr, ignoreEnd) {
  1772. var me = this;
  1773. function getNode(address, isStart) {
  1774. var tmpNode = me.document.body,
  1775. parentNode,
  1776. offset;
  1777. for (var i = 0, ci, l = address.length; i < l; i++) {
  1778. ci = address[i];
  1779. parentNode = tmpNode;
  1780. tmpNode = tmpNode.childNodes[ci];
  1781. if (!tmpNode) {
  1782. offset = ci;
  1783. break;
  1784. }
  1785. }
  1786. if (isStart) {
  1787. if (tmpNode) {
  1788. me.setStartBefore(tmpNode);
  1789. } else {
  1790. me.setStart(parentNode, offset);
  1791. }
  1792. } else {
  1793. if (tmpNode) {
  1794. me.setEndBefore(tmpNode);
  1795. } else {
  1796. me.setEnd(parentNode, offset);
  1797. }
  1798. }
  1799. }
  1800. getNode(addr.startAddress, true);
  1801. !ignoreEnd && addr.endAddress && getNode(addr.endAddress);
  1802. return me;
  1803. },
  1804. /**
  1805. * 判断给定的Range对象是否和当前Range对象表示的是同一个选区
  1806. * @method equals
  1807. * @param { UE.dom.Range } 需要判断的Range对象
  1808. * @return { Boolean } 如果给定的Range对象与当前Range对象表示的是同一个选区, 则返回true, 否则返回false
  1809. */
  1810. equals: function(rng) {
  1811. for (var p in this) {
  1812. if (this.hasOwnProperty(p)) {
  1813. if (this[p] !== rng[p]) return false;
  1814. }
  1815. }
  1816. return true;
  1817. },
  1818. /**
  1819. * 遍历range内的节点。每当遍历一个节点时, 都会执行参数项 doFn 指定的函数, 该函数的接受当前遍历的节点
  1820. * 作为其参数。
  1821. * @method traversal
  1822. * @param { Function } doFn 对每个遍历的节点要执行的方法, 该方法接受当前遍历的节点作为其参数
  1823. * @return { UE.dom.Range } 当前range对象
  1824. * @example
  1825. * ```html
  1826. *
  1827. * <body>
  1828. *
  1829. * <!-- 选区开始 -->
  1830. * <span></span>
  1831. * <a></a>
  1832. * <!-- 选区结束 -->
  1833. * </body>
  1834. *
  1835. * <script>
  1836. *
  1837. * //output: <span></span><a></a>
  1838. * console.log( range.cloneContents() );
  1839. *
  1840. * range.traversal( function ( node ) {
  1841. *
  1842. * if ( node.nodeType === 1 ) {
  1843. * node.className = "test";
  1844. * }
  1845. *
  1846. * } );
  1847. *
  1848. * //output: <span class="test"></span><a class="test"></a>
  1849. * console.log( range.cloneContents() );
  1850. *
  1851. * </script>
  1852. * ```
  1853. */
  1854. /**
  1855. * 遍历range内的节点。
  1856. * 每当遍历一个节点时, 都会执行参数项 doFn 指定的函数, 该函数的接受当前遍历的节点
  1857. * 作为其参数。
  1858. * 可以通过参数项 filterFn 来指定一个过滤器, 只有符合该过滤器过滤规则的节点才会触
  1859. * 发doFn函数的执行
  1860. * @method traversal
  1861. * @param { Function } doFn 对每个遍历的节点要执行的方法, 该方法接受当前遍历的节点作为其参数
  1862. * @param { Function } filterFn 过滤器, 该函数接受当前遍历的节点作为参数, 如果该节点满足过滤
  1863. * 规则, 请返回true, 该节点会触发doFn, 否则, 请返回false, 则该节点不
  1864. * 会触发doFn。
  1865. * @return { UE.dom.Range } 当前range对象
  1866. * @see UE.dom.Range:traversal(Function)
  1867. * @example
  1868. * ```html
  1869. *
  1870. * <body>
  1871. *
  1872. * <!-- 选区开始 -->
  1873. * <span></span>
  1874. * <a></a>
  1875. * <!-- 选区结束 -->
  1876. * </body>
  1877. *
  1878. * <script>
  1879. *
  1880. * //output: <span></span><a></a>
  1881. * console.log( range.cloneContents() );
  1882. *
  1883. * range.traversal( function ( node ) {
  1884. *
  1885. * node.className = "test";
  1886. *
  1887. * }, function ( node ) {
  1888. * return node.nodeType === 1;
  1889. * } );
  1890. *
  1891. * //output: <span class="test"></span><a class="test"></a>
  1892. * console.log( range.cloneContents() );
  1893. *
  1894. * </script>
  1895. * ```
  1896. */
  1897. traversal: function(doFn, filterFn) {
  1898. if (this.collapsed) return this;
  1899. var bookmark = this.createBookmark(),
  1900. end = bookmark.end,
  1901. current = domUtils.getNextDomNode(bookmark.start, false, filterFn);
  1902. while (
  1903. current &&
  1904. current !== end &&
  1905. domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING
  1906. ) {
  1907. var tmpNode = domUtils.getNextDomNode(current, false, filterFn);
  1908. doFn(current);
  1909. current = tmpNode;
  1910. }
  1911. return this.moveToBookmark(bookmark);
  1912. }
  1913. };
  1914. })();