selection.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. define("dijit/_editor/selection", [
  2. "dojo/dom", // dom.byId
  3. "dojo/_base/lang",
  4. "dojo/_base/sniff", // has("ie") has("opera")
  5. "dojo/_base/window", // win.body win.doc win.doc.createElement win.doc.selection win.doc.selection.createRange win.doc.selection.type.toLowerCase win.global win.global.getSelection
  6. ".." // for exporting symbols to dijit._editor.selection (TODO: remove in 2.0)
  7. ], function(dom, lang, has, win, dijit){
  8. // module:
  9. // dijit/_editor/selection
  10. // summary:
  11. // Text selection API
  12. lang.getObject("_editor.selection", true, dijit);
  13. // FIXME:
  14. // all of these methods branch internally for IE. This is probably
  15. // sub-optimal in terms of runtime performance. We should investigate the
  16. // size difference for differentiating at definition time.
  17. lang.mixin(dijit._editor.selection, {
  18. getType: function(){
  19. // summary:
  20. // Get the selection type (like win.doc.select.type in IE).
  21. if(!win.doc.getSelection){
  22. // IE6-8
  23. return win.doc.selection.type.toLowerCase();
  24. }else{
  25. // W3C
  26. var stype = "text";
  27. // Check if the actual selection is a CONTROL (IMG, TABLE, HR, etc...).
  28. var oSel;
  29. try{
  30. oSel = win.global.getSelection();
  31. }catch(e){ /*squelch*/ }
  32. if(oSel && oSel.rangeCount == 1){
  33. var oRange = oSel.getRangeAt(0);
  34. if( (oRange.startContainer == oRange.endContainer) &&
  35. ((oRange.endOffset - oRange.startOffset) == 1) &&
  36. (oRange.startContainer.nodeType != 3 /* text node*/)
  37. ){
  38. stype = "control";
  39. }
  40. }
  41. return stype; //String
  42. }
  43. },
  44. getSelectedText: function(){
  45. // summary:
  46. // Return the text (no html tags) included in the current selection or null if no text is selected
  47. if(!win.doc.getSelection){
  48. // IE6-8
  49. if(dijit._editor.selection.getType() == 'control'){
  50. return null;
  51. }
  52. return win.doc.selection.createRange().text;
  53. }else{
  54. // W3C
  55. var selection = win.global.getSelection();
  56. if(selection){
  57. return selection.toString(); //String
  58. }
  59. }
  60. return '';
  61. },
  62. getSelectedHtml: function(){
  63. // summary:
  64. // Return the html text of the current selection or null if unavailable
  65. if(!win.doc.getSelection){
  66. // IE6-8
  67. if(dijit._editor.selection.getType() == 'control'){
  68. return null;
  69. }
  70. return win.doc.selection.createRange().htmlText;
  71. }else{
  72. // W3C
  73. var selection = win.global.getSelection();
  74. if(selection && selection.rangeCount){
  75. var i;
  76. var html = "";
  77. for(i = 0; i < selection.rangeCount; i++){
  78. //Handle selections spanning ranges, such as Opera
  79. var frag = selection.getRangeAt(i).cloneContents();
  80. var div = win.doc.createElement("div");
  81. div.appendChild(frag);
  82. html += div.innerHTML;
  83. }
  84. return html; //String
  85. }
  86. return null;
  87. }
  88. },
  89. getSelectedElement: function(){
  90. // summary:
  91. // Retrieves the selected element (if any), just in the case that
  92. // a single element (object like and image or a table) is
  93. // selected.
  94. if(dijit._editor.selection.getType() == "control"){
  95. if(!win.doc.getSelection){
  96. // IE6-8
  97. var range = win.doc.selection.createRange();
  98. if(range && range.item){
  99. return win.doc.selection.createRange().item(0);
  100. }
  101. }else{
  102. // W3C
  103. var selection = win.global.getSelection();
  104. return selection.anchorNode.childNodes[ selection.anchorOffset ];
  105. }
  106. }
  107. return null;
  108. },
  109. getParentElement: function(){
  110. // summary:
  111. // Get the parent element of the current selection
  112. if(dijit._editor.selection.getType() == "control"){
  113. var p = this.getSelectedElement();
  114. if(p){ return p.parentNode; }
  115. }else{
  116. if(!win.doc.getSelection){
  117. // IE6-8
  118. var r = win.doc.selection.createRange();
  119. r.collapse(true);
  120. return r.parentElement();
  121. }else{
  122. // W3C
  123. var selection = win.global.getSelection();
  124. if(selection){
  125. var node = selection.anchorNode;
  126. while(node && (node.nodeType != 1)){ // not an element
  127. node = node.parentNode;
  128. }
  129. return node;
  130. }
  131. }
  132. }
  133. return null;
  134. },
  135. hasAncestorElement: function(/*String*/tagName /* ... */){
  136. // summary:
  137. // Check whether current selection has a parent element which is
  138. // of type tagName (or one of the other specified tagName)
  139. // tagName: String
  140. // The tag name to determine if it has an ancestor of.
  141. return this.getAncestorElement.apply(this, arguments) != null; //Boolean
  142. },
  143. getAncestorElement: function(/*String*/tagName /* ... */){
  144. // summary:
  145. // Return the parent element of the current selection which is of
  146. // type tagName (or one of the other specified tagName)
  147. // tagName: String
  148. // The tag name to determine if it has an ancestor of.
  149. var node = this.getSelectedElement() || this.getParentElement();
  150. return this.getParentOfType(node, arguments); //DOMNode
  151. },
  152. isTag: function(/*DomNode*/ node, /*String[]*/ tags){
  153. // summary:
  154. // Function to determine if a node is one of an array of tags.
  155. // node:
  156. // The node to inspect.
  157. // tags:
  158. // An array of tag name strings to check to see if the node matches.
  159. if(node && node.tagName){
  160. var _nlc = node.tagName.toLowerCase();
  161. for(var i=0; i<tags.length; i++){
  162. var _tlc = String(tags[i]).toLowerCase();
  163. if(_nlc == _tlc){
  164. return _tlc; // String
  165. }
  166. }
  167. }
  168. return "";
  169. },
  170. getParentOfType: function(/*DomNode*/ node, /*String[]*/ tags){
  171. // summary:
  172. // Function to locate a parent node that matches one of a set of tags
  173. // node:
  174. // The node to inspect.
  175. // tags:
  176. // An array of tag name strings to check to see if the node matches.
  177. while(node){
  178. if(this.isTag(node, tags).length){
  179. return node; // DOMNode
  180. }
  181. node = node.parentNode;
  182. }
  183. return null;
  184. },
  185. collapse: function(/*Boolean*/beginning){
  186. // summary:
  187. // Function to collapse (clear), the current selection
  188. // beginning: Boolean
  189. // Boolean to indicate whether to collapse the cursor to the beginning of the selection or end.
  190. if(window.getSelection){
  191. var selection = win.global.getSelection();
  192. if(selection.removeAllRanges){ // Mozilla
  193. if(beginning){
  194. selection.collapseToStart();
  195. }else{
  196. selection.collapseToEnd();
  197. }
  198. }else{ // Safari
  199. // pulled from WebCore/ecma/kjs_window.cpp, line 2536
  200. selection.collapse(beginning);
  201. }
  202. }else if(has("ie")){ // IE
  203. var range = win.doc.selection.createRange();
  204. range.collapse(beginning);
  205. range.select();
  206. }
  207. },
  208. remove: function(){
  209. // summary:
  210. // Function to delete the currently selected content from the document.
  211. var sel = win.doc.selection;
  212. if(!win.doc.getSelection){
  213. // IE6-8
  214. if(sel.type.toLowerCase() != "none"){
  215. sel.clear();
  216. }
  217. return sel; //Selection
  218. }else{
  219. // W3C
  220. sel = win.global.getSelection();
  221. sel.deleteFromDocument();
  222. return sel; //Selection
  223. }
  224. },
  225. selectElementChildren: function(/*DomNode*/element,/*Boolean?*/nochangefocus){
  226. // summary:
  227. // clear previous selection and select the content of the node
  228. // (excluding the node itself)
  229. // element: DOMNode
  230. // The element you wish to select the children content of.
  231. // nochangefocus: Boolean
  232. // Boolean to indicate if the foxus should change or not.
  233. var global = win.global;
  234. var doc = win.doc;
  235. var range;
  236. element = dom.byId(element);
  237. if(doc.selection && !doc.getSelection && win.body().createTextRange){
  238. // IE6-8
  239. range = element.ownerDocument.body.createTextRange();
  240. range.moveToElementText(element);
  241. if(!nochangefocus){
  242. try{
  243. range.select(); // IE throws an exception here if the widget is hidden. See #5439
  244. }catch(e){ /* squelch */}
  245. }
  246. }else if(global.getSelection){
  247. // W3C
  248. var selection = win.global.getSelection();
  249. if(has("opera")){
  250. //Opera's selectAllChildren doesn't seem to work right
  251. //against <body> nodes and possibly others ... so
  252. //we use the W3C range API
  253. if(selection.rangeCount){
  254. range = selection.getRangeAt(0);
  255. }else{
  256. range = doc.createRange();
  257. }
  258. range.setStart(element, 0);
  259. range.setEnd(element,(element.nodeType == 3)?element.length:element.childNodes.length);
  260. selection.addRange(range);
  261. }else{
  262. selection.selectAllChildren(element);
  263. }
  264. }
  265. },
  266. selectElement: function(/*DomNode*/element,/*Boolean?*/nochangefocus){
  267. // summary:
  268. // clear previous selection and select element (including all its children)
  269. // element: DOMNode
  270. // The element to select.
  271. // nochangefocus: Boolean
  272. // Boolean indicating if the focus should be changed. IE only.
  273. var range;
  274. var doc = win.doc;
  275. var global = win.global;
  276. element = dom.byId(element);
  277. if(!doc.getSelection && win.body().createTextRange){
  278. // IE6-8
  279. try{
  280. var tg = element.tagName ? element.tagName.toLowerCase() : "";
  281. if(tg === "img" || tg === "table"){
  282. range = win.body().createControlRange();
  283. }else{
  284. range = win.body().createRange();
  285. }
  286. range.addElement(element);
  287. if(!nochangefocus){
  288. range.select();
  289. }
  290. }catch(e){
  291. this.selectElementChildren(element,nochangefocus);
  292. }
  293. }else if(global.getSelection){
  294. // W3C
  295. var selection = global.getSelection();
  296. range = doc.createRange();
  297. if(selection.removeAllRanges){ // Mozilla
  298. // FIXME: does this work on Safari?
  299. if(has("opera")){
  300. //Opera works if you use the current range on
  301. //the selection if present.
  302. if(selection.getRangeAt(0)){
  303. range = selection.getRangeAt(0);
  304. }
  305. }
  306. range.selectNode(element);
  307. selection.removeAllRanges();
  308. selection.addRange(range);
  309. }
  310. }
  311. },
  312. inSelection: function(node){
  313. // summary:
  314. // This function determines if 'node' is
  315. // in the current selection.
  316. // tags:
  317. // public
  318. if(node){
  319. var newRange;
  320. var doc = win.doc;
  321. var range;
  322. if(win.global.getSelection){
  323. //WC3
  324. var sel = win.global.getSelection();
  325. if(sel && sel.rangeCount > 0){
  326. range = sel.getRangeAt(0);
  327. }
  328. if(range && range.compareBoundaryPoints && doc.createRange){
  329. try{
  330. newRange = doc.createRange();
  331. newRange.setStart(node, 0);
  332. if(range.compareBoundaryPoints(range.START_TO_END, newRange) === 1){
  333. return true;
  334. }
  335. }catch(e){ /* squelch */}
  336. }
  337. }else if(doc.selection){
  338. // Probably IE, so we can't use the range object as the pseudo
  339. // range doesn't implement the boundry checking, we have to
  340. // use IE specific crud.
  341. range = doc.selection.createRange();
  342. try{
  343. newRange = node.ownerDocument.body.createControlRange();
  344. if(newRange){
  345. newRange.addElement(node);
  346. }
  347. }catch(e1){
  348. try{
  349. newRange = node.ownerDocument.body.createTextRange();
  350. newRange.moveToElementText(node);
  351. }catch(e2){/* squelch */}
  352. }
  353. if(range && newRange){
  354. // We can finally compare similar to W3C
  355. if(range.compareEndPoints("EndToStart", newRange) === 1){
  356. return true;
  357. }
  358. }
  359. }
  360. }
  361. return false; // boolean
  362. }
  363. });
  364. return dijit._editor.selection;
  365. });