]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/classes/dom/Selection.js
Release 6.2.2
[Github/sugarcrm.git] / include / javascript / tiny_mce / classes / dom / Selection.js
1 /**
2  * Selection.js
3  *
4  * Copyright 2009, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
9  */
10
11 (function(tinymce) {
12         function trimNl(s) {
13                 return s.replace(/[\n\r]+/g, '');
14         };
15
16         // Shorten names
17         var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
18
19         /**
20          * This class handles text and control selection it's an crossbrowser utility class.
21          * Consult the TinyMCE Wiki API for more details and examples on how to use this class.
22          *
23          * @class tinymce.dom.Selection
24          * @example
25          * // Getting the currently selected node for the active editor
26          * alert(tinymce.activeEditor.selection.getNode().nodeName);
27          */
28         tinymce.create('tinymce.dom.Selection', {
29                 /**
30                  * Constructs a new selection instance.
31                  *
32                  * @constructor
33                  * @method Selection
34                  * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
35                  * @param {Window} win Window to bind the selection object to.
36                  * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent.
37                  */
38                 Selection : function(dom, win, serializer) {
39                         var t = this;
40
41                         t.dom = dom;
42                         t.win = win;
43                         t.serializer = serializer;
44
45                         // Add events
46                         each([
47                                 /**
48                                  * This event gets executed before contents is extracted from the selection.
49                                  *
50                                  * @event onBeforeSetContent
51                                  * @param {tinymce.dom.Selection} selection Selection object that fired the event.
52                                  * @param {Object} args Contains things like the contents that will be returned. 
53                                  */
54                                 'onBeforeSetContent',
55
56                                 /**
57                                  * This event gets executed before contents is inserted into selection. 
58                                  *
59                                  * @event onBeforeGetContent
60                                  * @param {tinymce.dom.Selection} selection Selection object that fired the event.
61                                  * @param {Object} args Contains things like the contents that will be inserted. 
62                                  */
63                                 'onBeforeGetContent',
64
65                                 /**
66                                  * This event gets executed when contents is inserted into selection.
67                                  *
68                                  * @event onSetContent
69                                  * @param {tinymce.dom.Selection} selection Selection object that fired the event.
70                                  * @param {Object} args Contains things like the contents that will be inserted. 
71                                  */
72                                 'onSetContent',
73
74                                 /**
75                                  * This event gets executed when contents is extracted from the selection.
76                                  *
77                                  * @event onGetContent
78                                  * @param {tinymce.dom.Selection} selection Selection object that fired the event.
79                                  * @param {Object} args Contains things like the contents that will be returned. 
80                                  */
81                                 'onGetContent'
82                         ], function(e) {
83                                 t[e] = new tinymce.util.Dispatcher(t);
84                         });
85
86                         // No W3C Range support
87                         if (!t.win.getSelection)
88                                 t.tridentSel = new tinymce.dom.TridentSelection(t);
89
90                         if (tinymce.isIE && dom.boxModel)
91                                 this._fixIESelection();
92
93                         // Prevent leaks
94                         tinymce.addUnload(t.destroy, t);
95                 },
96
97                 /**
98                  * Returns the selected contents using the DOM serializer passed in to this class.
99                  *
100                  * @method getContent
101                  * @param {Object} s Optional settings class with for example output format text or html.
102                  * @return {String} Selected contents in for example HTML format.
103                  * @example
104                  * // Alerts the currently selected contents
105                  * alert(tinyMCE.activeEditor.selection.getContent());
106                  * 
107                  * // Alerts the currently selected contents as plain text
108                  * alert(tinyMCE.activeEditor.selection.getContent({format : 'text'}));
109                  */
110                 getContent : function(s) {
111                         var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
112
113                         s = s || {};
114                         wb = wa = '';
115                         s.get = true;
116                         s.format = s.format || 'html';
117                         t.onBeforeGetContent.dispatch(t, s);
118
119                         if (s.format == 'text')
120                                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
121
122                         if (r.cloneContents) {
123                                 n = r.cloneContents();
124
125                                 if (n)
126                                         e.appendChild(n);
127                         } else if (is(r.item) || is(r.htmlText))
128                                 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
129                         else
130                                 e.innerHTML = r.toString();
131
132                         // Keep whitespace before and after
133                         if (/^\s/.test(e.innerHTML))
134                                 wb = ' ';
135
136                         if (/\s+$/.test(e.innerHTML))
137                                 wa = ' ';
138
139                         s.getInner = true;
140
141                         s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
142                         t.onGetContent.dispatch(t, s);
143
144                         return s.content;
145                 },
146
147                 /**
148                  * Sets the current selection to the specified content. If any contents is selected it will be replaced
149                  * with the contents passed in to this function. If there is no selection the contents will be inserted
150                  * where the caret is placed in the editor/page.
151                  *
152                  * @method setContent
153                  * @param {String} content HTML contents to set could also be other formats depending on settings.
154                  * @param {Object} args Optional settings object with for example data format.
155                  * @example
156                  * // Inserts some HTML contents at the current selection
157                  * tinyMCE.activeEditor.selection.setContent('<strong>Some contents</strong>');
158                  */
159                 setContent : function(content, args) {
160                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
161
162                         args = args || {format : 'html'};
163                         args.set = true;
164                         content = args.content = content;
165
166                         // Dispatch before set content event
167                         if (!args.no_events)
168                                 self.onBeforeSetContent.dispatch(self, args);
169
170                         content = args.content;
171
172                         if (rng.insertNode) {
173                                 // Make caret marker since insertNode places the caret in the beginning of text after insert
174                                 content += '<span id="__caret">_</span>';
175
176                                 // Delete and insert new node
177                                 if (rng.startContainer == doc && rng.endContainer == doc) {
178                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
179                                         doc.body.innerHTML = content;
180                                 } else {
181                                         rng.deleteContents();
182
183                                         if (doc.body.childNodes.length == 0) {
184                                                 doc.body.innerHTML = content;
185                                         } else {
186                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges
187                                                 if (rng.createContextualFragment) {
188                                                         rng.insertNode(rng.createContextualFragment(content));
189                                                 } else {
190                                                         // Fake createContextualFragment call in IE 9
191                                                         frag = doc.createDocumentFragment();
192                                                         temp = doc.createElement('div');
193
194                                                         frag.appendChild(temp);
195                                                         temp.outerHTML = content;
196
197                                                         rng.insertNode(frag);
198                                                 }
199                                         }
200                                 }
201
202                                 // Move to caret marker
203                                 caretNode = self.dom.get('__caret');
204
205                                 // Make sure we wrap it compleatly, Opera fails with a simple select call
206                                 rng = doc.createRange();
207                                 rng.setStartBefore(caretNode);
208                                 rng.setEndBefore(caretNode);
209                                 self.setRng(rng);
210
211                                 // Remove the caret position
212                                 self.dom.remove('__caret');
213                                 self.setRng(rng);
214                         } else {
215                                 if (rng.item) {
216                                         // Delete content and get caret text selection
217                                         doc.execCommand('Delete', false, null);
218                                         rng = self.getRng();
219                                 }
220
221                                 rng.pasteHTML(content);
222                         }
223
224                         // Dispatch set content event
225                         if (!args.no_events)
226                                 self.onSetContent.dispatch(self, args);
227                 },
228
229                 /**
230                  * Returns the start element of a selection range. If the start is in a text
231                  * node the parent element will be returned.
232                  *
233                  * @method getStart
234                  * @return {Element} Start element of selection range.
235                  */
236                 getStart : function() {
237                         var rng = this.getRng(), startElement, parentElement, checkRng, node;
238
239                         if (rng.duplicate || rng.item) {
240                                 // Control selection, return first item
241                                 if (rng.item)
242                                         return rng.item(0);
243
244                                 // Get start element
245                                 checkRng = rng.duplicate();
246                                 checkRng.collapse(1);
247                                 startElement = checkRng.parentElement();
248
249                                 // Check if range parent is inside the start element, then return the inner parent element
250                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
251                                 parentElement = node = rng.parentElement();
252                                 while (node = node.parentNode) {
253                                         if (node == startElement) {
254                                                 startElement = parentElement;
255                                                 break;
256                                         }
257                                 }
258
259                                 return startElement;
260                         } else {
261                                 startElement = rng.startContainer;
262
263                                 if (startElement.nodeType == 1 && startElement.hasChildNodes())
264                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
265
266                                 if (startElement && startElement.nodeType == 3)
267                                         return startElement.parentNode;
268
269                                 return startElement;
270                         }
271                 },
272
273                 /**
274                  * Returns the end element of a selection range. If the end is in a text
275                  * node the parent element will be returned.
276                  *
277                  * @method getEnd
278                  * @return {Element} End element of selection range.
279                  */
280                 getEnd : function() {
281                         var t = this, r = t.getRng(), e, eo;
282
283                         if (r.duplicate || r.item) {
284                                 if (r.item)
285                                         return r.item(0);
286
287                                 r = r.duplicate();
288                                 r.collapse(0);
289                                 e = r.parentElement();
290
291                                 if (e && e.nodeName == 'BODY')
292                                         return e.lastChild || e;
293
294                                 return e;
295                         } else {
296                                 e = r.endContainer;
297                                 eo = r.endOffset;
298
299                                 if (e.nodeType == 1 && e.hasChildNodes())
300                                         e = e.childNodes[eo > 0 ? eo - 1 : eo];
301
302                                 if (e && e.nodeType == 3)
303                                         return e.parentNode;
304
305                                 return e;
306                         }
307                 },
308
309                 /**
310                  * Returns a bookmark location for the current selection. This bookmark object
311                  * can then be used to restore the selection after some content modification to the document.
312                  *
313                  * @method getBookmark
314                  * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
315                  * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
316                  * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
317                  * @example
318                  * // Stores a bookmark of the current selection
319                  * var bm = tinyMCE.activeEditor.selection.getBookmark();
320                  * 
321                  * tinyMCE.activeEditor.setContent(tinyMCE.activeEditor.getContent() + 'Some new content');
322                  * 
323                  * // Restore the selection bookmark
324                  * tinyMCE.activeEditor.selection.moveToBookmark(bm);
325                  */
326                 getBookmark : function(type, normalized) {
327                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
328
329                         function findIndex(name, element) {
330                                 var index = 0;
331
332                                 each(dom.select(name), function(node, i) {
333                                         if (node == element)
334                                                 index = i;
335                                 });
336
337                                 return index;
338                         };
339
340                         if (type == 2) {
341                                 function getLocation() {
342                                         var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
343
344                                         function getPoint(rng, start) {
345                                                 var container = rng[start ? 'startContainer' : 'endContainer'],
346                                                         offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
347
348                                                 if (container.nodeType == 3) {
349                                                         if (normalized) {
350                                                                 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
351                                                                         offset += node.nodeValue.length;
352                                                         }
353
354                                                         point.push(offset);
355                                                 } else {
356                                                         childNodes = container.childNodes;
357
358                                                         if (offset >= childNodes.length && childNodes.length) {
359                                                                 after = 1;
360                                                                 offset = Math.max(0, childNodes.length - 1);
361                                                         }
362
363                                                         point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
364                                                 }
365
366                                                 for (; container && container != root; container = container.parentNode)
367                                                         point.push(t.dom.nodeIndex(container, normalized));
368
369                                                 return point;
370                                         };
371
372                                         bookmark.start = getPoint(rng, true);
373
374                                         if (!t.isCollapsed())
375                                                 bookmark.end = getPoint(rng);
376
377                                         return bookmark;
378                                 };
379
380                                 return getLocation();
381                         }
382
383                         // Handle simple range
384                         if (type)
385                                 return {rng : t.getRng()};
386
387                         rng = t.getRng();
388                         id = dom.uniqueId();
389                         collapsed = tinyMCE.activeEditor.selection.isCollapsed();
390                         styles = 'overflow:hidden;line-height:0px';
391
392                         // Explorer method
393                         if (rng.duplicate || rng.item) {
394                                 // Text selection
395                                 if (!rng.item) {
396                                         rng2 = rng.duplicate();
397
398                                         try {
399                                                 // Insert start marker
400                                                 rng.collapse();
401                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
402
403                                                 // Insert end marker
404                                                 if (!collapsed) {
405                                                         rng2.collapse(false);
406
407                                                         // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
408                                                         rng.moveToElementText(rng2.parentElement());
409                                                         if (rng.compareEndPoints('StartToEnd', rng2) == 0)
410                                                                 rng2.move('character', -1);
411
412                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
413                                                 }
414                                         } catch (ex) {
415                                                 // IE might throw unspecified error so lets ignore it
416                                                 return null;
417                                         }
418                                 } else {
419                                         // Control selection
420                                         element = rng.item(0);
421                                         name = element.nodeName;
422
423                                         return {name : name, index : findIndex(name, element)};
424                                 }
425                         } else {
426                                 element = t.getNode();
427                                 name = element.nodeName;
428                                 if (name == 'IMG')
429                                         return {name : name, index : findIndex(name, element)};
430
431                                 // W3C method
432                                 rng2 = rng.cloneRange();
433
434                                 // Insert end marker
435                                 if (!collapsed) {
436                                         rng2.collapse(false);
437                                         rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
438                                 }
439
440                                 rng.collapse(true);
441                                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
442                         }
443
444                         t.moveToBookmark({id : id, keep : 1});
445
446                         return {id : id};
447                 },
448
449                 /**
450                  * Restores the selection to the specified bookmark.
451                  *
452                  * @method moveToBookmark
453                  * @param {Object} bookmark Bookmark to restore selection from.
454                  * @return {Boolean} true/false if it was successful or not.
455                  * @example
456                  * // Stores a bookmark of the current selection
457                  * var bm = tinyMCE.activeEditor.selection.getBookmark();
458                  * 
459                  * tinyMCE.activeEditor.setContent(tinyMCE.activeEditor.getContent() + 'Some new content');
460                  * 
461                  * // Restore the selection bookmark
462                  * tinyMCE.activeEditor.selection.moveToBookmark(bm);
463                  */
464                 moveToBookmark : function(bookmark) {
465                         var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
466
467                         // Clear selection cache
468                         if (t.tridentSel)
469                                 t.tridentSel.destroy();
470
471                         if (bookmark) {
472                                 if (bookmark.start) {
473                                         rng = dom.createRng();
474                                         root = dom.getRoot();
475
476                                         function setEndPoint(start) {
477                                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
478
479                                                 if (point) {
480                                                         offset = point[0];
481
482                                                         // Find container node
483                                                         for (node = root, i = point.length - 1; i >= 1; i--) {
484                                                                 children = node.childNodes;
485
486                                                                 if (point[i] > children.length - 1)
487                                                                         return;
488
489                                                                 node = children[point[i]];
490                                                         }
491
492                                                         // Move text offset to best suitable location
493                                                         if (node.nodeType === 3)
494                                                                 offset = Math.min(point[0], node.nodeValue.length);
495
496                                                         // Move element offset to best suitable location
497                                                         if (node.nodeType === 1)
498                                                                 offset = Math.min(point[0], node.childNodes.length);
499
500                                                         // Set offset within container node
501                                                         if (start)
502                                                                 rng.setStart(node, offset);
503                                                         else
504                                                                 rng.setEnd(node, offset);
505                                                 }
506
507                                                 return true;
508                                         };
509
510                                         if (setEndPoint(true) && setEndPoint()) {
511                                                 t.setRng(rng);
512                                         }
513                                 } else if (bookmark.id) {
514                                         function restoreEndPoint(suffix) {
515                                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
516
517                                                 if (marker) {
518                                                         node = marker.parentNode;
519
520                                                         if (suffix == 'start') {
521                                                                 if (!keep) {
522                                                                         idx = dom.nodeIndex(marker);
523                                                                 } else {
524                                                                         node = marker.firstChild;
525                                                                         idx = 1;
526                                                                 }
527
528                                                                 startContainer = endContainer = node;
529                                                                 startOffset = endOffset = idx;
530                                                         } else {
531                                                                 if (!keep) {
532                                                                         idx = dom.nodeIndex(marker);
533                                                                 } else {
534                                                                         node = marker.firstChild;
535                                                                         idx = 1;
536                                                                 }
537
538                                                                 endContainer = node;
539                                                                 endOffset = idx;
540                                                         }
541
542                                                         if (!keep) {
543                                                                 prev = marker.previousSibling;
544                                                                 next = marker.nextSibling;
545
546                                                                 // Remove all marker text nodes
547                                                                 each(tinymce.grep(marker.childNodes), function(node) {
548                                                                         if (node.nodeType == 3)
549                                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
550                                                                 });
551
552                                                                 // Remove marker but keep children if for example contents where inserted into the marker
553                                                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
554                                                                 while (marker = dom.get(bookmark.id + '_' + suffix))
555                                                                         dom.remove(marker, 1);
556
557                                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
558                                                                 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
559                                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
560                                                                         idx = prev.nodeValue.length;
561                                                                         prev.appendData(next.nodeValue);
562                                                                         dom.remove(next);
563
564                                                                         if (suffix == 'start') {
565                                                                                 startContainer = endContainer = prev;
566                                                                                 startOffset = endOffset = idx;
567                                                                         } else {
568                                                                                 endContainer = prev;
569                                                                                 endOffset = idx;
570                                                                         }
571                                                                 }
572                                                         }
573                                                 }
574                                         };
575
576                                         function addBogus(node) {
577                                                 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
578                                                 if (dom.isBlock(node) && !node.innerHTML)
579                                                         node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
580
581                                                 return node;
582                                         };
583
584                                         // Restore start/end points
585                                         restoreEndPoint('start');
586                                         restoreEndPoint('end');
587
588                                         if (startContainer) {
589                                                 rng = dom.createRng();
590                                                 rng.setStart(addBogus(startContainer), startOffset);
591                                                 rng.setEnd(addBogus(endContainer), endOffset);
592                                                 t.setRng(rng);
593                                         }
594                                 } else if (bookmark.name) {
595                                         t.select(dom.select(bookmark.name)[bookmark.index]);
596                                 } else if (bookmark.rng)
597                                         t.setRng(bookmark.rng);
598                         }
599                 },
600
601                 /**
602                  * Selects the specified element. This will place the start and end of the selection range around the element.
603                  *
604                  * @method select
605                  * @param {Element} node HMTL DOM element to select.
606                  * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
607                  * @return {Element} Selected element the same element as the one that got passed in.
608                  * @example
609                  * // Select the first paragraph in the active editor
610                  * tinyMCE.activeEditor.selection.select(tinyMCE.activeEditor.dom.select('p')[0]);
611                  */
612                 select : function(node, content) {
613                         var t = this, dom = t.dom, rng = dom.createRng(), idx;
614
615                         if (node) {
616                                 idx = dom.nodeIndex(node);
617                                 rng.setStart(node.parentNode, idx);
618                                 rng.setEnd(node.parentNode, idx + 1);
619
620                                 // Find first/last text node or BR element
621                                 if (content) {
622                                         function setPoint(node, start) {
623                                                 var walker = new tinymce.dom.TreeWalker(node, node);
624
625                                                 do {
626                                                         // Text node
627                                                         if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
628                                                                 if (start)
629                                                                         rng.setStart(node, 0);
630                                                                 else
631                                                                         rng.setEnd(node, node.nodeValue.length);
632
633                                                                 return;
634                                                         }
635
636                                                         // BR element
637                                                         if (node.nodeName == 'BR') {
638                                                                 if (start)
639                                                                         rng.setStartBefore(node);
640                                                                 else
641                                                                         rng.setEndBefore(node);
642
643                                                                 return;
644                                                         }
645                                                 } while (node = (start ? walker.next() : walker.prev()));
646                                         };
647
648                                         setPoint(node, 1);
649                                         setPoint(node);
650                                 }
651
652                                 t.setRng(rng);
653                         }
654
655                         return node;
656                 },
657
658                 /**
659                  * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
660                  *
661                  * @method isCollapsed
662                  * @return {Boolean} true/false state if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
663                  */
664                 isCollapsed : function() {
665                         var t = this, r = t.getRng(), s = t.getSel();
666
667                         if (!r || r.item)
668                                 return false;
669
670                         if (r.compareEndPoints)
671                                 return r.compareEndPoints('StartToEnd', r) === 0;
672
673                         return !s || r.collapsed;
674                 },
675
676                 /**
677                  * Collapse the selection to start or end of range.
678                  *
679                  * @method collapse
680                  * @param {Boolean} to_start Optional boolean state if to collapse to end or not. Defaults to start.
681                  */
682                 collapse : function(to_start) {
683                         var self = this, rng = self.getRng(), node;
684
685                         // Control range on IE
686                         if (rng.item) {
687                                 node = rng.item(0);
688                                 rng = self.win.document.body.createTextRange();
689                                 rng.moveToElementText(node);
690                         }
691
692                         rng.collapse(!!to_start);
693                         self.setRng(rng);
694                 },
695
696                 /**
697                  * Returns the browsers internal selection object.
698                  *
699                  * @method getSel
700                  * @return {Selection} Internal browser selection object.
701                  */
702                 getSel : function() {
703                         var t = this, w = this.win;
704
705                         return w.getSelection ? w.getSelection() : w.document.selection;
706                 },
707
708                 /**
709                  * Returns the browsers internal range object.
710                  *
711                  * @method getRng
712                  * @param {Boolean} w3c Forces a compatible W3C range on IE.
713                  * @return {Range} Internal browser range object.
714                  * @see http://www.quirksmode.org/dom/range_intro.html
715                  * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/
716                  */
717                 getRng : function(w3c) {
718                         var t = this, s, r, elm, doc = t.win.document;
719
720                         // Found tridentSel object then we need to use that one
721                         if (w3c && t.tridentSel)
722                                 return t.tridentSel.getRangeAt(0);
723
724                         try {
725                                 if (s = t.getSel())
726                                         r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
727                         } catch (ex) {
728                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
729                         }
730
731                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
732                         if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
733                                 elm = doc.selection.createRange().item(0);
734                                 r = doc.createRange();
735                                 r.setStartBefore(elm);
736                                 r.setEndAfter(elm);
737                         }
738
739                         // No range found then create an empty one
740                         // This can occur when the editor is placed in a hidden container element on Gecko
741                         // Or on IE when there was an exception
742                         if (!r)
743                                 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
744
745                         if (t.selectedRange && t.explicitRange) {
746                                 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
747                                         // Safari, Opera and Chrome only ever select text which causes the range to change.
748                                         // This lets us use the originally set range if the selection hasn't been changed by the user.
749                                         r = t.explicitRange;
750                                 } else {
751                                         t.selectedRange = null;
752                                         t.explicitRange = null;
753                                 }
754                         }
755
756                         return r;
757                 },
758
759                 /**
760                  * Changes the selection to the specified DOM range.
761                  *
762                  * @method setRng
763                  * @param {Range} r Range to select.
764                  */
765                 setRng : function(r) {
766                         var s, t = this;
767                         
768                         if (!t.tridentSel) {
769                                 s = t.getSel();
770
771                                 if (s) {
772                                         t.explicitRange = r;
773
774                                         try {
775                                                 s.removeAllRanges();
776                                         } catch (ex) {
777                                                 // IE9 might throw errors here don't know why
778                                         }
779
780                                         s.addRange(r);
781                                         t.selectedRange = s.getRangeAt(0);
782                                 }
783                         } else {
784                                 // Is W3C Range
785                                 if (r.cloneRange) {
786                                         t.tridentSel.addRange(r);
787                                         return;
788                                 }
789
790                                 // Is IE specific range
791                                 try {
792                                         r.select();
793                                 } catch (ex) {
794                                         // Needed for some odd IE bug #1843306
795                                 }
796                         }
797                 },
798
799                 /**
800                  * Sets the current selection to the specified DOM element.
801                  *
802                  * @method setNode
803                  * @param {Element} n Element to set as the contents of the selection.
804                  * @return {Element} Returns the element that got passed in.
805                  * @example
806                  * // Inserts a DOM node at current selection/caret location
807                  * tinyMCE.activeEditor.selection.setNode(tinyMCE.activeEditor.dom.create('img', {src : 'some.gif', title : 'some title'}));
808                  */
809                 setNode : function(n) {
810                         var t = this;
811
812                         t.setContent(t.dom.getOuterHTML(n));
813
814                         return n;
815                 },
816
817                 /**
818                  * Returns the currently selected element or the common ancestor element for both start and end of the selection.
819                  *
820                  * @method getNode
821                  * @return {Element} Currently selected element or common ancestor element.
822                  * @example
823                  * // Alerts the currently selected elements node name
824                  * alert(tinyMCE.activeEditor.selection.getNode().nodeName);
825                  */
826                 getNode : function() {
827                         var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
828
829                         // Range maybe lost after the editor is made visible again
830                         if (!rng)
831                                 return t.dom.getRoot();
832
833                         if (rng.setStart) {
834                                 elm = rng.commonAncestorContainer;
835
836                                 // Handle selection a image or other control like element such as anchors
837                                 if (!rng.collapsed) {
838                                         if (rng.startContainer == rng.endContainer) {
839                                                 if (rng.endOffset - rng.startOffset < 2) {
840                                                         if (rng.startContainer.hasChildNodes())
841                                                                 elm = rng.startContainer.childNodes[rng.startOffset];
842                                                 }
843                                         }
844
845                                         // If the anchor node is a element instead of a text node then return this element
846                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 
847                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];
848
849                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
850                                         // This happens when you double click an underlined word in FireFox.
851                                         if (start.nodeType === 3 && end.nodeType === 3) {
852                                                 function skipEmptyTextNodes(n, forwards) {
853                                                         var orig = n;
854                                                         while (n && n.nodeType === 3 && n.length === 0) {
855                                                                 n = forwards ? n.nextSibling : n.previousSibling;
856                                                         }
857                                                         return n || orig;
858                                                 }
859                                                 if (start.length === rng.startOffset) {
860                                                         start = skipEmptyTextNodes(start.nextSibling, true);
861                                                 } else {
862                                                         start = start.parentNode;
863                                                 }
864                                                 if (rng.endOffset === 0) {
865                                                         end = skipEmptyTextNodes(end.previousSibling, false);
866                                                 } else {
867                                                         end = end.parentNode;
868                                                 }
869
870                                                 if (start && start === end)
871                                                         return start;
872                                         }
873                                 }
874
875                                 if (elm && elm.nodeType == 3)
876                                         return elm.parentNode;
877
878                                 return elm;
879                         }
880
881                         return rng.item ? rng.item(0) : rng.parentElement();
882                 },
883
884                 getSelectedBlocks : function(st, en) {
885                         var t = this, dom = t.dom, sb, eb, n, bl = [];
886
887                         sb = dom.getParent(st || t.getStart(), dom.isBlock);
888                         eb = dom.getParent(en || t.getEnd(), dom.isBlock);
889
890                         if (sb)
891                                 bl.push(sb);
892
893                         if (sb && eb && sb != eb) {
894                                 n = sb;
895
896                                 while ((n = n.nextSibling) && n != eb) {
897                                         if (dom.isBlock(n))
898                                                 bl.push(n);
899                                 }
900                         }
901
902                         if (eb && sb != eb)
903                                 bl.push(eb);
904
905                         return bl;
906                 },
907
908                 destroy : function(s) {
909                         var t = this;
910
911                         t.win = null;
912
913                         if (t.tridentSel)
914                                 t.tridentSel.destroy();
915
916                         // Manual destroy then remove unload handler
917                         if (!s)
918                                 tinymce.removeUnload(t.destroy);
919                 },
920
921                 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
922                 _fixIESelection : function() {
923                         var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
924
925                         // Make HTML element unselectable since we are going to handle selection by hand
926                         doc.documentElement.unselectable = true;
927
928                         // Return range from point or null if it failed
929                         function rngFromPoint(x, y) {
930                                 var rng = body.createTextRange();
931
932                                 try {
933                                         rng.moveToPoint(x, y);
934                                 } catch (ex) {
935                                         // IE sometimes throws and exception, so lets just ignore it
936                                         rng = null;
937                                 }
938
939                                 return rng;
940                         };
941
942                         // Fires while the selection is changing
943                         function selectionChange(e) {
944                                 var pointRng;
945
946                                 // Check if the button is down or not
947                                 if (e.button) {
948                                         // Create range from mouse position
949                                         pointRng = rngFromPoint(e.x, e.y);
950
951                                         if (pointRng) {
952                                                 // Check if pointRange is before/after selection then change the endPoint
953                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
954                                                         pointRng.setEndPoint('StartToStart', startRng);
955                                                 else
956                                                         pointRng.setEndPoint('EndToEnd', startRng);
957
958                                                 pointRng.select();
959                                         }
960                                 } else
961                                         endSelection();
962                         }
963
964                         // Removes listeners
965                         function endSelection() {
966                                 var rng = doc.selection.createRange();
967
968                                 // If the range is collapsed then use the last start range
969                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
970                                         startRng.select();
971
972                                 dom.unbind(doc, 'mouseup', endSelection);
973                                 dom.unbind(doc, 'mousemove', selectionChange);
974                                 startRng = started = 0;
975                         };
976
977                         // Detect when user selects outside BODY
978                         dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
979                                 if (e.target.nodeName === 'HTML') {
980                                         if (started)
981                                                 endSelection();
982
983                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
984                                         htmlElm = doc.documentElement;
985                                         if (htmlElm.scrollHeight > htmlElm.clientHeight)
986                                                 return;
987
988                                         started = 1;
989                                         // Setup start position
990                                         startRng = rngFromPoint(e.x, e.y);
991                                         if (startRng) {
992                                                 // Listen for selection change events
993                                                 dom.bind(doc, 'mouseup', endSelection);
994                                                 dom.bind(doc, 'mousemove', selectionChange);
995
996                                                 dom.win.focus();
997                                                 startRng.select();
998                                         }
999                                 }
1000                         });
1001                 }
1002         });
1003 })(tinymce);