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

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

Added Dojo 1.9.3 release.

File size: 37.0 KB
Line 
1define([
2        "dojo/_base/declare",
3        "dojo/_base/array",
4        "dojo/_base/Color",
5        "dojo/aspect",
6        "dojo/dom-attr",
7        "dojo/dom-style",
8        "dijit/_editor/_Plugin",
9        "dijit/_WidgetBase",
10        "dijit/_TemplatedMixin",
11        "dijit/_WidgetsInTemplateMixin",
12        "dijit/Dialog",
13        "dijit/Menu",
14        "dijit/MenuItem",
15        "dijit/MenuSeparator",
16        "dijit/ColorPalette",
17        "dojox/widget/ColorPicker",
18        "dojo/text!./resources/insertTable.html",
19        "dojo/text!./resources/modifyTable.html",
20        "dojo/i18n!./nls/TableDialog",
21        "dijit/_base/popup",
22        "dijit/popup",
23        "dojo/_base/connect",
24        "dijit/TooltipDialog",
25        "dijit/form/Button",
26        "dijit/form/DropDownButton",
27        "dijit/form/TextBox",
28        "dijit/form/FilteringSelect"
29], function(
30        declare,
31        array,
32        Color,
33        aspect,
34        domAttr,
35        domStyle,
36        _Plugin,
37        _WidgetBase,
38        _TemplatedMixin,
39        _WidgetsInTemplateMixin,
40        Dialog,
41        Menu,
42        MenuItem,
43        MenuSeparator,
44        ColorPalette,
45        ColorPicker,
46        insertTableTemplate,
47        modifyTableTemplate,
48        tableDialogStrings
49) {
50
51dojo.experimental("dojox.editor.plugins.TablePlugins");
52
53// TODO:
54//              Currently not supporting merging or splitting cells
55//
56// FIXME:       Undo is very buggy, and therefore unimplemented in all browsers
57//                      except IE - which itself has only been lightly tested.
58//
59// FIXME:       Selecting multiple table cells in Firefox looks to be impossible.
60//                      This affect the 'colorTableCell' plugin. Cells can still be
61//                      colored individually or in rows.
62
63var TableHandler = declare(_Plugin, {
64        // summary:
65        //              A global object that handles common tasks for all the plugins. Since
66        //              there are several plugins that are all calling common methods, it's preferable
67        //              that they call a centralized location that either has a set variable or a
68        //              timeout to only repeat code-heavy calls when necessary.
69        //
70        tablesConnected:false,
71        currentlyAvailable: false,
72        alwaysAvailable:false,
73        availableCurrentlySet:false,
74        initialized:false,
75        tableData: null,
76        shiftKeyDown:false,
77        editorDomNode: null,
78        undoEnabled: true, //Using custom undo for all browsers.
79        refCount: 0,
80       
81        doMixins: function(){
82               
83                dojo.mixin(this.editor,{
84                        getAncestorElement: function(tagName){
85                                return this._sCall("getAncestorElement", [tagName]);
86                        },
87                        hasAncestorElement: function(tagName){
88                                return this._sCall("hasAncestorElement", [tagName]);
89                        },
90                        selectElement: function(elem){
91                                this._sCall("selectElement", [elem]);
92                        },
93                        byId: function(id){
94                                return dojo.byId(id, this.document);
95                        },
96                        query: function(arg, scope, returnFirstOnly){
97                                // this shortcut is dubious - not sure scoping is necessary
98                                var ar = dojo.query(arg, scope || this.document);
99                                return (returnFirstOnly) ? ar[0] : ar;
100                        }
101                });
102
103        },
104        initialize: function(editor){
105                // summary:
106                //              Initialize the global handler upon a plugin's first instance of setEditor
107                //
108               
109                // All plugins will attempt initialization. We only need to do so once.
110                // But keep track so that it is cleaned up when all usage of it for an editor has
111                // been removed.
112                this.refCount++;
113               
114                // Turn on custom undo for all.
115                editor.customUndo = true;
116
117                if(this.initialized){ return; }
118               
119                this.initialized = true;
120                this.editor = editor;
121
122                this.editor._tablePluginHandler = this;
123               
124                //Editor loads async, can't assume doc is ready yet.  So, use the deferred of the
125                //editor to init at the right time.
126                editor.onLoadDeferred.addCallback(dojo.hitch(this, function(){
127                        this.editorDomNode = this.editor.editNode || this.editor.iframe.document.body.firstChild;
128
129                        // RichText should have a mouseup connection to recognize drag-selections
130                        // Example would be selecting multiple table cells
131                        this._myListeners = [
132                                dojo.connect(this.editorDomNode , "mouseup", this.editor, "onClick"),
133                                dojo.connect(this.editor, "onDisplayChanged", this, "checkAvailable"),
134                                dojo.connect(this.editor, "onBlur", this, "checkAvailable"),
135                                dojo.connect(this.editor, "_saveSelection", this, function(){
136                                        // because on IE, the selection is lost when the iframe goes out of focus
137                                        this._savedTableInfo = this.getTableInfo();
138                                }),
139                                dojo.connect(this.editor, "_restoreSelection", this, function(){
140                                        delete this._savedTableInfo;
141                                })
142                        ];
143                        this.doMixins();
144                        this.connectDraggable();
145                }));
146        },
147       
148        getTableInfo: function(forceNewData){
149                // summary:
150                //              Gets the table in focus
151                //              Collects info on the table - see return params
152                //
153
154                if(this._savedTableInfo){
155                        // Avoid trying to query the table info when the iframe is blurred; doesn't work on IE.
156                        return this._savedTableInfo;
157                }
158
159                if(forceNewData){ this._tempStoreTableData(false); }
160                if(this.tableData){
161                        // tableData is set for a short amount of time, so that all
162                        // plugins get the same return without doing the method over
163                        //console.log("returning current tableData:", this.tableData);
164                        return this.tableData;
165                }
166                var tr, trs, td, tds, tbl, cols, tdIndex, trIndex, o;
167
168                td = this.editor.getAncestorElement("td");
169                if(td){ tr = td.parentNode; }
170               
171                tbl = this.editor.getAncestorElement("table");
172                //console.log("td:", td);console.log("tr:", tr);console.log("tbl:", tbl)
173
174                if(tbl){
175                        tds = dojo.query("td", tbl);
176                        tds.forEach(function(d, i){
177                                if(td==d){tdIndex = i;}
178                        });
179                        trs = dojo.query("tr", tbl);
180                        trs.forEach(function(r, i){
181                                if(tr==r){trIndex = i;}
182                        });
183                        cols = tds.length/trs.length;
184
185                        o = {
186                                tbl:tbl,                // focused table
187                                td:td,                  // focused TD
188                                tr:tr,                  // focused TR
189                                trs:trs,                // rows
190                                tds:tds,                // cells
191                                rows:trs.length,// row amount
192                                cols:cols,              // column amount
193                                tdIndex:tdIndex,// index of focused cell
194                                trIndex:trIndex,        // index of focused row
195                                colIndex:tdIndex%cols
196                        };
197                }else{
198                        // Means there's no table in focus.   Use {} not null so that this._savedTableInfo is non-null
199                        o = {};
200                }
201                //console.log("NEW tableData:",o);
202                this.tableData = o;
203                this._tempStoreTableData(500);
204                return this.tableData;
205        },
206       
207        connectDraggable: function(){
208                // summary:
209                //              Detects drag-n-drop in the editor (could probably be moved to there)
210                //              Currently only checks if item dragged was a TABLE, and removes its align attr
211                //              DOES NOT WORK IN FF - it could - but FF's drag detection is a monster
212                //
213                if(!dojo.isIE){
214                        //console.warn("Drag and Drop is currently only detectable in IE.");
215                        return;
216                }
217               
218                // IE ONLY
219                this.editorDomNode.ondragstart = dojo.hitch(this, "onDragStart");
220                this.editorDomNode.ondragend = dojo.hitch(this, "onDragEnd");
221               
222                //NOTES:
223                // FF _ Able to detect the drag-over object (the editor.domNode)
224                //      Not able to detect an item's ondrag() event
225                //      Don't know why - I actually got it working when there was an error
226                //      Something to do with different documents or windows I'm sure
227                //
228                //console.log("connectDraggable", tbl);
229                /*tbl.ondragstart=dojo.hitch(this, "onDragStart");
230               
231                tbl.addEventListener("dragstart", dojo.hitch(this, "onDragStart"), false);
232                tbl.addEventListener("drag", dojo.hitch(this, "onDragStart2"), false);
233                tbl.addEventListener("dragend", dojo.hitch(this, "onDragStart3"), false);
234       
235                this.editor._sCall("selectElement", [tbl]);
236               
237                tbl.ondragstart = function(){
238                        //console.log("ondragstart");
239                };
240                tbl.ondrag = function(){
241                        alert("drag")
242                        //console.log("ondrag");
243                */
244        },
245        onDragStart: function(){
246                var e = window.event;
247                if(!e.srcElement.id){
248                        e.srcElement.id = "tbl_"+(new Date().getTime());
249                }
250                //console.log("onDragStart", e.srcElement.id);
251        },
252        onDragEnd: function(){
253                // summary:
254                //              Detects that an object has been dragged into place
255                //              Currently, this code is only used for when a table is dragged
256                //              and clears the "align" attribute, so that the table will look
257                //              to be more in the place that the user expected.
258                //              TODO: This code can be used for other things, most
259                //              notably UNDO, which currently is not quite usable.
260                //              This code could also find itself in the Editor code when it is
261                //              complete.
262               
263                //console.log("onDragEnd");
264                var e = window.event;
265                var node = e.srcElement;
266                var id = node.id;
267                var doc = this.editor.document;
268                //console.log("NODE:", node.tagName, node.id,  dojo.attr(node, "align"));
269               
270                // clearing a table's align attr
271                // TODO: when ondrag becomes more robust, this code block
272                //      should move to its own method
273                if(node.tagName.toLowerCase()=="table"){
274                        setTimeout(function(){
275                                var node = dojo.byId(id, doc);
276                                dojo.removeAttr(node, "align");
277                                //console.log("set", node.tagName, dojo.attr(node, "align"))
278                        }, 100);
279                }
280        },
281        checkAvailable: function(){
282                // summary:
283                //              For table plugs
284                //              Checking if a table or part of a table has focus so that
285                //              Plugs can change their status
286                //
287                if(this.availableCurrentlySet){
288                        // availableCurrentlySet is set for a short amount of time, so that all
289                        // plugins get the same return without doing the method over
290                        //console.log("availableCurrentlySet:", this.availableCurrentlySet, "currentlyAvailable:", this.currentlyAvailable)
291                        return this.currentlyAvailable;
292                }
293                //console.log("G - checkAvailable...");
294               
295                if(!this.editor) {
296                        //console.log("editor not ready")
297                        return false;
298                }
299                if(this.alwaysAvailable) {
300                        //console.log(" return always available")
301                        return true;
302                }
303
304                // Only return available if the editor is focused.
305                this.currentlyAvailable = this.editor.focused && (this._savedTableInfo ? this._savedTableInfo.tbl :
306                        this.editor.hasAncestorElement("table"));
307
308                if(this.currentlyAvailable){
309                        this.connectTableKeys();
310                }else{
311                        this.disconnectTableKeys();
312                }
313               
314                this._tempAvailability(500);
315
316                dojo.publish(this.editor.id + "_tablePlugins", [ this.currentlyAvailable ]);
317                return this.currentlyAvailable;
318        },
319       
320        _prepareTable: function(tbl){
321                //      For IE's sake, we are adding IDs to the TDs if none is there
322                //      We go ahead and use it for other code for convenience
323                //
324                var tds = this.editor.query("td", tbl);
325                console.log("prep:", tds, tbl);
326                if(!tds[0].id){
327                        tds.forEach(function(td, i){
328                                if(!td.id){
329                                        td.id = "tdid"+i+this.getTimeStamp();
330                                }
331                        }, this);
332                }
333                return tds;
334        },
335       
336        getTimeStamp: function(){
337                return new Date().getTime(); // Fixed the bug that this method always returns the same timestamp
338//              return Math.floor(new Date().getTime() * 0.00000001);
339        },
340       
341        _tempStoreTableData: function(type){
342                // caching or clearing table data, depending on the arg
343                //
344                if(type===true){
345                        //store indefinitely
346                }else if(type===false){
347                        // clear object
348                        this.tableData = null;
349                }else if(type===undefined){
350                        console.warn("_tempStoreTableData must be passed an argument");
351                }else{
352                        // type is a number/ms
353                        setTimeout(dojo.hitch(this, function(){
354                                this.tableData = null;
355                        }), type);
356                }
357        },
358       
359        _tempAvailability: function(type){
360                        // caching or clearing availability, depending on the arg
361                if(type===true){
362                        //store indefinitely
363                        this.availableCurrentlySet = true;
364                }else if(type===false){
365                        // clear object
366                        this.availableCurrentlySet = false;
367                }else if(type===undefined){
368                        console.warn("_tempAvailability must be passed an argument");
369                }else{
370                        // type is a number/ms
371                        this.availableCurrentlySet = true;
372                        setTimeout(dojo.hitch(this, function(){
373                                this.availableCurrentlySet = false;
374                        }), type);
375                }
376               
377        },
378       
379        connectTableKeys: function(){
380                // summary:
381                //              When a table is in focus, start detecting keys
382                //              Mainly checking for the TAB key so user can tab
383                //              through a table (blocking the browser's desire to
384                //              tab away from teh editor completely)
385                if(this.tablesConnected){ return; }
386                this.tablesConnected = true;
387                var node = (this.editor.iframe) ? this.editor.document : this.editor.editNode;
388                this.cnKeyDn = dojo.connect(node, "onkeydown", this, "onKeyDown");
389                this.cnKeyUp = dojo.connect(node, "onkeyup", this, "onKeyUp");
390                this._myListeners.push(dojo.connect(node, "onkeypress", this, "onKeyUp"));
391        },
392       
393        disconnectTableKeys: function(){
394                //console.log("disconnect")
395                dojo.disconnect(this.cnKeyDn);
396                dojo.disconnect(this.cnKeyUp);
397                this.tablesConnected = false;
398        },
399       
400        onKeyDown: function(evt){
401                var key = evt.keyCode;
402                //console.log(" -> DOWN:", key);
403                if(key == 16){ this.shiftKeyDown = true;}
404                if(key == 9) {
405                        var o = this.getTableInfo();
406                        //console.log("TAB ", o.tdIndex, o);
407                        // modifying the o.tdIndex in the tableData directly, because we may save it
408                        // FIXME: tabTo is a global
409                        o.tdIndex = (this.shiftKeyDown) ? o.tdIndex-1 : tabTo = o.tdIndex+1;
410                        if(o.tdIndex>=0 && o.tdIndex<o.tds.length){
411                               
412                                this.editor.selectElement(o.tds[o.tdIndex]);
413                               
414                                // we know we are still within a table, so block the need
415                                //      to run the method
416                                this.currentlyAvailable = true;
417                                this._tempAvailability(true);
418                                //
419                                this._tempStoreTableData(true);
420                                this.stopEvent = true;
421                        }else{
422                                //tabbed out of table
423                                this.stopEvent = false;
424                                this.onDisplayChanged();
425                        }
426                        if(this.stopEvent) {
427                                dojo.stopEvent(evt);
428                        }
429                }
430        },
431       
432        onKeyUp: function(evt){
433                var key = evt.keyCode;
434                //console.log(" -> UP:", key)
435                if(key == 16){ this.shiftKeyDown = false;}
436                if(key == 37 || key == 38 || key == 39 || key == 40 ){
437                        // user can arrow or tab out of table - need to recheck
438                        this.onDisplayChanged();
439                }
440                if(key == 9 && this.stopEvent){ dojo.stopEvent(evt);}
441        },
442       
443        onDisplayChanged: function(){
444                //console.log("onDisplayChanged")
445                this.currentlyAvailable = false;
446                this._tempStoreTableData(false);
447                this._tempAvailability(false);
448                this.checkAvailable();
449        },
450
451        uninitialize: function(editor){
452                // summary:
453                //              Function to handle cleaning up of connects
454                //              and such.  It only finally destroys everything once
455                //              all 'references' to it have gone.  As in all plugins
456                //              that called init on it destroyed their refs in their
457                //              cleanup calls.
458                // editor:
459                //              The editor to detach from.
460                if(this.editor == editor){
461                        this.refCount--;
462                        if(!this.refCount && this.initialized){
463                                if(this.tablesConnected){
464                                        this.disconnectTableKeys();
465                                }
466                                this.initialized = false;
467                                dojo.forEach(this._myListeners, function(l){
468                                        dojo.disconnect(l);
469                                });
470                                delete this._myListeners;
471                                delete this.editor._tablePluginHandler;
472                                delete this.editor;
473                        }
474                        this.inherited(arguments);
475                }
476        }
477});
478
479var TablePlugins = declare("dojox.editor.plugins.TablePlugins", _Plugin, {
480                // summary:
481                //              A collection of Plugins for inserting and modifying tables in the Editor
482                //              See end of this document for all available plugs
483                //              and dojox/editorPlugins/tests/editorTablePlugs.html for an example
484                //
485                //              NOT IMPLEMENTED: Not handling cell merge, span or split
486                //
487               
488                iconClassPrefix: "editorIcon",
489                useDefaultCommand: false,
490                buttonClass: dijit.form.Button,
491                commandName:"",
492                label:"",
493                alwaysAvailable:false,
494                undoEnabled:true,
495               
496                onDisplayChanged: function(withinTable){
497                        // summary:
498                        //              subscribed to from the global object's publish method
499                       
500                        //console.log("onDisplayChanged", this.name);
501                        if(!this.alwaysAvailable){
502                                this.available = withinTable;
503                                this.button.set('disabled', !this.available);
504                        }
505                },
506               
507                setEditor: function(editor){
508                        this.editor = editor;
509                        this.editor.customUndo = true;
510                        this.inherited(arguments);
511                        this._availableTopic = dojo.subscribe(this.editor.id + "_tablePlugins", this, "onDisplayChanged");
512                        this.onEditorLoaded();
513                },
514                onEditorLoaded: function(){
515                        if(!this.editor._tablePluginHandler){
516                                // Create it and init it off the editor.  This
517                                // will create the _tablePluginHandler reference on
518                                // the dijit.Editor instance.  This avoids a global.
519                                var tablePluginHandler = new TableHandler();
520                                tablePluginHandler.initialize(this.editor);
521                        }else{
522                                this.editor._tablePluginHandler.initialize(this.editor);
523                        }
524                },
525               
526                selectTable: function(){
527                        // selects table that is in focus
528                        var o = this.getTableInfo();
529                        if(o && o.tbl){
530                                this.editor._sCall("selectElement", [o.tbl]);
531                        }
532                },
533               
534                _initButton: function(){
535                        this.command = this.name;
536                       
537                        this.label = this.editor.commands[this.command] = this._makeTitle(this.command);
538                        this.inherited(arguments);
539                        delete this.command;
540
541                        this.connect(this.button, "onClick", "modTable");
542
543                        this.onDisplayChanged(false);
544                },
545               
546                modTable: function(cmd, args){
547                        // summary:
548                        //              Where each plugin performs its action.
549                        //              Note: not using execCommand. In spite of their presence in the
550                        //              Editor as query-able plugins, I was not able to find any evidence
551                        //              that they are supported (especially in NOT IE). If they are
552                        //              supported in other browsers, it may help with the undo problem.
553
554                        if(dojo.isIE){
555                                // IE can lose selections on focus changes, so focus back
556                                // in order to restore it.
557                                this.editor.focus();
558                        }
559
560                        this.begEdit();
561                        var o = this.getTableInfo();
562                        var sw = (dojo.isString(cmd))?cmd : this.name;
563                        var r, c, i;
564                        var adjustColWidth = false;
565                        //console.log("modTable:", sw)
566
567                        switch(sw){
568                                case "insertTableRowBefore":
569                                        r = o.tbl.insertRow(o.trIndex);
570                                        for(i=0;i<o.cols;i++){
571                                                c = r.insertCell(-1);
572                                                c.innerHTML = "&nbsp;";
573                                        }
574                                        break;
575                                case "insertTableRowAfter":
576                                        r = o.tbl.insertRow(o.trIndex+1);
577                                        for(i=0;i<o.cols;i++){
578                                                c = r.insertCell(-1);
579                                                c.innerHTML = "&nbsp;";
580                                        }
581                                        break;
582                                case "insertTableColumnBefore":
583                                        o.trs.forEach(function(r){
584                                                c = r.insertCell(o.colIndex);
585                                                c.innerHTML = "&nbsp;";
586                                        });
587                                        adjustColWidth = true;
588                                        break;
589                                case "insertTableColumnAfter":
590                                        o.trs.forEach(function(r){
591                                                c = r.insertCell(o.colIndex+1);
592                                                c.innerHTML = "&nbsp;";
593                                        });
594                                        adjustColWidth = true;
595                                        break;
596                                case "deleteTableRow":
597                                        o.tbl.deleteRow(o.trIndex);
598                                        console.log("TableInfo:", this.getTableInfo());
599                                        break;
600                                case "deleteTableColumn":
601                                        o.trs.forEach(function(tr){
602                                                tr.deleteCell(o.colIndex);
603                                        });
604                                        adjustColWidth = true;
605                                        break;
606
607                                case "modifyTable":
608                                        break;
609                                case "insertTable":
610                                        break;
611                               
612                        }
613                        if(adjustColWidth){
614                                this.makeColumnsEven();
615                        }
616                        this.endEdit();
617                },
618               
619                begEdit: function(){
620                        if(this.editor._tablePluginHandler.undoEnabled){
621                                //console.log("UNDO:", this.editor.customUndo);
622                                if(this.editor.customUndo){
623                                        this.editor.beginEditing();
624                                }else{
625                                        this.valBeforeUndo = this.editor.getValue();
626                                        //console.log("VAL:", this.valBeforeUndo);
627                                       
628                                }
629                        }
630                },
631                endEdit: function(){
632                        if(this.editor._tablePluginHandler.undoEnabled){
633                                if(this.editor.customUndo){
634                                        this.editor.endEditing();
635                                }else{
636                                        // This code ALMOST works for undo -
637                                        //      It seems to only work for one step
638                                        //      back in history however
639                                        var afterUndo = this.editor.getValue();
640                                        //this.editor.execCommand("inserthtml", "<p>mike</p>");
641                                        this.editor.setValue(this.valBeforeUndo);
642                                        this.editor.replaceValue(afterUndo);
643                                }
644                               
645                                this.editor.onDisplayChanged();
646                        }
647                },
648               
649                makeColumnsEven: function(){
650                        // summary:
651                        //              After changing column amount, change widths to
652                        //              keep columns even
653                       
654                        // the timeout helps prevent an occasional snafu
655                        setTimeout(dojo.hitch(this, function(){
656                                var o = this.getTableInfo(true);
657                                var w = Math.floor(100/o.cols);
658                                o.tds.forEach(function(d){
659                                        dojo.attr(d, "width", w+"%");
660                                });
661                        }), 10);
662                },
663               
664                getTableInfo: function(forceNewData){
665                        // summary:
666                        //              Gets the table in focus
667                        //              Collects info on the table - see return params
668                        //
669                        return this.editor._tablePluginHandler.getTableInfo(forceNewData);
670                },
671                _makeTitle: function(str){
672                        // Uses the commandName to get the localized Title
673                        this._strings = dojo.i18n.getLocalization("dojox.editor.plugins", "TableDialog");
674                        var title = this._strings[str+"Title"] || this._strings[str+"Label"] || str;
675                        return title;
676                },
677               
678               
679               
680                getSelectedCells: function(){
681                        // summary:
682                        //              Gets the selected cells from the passed table.
683                        // returns:
684                        //              array of TDs or empty array
685                        var cells = [];
686                        var tbl = this.getTableInfo().tbl;
687                        this.editor._tablePluginHandler._prepareTable(tbl);
688                        var e = this.editor;
689
690                        // Lets do this the way IE originally was (Looking up ids).  Walking the selection
691                        // is inconsistent in the browsers (and painful), so going by ids is simpler.
692                        var text = e._sCall("getSelectedHtml", [null]);
693                        var str = text.match(/id="*\w*"*/g);
694                        dojo.forEach(str, function(a){
695                                var id = a.substring(3, a.length);
696                                if(id.charAt(0) == "\"" && id.charAt(id.length - 1) == "\""){
697                                        id = id.substring(1, id.length - 1);
698                                }
699                                var node = e.byId(id);
700                                if(node && node.tagName.toLowerCase() == "td"){
701                                        cells.push(node);
702                                }
703                        }, this);
704
705                        if(!cells.length){
706                                //May just be in a cell (cursor point, or selection in a cell), so look upwards.
707                                //for a cell container.
708                                var sel = dijit.range.getSelection(e.window);
709                                if(sel.rangeCount){
710                                        var r = sel.getRangeAt(0);
711                                        var node = r.startContainer;
712                                        while(node && node != e.editNode && node != e.document){
713                                                if(node.nodeType === 1){
714                                                        var tg = node.tagName ? node.tagName.toLowerCase() : "";
715                                                        if(tg === "td"){
716                                                                return [node];
717                                                        }
718                                                }
719                                                node = node.parentNode;
720                                        }
721                                }
722                        }
723                        return cells;
724                },
725               
726                updateState: function(){
727                        // summary:
728                        //              Over-ride for button state control for disabled to work.
729                        if(this.button){
730                                if((this.available || this.alwaysAvailable) && !this.get("disabled")){
731                                        this.button.set("disabled",false);
732                                }else{
733                                        this.button.set("disabled",true);
734                                }
735                        }
736                },
737
738                destroy: function(){
739                        // summary:
740                        //              Over-ridden destroy to do some cleanup.
741                        this.inherited(arguments);
742                        dojo.unsubscribe(this._availableTopic);
743
744                        // Disconnect the editor from the handler
745                        // to clean up refs.  Moved to using a per-editor
746                        // 'handler' to avoid collisions on the old global.
747                        this.editor._tablePluginHandler.uninitialize(this.editor);
748                }
749               
750        }
751);
752
753var TableContextMenu = declare(TablePlugins, {
754                constructor: function(){
755                        // summary:
756                        //              Initialize certain plugins
757                        //
758                        this.connect(this, "setEditor", function(editor){
759                                editor.onLoadDeferred.addCallback(dojo.hitch(this, function() {
760                                        this._createContextMenu();
761                                }));
762                                this.button.domNode.style.display = "none";
763                        });
764                },
765
766                destroy: function(){
767                        // summary:
768                        //      Over-ride to do menu cleanup.
769                        if(this.menu){
770                                this.menu.destroyRecursive();
771                                delete this.menu;
772                        }
773                        this.inherited(arguments);
774                },
775       
776               
777                _initButton: function(){
778                        this.inherited(arguments);
779                        if(this.name==="tableContextMenu"){ this.button.domNode.display = "none";}
780                },
781               
782                _createContextMenu: function(){
783                        // summary:
784                        //              Building context menu for right-click shortcuts within a table
785               
786                        var pMenu = new Menu({targetNodeIds:[this.editor.iframe]});
787                        var messages = tableDialogStrings;
788                        pMenu.addChild(new MenuItem({label: messages.selectTableLabel, onClick: dojo.hitch(this, "selectTable")}));
789                        pMenu.addChild(new MenuSeparator());
790                       
791                        pMenu.addChild(new MenuItem({label: messages.insertTableRowBeforeLabel, onClick: dojo.hitch(this, "modTable", "insertTableRowBefore" )}));
792                        pMenu.addChild(new MenuItem({label: messages.insertTableRowAfterLabel, onClick: dojo.hitch(this, "modTable", "insertTableRowAfter" )}));
793                        pMenu.addChild(new MenuItem({label: messages.insertTableColumnBeforeLabel, onClick: dojo.hitch(this, "modTable", "insertTableColumnBefore" )}));
794                        pMenu.addChild(new MenuItem({label: messages.insertTableColumnAfterLabel, onClick: dojo.hitch(this, "modTable", "insertTableColumnAfter" )}));
795                        pMenu.addChild(new MenuSeparator());
796                        pMenu.addChild(new MenuItem({label: messages.deleteTableRowLabel, onClick: dojo.hitch(this, "modTable", "deleteTableRow" )}));
797                        pMenu.addChild(new MenuItem({label: messages.deleteTableColumnLabel, onClick: dojo.hitch(this, "modTable", "deleteTableColumn" )}));
798
799                        this.menu = pMenu;
800                }
801});
802
803var EditorTableDialog = declare("dojox.editor.plugins.EditorTableDialog", [Dialog, _TemplatedMixin, _WidgetsInTemplateMixin], {
804        // summary:
805        //              Dialog box with options for table creation
806
807        baseClass:"EditorTableDialog",
808
809        templateString: insertTableTemplate,
810
811        postMixInProperties: function(){
812                dojo.mixin(this, tableDialogStrings);
813                this.inherited(arguments);
814        },
815
816        postCreate: function(){
817                dojo.addClass(this.domNode, this.baseClass); //FIXME - why isn't Dialog accepting the baseClass?
818                this.inherited(arguments);
819        },
820
821        onInsert: function(){
822                console.log("insert");
823
824                var rows =              this.selectRow.get("value") || 1,
825                        cols =          this.selectCol.get("value") || 1,
826                        width =         this.selectWidth.get("value"),
827                        widthType = this.selectWidthType.get("value"),
828                        border =        this.selectBorder.get("value"),
829                        pad =           this.selectPad.get("value"),
830                        space =         this.selectSpace.get("value"),
831                        _id =           "tbl_"+(new Date().getTime()),
832                        t = '<table id="'+_id+'"width="'+width+((widthType=="percent")?'%':'')+'" border="'+border+'" cellspacing="'+space+'" cellpadding="'+pad+'">\n';
833
834                for(var r=0;r<rows;r++){
835                        t += '\t<tr>\n';
836                        for(var c=0;c<cols;c++){
837                                t += '\t\t<td width="'+(Math.floor(100/cols))+'%">&nbsp;</td>\n';
838                        }
839                        t += '\t</tr>\n';
840                }
841                t += '</table><br />';
842
843                var cl = dojo.connect(this, "onHide", function(){
844                        dojo.disconnect(cl);
845                        var self = this;
846                        setTimeout(function(){
847                                self.destroyRecursive();
848                        }, 10);
849                });
850                this.hide();
851
852                //console.log(t);
853                this.onBuildTable({htmlText:t, id:_id});
854        },
855
856        onCancel: function(){
857                // summary:
858                //              Function to clean up memory so that the dialog is destroyed
859                //              when closed.
860                var c = dojo.connect(this, "onHide", function(){
861                        dojo.disconnect(c);
862                        var self = this;
863                        setTimeout(function(){
864                                self.destroyRecursive();
865                        }, 10);
866                });
867        },
868
869        onBuildTable: function(tableText){
870                //stub
871        }
872});
873
874var InsertTable = declare("dojox.editor.plugins.InsertTable", TablePlugins, {
875        alwaysAvailable: true,
876
877        modTable: function(){
878                var w = new EditorTableDialog({});
879                w.show();
880                var c = dojo.connect(w, "onBuildTable", this, function(obj){
881                        dojo.disconnect(c);
882
883                        this.editor.focus();
884                        var res = this.editor.execCommand('inserthtml', obj.htmlText);
885
886                        // commenting this line, due to msg below
887                        //var td = this.editor.query("td", this.editor.byId(obj.id));
888
889                        //HMMMM.... This throws a security error now. didn't used to.
890                        //this.editor.selectElement(td);
891                });
892        }
893});
894
895var EditorModifyTableDialog = declare([Dialog, _TemplatedMixin, _WidgetsInTemplateMixin], {
896
897        // summary:
898        //              Dialog box with options for editing a table
899        //
900
901        baseClass:"EditorTableDialog",
902
903        table:null, //html table to be modified
904        tableAtts:{},
905        templateString: modifyTableTemplate,
906
907        postMixInProperties: function(){
908                dojo.mixin(this, tableDialogStrings);
909                this.inherited(arguments);
910        },
911
912        postCreate: function(){
913                dojo.addClass(this.domNode, this.baseClass); //FIXME - why isn't Dialog accepting the baseClass?
914                this.inherited(arguments);
915                var w1 = new this.colorPicker({params: this.params});
916                this.connect(w1, "onChange", function(color){
917                        if(!this._started){ return; } // not during startup()
918                        dijit.popup.close(w1);
919                        this.setBrdColor(color);
920                });
921                this.connect(w1, "onBlur", function(){
922                        dijit.popup.close(w1);
923                });
924                this.connect(this.borderCol, "click", function(){
925                        w1.set('value', this.brdColor, false);
926                        dijit.popup.open({popup:w1, around:this.borderCol});
927                        w1.focus();
928                });
929                var w2 = new this.colorPicker({params: this.params});
930
931                this.connect(w2, "onChange", function(color){
932                        if(!this._started){ return; } // not during startup()
933                        dijit.popup.close(w2);
934                        this.setBkColor(color);
935                });
936                this.connect(w2, "onBlur", function(){
937                        dijit.popup.close(w2);
938                });
939                this.connect(this.backgroundCol, "click", function(){
940                        w2.set('value', this.bkColor, false);
941                        dijit.popup.open({popup:w2, around:this.backgroundCol});
942                        w2.focus();
943                });
944                this.own(w1, w2);
945                this.pickers = [ w1, w2 ];
946
947                this.setBrdColor(domStyle.get(this.table, "borderColor"));
948                this.setBkColor(domStyle.get(this.table, "backgroundColor"));
949                var w = domAttr.get(this.table, "width");
950                if(!w){
951                        w = this.table.style.width;
952                }
953                var p = "pixels";
954                if(dojo.isString(w) && w.indexOf("%")>-1){
955                        p = "percent";
956                        w = w.replace(/%/, "");
957                }
958
959                if(w){
960                        this.selectWidth.set("value", w);
961                        this.selectWidthType.set("value", p);
962                }else{
963                        this.selectWidth.set("value", "");
964                        this.selectWidthType.set("value", "percent");
965                }
966
967                this.selectBorder.set("value", domAttr.get(this.table, "border"));
968                this.selectPad.set("value", domAttr.get(this.table, "cellPadding"));
969                this.selectSpace.set("value", domAttr.get(this.table, "cellSpacing"));
970                this.selectAlign.set("value", domAttr.get(this.table, "align"));
971        },
972        startup: function() {
973                array.forEach(this.pickers, function(picker){ picker.startup(); });
974                this.inherited(arguments);
975        },
976
977        setBrdColor: function(color){
978                this.brdColor = color;
979                domStyle.set(this.borderCol, "backgroundColor", color);
980        },
981
982        setBkColor: function(color){
983                this.bkColor = color;
984                domStyle.set(this.backgroundCol, "backgroundColor", color);
985        },
986        onSet: function(){
987                domStyle.set(this.table, "borderColor", this.brdColor);
988                domStyle.set(this.table, "backgroundColor", this.bkColor);
989                if(this.selectWidth.get("value")){
990                        // Just in case, remove it from style since we're setting it as a table attribute.
991                        domStyle.set(this.table, "width", "");
992                        domAttr.set(this.table, "width", (this.selectWidth.get("value") + ((this.selectWidthType.get("value")=="pixels")?"":"%") ));
993                }
994                domAttr.set(this.table, "border", this.selectBorder.get("value"));
995                domAttr.set(this.table, "cellPadding", this.selectPad.get("value"));
996                domAttr.set(this.table, "cellSpacing", this.selectSpace.get("value"));
997                domAttr.set(this.table, "align", this.selectAlign.get("value"));
998                var c = dojo.connect(this, "onHide", function(){
999                        dojo.disconnect(c);
1000                        var self = this;
1001                        setTimeout(function(){
1002                                self.destroyRecursive();
1003                        }, 10);
1004                });
1005                this.hide();
1006        },
1007
1008        onCancel: function(){
1009                // summary:
1010                //              Function to clean up memory so that the dialog is destroyed
1011                //              when closed.
1012                var c = dojo.connect(this, "onHide", function(){
1013                        dojo.disconnect(c);
1014                        var self = this;
1015                        setTimeout(function(){
1016                                self.destroyRecursive();
1017                        }, 10);
1018                });
1019        },
1020
1021        onSetTable: function(tableText){
1022                //stub
1023        }
1024});
1025
1026var ModifyTable = declare("dojox.editor.plugins.ModifyTable", TablePlugins, {
1027        // colorPicker: Constructor
1028        //              The color picker dijit to use, defaults to dijit/form/ColorPalette
1029        colorPicker: ColorPalette,
1030
1031        modTable: function(){
1032                if (!this.editor._tablePluginHandler.checkAvailable()) {return;}
1033                var o = this.getTableInfo();
1034                //console.log("LAUNCH DIALOG");
1035
1036                var w = new EditorModifyTableDialog({
1037                        table:o.tbl,
1038                        colorPicker: typeof this.colorPicker === 'string' ? require(this.colorPicker) : this.colorPicker,
1039                        params: this.params
1040                });
1041                w.show();
1042                this.connect(w, "onSetTable", function(color){
1043                        // uhm... not sure whats going on here...
1044                        var o = this.getTableInfo();
1045                        //console.log("set color:", color);
1046                        domStyle.set(o.td, "backgroundColor", color);
1047                });
1048        }
1049});
1050
1051var CellColorDropDown = declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], {
1052        // summary:
1053        //              A simple widget that uses/creates a dropdown with a customisable color picker.  Also provides
1054        //              passthroughs to the value of the color picker and convenient hook points.
1055        // tags:
1056        //              private
1057
1058        // colorPicker: Constructor
1059        //              The color picker dijit to use, defaults to dojox/widget/ColorPicker
1060        colorPicker: ColorPicker,
1061
1062        // templateString: String
1063        //              The template used to create the ColorPicker.
1064        templateString:
1065                "<div style='display: none; position: absolute; top: -10000; z-index: -10000'>" +
1066                        "<div dojoType='dijit.TooltipDialog' dojoAttachPoint='dialog' class='dojoxEditorColorPicker'>" +
1067                                "<div dojoAttachPoint='_colorPicker'></div>" +
1068                                "<div style='margin: 0.5em 0em 0em 0em'>" +
1069                                        "<button dojoType='dijit.form.Button' type='submit' dojoAttachPoint='_setButton'>${buttonSet}</button>" +
1070                                        "&nbsp;" +
1071                                        "<button dojoType='dijit.form.Button' type='button' dojoAttachPoint='_cancelButton'>${buttonCancel}</button>" +
1072                                "</div>" +
1073                        "</div>" +
1074                "</div>",
1075
1076        // widgetsInTemplate: Boolean
1077        //              Flag denoting widgets are contained in the template.
1078        widgetsInTemplate: true,
1079
1080        constructor: function(){
1081                // summary:
1082                //              Constructor over-ride so that the translated strings are mixed in so
1083                //              the template fills out.
1084                dojo.mixin(this, tableDialogStrings);
1085        },
1086        postCreate: function() {
1087                // summary:
1088                //              Create color picker dynamically rather than hardcode in template.
1089                var ColorPicker = typeof this.colorPicker == "string" ? require(this.colorPicker) : this.colorPicker;
1090                this._colorPicker = new ColorPicker({
1091                        params: this.params
1092                }, this._colorPicker);
1093        },
1094
1095        startup: function(){
1096                // summary:
1097                //              Over-ride of startup to do the basic connect setups and such.
1098                if(!this._started){
1099                        this.inherited(arguments);
1100                        this.connect(this.dialog, "execute", function(){
1101                                this.onChange(this.get("value"));
1102                        });
1103                        this.connect(this._cancelButton, "onClick", function(){
1104                                dijit.popup.close(this.dialog);
1105                        });
1106                        this.connect(this.dialog, "onCancel", "onCancel");
1107
1108                        // Fully started, so go ahead and remove the hide.
1109                        dojo.style(this.domNode, "display", "block");
1110                }
1111        },
1112
1113        _setValueAttr: function(value, priorityChange){
1114                // summary:
1115                //              Passthrough function for the color picker value.
1116                // value: String
1117                //              The value to set in the color picker
1118                // priorityChange:
1119                //              Value to indicate whether or not to trigger an onChange event.
1120                this._colorPicker.set("value", value, priorityChange);
1121        },
1122
1123        _getValueAttr: function(){
1124                // summary:
1125                //              Passthrough function for the color picker value.
1126                return this._colorPicker.get("value");
1127        },
1128
1129        onChange: function(value){
1130                // summary:
1131                //              Hook point to get the value when the color picker value is selected.
1132                // value: String
1133                //              The value from the color picker.
1134        },
1135
1136        onCancel: function(){
1137                // summary:
1138                //              Hook point to get when the dialog is canceled.
1139        }
1140});
1141
1142var ColorTableCell = declare("dojox.editor.plugins.ColorTableCell", TablePlugins, {
1143        // colorPicker: Constructor
1144        //              The color picker dijit to use, defaults to dojox/widget/ColorPicker
1145        colorPicker: ColorPicker,
1146
1147        constructor: function(){
1148                // summary:
1149                //              Initialize ColorTableCell plugin
1150                this.closable = true;
1151                this.buttonClass = dijit.form.DropDownButton;
1152
1153                var self = this,
1154                        picker,
1155                        pickerInit = {
1156                                colorPicker: this.colorPicker,
1157                                params: this.params
1158                        };
1159
1160                // We may have been given the dropdown to use, or we can use a default.
1161                if(!this.dropDown){
1162                        // Create our default dropdown dialog
1163                        picker = new CellColorDropDown(pickerInit);
1164                        picker.startup(); // we don't have startup so just invoke it now
1165
1166                        // In this case the dropdown isn't the thing firing events, its
1167                        //  dialog is.
1168                        this.dropDown = picker.dialog;
1169                }else{
1170                        // Assume the dropdown we've been given is the picker we should attach to.
1171                        picker = this.dropDown;
1172                        picker.set(pickerInit);
1173                }
1174                this.connect(picker, "onChange", function(color){
1175                        this.editor.focus();
1176                        this.modTable(null, color);
1177                });
1178                this.connect(picker, "onCancel", function(){
1179                        this.editor.focus();
1180                });
1181                // Calculate and assign value before onOpen fires, so onOpen may rely on
1182                //  having a value when it runs.
1183                aspect.before(this.dropDown, "onOpen", function(){
1184                        var o = self.getTableInfo(),
1185                                tds = self.getSelectedCells(o.tbl);
1186                        if(tds && tds.length > 0){
1187                                var t = tds[0] === self.lastObject ? tds[0] : tds[tds.length - 1],
1188                                        color;
1189                                while(t && t !== self.editor.document && ((color = dojo.style(t, "backgroundColor")) === "transparent" || color.indexOf("rgba") === 0)){
1190                                        t = t.parentNode;
1191                                }
1192                                if(color !== "transparent" && color.indexOf("rgba") !== 0){
1193                                        picker.set('value', Color.fromString(color).toHex());
1194                                }
1195                        }
1196                });
1197                this.connect(this, "setEditor", function(editor){
1198                        editor.onLoadDeferred.addCallback(dojo.hitch(this, function(){
1199                                this.connect(this.editor.editNode, "onmouseup", function(evt){
1200                                        this.lastObject = evt.target;
1201                                });
1202                        }));
1203                });
1204        },
1205       
1206        _initButton: function(){
1207                this.command = this.name;
1208
1209                this.label = this.editor.commands[this.command] = this._makeTitle(this.command);
1210                this.inherited(arguments);
1211                delete this.command;
1212
1213                this.onDisplayChanged(false);
1214        },
1215
1216        modTable: function(cmd, args){
1217                // summary:
1218                //              Where each plugin performs its action.
1219                //              Note: not using execCommand. In spite of their presence in the
1220                //              Editor as query-able plugins, I was not able to find any evidence
1221                //              that they are supported (especially in NOT IE). If they are
1222                //              supported in other browsers, it may help with the undo problem.
1223
1224                this.begEdit();
1225                var o = this.getTableInfo();
1226                // The one plugin that really needs use of the very verbose
1227                //      getSelectedCells()
1228                var tds = this.getSelectedCells(o.tbl);
1229                //console.debug("SELECTED CELLS ", tds , " FOR ", o);
1230                dojo.forEach(tds, function(td){
1231                        dojo.style(td, "backgroundColor", args);
1232                });
1233                this.endEdit();
1234        }
1235});
1236
1237// Register these plugins.
1238function registerGeneric(args) {
1239        return new TablePlugins(args);
1240}
1241_Plugin.registry["insertTableRowBefore"] = registerGeneric;
1242_Plugin.registry["insertTableRowAfter"] = registerGeneric;
1243_Plugin.registry["insertTableColumnBefore"] = registerGeneric;
1244_Plugin.registry["insertTableColumnAfter"] = registerGeneric;
1245_Plugin.registry["deleteTableRow"] = registerGeneric;
1246_Plugin.registry["deleteTableColumn"] = registerGeneric;
1247_Plugin.registry["colorTableCell"] = function(args) {
1248        return new ColorTableCell(args);
1249};
1250_Plugin.registry["modifyTable"] = function(args) {
1251        return new ModifyTable(args);
1252};
1253_Plugin.registry["insertTable"] = function(args) {
1254        return new InsertTable(args);
1255};
1256_Plugin.registry["tableContextMenu"] = function(args) {
1257        return new TableContextMenu(args);
1258};
1259
1260return TablePlugins;
1261});
Note: See TracBrowser for help on using the repository browser.