source: Dev/trunk/src/client/dijit/_editor/plugins/FontChoice.js

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

Added Dojo 1.9.3 release.

File size: 19.5 KB
Line 
1define([
2        "require",
3        "dojo/_base/array", // array.indexOf array.map
4        "dojo/_base/declare", // declare
5        "dojo/dom-construct", // domConstruct.place
6        "dojo/i18n", // i18n.getLocalization
7        "dojo/_base/lang", // lang.delegate lang.hitch lang.isString
8        "dojo/store/Memory", // MemoryStore
9        "../../registry", // registry.getUniqueId
10        "../../_Widget",
11        "../../_TemplatedMixin",
12        "../../_WidgetsInTemplateMixin",
13        "../../form/FilteringSelect",
14        "../_Plugin",
15        "../range",
16        "dojo/i18n!../nls/FontChoice"
17], function(require, array, declare, domConstruct, i18n, lang, MemoryStore,
18        registry, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, FilteringSelect, _Plugin, rangeapi){
19
20        // module:
21        //              dijit/_editor/plugins/FontChoice
22
23        var _FontDropDown = declare("dijit._editor.plugins._FontDropDown",
24                [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], {
25                        // summary:
26                        //              Base class for widgets that contains a label (like "Font:")
27                        //              and a FilteringSelect drop down to pick a value.
28                        //              Used as Toolbar entry.
29
30                        // label: [public] String
31                        //              The label to apply to this particular FontDropDown.
32                        label: "",
33
34                        // plainText: [public] boolean
35                        //              Flag to indicate that the returned label should be plain text
36                        //              instead of an example.
37                        plainText: false,
38
39                        // templateString: [public] String
40                        //              The template used to construct the labeled dropdown.
41                        templateString: "<span style='white-space: nowrap' class='dijit dijitReset dijitInline'>" +
42                                "<label class='dijitLeft dijitInline' for='${selectId}'>${label}</label>" +
43                                "<input data-dojo-type='../../form/FilteringSelect' required='false' " +
44                                "data-dojo-props='labelType:\"html\", labelAttr:\"label\", searchAttr:\"name\"' " +
45                                "class='${comboClass}' " +
46                                "tabIndex='-1' id='${selectId}' data-dojo-attach-point='select' value=''/>" +
47                                "</span>",
48
49                        // contextRequire: [public] Function
50                        //              The context require that is used to resolve modules in template.
51                        contextRequire: require,
52
53                        postMixInProperties: function(){
54                                // summary:
55                                //              Over-ride to set specific properties.
56                                this.inherited(arguments);
57
58                                this.strings = i18n.getLocalization("dijit._editor", "FontChoice");
59
60                                // Set some substitution variables used in the template
61                                this.label = this.strings[this.command];
62
63                                // _WidgetBase sets the id after postMixInProperties(), but we need it now.
64                                // Alternative is to have a buildRendering() method and move this.selectId setting there,
65                                // or alternately get rid of selectId variable and just access ${id} in template?
66                                this.id = registry.getUniqueId(this.declaredClass.replace(/\./g, "_"));
67
68                                this.selectId = this.id + "_select";    // used in template
69
70                                this.inherited(arguments);
71                        },
72
73                        postCreate: function(){
74                                // summary:
75                                //              Over-ride for the default postCreate action
76                                //              This establishes the filtering selects and the like.
77
78                                // Initialize the list of items in the drop down by creating data store with items like:
79                                // {value: 1, name: "xx-small", label: "<font size=1>xx-small</font-size>" }
80                                this.select.set("store", new MemoryStore({
81                                        idProperty: "value",
82                                        data: array.map(this.values, function(value){
83                                                var name = this.strings[value] || value;
84                                                return {
85                                                        label: this.getLabel(value, name),
86                                                        name: name,
87                                                        value: value
88                                                };
89                                        }, this)
90                                }));
91
92                                this.select.set("value", "", false);
93                                this.disabled = this.select.get("disabled");
94                        },
95
96                        _setValueAttr: function(value, priorityChange){
97                                // summary:
98                                //              Over-ride for the default action of setting the
99                                //              widget value, maps the input to known values
100                                // value: Object|String
101                                //              The value to set in the select.
102                                // priorityChange:
103                                //              Optional parameter used to tell the select whether or not to fire
104                                //              onChange event.
105
106                                // if the value is not a permitted value, just set empty string to prevent showing the warning icon
107                                priorityChange = priorityChange !== false;
108                                this.select.set('value', array.indexOf(this.values, value) < 0 ? "" : value, priorityChange);
109                                if(!priorityChange){
110                                        // Clear the last state in case of updateState calls.  Ref: #10466
111                                        this.select._lastValueReported = null;
112                                }
113                        },
114
115                        _getValueAttr: function(){
116                                // summary:
117                                //              Allow retrieving the value from the composite select on
118                                //              call to button.get("value");
119                                return this.select.get('value');
120                        },
121
122                        focus: function(){
123                                // summary:
124                                //              Over-ride for focus control of this widget.  Delegates focus down to the
125                                //              filtering select.
126                                this.select.focus();
127                        },
128
129                        _setDisabledAttr: function(value){
130                                // summary:
131                                //              Over-ride for the button's 'disabled' attribute so that it can be
132                                //              disabled programmatically.
133
134                                // Save off ths disabled state so the get retrieves it correctly
135                                //without needing to have a function proxy it.
136                                this._set("disabled", value);
137                                this.select.set("disabled", value);
138                        }
139                });
140
141
142        var _FontNameDropDown = declare("dijit._editor.plugins._FontNameDropDown", _FontDropDown, {
143                // summary:
144                //              Dropdown to select a font; goes in editor toolbar.
145
146                // generic: [const] Boolean
147                //              Use generic (web standard) font names
148                generic: false,
149
150                // command: [public] String
151                //              The editor 'command' implemented by this plugin.
152                command: "fontName",
153
154                comboClass: "dijitFontNameCombo",
155
156                postMixInProperties: function(){
157                        // summary:
158                        //              Over-ride for the default posr mixin control
159                        if(!this.values){
160                                this.values = this.generic ?
161                                        ["serif", "sans-serif", "monospace", "cursive", "fantasy"] : // CSS font-family generics
162                                        ["Arial", "Times New Roman", "Comic Sans MS", "Courier New"];
163                        }
164                        this.inherited(arguments);
165                },
166
167                getLabel: function(value, name){
168                        // summary:
169                        //              Function used to generate the labels of the format dropdown
170                        //              will return a formatted, or plain label based on the value
171                        //              of the plainText option.
172                        // value: String
173                        //              The 'insert value' associated with a name
174                        // name: String
175                        //              The text name of the value
176                        if(this.plainText){
177                                return name;
178                        }else{
179                                return "<div style='font-family: " + value + "'>" + name + "</div>";
180                        }
181                },
182
183                _setValueAttr: function(value, priorityChange){
184                        // summary:
185                        //              Over-ride for the default action of setting the
186                        //              widget value, maps the input to known values
187
188                        priorityChange = priorityChange !== false;
189                        if(this.generic){
190                                var map = {
191                                        "Arial": "sans-serif",
192                                        "Helvetica": "sans-serif",
193                                        "Myriad": "sans-serif",
194                                        "Times": "serif",
195                                        "Times New Roman": "serif",
196                                        "Comic Sans MS": "cursive",
197                                        "Apple Chancery": "cursive",
198                                        "Courier": "monospace",
199                                        "Courier New": "monospace",
200                                        "Papyrus": "fantasy",
201                                        "Estrangelo Edessa": "cursive", // Windows 7
202                                        "Gabriola": "fantasy" // Windows 7
203                                };
204                                value = map[value] || value;
205                        }
206                        this.inherited(arguments, [value, priorityChange]);
207                }
208        });
209
210        var _FontSizeDropDown = declare("dijit._editor.plugins._FontSizeDropDown", _FontDropDown, {
211                // summary:
212                //              Dropdown to select a font size; goes in editor toolbar.
213
214                // command: [public] String
215                //              The editor 'command' implemented by this plugin.
216                command: "fontSize",
217
218                comboClass: "dijitFontSizeCombo",
219
220                // values: [public] Number[]
221                //              The HTML font size values supported by this plugin
222                values: [1, 2, 3, 4, 5, 6, 7], // sizes according to the old HTML FONT SIZE
223
224                getLabel: function(value, name){
225                        // summary:
226                        //              Function used to generate the labels of the format dropdown
227                        //              will return a formatted, or plain label based on the value
228                        //              of the plainText option.
229                        //              We're stuck using the deprecated FONT tag to correspond
230                        //              with the size measurements used by the editor
231                        // value: String
232                        //              The 'insert value' associated with a name
233                        // name: String
234                        //              The text name of the value
235                        if(this.plainText){
236                                return name;
237                        }else{
238                                return "<font size=" + value + "'>" + name + "</font>";
239                        }
240                },
241
242                _setValueAttr: function(value, priorityChange){
243                        // summary:
244                        //              Over-ride for the default action of setting the
245                        //              widget value, maps the input to known values
246                        priorityChange = priorityChange !== false;
247                        if(value.indexOf && value.indexOf("px") != -1){
248                                var pixels = parseInt(value, 10);
249                                value = {10: 1, 13: 2, 16: 3, 18: 4, 24: 5, 32: 6, 48: 7}[pixels] || value;
250                        }
251
252                        this.inherited(arguments, [value, priorityChange]);
253                }
254        });
255
256
257        var _FormatBlockDropDown = declare("dijit._editor.plugins._FormatBlockDropDown", _FontDropDown, {
258                // summary:
259                //              Dropdown to select a format (like paragraph or heading); goes in editor toolbar.
260
261                // command: [public] String
262                //              The editor 'command' implemented by this plugin.
263                command: "formatBlock",
264
265                comboClass: "dijitFormatBlockCombo",
266
267                // values: [public] Array
268                //              The HTML format tags supported by this plugin
269                values: ["noFormat", "p", "h1", "h2", "h3", "pre"],
270
271                postCreate: function(){
272                        // Init and set the default value to no formatting.  Update state will adjust it
273                        // as needed.
274                        this.inherited(arguments);
275                        this.set("value", "noFormat", false);
276                },
277
278                getLabel: function(value, name){
279                        // summary:
280                        //              Function used to generate the labels of the format dropdown
281                        //              will return a formatted, or plain label based on the value
282                        //              of the plainText option.
283                        // value: String
284                        //              The 'insert value' associated with a name
285                        // name: String
286                        //              The text name of the value
287                        if(this.plainText || value == "noFormat"){
288                                return name;
289                        }else{
290                                return "<" + value + ">" + name + "</" + value + ">";
291                        }
292                },
293
294                _execCommand: function(editor, command, choice){
295                        // summary:
296                        //              Over-ride for default exec-command label.
297                        //              Allows us to treat 'none' as special.
298                        if(choice === "noFormat"){
299                                var start;
300                                var end;
301                                var sel = rangeapi.getSelection(editor.window);
302                                if(sel && sel.rangeCount > 0){
303                                        var range = sel.getRangeAt(0);
304                                        var node, tag;
305                                        if(range){
306                                                start = range.startContainer;
307                                                end = range.endContainer;
308
309                                                // find containing nodes of start/end.
310                                                while(start && start !== editor.editNode &&
311                                                        start !== editor.document.body &&
312                                                        start.nodeType !== 1){
313                                                        start = start.parentNode;
314                                                }
315
316                                                while(end && end !== editor.editNode &&
317                                                        end !== editor.document.body &&
318                                                        end.nodeType !== 1){
319                                                        end = end.parentNode;
320                                                }
321
322                                                var processChildren = lang.hitch(this, function(node, ary){
323                                                        if(node.childNodes && node.childNodes.length){
324                                                                var i;
325                                                                for(i = 0; i < node.childNodes.length; i++){
326                                                                        var c = node.childNodes[i];
327                                                                        if(c.nodeType == 1){
328                                                                                if(editor.selection.inSelection(c)){
329                                                                                        var tag = c.tagName ? c.tagName.toLowerCase() : "";
330                                                                                        if(array.indexOf(this.values, tag) !== -1){
331                                                                                                ary.push(c);
332                                                                                        }
333                                                                                        processChildren(c, ary);
334                                                                                }
335                                                                        }
336                                                                }
337                                                        }
338                                                });
339
340                                                var unformatNodes = lang.hitch(this, function(nodes){
341                                                        // summary:
342                                                        //              Internal function to clear format nodes.
343                                                        // nodes:
344                                                        //              The array of nodes to strip formatting from.
345                                                        if(nodes && nodes.length){
346                                                                editor.beginEditing();
347                                                                while(nodes.length){
348                                                                        this._removeFormat(editor, nodes.pop());
349                                                                }
350                                                                editor.endEditing();
351                                                        }
352                                                });
353
354                                                var clearNodes = [];
355                                                if(start == end){
356                                                        //Contained within the same block, may be collapsed, but who cares, see if we
357                                                        // have a block element to remove.
358                                                        var block;
359                                                        node = start;
360                                                        while(node && node !== editor.editNode && node !== editor.document.body){
361                                                                if(node.nodeType == 1){
362                                                                        tag = node.tagName ? node.tagName.toLowerCase() : "";
363                                                                        if(array.indexOf(this.values, tag) !== -1){
364                                                                                block = node;
365                                                                                break;
366                                                                        }
367                                                                }
368                                                                node = node.parentNode;
369                                                        }
370
371                                                        //Also look for all child nodes in the selection that may need to be
372                                                        //cleared of formatting
373                                                        processChildren(start, clearNodes);
374                                                        if(block){
375                                                                clearNodes = [block].concat(clearNodes);
376                                                        }
377                                                        unformatNodes(clearNodes);
378                                                }else{
379                                                        // Probably a multi select, so we have to process it.  Whee.
380                                                        node = start;
381                                                        while(editor.selection.inSelection(node)){
382                                                                if(node.nodeType == 1){
383                                                                        tag = node.tagName ? node.tagName.toLowerCase() : "";
384                                                                        if(array.indexOf(this.values, tag) !== -1){
385                                                                                clearNodes.push(node);
386                                                                        }
387                                                                        processChildren(node, clearNodes);
388                                                                }
389                                                                node = node.nextSibling;
390                                                        }
391                                                        unformatNodes(clearNodes);
392                                                }
393                                                editor.onDisplayChanged();
394                                        }
395                                }
396                        }else{
397                                editor.execCommand(command, choice);
398                        }
399                },
400
401                _removeFormat: function(editor, node){
402                        // summary:
403                        //              function to remove the block format node.
404                        // node:
405                        //              The block format node to remove (and leave the contents behind)
406                        if(editor.customUndo){
407                                // So of course IE doesn't work right with paste-overs.
408                                // We have to do this manually, which is okay since IE already uses
409                                // customUndo and we turned it on for WebKit.  WebKit pasted funny,
410                                // so couldn't use the execCommand approach
411                                while(node.firstChild){
412                                        domConstruct.place(node.firstChild, node, "before");
413                                }
414                                node.parentNode.removeChild(node);
415                        }else{
416                                // Everyone else works fine this way, a paste-over and is native
417                                // undo friendly.
418                                editor.selection.selectElementChildren(node);
419                                var html = editor.selection.getSelectedHtml();
420                                editor.selection.selectElement(node);
421                                editor.execCommand("inserthtml", html || "");
422                        }
423                }
424        });
425
426        // TODO: for 2.0, split into FontChoice plugin into three separate classes,
427        // one for each command (and change registry below)
428        var FontChoice = declare("dijit._editor.plugins.FontChoice", _Plugin, {
429                // summary:
430                //              This plugin provides three drop downs for setting style in the editor
431                //              (font, font size, and format block), as controlled by command.
432                //
433                // description:
434                //              The commands provided by this plugin are:
435                //
436                //              - fontName: Provides a drop down to select from a list of font names
437                //              - fontSize: Provides a drop down to select from a list of font sizes
438                //              - formatBlock: Provides a drop down to select from a list of block styles
439                //                which can easily be added to an editor by including one or more of the above commands
440                //                in the `plugins` attribute as follows:
441                //
442                //      |       plugins="['fontName','fontSize',...]"
443                //
444                //              It is possible to override the default dropdown list by providing an Array for the `custom` property when
445                //              instantiating this plugin, e.g.
446                //
447                //      |       plugins="[{name:'dijit._editor.plugins.FontChoice', command:'fontName', values:['Verdana','Myriad','Garamond']},...]"
448                //
449                //              Alternatively, for `fontName` only, `generic:true` may be specified to provide a dropdown with
450                //              [CSS generic font families](http://www.w3.org/TR/REC-CSS2/fonts.html#generic-font-families).
451                //
452                //              Note that the editor is often unable to properly handle font styling information defined outside
453                //              the context of the current editor instance, such as pre-populated HTML.
454
455                // useDefaultCommand: [protected] Boolean
456                //              Override _Plugin.useDefaultCommand...
457                //              processing is handled by this plugin, not by dijit/Editor.
458                useDefaultCommand: false,
459
460                _initButton: function(){
461                        // summary:
462                        //              Overrides _Plugin._initButton(), to initialize the FilteringSelect+label in toolbar,
463                        //              rather than a simple button.
464                        // tags:
465                        //              protected
466
467                        // Create the widget to go into the toolbar (the so-called "button")
468                        var clazz = {
469                                        fontName: _FontNameDropDown,
470                                        fontSize: _FontSizeDropDown,
471                                        formatBlock: _FormatBlockDropDown
472                                }[this.command],
473                                params = this.params;
474
475                        // For back-compat reasons support setting custom values via "custom" parameter
476                        // rather than "values" parameter.   Remove in 2.0.
477                        if(this.params.custom){
478                                params.values = this.params.custom;
479                        }
480
481                        var editor = this.editor;
482                        this.button = new clazz(lang.delegate({dir: editor.dir, lang: editor.lang}, params));
483
484                        // Reflect changes to the drop down in the editor
485                        this.own(this.button.select.on("change", lang.hitch(this, function(choice){
486                                // User invoked change, since all internal updates set priorityChange to false and will
487                                // not trigger an onChange event.
488
489                                if(this.editor.focused){
490                                        // put focus back in the iframe, unless focus has somehow been shifted out of the editor completely
491                                        this.editor.focus();
492                                }
493
494                                if(this.command == "fontName" && choice.indexOf(" ") != -1){
495                                        choice = "'" + choice + "'";
496                                }
497
498                                // Invoke, the editor already normalizes commands called through its
499                                // execCommand.
500                                if(this.button._execCommand){
501                                        this.button._execCommand(this.editor, this.command, choice);
502                                }else{
503                                        this.editor.execCommand(this.command, choice);
504                                }
505                        })));
506                },
507
508                updateState: function(){
509                        // summary:
510                        //              Overrides _Plugin.updateState().  This controls updating the menu
511                        //              options to the right values on state changes in the document (that trigger a
512                        //              test of the actions.)
513                        //              It set value of drop down in toolbar to reflect font/font size/format block
514                        //              of text at current caret position.
515                        // tags:
516                        //              protected
517                        var _e = this.editor;
518                        var _c = this.command;
519                        if(!_e || !_e.isLoaded || !_c.length){
520                                return;
521                        }
522
523                        if(this.button){
524                                var disabled = this.get("disabled");
525                                this.button.set("disabled", disabled);
526                                if(disabled){
527                                        return;
528                                }
529                                var value;
530                                try{
531                                        value = _e.queryCommandValue(_c) || "";
532                                }catch(e){
533                                        //Firefox may throw error above if the editor is just loaded, ignore it
534                                        value = "";
535                                }
536
537                                // strip off single quotes, if any
538                                var quoted = lang.isString(value) && value.match(/'([^']*)'/);
539                                if(quoted){
540                                        value = quoted[1];
541                                }
542
543                                if(_c === "formatBlock"){
544                                        if(!value || value == "p"){
545                                                // Some browsers (WebKit) doesn't actually get the tag info right.
546                                                // and IE returns paragraph when in a DIV!, so incorrect a lot,
547                                                // so we have double-check it.
548                                                value = null;
549                                                var elem;
550                                                // Try to find the current element where the caret is.
551                                                var sel = rangeapi.getSelection(this.editor.window);
552                                                if(sel && sel.rangeCount > 0){
553                                                        var range = sel.getRangeAt(0);
554                                                        if(range){
555                                                                elem = range.endContainer;
556                                                        }
557                                                }
558
559                                                // Okay, now see if we can find one of the formatting types we're in.
560                                                while(elem && elem !== _e.editNode && elem !== _e.document){
561                                                        var tg = elem.tagName ? elem.tagName.toLowerCase() : "";
562                                                        if(tg && array.indexOf(this.button.values, tg) > -1){
563                                                                value = tg;
564                                                                break;
565                                                        }
566                                                        elem = elem.parentNode;
567                                                }
568                                                if(!value){
569                                                        // Still no value, so lets select 'none'.
570                                                        value = "noFormat";
571                                                }
572                                        }else{
573                                                // Check that the block format is one allowed, if not,
574                                                // null it so that it gets set to empty.
575                                                if(array.indexOf(this.button.values, value) < 0){
576                                                        value = "noFormat";
577                                                }
578                                        }
579                                }
580                                if(value !== this.button.get("value")){
581                                        // Set the value, but denote it is not a priority change, so no
582                                        // onchange fires.
583                                        this.button.set('value', value, false);
584                                }
585                        }
586                }
587        });
588
589        // Register these plugins
590        array.forEach(["fontName", "fontSize", "formatBlock"], function(name){
591                _Plugin.registry[name] = function(args){
592                        return new FontChoice({
593                                command: name,
594                                plainText: args.plainText
595                        });
596                };
597        });
598
599        // Make all classes available through AMD, and return main class
600        FontChoice._FontDropDown = _FontDropDown;
601        FontChoice._FontNameDropDown = _FontNameDropDown;
602        FontChoice._FontSizeDropDown = _FontSizeDropDown;
603        FontChoice._FormatBlockDropDown = _FormatBlockDropDown;
604        return FontChoice;
605
606});
Note: See TracBrowser for help on using the repository browser.