source: Dev/trunk/src/client/dijit/selection.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 14.6 KB
Line 
1define([
2        "dojo/_base/array",
3        "dojo/dom", // dom.byId
4        "dojo/_base/lang",
5        "dojo/sniff", // has("ie") has("opera")
6        "dojo/_base/window",
7        "dijit/focus"
8], function(array, dom, lang, has, baseWindow, focus){
9
10        // module:
11        //              dijit/selection
12
13        // Note that this class is using feature detection, but doesn't use has() because sometimes on IE the outer window
14        // may be running in standards mode (ie, IE9 mode) but an iframe may be in compatibility mode.   So the code path
15        // used will vary based on the window.
16
17        var SelectionManager = function(win){
18                // summary:
19                //              Class for monitoring / changing the selection (typically highlighted text) in a given window
20                // win: Window
21                //              The window to monitor/adjust the selection on.
22
23                var doc = win.document;
24
25                this.getType = function(){
26                        // summary:
27                        //              Get the selection type (like doc.select.type in IE).
28                        if(doc.getSelection){
29                                // W3C path
30                                var stype = "text";
31
32                                // Check if the actual selection is a CONTROL (IMG, TABLE, HR, etc...).
33                                var oSel;
34                                try{
35                                        oSel = win.getSelection();
36                                }catch(e){ /*squelch*/ }
37
38                                if(oSel && oSel.rangeCount == 1){
39                                        var oRange = oSel.getRangeAt(0);
40                                        if(     (oRange.startContainer == oRange.endContainer) &&
41                                                ((oRange.endOffset - oRange.startOffset) == 1) &&
42                                                (oRange.startContainer.nodeType != 3 /* text node*/)
43                                                ){
44                                                stype = "control";
45                                        }
46                                }
47                                return stype; //String
48                        }else{
49                                // IE6-8
50                                return doc.selection.type.toLowerCase();
51                        }
52                };
53
54                this.getSelectedText = function(){
55                        // summary:
56                        //              Return the text (no html tags) included in the current selection or null if no text is selected
57                        if(doc.getSelection){
58                                // W3C path
59                                var selection = win.getSelection();
60                                return selection ? selection.toString() : ""; //String
61                        }else{
62                                // IE6-8
63                                if(this.getType() == 'control'){
64                                        return null;
65                                }
66                                return doc.selection.createRange().text;
67                        }
68                };
69
70                this.getSelectedHtml = function(){
71                        // summary:
72                        //              Return the html text of the current selection or null if unavailable
73                        if(doc.getSelection){
74                                // W3C path
75                                var selection = win.getSelection();
76                                if(selection && selection.rangeCount){
77                                        var i;
78                                        var html = "";
79                                        for(i = 0; i < selection.rangeCount; i++){
80                                                //Handle selections spanning ranges, such as Opera
81                                                var frag = selection.getRangeAt(i).cloneContents();
82                                                var div = doc.createElement("div");
83                                                div.appendChild(frag);
84                                                html += div.innerHTML;
85                                        }
86                                        return html; //String
87                                }
88                                return null;
89                        }else{
90                                // IE6-8
91                                if(this.getType() == 'control'){
92                                        return null;
93                                }
94                                return doc.selection.createRange().htmlText;
95                        }
96                };
97
98                this.getSelectedElement = function(){
99                        // summary:
100                        //              Retrieves the selected element (if any), just in the case that
101                        //              a single element (object like and image or a table) is
102                        //              selected.
103                        if(this.getType() == "control"){
104                                if(doc.getSelection){
105                                        // W3C path
106                                        var selection = win.getSelection();
107                                        return selection.anchorNode.childNodes[ selection.anchorOffset ];
108                                }else{
109                                        // IE6-8
110                                        var range = doc.selection.createRange();
111                                        if(range && range.item){
112                                                return doc.selection.createRange().item(0);
113                                        }
114                                }
115                        }
116                        return null;
117                };
118
119                this.getParentElement = function(){
120                        // summary:
121                        //              Get the parent element of the current selection
122                        if(this.getType() == "control"){
123                                var p = this.getSelectedElement();
124                                if(p){ return p.parentNode; }
125                        }else{
126                                if(doc.getSelection){
127                                        var selection = doc.getSelection();
128                                        if(selection){
129                                                var node = selection.anchorNode;
130                                                while(node && (node.nodeType != 1)){ // not an element
131                                                        node = node.parentNode;
132                                                }
133                                                return node;
134                                        }
135                                }else{
136                                        var r = doc.selection.createRange();
137                                        r.collapse(true);
138                                        return r.parentElement();
139                                }
140                        }
141                        return null;
142                };
143
144                this.hasAncestorElement = function(/*String*/ tagName /* ... */){
145                        // summary:
146                        //              Check whether current selection has a  parent element which is
147                        //              of type tagName (or one of the other specified tagName)
148                        // tagName: String
149                        //              The tag name to determine if it has an ancestor of.
150                        return this.getAncestorElement.apply(this, arguments) != null; //Boolean
151                };
152
153                this.getAncestorElement = function(/*String*/ tagName /* ... */){
154                        // summary:
155                        //              Return the parent element of the current selection which is of
156                        //              type tagName (or one of the other specified tagName)
157                        // tagName: String
158                        //              The tag name to determine if it has an ancestor of.
159                        var node = this.getSelectedElement() || this.getParentElement();
160                        return this.getParentOfType(node, arguments); //DOMNode
161                };
162
163                this.isTag = function(/*DomNode*/ node, /*String[]*/ tags){
164                        // summary:
165                        //              Function to determine if a node is one of an array of tags.
166                        // node:
167                        //              The node to inspect.
168                        // tags:
169                        //              An array of tag name strings to check to see if the node matches.
170                        if(node && node.tagName){
171                                var _nlc = node.tagName.toLowerCase();
172                                for(var i=0; i<tags.length; i++){
173                                        var _tlc = String(tags[i]).toLowerCase();
174                                        if(_nlc == _tlc){
175                                                return _tlc; // String
176                                        }
177                                }
178                        }
179                        return "";
180                };
181
182                this.getParentOfType = function(/*DomNode*/ node, /*String[]*/ tags){
183                        // summary:
184                        //              Function to locate a parent node that matches one of a set of tags
185                        // node:
186                        //              The node to inspect.
187                        // tags:
188                        //              An array of tag name strings to check to see if the node matches.
189                        while(node){
190                                if(this.isTag(node, tags).length){
191                                        return node; // DOMNode
192                                }
193                                node = node.parentNode;
194                        }
195                        return null;
196                };
197
198                this.collapse = function(/*Boolean*/ beginning){
199                        // summary:
200                        //              Function to collapse (clear), the current selection
201                        // beginning: Boolean
202                        //              Indicates whether to collapse the cursor to the beginning of the selection or end.
203                        if(doc.getSelection){
204                                // W3C path
205                                var selection = win.getSelection();
206                                if(selection.removeAllRanges){ // Mozilla
207                                        if(beginning){
208                                                selection.collapseToStart();
209                                        }else{
210                                                selection.collapseToEnd();
211                                        }
212                                }else{ // Safari
213                                        // pulled from WebCore/ecma/kjs_window.cpp, line 2536
214                                        selection.collapse(beginning);
215                                }
216                        }else{
217                                // IE6-8
218                                var range = doc.selection.createRange();
219                                range.collapse(beginning);
220                                range.select();
221                        }
222                };
223
224                this.remove = function(){
225                        // summary:
226                        //              Function to delete the currently selected content from the document.
227                        var sel = doc.selection;
228                        if(doc.getSelection){
229                                // W3C path
230                                sel = win.getSelection();
231                                sel.deleteFromDocument();
232                                return sel; //Selection
233                        }else{
234                                // IE6-8
235                                if(sel.type.toLowerCase() != "none"){
236                                        sel.clear();
237                                }
238                                return sel; //Selection
239                        }
240                };
241
242                this.selectElementChildren = function(/*DomNode*/ element, /*Boolean?*/ nochangefocus){
243                        // summary:
244                        //              clear previous selection and select the content of the node
245                        //              (excluding the node itself)
246                        // element: DOMNode
247                        //              The element you wish to select the children content of.
248                        // nochangefocus: Boolean
249                        //              Indicates if the focus should change or not.
250
251                        var range;
252                        element = dom.byId(element);
253                        if(doc.getSelection){
254                                // W3C
255                                var selection = win.getSelection();
256                                if(has("opera")){
257                                        //Opera's selectAllChildren doesn't seem to work right
258                                        //against <body> nodes and possibly others ... so
259                                        //we use the W3C range API
260                                        if(selection.rangeCount){
261                                                range = selection.getRangeAt(0);
262                                        }else{
263                                                range = doc.createRange();
264                                        }
265                                        range.setStart(element, 0);
266                                        range.setEnd(element,(element.nodeType == 3) ? element.length : element.childNodes.length);
267                                        selection.addRange(range);
268                                }else{
269                                        selection.selectAllChildren(element);
270                                }
271                        }else{
272                                // IE6-8
273                                range = element.ownerDocument.body.createTextRange();
274                                range.moveToElementText(element);
275                                if(!nochangefocus){
276                                        try{
277                                                range.select(); // IE throws an exception here if the widget is hidden.  See #5439
278                                        }catch(e){ /* squelch */}
279                                }
280                        }
281                };
282
283                this.selectElement = function(/*DomNode*/ element, /*Boolean?*/ nochangefocus){
284                        // summary:
285                        //              clear previous selection and select element (including all its children)
286                        // element: DOMNode
287                        //              The element to select.
288                        // nochangefocus: Boolean
289                        //              Boolean indicating if the focus should be changed.  IE only.
290                        var range;
291                        element = dom.byId(element);    // TODO: remove for 2.0 or sooner, spec listed above doesn't allow for string
292                        if(doc.getSelection){
293                                // W3C path
294                                var selection = doc.getSelection();
295                                range = doc.createRange();
296                                if(selection.removeAllRanges){ // Mozilla
297                                        // FIXME: does this work on Safari?
298                                        if(has("opera")){
299                                                //Opera works if you use the current range on
300                                                //the selection if present.
301                                                if(selection.getRangeAt(0)){
302                                                        range = selection.getRangeAt(0);
303                                                }
304                                        }
305                                        range.selectNode(element);
306                                        selection.removeAllRanges();
307                                        selection.addRange(range);
308                                }
309                        }else{
310                                // IE6-8
311                                try{
312                                        var tg = element.tagName ? element.tagName.toLowerCase() : "";
313                                        if(tg === "img" || tg === "table"){
314                                                range = baseWindow.body(doc).createControlRange();
315                                        }else{
316                                                range = baseWindow.body(doc).createRange();
317                                        }
318                                        range.addElement(element);
319                                        if(!nochangefocus){
320                                                range.select();
321                                        }
322                                }catch(e){
323                                        this.selectElementChildren(element, nochangefocus);
324                                }
325                        }
326                };
327
328                this.inSelection = function(node){
329                        // summary:
330                        //              This function determines if 'node' is
331                        //              in the current selection.
332                        // tags:
333                        //              public
334                        if(node){
335                                var newRange;
336                                var range;
337
338                                if(doc.getSelection){
339                                        // WC3
340                                        var sel = win.getSelection();
341                                        if(sel && sel.rangeCount > 0){
342                                                range = sel.getRangeAt(0);
343                                        }
344                                        if(range && range.compareBoundaryPoints && doc.createRange){
345                                                try{
346                                                        newRange = doc.createRange();
347                                                        newRange.setStart(node, 0);
348                                                        if(range.compareBoundaryPoints(range.START_TO_END, newRange) === 1){
349                                                                return true;
350                                                        }
351                                                }catch(e){ /* squelch */}
352                                        }
353                                }else{
354                                        // IE6-8, so we can't use the range object as the pseudo
355                                        // range doesn't implement the boundary checking, we have to
356                                        // use IE specific crud.
357                                        range = doc.selection.createRange();
358                                        try{
359                                                newRange = node.ownerDocument.body.createTextRange();
360                                                newRange.moveToElementText(node);
361                                        }catch(e2){/* squelch */}
362                                        if(range && newRange){
363                                                // We can finally compare similar to W3C
364                                                if(range.compareEndPoints("EndToStart", newRange) === 1){
365                                                        return true;
366                                                }
367                                        }
368                                }
369                        }
370                        return false; // Boolean
371                },
372
373                this.getBookmark = function(){
374                        // summary:
375                        //              Retrieves a bookmark that can be used with moveToBookmark to reselect the currently selected range.
376
377                        // TODO: merge additional code from Editor._getBookmark into this method
378
379                        var bm, rg, tg, sel = doc.selection, cf = focus.curNode;
380
381                        if(doc.getSelection){
382                                // W3C Range API for selections.
383                                sel = win.getSelection();
384                                if(sel){
385                                        if(sel.isCollapsed){
386                                                tg = cf? cf.tagName : "";
387                                                if(tg){
388                                                        // Create a fake rangelike item to restore selections.
389                                                        tg = tg.toLowerCase();
390                                                        if(tg == "textarea" ||
391                                                                (tg == "input" && (!cf.type || cf.type.toLowerCase() == "text"))){
392                                                                sel = {
393                                                                        start: cf.selectionStart,
394                                                                        end: cf.selectionEnd,
395                                                                        node: cf,
396                                                                        pRange: true
397                                                                };
398                                                                return {isCollapsed: (sel.end <= sel.start), mark: sel}; //Object.
399                                                        }
400                                                }
401                                                bm = {isCollapsed:true};
402                                                if(sel.rangeCount){
403                                                        bm.mark = sel.getRangeAt(0).cloneRange();
404                                                }
405                                        }else{
406                                                rg = sel.getRangeAt(0);
407                                                bm = {isCollapsed: false, mark: rg.cloneRange()};
408                                        }
409                                }
410                        }else if(sel){
411                                // If the current focus was a input of some sort and no selection, don't bother saving
412                                // a native bookmark.  This is because it causes issues with dialog/page selection restore.
413                                // So, we need to create pseudo bookmarks to work with.
414                                tg = cf ? cf.tagName : "";
415                                tg = tg.toLowerCase();
416                                if(cf && tg && (tg == "button" || tg == "textarea" || tg == "input")){
417                                        if(sel.type && sel.type.toLowerCase() == "none"){
418                                                return {
419                                                        isCollapsed: true,
420                                                        mark: null
421                                                }
422                                        }else{
423                                                rg = sel.createRange();
424                                                return {
425                                                        isCollapsed: rg.text && rg.text.length?false:true,
426                                                        mark: {
427                                                                range: rg,
428                                                                pRange: true
429                                                        }
430                                                };
431                                        }
432                                }
433                                bm = {};
434
435                                //'IE' way for selections.
436                                try{
437                                        // createRange() throws exception when dojo in iframe
438                                        // and nothing selected, see #9632
439                                        rg = sel.createRange();
440                                        bm.isCollapsed = !(sel.type == 'Text' ? rg.htmlText.length : rg.length);
441                                }catch(e){
442                                        bm.isCollapsed = true;
443                                        return bm;
444                                }
445                                if(sel.type.toUpperCase() == 'CONTROL'){
446                                        if(rg.length){
447                                                bm.mark=[];
448                                                var i=0,len=rg.length;
449                                                while(i<len){
450                                                        bm.mark.push(rg.item(i++));
451                                                }
452                                        }else{
453                                                bm.isCollapsed = true;
454                                                bm.mark = null;
455                                        }
456                                }else{
457                                        bm.mark = rg.getBookmark();
458                                }
459                        }else{
460                                console.warn("No idea how to store the current selection for this browser!");
461                        }
462                        return bm; // Object
463                };
464
465                this.moveToBookmark = function(/*Object*/ bookmark){
466                        // summary:
467                        //              Moves current selection to a bookmark.
468                        // bookmark:
469                        //              This should be a returned object from getBookmark().
470
471                        // TODO: merge additional code from Editor._moveToBookmark into this method
472
473                        var mark = bookmark.mark;
474                        if(mark){
475                                if(doc.getSelection){
476                                        // W3C Range API (FF, WebKit, Opera, etc)
477                                        var sel = win.getSelection();
478                                        if(sel && sel.removeAllRanges){
479                                                if(mark.pRange){
480                                                        var n = mark.node;
481                                                        n.selectionStart = mark.start;
482                                                        n.selectionEnd = mark.end;
483                                                }else{
484                                                        sel.removeAllRanges();
485                                                        sel.addRange(mark);
486                                                }
487                                        }else{
488                                                console.warn("No idea how to restore selection for this browser!");
489                                        }
490                                }else if(doc.selection && mark){
491                                        //'IE' way.
492                                        var rg;
493                                        if(mark.pRange){
494                                                rg = mark.range;
495                                        }else if(lang.isArray(mark)){
496                                                rg = doc.body.createControlRange();
497                                                //rg.addElement does not have call/apply method, so can not call it directly
498                                                //rg is not available in "range.addElement(item)", so can't use that either
499                                                array.forEach(mark, function(n){
500                                                        rg.addElement(n);
501                                                });
502                                        }else{
503                                                rg = doc.body.createTextRange();
504                                                rg.moveToBookmark(mark);
505                                        }
506                                        rg.select();
507                                }
508                        }
509                };
510
511                this.isCollapsed = function(){
512                        // summary:
513                        //              Returns true if there is no text selected
514                        return this.getBookmark().isCollapsed;
515                };
516        };
517
518        // singleton on the main window
519        var selection = new SelectionManager(window);
520
521        // hook for editor to use class
522        selection.SelectionManager = SelectionManager;
523
524        return selection;
525});
Note: See TracBrowser for help on using the repository browser.