]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/classes/dom/TridentSelection.js
Release 6.2.2
[Github/sugarcrm.git] / include / javascript / tiny_mce / classes / dom / TridentSelection.js
1 /**
2  * TridentSelection.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() {
12         function Selection(selection) {
13                 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
14
15                 // Returns a W3C DOM compatible range object by using the IE Range API
16                 function getRange() {
17                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
18
19                         // If selection is outside the current document just return an empty range
20                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
21                         if (element.ownerDocument != dom.doc)
22                                 return domRange;
23
24                         collapsed = selection.isCollapsed();
25
26                         // Handle control selection or text selection of a image
27                         if (ieRange.item || !element.hasChildNodes()) {
28                                 if (collapsed) {
29                                         domRange.setStart(element, 0);
30                                         domRange.setEnd(element, 0);
31                                 } else {
32                                         domRange.setStart(element.parentNode, dom.nodeIndex(element));
33                                         domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
34                                 }
35
36                                 return domRange;
37                         }
38
39                         function findEndPoint(start) {
40                                 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
41
42                                 // Setup temp range and collapse it
43                                 checkRng = ieRange.duplicate();
44                                 checkRng.collapse(start);
45
46                                 // Create marker and insert it at the end of the endpoints parent
47                                 marker = dom.create('a');
48                                 parent = checkRng.parentElement();
49
50                                 // If parent doesn't have any children then set the container to that parent and the index to 0
51                                 if (!parent.hasChildNodes()) {
52                                         domRange[start ? 'setStart' : 'setEnd'](parent, 0);
53                                         return;
54                                 }
55
56                                 parent.appendChild(marker);
57                                 checkRng.moveToElementText(marker);
58                                 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
59                                 if (position > 0) {
60                                         // The position is after the end of the parent element.
61                                         // This is the case where IE puts the caret to the left edge of a table.
62                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
63                                         dom.remove(marker);
64                                         return;
65                                 }
66
67                                 // Setup node list and endIndex
68                                 nodes = tinymce.grep(parent.childNodes);
69                                 endIndex = nodes.length - 1;
70                                 // Perform a binary search for the position
71                                 while (startIndex <= endIndex) {
72                                         index = Math.floor((startIndex + endIndex) / 2);
73
74                                         // Insert marker and check it's position relative to the selection
75                                         parent.insertBefore(marker, nodes[index]);
76                                         checkRng.moveToElementText(marker);
77                                         position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
78                                         if (position > 0) {
79                                                 // Marker is to the right
80                                                 startIndex = index + 1;
81                                         } else if (position < 0) {
82                                                 // Marker is to the left
83                                                 endIndex = index - 1;
84                                         } else {
85                                                 // Maker is where we are
86                                                 found = true;
87                                                 break;
88                                         }
89                                 }
90
91                                 // Setup container
92                                 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
93
94                                 // Handle element selection
95                                 if (container.nodeType == 1) {
96                                         dom.remove(marker);
97
98                                         // Find offset and container
99                                         offset = dom.nodeIndex(container);
100                                         container = container.parentNode;
101
102                                         // Move the offset if we are setting the end or the position is after an element
103                                         if (!start || index > 0)
104                                                 offset++;
105                                 } else {
106                                         // Calculate offset within text node
107                                         if (position > 0 || index == 0) {
108                                                 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
109                                                 offset = checkRng.text.length;
110                                         } else {
111                                                 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
112                                                 offset = container.nodeValue.length - checkRng.text.length;
113                                         }
114
115                                         dom.remove(marker);
116                                 }
117
118                                 domRange[start ? 'setStart' : 'setEnd'](container, offset);
119                         };
120
121                         // Find start point
122                         findEndPoint(true);
123
124                         // Find end point if needed
125                         if (!collapsed)
126                                 findEndPoint();
127
128                         return domRange;
129                 };
130
131                 this.addRange = function(rng) {
132                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
133
134                         function setEndPoint(start) {
135                                 var container, offset, marker, tmpRng, nodes;
136
137                                 marker = dom.create('a');
138                                 container = start ? startContainer : endContainer;
139                                 offset = start ? startOffset : endOffset;
140                                 tmpRng = ieRng.duplicate();
141
142                                 if (container == doc || container == doc.documentElement) {
143                                         container = body;
144                                         offset = 0;
145                                 }
146
147                                 if (container.nodeType == 3) {
148                                         container.parentNode.insertBefore(marker, container);
149                                         tmpRng.moveToElementText(marker);
150                                         tmpRng.moveStart('character', offset);
151                                         dom.remove(marker);
152                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
153                                 } else {
154                                         nodes = container.childNodes;
155
156                                         if (nodes.length) {
157                                                 if (offset >= nodes.length) {
158                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);
159                                                 } else {
160                                                         container.insertBefore(marker, nodes[offset]);
161                                                 }
162
163                                                 tmpRng.moveToElementText(marker);
164                                         } else {
165                                                 // Empty node selection for example <div>|</div>
166                                                 marker = doc.createTextNode(invisibleChar);
167                                                 container.appendChild(marker);
168                                                 tmpRng.moveToElementText(marker.parentNode);
169                                                 tmpRng.collapse(TRUE);
170                                         }
171
172                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
173                                         dom.remove(marker);
174                                 }
175                         }
176
177                         // Destroy cached range
178                         this.destroy();
179
180                         // Setup some shorter versions
181                         startContainer = rng.startContainer;
182                         startOffset = rng.startOffset;
183                         endContainer = rng.endContainer;
184                         endOffset = rng.endOffset;
185                         ieRng = body.createTextRange();
186
187                         // If single element selection then try making a control selection out of it
188                         if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
189                                 if (startOffset == endOffset - 1) {
190                                         try {
191                                                 ctrlRng = body.createControlRange();
192                                                 ctrlRng.addElement(startContainer.childNodes[startOffset]);
193                                                 ctrlRng.select();
194                                                 return;
195                                         } catch (ex) {
196                                                 // Ignore
197                                         }
198                                 }
199                         }
200
201                         // Set start/end point of selection
202                         setEndPoint(true);
203                         setEndPoint();
204
205                         // Select the new range and scroll it into view
206                         ieRng.select();
207                 };
208
209                 this.getRangeAt = function() {
210                         // Setup new range if the cache is empty
211                         if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
212                                 range = getRange();
213
214                                 // Store away text range for next call
215                                 lastIERng = selection.getRng();
216                         }
217
218                         // IE will say that the range is equal then produce an invalid argument exception
219                         // if you perform specific operations in a keyup event. For example Ctrl+Del.
220                         // This hack will invalidate the range cache if the exception occurs
221                         try {
222                                 range.startContainer.nextSibling;
223                         } catch (ex) {
224                                 range = getRange();
225                                 lastIERng = null;
226                         }
227
228                         // Return cached range
229                         return range;
230                 };
231
232                 this.destroy = function() {
233                         // Destroy cached range and last IE range to avoid memory leaks
234                         lastIERng = range = null;
235                 };
236         };
237
238         // Expose the selection object
239         tinymce.dom.TridentSelection = Selection;
240 })();