source: Dev/branches/rest-dojo-ui/client/dijit/Editor.js @ 274

Last change on this file since 274 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 27.6 KB
Line 
1define([
2        "dojo/_base/array", // array.forEach
3        "dojo/_base/declare", // declare
4        "dojo/_base/Deferred", // Deferred
5        "dojo/i18n", // i18n.getLocalization
6        "dojo/dom-attr", // domAttr.set
7        "dojo/dom-class", // domClass.add
8        "dojo/dom-geometry",
9        "dojo/dom-style", // domStyle.set, get
10        "dojo/_base/event", // event.stop
11        "dojo/keys", // keys.F1 keys.F15 keys.TAB
12        "dojo/_base/lang", // lang.getObject lang.hitch
13        "dojo/_base/sniff", // has("ie") has("mac") has("webkit")
14        "dojo/string", // string.substitute
15        "dojo/topic", // topic.publish()
16        "dojo/_base/window", // win.withGlobal
17        "./_base/focus",        // dijit.getBookmark()
18        "./_Container",
19        "./Toolbar",
20        "./ToolbarSeparator",
21        "./layout/_LayoutWidget",
22        "./form/ToggleButton",
23        "./_editor/_Plugin",
24        "./_editor/plugins/EnterKeyHandling",
25        "./_editor/html",
26        "./_editor/range",
27        "./_editor/RichText",
28        ".",    // dijit._scopeName
29        "dojo/i18n!./_editor/nls/commands"
30], function(array, declare, Deferred, i18n, domAttr, domClass, domGeometry, domStyle,
31                        event, keys, lang, has, string, topic, win,
32                        focusBase, _Container, Toolbar, ToolbarSeparator, _LayoutWidget, ToggleButton,
33                        _Plugin, EnterKeyHandling, html, rangeapi, RichText, dijit){
34
35        // module:
36        //              dijit/Editor
37        // summary:
38        //              A rich text Editing widget
39
40        var Editor = declare("dijit.Editor", RichText, {
41                // summary:
42                //              A rich text Editing widget
43                //
44                // description:
45                //              This widget provides basic WYSIWYG editing features, based on the browser's
46                //              underlying rich text editing capability, accompanied by a toolbar (`dijit.Toolbar`).
47                //              A plugin model is available to extend the editor's capabilities as well as the
48                //              the options available in the toolbar.  Content generation may vary across
49                //              browsers, and clipboard operations may have different results, to name
50                //              a few limitations.  Note: this widget should not be used with the HTML
51                //              <TEXTAREA> tag -- see dijit._editor.RichText for details.
52
53                // plugins: [const] Object[]
54                //              A list of plugin names (as strings) or instances (as objects)
55                //              for this widget.
56                //
57                //              When declared in markup, it might look like:
58                //      |       plugins="['bold',{name:'dijit._editor.plugins.FontChoice', command:'fontName', generic:true}]"
59                plugins: null,
60
61                // extraPlugins: [const] Object[]
62                //              A list of extra plugin names which will be appended to plugins array
63                extraPlugins: null,
64
65                constructor: function(){
66                        // summary:
67                        //              Runs on widget initialization to setup arrays etc.
68                        // tags:
69                        //              private
70
71                        if(!lang.isArray(this.plugins)){
72                                this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|",
73                                "insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull",
74                                EnterKeyHandling /*, "createLink"*/];
75                        }
76
77                        this._plugins=[];
78                        this._editInterval = this.editActionInterval * 1000;
79
80                        //IE will always lose focus when other element gets focus, while for FF and safari,
81                        //when no iframe is used, focus will be lost whenever another element gets focus.
82                        //For IE, we can connect to onBeforeDeactivate, which will be called right before
83                        //the focus is lost, so we can obtain the selected range. For other browsers,
84                        //no equivalent of onBeforeDeactivate, so we need to do two things to make sure
85                        //selection is properly saved before focus is lost: 1) when user clicks another
86                        //element in the page, in which case we listen to mousedown on the entire page and
87                        //see whether user clicks out of a focus editor, if so, save selection (focus will
88                        //only lost after onmousedown event is fired, so we can obtain correct caret pos.)
89                        //2) when user tabs away from the editor, which is handled in onKeyDown below.
90                        if(has("ie")){
91                                this.events.push("onBeforeDeactivate");
92                                this.events.push("onBeforeActivate");
93                        }
94                },
95
96                postMixInProperties: function(){
97                        // summary:
98                        //      Extension to make sure a deferred is in place before certain functions
99                        //      execute, like making sure all the plugins are properly inserted.
100
101                        // Set up a deferred so that the value isn't applied to the editor
102                        // until all the plugins load, needed to avoid timing condition
103                        // reported in #10537.
104                        this.setValueDeferred = new Deferred();
105                        this.inherited(arguments);
106                },
107
108                postCreate: function(){
109                        //for custom undo/redo, if enabled.
110                        this._steps=this._steps.slice(0);
111                        this._undoedSteps=this._undoedSteps.slice(0);
112
113                        if(lang.isArray(this.extraPlugins)){
114                                this.plugins=this.plugins.concat(this.extraPlugins);
115                        }
116
117                        this.inherited(arguments);
118
119                        this.commands = i18n.getLocalization("dijit._editor", "commands", this.lang);
120
121                        if(!this.toolbar){
122                                // if we haven't been assigned a toolbar, create one
123                                this.toolbar = new Toolbar({
124                                        dir: this.dir,
125                                        lang: this.lang
126                                });
127                                this.header.appendChild(this.toolbar.domNode);
128                        }
129
130                        array.forEach(this.plugins, this.addPlugin, this);
131
132                        // Okay, denote the value can now be set.
133                        this.setValueDeferred.callback(true);
134
135                        domClass.add(this.iframe.parentNode, "dijitEditorIFrameContainer");
136                        domClass.add(this.iframe, "dijitEditorIFrame");
137                        domAttr.set(this.iframe, "allowTransparency", true);
138
139                        if(has("webkit")){
140                                // Disable selecting the entire editor by inadvertent double-clicks.
141                                // on buttons, title bar, etc.  Otherwise clicking too fast on
142                                // a button such as undo/redo selects the entire editor.
143                                domStyle.set(this.domNode, "KhtmlUserSelect", "none");
144                        }
145                        this.toolbar.startup();
146                        this.onNormalizedDisplayChanged(); //update toolbar button status
147                },
148                destroy: function(){
149                        array.forEach(this._plugins, function(p){
150                                if(p && p.destroy){
151                                        p.destroy();
152                                }
153                        });
154                        this._plugins=[];
155                        this.toolbar.destroyRecursive();
156                        delete this.toolbar;
157                        this.inherited(arguments);
158                },
159                addPlugin: function(/*String||Object||Function*/plugin, /*Integer?*/index){
160                        // summary:
161                        //              takes a plugin name as a string or a plugin instance and
162                        //              adds it to the toolbar and associates it with this editor
163                        //              instance. The resulting plugin is added to the Editor's
164                        //              plugins array. If index is passed, it's placed in the plugins
165                        //              array at that index. No big magic, but a nice helper for
166                        //              passing in plugin names via markup.
167                        //
168                        // plugin: String, args object, plugin instance, or plugin constructor
169                        //
170                        // args:
171                        //              This object will be passed to the plugin constructor
172                        //
173                        // index: Integer
174                        //              Used when creating an instance from
175                        //              something already in this.plugins. Ensures that the new
176                        //              instance is assigned to this.plugins at that index.
177                        var args=lang.isString(plugin)?{name:plugin}:lang.isFunction(plugin)?{ctor:plugin}:plugin;
178                        if(!args.setEditor){
179                                var o={"args":args,"plugin":null,"editor":this};
180                                if(args.name){
181                                        // search registry for a plugin factory matching args.name, if it's not there then
182                                        // fallback to 1.0 API:
183                                        // ask all loaded plugin modules to fill in o.plugin if they can (ie, if they implement args.name)
184                                        // remove fallback for 2.0.
185                                        if(_Plugin.registry[args.name]){
186                                                o.plugin = _Plugin.registry[args.name](args);
187                                        }else{
188                                                topic.publish(dijit._scopeName + ".Editor.getPlugin", o);       // publish
189                                        }
190                                }
191                                if(!o.plugin){
192                                        var pc = args.ctor || lang.getObject(args.name);
193                                        if(pc){
194                                                o.plugin=new pc(args);
195                                        }
196                                }
197                                if(!o.plugin){
198                                        console.warn('Cannot find plugin',plugin);
199                                        return;
200                                }
201                                plugin=o.plugin;
202                        }
203                        if(arguments.length > 1){
204                                this._plugins[index] = plugin;
205                        }else{
206                                this._plugins.push(plugin);
207                        }
208                        plugin.setEditor(this);
209                        if(lang.isFunction(plugin.setToolbar)){
210                                plugin.setToolbar(this.toolbar);
211                        }
212                },
213
214                //the following 2 functions are required to make the editor play nice under a layout widget, see #4070
215
216                resize: function(size){
217                        // summary:
218                        //              Resize the editor to the specified size, see `dijit.layout._LayoutWidget.resize`
219                        if(size){
220                                // we've been given a height/width for the entire editor (toolbar + contents), calls layout()
221                                // to split the allocated size between the toolbar and the contents
222                                _LayoutWidget.prototype.resize.apply(this, arguments);
223                        }
224                        /*
225                        else{
226                                // do nothing, the editor is already laid out correctly.   The user has probably specified
227                                // the height parameter, which was used to set a size on the iframe
228                        }
229                        */
230                },
231                layout: function(){
232                        // summary:
233                        //              Called from `dijit.layout._LayoutWidget.resize`.  This shouldn't be called directly
234                        // tags:
235                        //              protected
236
237                        // Converts the iframe (or rather the <div> surrounding it) to take all the available space
238                        // except what's needed for the header (toolbars) and footer (breadcrumbs, etc).
239                        // A class was added to the iframe container and some themes style it, so we have to
240                        // calc off the added margins and padding too. See tracker: #10662
241                        var areaHeight = (this._contentBox.h -
242                                (this.getHeaderHeight() + this.getFooterHeight() +
243                                 domGeometry.getPadBorderExtents(this.iframe.parentNode).h +
244                                 domGeometry.getMarginExtents(this.iframe.parentNode).h));
245                        this.editingArea.style.height = areaHeight + "px";
246                        if(this.iframe){
247                                this.iframe.style.height="100%";
248                        }
249                        this._layoutMode = true;
250                },
251
252                _onIEMouseDown: function(/*Event*/ e){
253                        // summary:
254                        //              IE only to prevent 2 clicks to focus
255                        // tags:
256                        //              private
257                        var outsideClientArea;
258                        // IE 8's componentFromPoint is broken, which is a shame since it
259                        // was smaller code, but oh well.  We have to do this brute force
260                        // to detect if the click was scroller or not.
261                        var b = this.document.body;
262                        var clientWidth = b.clientWidth;
263                        var clientHeight = b.clientHeight;
264                        var clientLeft = b.clientLeft;
265                        var offsetWidth = b.offsetWidth;
266                        var offsetHeight = b.offsetHeight;
267                        var offsetLeft = b.offsetLeft;
268
269                        //Check for vertical scroller click.
270                        if(/^rtl$/i.test(b.dir || "")){
271                                if(clientWidth < offsetWidth && e.x > clientWidth && e.x < offsetWidth){
272                                        // Check the click was between width and offset width, if so, scroller
273                                        outsideClientArea = true;
274                                }
275                        }else{
276                                // RTL mode, we have to go by the left offsets.
277                                if(e.x < clientLeft && e.x > offsetLeft){
278                                        // Check the click was between width and offset width, if so, scroller
279                                        outsideClientArea = true;
280                                }
281                        }
282                        if(!outsideClientArea){
283                                // Okay, might be horiz scroller, check that.
284                                if(clientHeight < offsetHeight && e.y > clientHeight && e.y < offsetHeight){
285                                        // Horizontal scroller.
286                                        outsideClientArea = true;
287                                }
288                        }
289                        if(!outsideClientArea){
290                                delete this._cursorToStart; // Remove the force to cursor to start position.
291                                delete this._savedSelection; // new mouse position overrides old selection
292                                if(e.target.tagName == "BODY"){
293                                        setTimeout(lang.hitch(this, "placeCursorAtEnd"), 0);
294                                }
295                                this.inherited(arguments);
296                        }
297                },
298                onBeforeActivate: function(){
299                        this._restoreSelection();
300                },
301                onBeforeDeactivate: function(e){
302                        // summary:
303                        //              Called on IE right before focus is lost.   Saves the selected range.
304                        // tags:
305                        //              private
306                        if(this.customUndo){
307                                this.endEditing(true);
308                        }
309                        //in IE, the selection will be lost when other elements get focus,
310                        //let's save focus before the editor is deactivated
311                        if(e.target.tagName != "BODY"){
312                                this._saveSelection();
313                        }
314                        //console.log('onBeforeDeactivate',this);
315                },
316
317                /* beginning of custom undo/redo support */
318
319                // customUndo: Boolean
320                //              Whether we shall use custom undo/redo support instead of the native
321                //              browser support. By default, we now use custom undo.  It works better
322                //              than native browser support and provides a consistent behavior across
323                //              browsers with a minimal performance hit.  We already had the hit on
324                //              the slowest browser, IE, anyway.
325                customUndo: true,
326
327                // editActionInterval: Integer
328                //              When using customUndo, not every keystroke will be saved as a step.
329                //              Instead typing (including delete) will be grouped together: after
330                //              a user stops typing for editActionInterval seconds, a step will be
331                //              saved; if a user resume typing within editActionInterval seconds,
332                //              the timeout will be restarted. By default, editActionInterval is 3
333                //              seconds.
334                editActionInterval: 3,
335
336                beginEditing: function(cmd){
337                        // summary:
338                        //              Called to note that the user has started typing alphanumeric characters, if it's not already noted.
339                        //              Deals with saving undo; see editActionInterval parameter.
340                        // tags:
341                        //              private
342                        if(!this._inEditing){
343                                this._inEditing=true;
344                                this._beginEditing(cmd);
345                        }
346                        if(this.editActionInterval>0){
347                                if(this._editTimer){
348                                        clearTimeout(this._editTimer);
349                                }
350                                this._editTimer = setTimeout(lang.hitch(this, this.endEditing), this._editInterval);
351                        }
352                },
353
354                // TODO: declaring these in the prototype is meaningless, just create in the constructor/postCreate
355                _steps:[],
356                _undoedSteps:[],
357
358                execCommand: function(cmd){
359                        // summary:
360                        //              Main handler for executing any commands to the editor, like paste, bold, etc.
361                        //      Called by plugins, but not meant to be called by end users.
362                        // tags:
363                        //              protected
364                        if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
365                                return this[cmd]();
366                        }else{
367                                if(this.customUndo){
368                                        this.endEditing();
369                                        this._beginEditing();
370                                }
371                                var r = this.inherited(arguments);
372                                if(this.customUndo){
373                                        this._endEditing();
374                                }
375                                return r;
376                        }
377                },
378
379                _pasteImpl: function(){
380                        // summary:
381                        //              Over-ride of paste command control to make execCommand cleaner
382                        // tags:
383                        //              Protected
384                        return this._clipboardCommand("paste");
385                },
386
387                _cutImpl: function(){
388                        // summary:
389                        //              Over-ride of cut command control to make execCommand cleaner
390                        // tags:
391                        //              Protected
392                        return this._clipboardCommand("cut");
393                },
394
395                _copyImpl: function(){
396                        // summary:
397                        //              Over-ride of copy command control to make execCommand cleaner
398                        // tags:
399                        //              Protected
400                        return this._clipboardCommand("copy");
401                },
402
403                _clipboardCommand: function(cmd){
404                        // summary:
405                        //              Function to handle processing clipboard commands (or at least try to).
406                        // tags:
407                        //              Private
408                        var r;
409                        try{
410                                // Try to exec the superclass exec-command and see if it works.
411                                r = this.document.execCommand(cmd, false, null);
412                                if(has("webkit") && !r){ //see #4598: webkit does not guarantee clipboard support from js
413                                        throw { code: 1011 }; // throw an object like Mozilla's error
414                                }
415                        }catch(e){
416                                //TODO: when else might we get an exception?  Do we need the Mozilla test below?
417                                if(e.code == 1011 /* Mozilla: service denied */){
418                                        // Warn user of platform limitation.  Cannot programmatically access clipboard. See ticket #4136
419                                        var sub = string.substitute,
420                                                accel = {cut:'X', copy:'C', paste:'V'};
421                                        alert(sub(this.commands.systemShortcut,
422                                                [this.commands[cmd], sub(this.commands[has("mac") ? 'appleKey' : 'ctrlKey'], [accel[cmd]])]));
423                                }
424                                r = false;
425                        }
426                        return r;
427                },
428
429                queryCommandEnabled: function(cmd){
430                        // summary:
431                        //              Returns true if specified editor command is enabled.
432                        //      Used by the plugins to know when to highlight/not highlight buttons.
433                        // tags:
434                        //              protected
435                        if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
436                                return cmd == 'undo' ? (this._steps.length > 1) : (this._undoedSteps.length > 0);
437                        }else{
438                                return this.inherited(arguments);
439                        }
440                },
441                _moveToBookmark: function(b){
442                        // summary:
443                        //              Selects the text specified in bookmark b
444                        // tags:
445                        //              private
446                        var bookmark = b.mark;
447                        var mark = b.mark;
448                        var col = b.isCollapsed;
449                        var r, sNode, eNode, sel;
450                        if(mark){
451                                if(has("ie") < 9){
452                                        if(lang.isArray(mark)){
453                                                //IE CONTROL, have to use the native bookmark.
454                                                bookmark = [];
455                                                array.forEach(mark,function(n){
456                                                        bookmark.push(rangeapi.getNode(n,this.editNode));
457                                                },this);
458                                                win.withGlobal(this.window,'moveToBookmark',dijit,[{mark: bookmark, isCollapsed: col}]);
459                                        }else{
460                                                if(mark.startContainer && mark.endContainer){
461                                                        // Use the pseudo WC3 range API.  This works better for positions
462                                                        // than the IE native bookmark code.
463                                                        sel = rangeapi.getSelection(this.window);
464                                                        if(sel && sel.removeAllRanges){
465                                                                sel.removeAllRanges();
466                                                                r = rangeapi.create(this.window);
467                                                                sNode = rangeapi.getNode(mark.startContainer,this.editNode);
468                                                                eNode = rangeapi.getNode(mark.endContainer,this.editNode);
469                                                                if(sNode && eNode){
470                                                                        // Okay, we believe we found the position, so add it into the selection
471                                                                        // There are cases where it may not be found, particularly in undo/redo, when
472                                                                        // IE changes the underlying DOM on us (wraps text in a <p> tag or similar.
473                                                                        // So, in those cases, don't bother restoring selection.
474                                                                        r.setStart(sNode,mark.startOffset);
475                                                                        r.setEnd(eNode,mark.endOffset);
476                                                                        sel.addRange(r);
477                                                                }
478                                                        }
479                                                }
480                                        }
481                                }else{//w3c range
482                                        sel = rangeapi.getSelection(this.window);
483                                        if(sel && sel.removeAllRanges){
484                                                sel.removeAllRanges();
485                                                r = rangeapi.create(this.window);
486                                                sNode = rangeapi.getNode(mark.startContainer,this.editNode);
487                                                eNode = rangeapi.getNode(mark.endContainer,this.editNode);
488                                                if(sNode && eNode){
489                                                        // Okay, we believe we found the position, so add it into the selection
490                                                        // There are cases where it may not be found, particularly in undo/redo, when
491                                                        // formatting as been done and so on, so don't restore selection then.
492                                                        r.setStart(sNode,mark.startOffset);
493                                                        r.setEnd(eNode,mark.endOffset);
494                                                        sel.addRange(r);
495                                                }
496                                        }
497                                }
498                        }
499                },
500                _changeToStep: function(from, to){
501                        // summary:
502                        //              Reverts editor to "to" setting, from the undo stack.
503                        // tags:
504                        //              private
505                        this.setValue(to.text);
506                        var b=to.bookmark;
507                        if(!b){ return; }
508                        this._moveToBookmark(b);
509                },
510                undo: function(){
511                        // summary:
512                        //              Handler for editor undo (ex: ctrl-z) operation
513                        // tags:
514                        //              private
515                        //console.log('undo');
516                        var ret = false;
517                        if(!this._undoRedoActive){
518                                this._undoRedoActive = true;
519                                this.endEditing(true);
520                                var s=this._steps.pop();
521                                if(s && this._steps.length>0){
522                                        this.focus();
523                                        this._changeToStep(s,this._steps[this._steps.length-1]);
524                                        this._undoedSteps.push(s);
525                                        this.onDisplayChanged();
526                                        delete this._undoRedoActive;
527                                        ret = true;
528                                }
529                                delete this._undoRedoActive;
530                        }
531                        return ret;
532                },
533                redo: function(){
534                        // summary:
535                        //              Handler for editor redo (ex: ctrl-y) operation
536                        // tags:
537                        //              private
538                        //console.log('redo');
539                        var ret = false;
540                        if(!this._undoRedoActive){
541                                this._undoRedoActive = true;
542                                this.endEditing(true);
543                                var s=this._undoedSteps.pop();
544                                if(s && this._steps.length>0){
545                                        this.focus();
546                                        this._changeToStep(this._steps[this._steps.length-1],s);
547                                        this._steps.push(s);
548                                        this.onDisplayChanged();
549                                        ret = true;
550                                }
551                                delete this._undoRedoActive;
552                        }
553                        return ret;
554                },
555                endEditing: function(ignore_caret){
556                        // summary:
557                        //              Called to note that the user has stopped typing alphanumeric characters, if it's not already noted.
558                        //              Deals with saving undo; see editActionInterval parameter.
559                        // tags:
560                        //              private
561                        if(this._editTimer){
562                                clearTimeout(this._editTimer);
563                        }
564                        if(this._inEditing){
565                                this._endEditing(ignore_caret);
566                                this._inEditing=false;
567                        }
568                },
569
570                _getBookmark: function(){
571                        // summary:
572                        //              Get the currently selected text
573                        // tags:
574                        //              protected
575                        var b=win.withGlobal(this.window,focusBase.getBookmark);
576                        var tmp=[];
577                        if(b && b.mark){
578                                var mark = b.mark;
579                                if(has("ie") < 9){
580                                        // Try to use the pseudo range API on IE for better accuracy.
581                                        var sel = rangeapi.getSelection(this.window);
582                                        if(!lang.isArray(mark)){
583                                                if(sel){
584                                                        var range;
585                                                        if(sel.rangeCount){
586                                                                range = sel.getRangeAt(0);
587                                                        }
588                                                        if(range){
589                                                                b.mark = range.cloneRange();
590                                                        }else{
591                                                                b.mark = win.withGlobal(this.window,focusBase.getBookmark);
592                                                        }
593                                                }
594                                        }else{
595                                                // Control ranges (img, table, etc), handle differently.
596                                                array.forEach(b.mark,function(n){
597                                                        tmp.push(rangeapi.getIndex(n,this.editNode).o);
598                                                },this);
599                                                b.mark = tmp;
600                                        }
601                                }
602                                try{
603                                        if(b.mark && b.mark.startContainer){
604                                                tmp=rangeapi.getIndex(b.mark.startContainer,this.editNode).o;
605                                                b.mark={startContainer:tmp,
606                                                        startOffset:b.mark.startOffset,
607                                                        endContainer:b.mark.endContainer===b.mark.startContainer?tmp:rangeapi.getIndex(b.mark.endContainer,this.editNode).o,
608                                                        endOffset:b.mark.endOffset};
609                                        }
610                                }catch(e){
611                                        b.mark = null;
612                                }
613                        }
614                        return b;
615                },
616                _beginEditing: function(){
617                        // summary:
618                        //              Called when the user starts typing alphanumeric characters.
619                        //              Deals with saving undo; see editActionInterval parameter.
620                        // tags:
621                        //              private
622                        if(this._steps.length === 0){
623                                // You want to use the editor content without post filtering
624                                // to make sure selection restores right for the 'initial' state.
625                                // and undo is called.  So not using this.value, as it was 'processed'
626                                // and the line-up for selections may have been altered.
627                                this._steps.push({'text':html.getChildrenHtml(this.editNode),'bookmark':this._getBookmark()});
628                        }
629                },
630                _endEditing: function(){
631                        // summary:
632                        //              Called when the user stops typing alphanumeric characters.
633                        //              Deals with saving undo; see editActionInterval parameter.
634                        // tags:
635                        //              private
636                        // Avoid filtering to make sure selections restore.
637                        var v = html.getChildrenHtml(this.editNode);
638
639                        this._undoedSteps=[];//clear undoed steps
640                        this._steps.push({text: v, bookmark: this._getBookmark()});
641                },
642                onKeyDown: function(e){
643                        // summary:
644                        //              Handler for onkeydown event.
645                        // tags:
646                        //              private
647
648                        //We need to save selection if the user TAB away from this editor
649                        //no need to call _saveSelection for IE, as that will be taken care of in onBeforeDeactivate
650                        if(!has("ie") && !this.iframe && e.keyCode == keys.TAB && !this.tabIndent){
651                                this._saveSelection();
652                        }
653                        if(!this.customUndo){
654                                this.inherited(arguments);
655                                return;
656                        }
657                        var k = e.keyCode;
658                        if(e.ctrlKey && !e.altKey){//undo and redo only if the special right Alt + z/y are not pressed #5892
659                                if(k == 90 || k == 122){ //z
660                                        event.stop(e);
661                                        this.undo();
662                                        return;
663                                }else if(k == 89 || k == 121){ //y
664                                        event.stop(e);
665                                        this.redo();
666                                        return;
667                                }
668                        }
669                        this.inherited(arguments);
670
671                        switch(k){
672                                        case keys.ENTER:
673                                        case keys.BACKSPACE:
674                                        case keys.DELETE:
675                                                this.beginEditing();
676                                                break;
677                                        case 88: //x
678                                        case 86: //v
679                                                if(e.ctrlKey && !e.altKey && !e.metaKey){
680                                                        this.endEditing();//end current typing step if any
681                                                        if(e.keyCode == 88){
682                                                                this.beginEditing('cut');
683                                                                //use timeout to trigger after the cut is complete
684                                                                setTimeout(lang.hitch(this, this.endEditing), 1);
685                                                        }else{
686                                                                this.beginEditing('paste');
687                                                                //use timeout to trigger after the paste is complete
688                                                                setTimeout(lang.hitch(this, this.endEditing), 1);
689                                                        }
690                                                        break;
691                                                }
692                                                //pass through
693                                        default:
694                                                if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode<keys.F1 || e.keyCode>keys.F15)){
695                                                        this.beginEditing();
696                                                        break;
697                                                }
698                                                //pass through
699                                        case keys.ALT:
700                                                this.endEditing();
701                                                break;
702                                        case keys.UP_ARROW:
703                                        case keys.DOWN_ARROW:
704                                        case keys.LEFT_ARROW:
705                                        case keys.RIGHT_ARROW:
706                                        case keys.HOME:
707                                        case keys.END:
708                                        case keys.PAGE_UP:
709                                        case keys.PAGE_DOWN:
710                                                this.endEditing(true);
711                                                break;
712                                        //maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed
713                                        case keys.CTRL:
714                                        case keys.SHIFT:
715                                        case keys.TAB:
716                                                break;
717                                }
718                },
719                _onBlur: function(){
720                        // summary:
721                        //              Called from focus manager when focus has moved away from this editor
722                        // tags:
723                        //              protected
724
725                        //this._saveSelection();
726                        this.inherited(arguments);
727                        this.endEditing(true);
728                },
729                _saveSelection: function(){
730                        // summary:
731                        //              Save the currently selected text in _savedSelection attribute
732                        // tags:
733                        //              private
734                        try{
735                                this._savedSelection=this._getBookmark();
736                        }catch(e){ /* Squelch any errors that occur if selection save occurs due to being hidden simultaneously. */}
737                },
738                _restoreSelection: function(){
739                        // summary:
740                        //              Re-select the text specified in _savedSelection attribute;
741                        //              see _saveSelection().
742                        // tags:
743                        //              private
744                        if(this._savedSelection){
745                                // Clear off cursor to start, we're deliberately going to a selection.
746                                delete this._cursorToStart;
747                                // only restore the selection if the current range is collapsed
748                                // if not collapsed, then it means the editor does not lose
749                                // selection and there is no need to restore it
750                                if(win.withGlobal(this.window,'isCollapsed',dijit)){
751                                        this._moveToBookmark(this._savedSelection);
752                                }
753                                delete this._savedSelection;
754                        }
755                },
756
757                onClick: function(){
758                        // summary:
759                        //              Handler for when editor is clicked
760                        // tags:
761                        //              protected
762                        this.endEditing(true);
763                        this.inherited(arguments);
764                },
765
766                replaceValue: function(/*String*/ html){
767                        // summary:
768                        //              over-ride of replaceValue to support custom undo and stack maintenance.
769                        // tags:
770                        //              protected
771                        if(!this.customUndo){
772                                this.inherited(arguments);
773                        }else{
774                                if(this.isClosed){
775                                        this.setValue(html);
776                                }else{
777                                        this.beginEditing();
778                                        if(!html){
779                                                html = "&#160;";        // &nbsp;
780                                        }
781                                        this.setValue(html);
782                                        this.endEditing();
783                                }
784                        }
785                },
786
787                _setDisabledAttr: function(/*Boolean*/ value){
788                        var disableFunc = lang.hitch(this, function(){
789                                if((!this.disabled && value) || (!this._buttonEnabledPlugins && value)){
790                                // Disable editor: disable all enabled buttons and remember that list
791                                        array.forEach(this._plugins, function(p){
792                                                p.set("disabled", true);
793                                });
794                        }else if(this.disabled && !value){
795                                        // Restore plugins to being active.
796                                        array.forEach(this._plugins, function(p){
797                                                p.set("disabled", false);
798                                });
799                        }
800                        });
801                        this.setValueDeferred.addCallback(disableFunc);
802                        this.inherited(arguments);
803                },
804
805                _setStateClass: function(){
806                        try{
807                                this.inherited(arguments);
808
809                                // Let theme set the editor's text color based on editor enabled/disabled state.
810                                // We need to jump through hoops because the main document (where the theme CSS is)
811                                // is separate from the iframe's document.
812                                if(this.document && this.document.body){
813                                        domStyle.set(this.document.body, "color", domStyle.get(this.iframe, "color"));
814                                }
815                        }catch(e){ /* Squelch any errors caused by focus change if hidden during a state change */}
816                }
817        });
818
819        // Register the "default plugins", ie, the built-in editor commands
820        function simplePluginFactory(args){
821                return new _Plugin({ command: args.name });
822        }
823        function togglePluginFactory(args){
824                return new _Plugin({ buttonClass: ToggleButton, command: args.name });
825        }
826        lang.mixin(_Plugin.registry, {
827                "undo": simplePluginFactory,
828                "redo": simplePluginFactory,
829                "cut": simplePluginFactory,
830                "copy": simplePluginFactory,
831                "paste": simplePluginFactory,
832                "insertOrderedList": simplePluginFactory,
833                "insertUnorderedList": simplePluginFactory,
834                "indent": simplePluginFactory,
835                "outdent": simplePluginFactory,
836                "justifyCenter": simplePluginFactory,
837                "justifyFull": simplePluginFactory,
838                "justifyLeft": simplePluginFactory,
839                "justifyRight": simplePluginFactory,
840                "delete": simplePluginFactory,
841                "selectAll": simplePluginFactory,
842                "removeFormat": simplePluginFactory,
843                "unlink": simplePluginFactory,
844                "insertHorizontalRule": simplePluginFactory,
845
846                "bold": togglePluginFactory,
847                "italic": togglePluginFactory,
848                "underline": togglePluginFactory,
849                "strikethrough": togglePluginFactory,
850                "subscript": togglePluginFactory,
851                "superscript": togglePluginFactory,
852
853                "|": function(){
854                        return new _Plugin({ button: new ToolbarSeparator(), setEditor: function(editor){this.editor = editor;}});
855                }
856        });
857
858        return Editor;
859});
Note: See TracBrowser for help on using the repository browser.