source: Dev/trunk/src/client/dojox/editor/plugins/InsertAnchor.js

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

Added Dojo 1.9.3 release.

  • Property svn:executable set to *
File size: 13.4 KB
Line 
1define([
2        "dojo",
3        "dijit",
4        "dojox",
5        "dijit/_editor/_Plugin",
6        "dijit/_base/manager",  // TODO: change to dijit/registry, and change dijit.byId to registry.byId
7        "dijit/_editor/range",
8        "dijit/_Templated",
9        "dijit/TooltipDialog",
10        "dijit/form/ValidationTextBox",
11        "dijit/form/Select",
12        "dijit/form/Button",
13        "dijit/form/DropDownButton",
14        "dojo/_base/declare",
15        "dojo/i18n",
16        "dojo/string",
17        "dojo/NodeList-dom",
18        "dojox/editor/plugins/ToolbarLineBreak",
19        "dojo/i18n!dojox/editor/plugins/nls/InsertAnchor",
20        "dojo/i18n!dijit/nls/common"
21], function(dojo, dijit, dojox, _Plugin ) {
22
23var InsertAnchor = dojo.declare("dojox.editor.plugins.InsertAnchor", _Plugin, {
24        // summary:
25        //              This plugin provides the basis for an insert anchor dialog for the
26        //              dijit.Editor
27        //
28        // description:
29        //              The command provided by this plugin is:
30        //
31        //              - insertAnchor
32
33        // htmlTemplate: [protected] String
34        //              String used for templating the HTML to insert at the desired point.
35        htmlTemplate: "<a name=\"${anchorInput}\" class=\"dijitEditorPluginInsertAnchorStyle\">${textInput}</a>",
36
37        // iconClassPrefix: [const] String
38        //              The CSS class name for the button node icon.
39        iconClassPrefix: "dijitAdditionalEditorIcon",
40
41        // linkDialogTemplate: [private] String
42        //              Template for contents of TooltipDialog to pick URL
43        _template: [
44                "<table role='presentation'><tr><td>",
45                "<label for='${id}_anchorInput'>${anchor}</label>",
46                "</td><td>",
47                "<input dojoType='dijit.form.ValidationTextBox' required='true' " +
48                "id='${id}_anchorInput' name='anchorInput' intermediateChanges='true'>",
49                "</td></tr><tr><td>",
50                "<label for='${id}_textInput'>${text}</label>",
51                "</td><td>",
52                "<input dojoType='dijit.form.ValidationTextBox' required='true' id='${id}_textInput' " +
53                "name='textInput' intermediateChanges='true'>",
54                "</td></tr>",
55                "<tr><td colspan='2'>",
56                "<button dojoType='dijit.form.Button' type='submit' id='${id}_setButton'>${set}</button>",
57                "<button dojoType='dijit.form.Button' type='button' id='${id}_cancelButton'>${cancel}</button>",
58                "</td></tr></table>"
59        ].join(""),
60
61        _initButton: function(){
62                // summary:
63                //              Override _Plugin._initButton() to initialize DropDownButton and TooltipDialog.
64                var _this = this;
65                var messages = dojo.i18n.getLocalization("dojox.editor.plugins", "InsertAnchor", this.lang);
66
67                // Build the dropdown dialog we'll use for the button
68                var dropDown = (this.dropDown = new dijit.TooltipDialog({
69                        title: messages["title"],
70                        execute: dojo.hitch(this, "setValue"),
71                        onOpen: function(){
72                                _this._onOpenDialog();
73                                dijit.TooltipDialog.prototype.onOpen.apply(this, arguments);
74                        },
75                        onCancel: function(){
76                                setTimeout(dojo.hitch(_this, "_onCloseDialog"),0);
77                        }
78                }));
79
80                this.button = new dijit.form.DropDownButton({
81                        label: messages["insertAnchor"],
82                        showLabel: false,
83                        iconClass: this.iconClassPrefix + " " + this.iconClassPrefix + "InsertAnchor",
84                        tabIndex: "-1",
85                        dropDown: this.dropDown
86                });
87
88                messages.id = dijit.getUniqueId(this.editor.id);
89                this._uniqueId = messages.id;
90
91                this.dropDown.set('content', dropDown.title +
92                        "<div style='border-bottom: 1px black solid;padding-bottom:2pt;margin-bottom:4pt'></div>" +
93                        dojo.string.substitute(this._template, messages));
94
95                dropDown.startup();
96                this._anchorInput = dijit.byId(this._uniqueId + "_anchorInput");
97                this._textInput = dijit.byId(this._uniqueId + "_textInput");
98                this._setButton = dijit.byId(this._uniqueId + "_setButton");
99                this.connect(dijit.byId(this._uniqueId + "_cancelButton"), "onClick", function(){
100                        this.dropDown.onCancel();
101                });
102
103                if(this._anchorInput){
104                        this.connect(this._anchorInput, "onChange", "_checkInput");
105                }
106                if(this._textInput){
107                        this.connect(this._anchorInput, "onChange", "_checkInput");
108                }
109
110                //Register some filters to handle setting/removing the class tags on anchors.
111                this.editor.contentDomPreFilters.push(dojo.hitch(this, this._preDomFilter));
112                this.editor.contentDomPostFilters.push(dojo.hitch(this, this._postDomFilter));
113                this._setup();
114        },
115       
116        updateState: function(){
117                // summary:
118                //              Over-ride for button state control for disabled to work.
119                this.button.set("disabled", this.get("disabled"));
120        },
121
122        setEditor: function(editor){
123                // summary:
124                //              Over-ride for the setting of the editor.
125                // editor: Object
126                //              The editor to configure for this plugin to use.
127                this.editor = editor;
128                this._initButton();
129        },
130
131        _checkInput: function(){
132                // summary:
133                //              Function to check the input to the dialog is valid
134                //              and enable/disable set button
135                // tags:
136                //              private
137                var disable = true;
138                if(this._anchorInput.isValid()){
139                        disable = false;
140                }
141                this._setButton.set("disabled", disable);
142        },
143
144        _setup: function(){
145                // summary:
146                //              Over-ridable function that connects tag specific events.
147                this.editor.onLoadDeferred.addCallback(dojo.hitch(this, function(){
148                        this.connect(this.editor.editNode, "ondblclick", this._onDblClick);
149                        setTimeout(dojo.hitch(this, function() {
150                                this._applyStyles();
151                        }), 100);
152                }));
153        },
154
155        getAnchorStyle: function(){
156                // summary:
157                //              Over-ridable function for getting the style to apply to the anchor.
158                //              The default is a dashed border with an anchor symbol.
159                // tags:
160                //              public
161                var style = "@media screen {\n" +
162                                "\t.dijitEditorPluginInsertAnchorStyle {\n" +
163                                "\t\tbackground-image: url({MODURL}/images/anchor.gif);\n" +
164                                "\t\tbackground-repeat: no-repeat;\n"   +
165                                "\t\tbackground-position: top left;\n" +
166                                "\t\tborder-width: 1px;\n" +
167                                "\t\tborder-style: dashed;\n" +
168                                "\t\tborder-color: #D0D0D0;\n" +
169                                 "\t\tpadding-left: 20px;\n" +
170                        "\t}\n" +
171                "}\n";
172
173                //Finally associate in the image locations based off the module url.
174                var modurl = dojo.moduleUrl(dojox._scopeName, "editor/plugins/resources").toString();
175                if(!(modurl.match(/^https?:\/\//i)) &&
176                        !(modurl.match(/^file:\/\//i))){
177                        // We have to root it to the page location on webkit for some nutball reason.
178                        // Probably has to do with how iframe was loaded.
179                        var bUrl;
180                        if(modurl.charAt(0) === "/"){
181                                //Absolute path on the server, so lets handle...
182                                var proto = dojo.doc.location.protocol;
183                                var hostn = dojo.doc.location.host;
184                                bUrl = proto + "//" + hostn;
185                        }else{
186                                bUrl = this._calcBaseUrl(dojo.global.location.href);
187                        }
188                        if(bUrl[bUrl.length - 1] !== "/" && modurl.charAt(0) !== "/"){
189                                bUrl += "/";
190                        }
191                        modurl = bUrl + modurl;
192                }
193                return style.replace(/\{MODURL\}/gi, modurl);
194        },
195
196        _applyStyles: function(){
197                // summary:
198                //              Function to apply a style to inserted anchor tags so that
199                //              they are obviously anchors.
200                if(!this._styled){
201                        try{
202                                //Attempt to inject our specialized style rules for doing this.
203                                this._styled = true;
204                                var doc = this.editor.document;
205                                var style = this.getAnchorStyle();
206                                if(!dojo.isIE){
207                                        var sNode = doc.createElement("style");
208                                        sNode.appendChild(doc.createTextNode(style));
209                                        doc.getElementsByTagName("head")[0].appendChild(sNode);
210                                }else{
211                                        var ss = doc.createStyleSheet("");
212                                        ss.cssText = style;
213                                }
214                         }catch(e){ /* Squelch */ }
215                 }
216        },
217
218        _calcBaseUrl: function(fullUrl) {
219                // summary:
220                //              Internal function used to figure out the full root url (no relatives)
221                //              for loading images in the styles in the iframe.
222                // fullUrl: String
223                //              The full url to tear down to the base.
224                // tags:
225                //              private
226                var baseUrl = null;
227                if (fullUrl !== null) {
228                        // Check to see if we need to strip off any query parameters from the Url.
229                        var index = fullUrl.indexOf("?");
230                        if (index != -1) {
231                                fullUrl = fullUrl.substring(0,index);
232                        }
233
234                        // Now we need to trim if necessary.  If it ends in /, then we don't
235                        // have a filename to trim off so we can return.
236                        index = fullUrl.lastIndexOf("/");
237                        if (index > 0 && index < fullUrl.length) {
238                                baseUrl = fullUrl.substring(0,index);
239                        }else{
240                                baseUrl = fullUrl;
241                        }
242                }
243                return baseUrl; //String
244        },
245
246        _checkValues: function(args){
247                // summary:
248                //              Function to check the values in args and 'fix' them up as needed.
249                // args: Object
250                //              Content being set.
251                // tags:
252                //              protected
253                if(args){
254                        if(args.anchorInput){
255                                args.anchorInput = args.anchorInput.replace(/"/g, "&quot;");
256                        }
257                        if(!args.textInput){
258                                // WebKit doesn't work with double-click select unless there's
259                                // a space in the anchor text, so put a in the case of
260                                // empty desc.
261                                args.textInput = "&nbsp;";
262                        }
263                }
264                return args;
265        },
266
267        setValue: function(args){
268                // summary:
269                //              Callback from the dialog when user presses "set" button.
270                // tags:
271                //              private
272                this._onCloseDialog();
273                if(!this.editor.window.getSelection){
274                        // IE check without using user agent string.
275                        var sel = dijit.range.getSelection(this.editor.window);
276                        var range = sel.getRangeAt(0);
277                        var a = range.endContainer;
278                        if(a.nodeType === 3){
279                                // Text node, may be the link contents, so check parent.
280                                // This plugin doesn't really support nested HTML elements
281                                // in the link, it assumes all link content is text.
282                                a = a.parentNode;
283                        }
284                        if(a && (a.nodeName && a.nodeName.toLowerCase() !== "a")){
285                                // Stll nothing, one last thing to try on IE, as it might be 'img'
286                                // and thus considered a control.
287                                a = this.editor._sCall("getSelectedElement", ["a"]);
288                        }
289                        if(a && (a.nodeName && a.nodeName.toLowerCase() === "a")){
290                                // Okay, we do have a match.  IE, for some reason, sometimes pastes before
291                                // instead of removing the targetted paste-over element, so we unlink the
292                                // old one first.  If we do not the <a> tag remains, but it has no content,
293                                // so isn't readily visible (but is wrong for the action).
294                                if(this.editor.queryCommandEnabled("unlink")){
295                                        // Select all the link childent, then unlink.  The following insert will
296                                        // then replace the selected text.
297                                        this.editor._sCall("selectElementChildren", [a]);
298                                        this.editor.execCommand("unlink");
299                                }
300                        }
301                }
302                // make sure values are properly escaped, etc.
303                args = this._checkValues(args);
304                this.editor.execCommand('inserthtml',
305                        dojo.string.substitute(this.htmlTemplate, args));
306        },
307
308        _onCloseDialog: function(){
309                // summary:
310                //              Handler for close event on the dialog
311                this.editor.focus();
312        },
313
314        _getCurrentValues: function(a){
315                // summary:
316                //              Over-ride for getting the values to set in the dropdown.
317                // a:
318                //              The anchor/link to process for data for the dropdown.
319                // tags:
320                //              protected
321                var anchor, text;
322                if(a && a.tagName.toLowerCase() === "a" && dojo.attr(a, "name")){
323                        anchor = dojo.attr(a, "name");
324                        text = a.textContent || a.innerText;
325                        this.editor._sCall("selectElement", [a, true]);
326                }else{
327                        text = this.editor._sCall("getSelectedText");
328                }
329                return {anchorInput: anchor || '', textInput: text || ''}; //Object;
330        },
331
332        _onOpenDialog: function(){
333                // summary:
334                //              Handler for when the dialog is opened.
335                //              If the caret is currently in a URL then populate the URL's info into the dialog.
336                var a;
337                if(!this.editor.window.getSelection){
338                        // IE is difficult to select the element in, using the range unified
339                        // API seems to work reasonably well.
340                        var sel = dijit.range.getSelection(this.editor.window);
341                        var range = sel.getRangeAt(0);
342                        a = range.endContainer;
343                        if(a.nodeType === 3){
344                                // Text node, may be the link contents, so check parent.
345                                // This plugin doesn't really support nested HTML elements
346                                // in the link, it assumes all link content is text.
347                                a = a.parentNode;
348                        }
349                        if(a && (a.nodeName && a.nodeName.toLowerCase() !== "a")){
350                                // Stll nothing, one last thing to try on IE, as it might be 'img'
351                                // and thus considered a control.
352                                a = this.editor._sCall("getSelectedElement", ["a"]);
353                        }
354                }else{
355                        a = this.editor._sCall("getAncestorElement", ["a"]);
356                }
357                this.dropDown.reset();
358                this._setButton.set("disabled", true);
359                this.dropDown.set("value", this._getCurrentValues(a));
360        },
361
362        _onDblClick: function(e){
363                // summary:
364                //              Function to define a behavior on double clicks on the element
365                //              type this dialog edits to select it and pop up the editor
366                //              dialog.
367                // e: Object
368                //              The double-click event.
369                // tags:
370                //              protected.
371                if(e && e.target){
372                        var t = e.target;
373                        var tg = t.tagName? t.tagName.toLowerCase() : "";
374                        if(tg === "a" && dojo.attr(t, "name")){
375                                this.editor.onDisplayChanged();
376                                this.editor._sCall("selectElement", [t]);
377                                setTimeout(dojo.hitch(this, function(){
378                                        // Focus shift outside the event handler.
379                                        // IE doesn't like focus changes in event handles.
380                                        this.button.set("disabled", false);
381                                        this.button.openDropDown();
382                                        if(this.button.dropDown.focus){
383                                                this.button.dropDown.focus();
384                                        }
385                                }), 10);
386                        }
387                }
388        },
389
390        _preDomFilter: function(node){
391                // summary:
392                //              A filter to identify the 'a' tags and if they're anchors,
393                //              apply the right style to them.
394                // node:
395                //              The node to search from.
396                // tags:
397                //              private
398
399                dojo.query("a[name]:not([href])", this.editor.editNode).addClass("dijitEditorPluginInsertAnchorStyle");
400        },
401
402        _postDomFilter: function(node){
403                // summary:
404                //              A filter to identify the 'a' tags and if they're anchors,
405                //              remove the class style that shows up in the editor from
406                //              them.
407                // node:
408                //              The node to search from.
409                // tags:
410                //              private
411                if(node){       // avoid error when Editor.get("value") called before editor's iframe initialized
412                        dojo.query("a[name]:not([href])", node).removeClass("dijitEditorPluginInsertAnchorStyle");
413                }
414                return node;
415        }
416});
417
418
419// Register this plugin.
420dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
421        if(o.plugin){ return; }
422        var name = o.args.name;
423        if(name) { name = name.toLowerCase(); }
424        if(name === "insertanchor"){
425                o.plugin = new InsertAnchor();
426        }
427});
428
429return InsertAnchor;
430
431});
Note: See TracBrowser for help on using the repository browser.