define("dojox/editor/plugins/TablePlugins", ["dojo", "dijit", "dojox", "dijit/form/Button", "dijit/Dialog", "dijit/form/TextBox", "dijit/form/FilteringSelect", "dijit/_editor/_Plugin", "dijit/_editor/selection", "dijit/Menu", "dijit/ColorPalette", "dojox/widget/ColorPicker", "dojo/i18n", "i18n!dojox/editor/plugins/nls/TableDialog"], function(dojo, dijit, dojox) {
dojo.experimental("dojox.editor.plugins.TablePlugins");
// summary:
// A series of plugins that give the Editor the ability to create and edit
// HTML tables. See the end of this document for all avaiable plugins
// and dojox/editorPlugins/tests/editorTablePlugs.html for an example
//
// example:
// |
// | Editor text is here
// |
//
// TODO:
// Currently not supporting merging or splitting cells
//
// FIXME: Undo is very buggy, and therefore unimeplented in all browsers
// except IE - which itself has only been lightly tested.
//
// FIXME: Selecting multiple table cells in Firefox looks to be impossible.
// This affect the 'colorTableCell' plugin. Cells can still be
// colored individually or in rows.
dojo.declare("dojox.editor.plugins._TableHandler", dijit._editor._Plugin,{
// summary:
// A global object that handles common tasks for all the plugins. Since
// there are several plugins that are all calling common methods, it's preferable
// that they call a centralized location that either has a set variable or a
// timeout to only repeat code-heavy calls when necessary.
//
tablesConnected:false,
currentlyAvailable: false,
alwaysAvailable:false,
availableCurrentlySet:false,
initialized:false,
tableData: null,
shiftKeyDown:false,
editorDomNode: null,
undoEnabled: true, //Using custom undo for all browsers.
refCount: 0,
doMixins: function(){
dojo.mixin(this.editor,{
getAncestorElement: function(tagName){
return dojo.withGlobal(this.window, "getAncestorElement",dijit._editor.selection, [tagName]);
},
hasAncestorElement: function(tagName){
return dojo.withGlobal(this.window, "hasAncestorElement",dijit._editor.selection, [tagName]);
},
selectElement: function(elem){
dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [elem]);
},
byId: function(id){
return dojo.withGlobal(this.window, "byId", dojo, [id]);
},
query: function(arg, scope, returnFirstOnly){
// this shortcut is dubious - not sure scoping is necessary
var ar = dojo.withGlobal(this.window, "query", dojo, [arg, scope]);
return (returnFirstOnly) ? ar[0] : ar;
}
});
},
initialize: function(editor){
// summary:
// Initialize the global handler upon a plugin's first instance of setEditor
//
// All plugins will attempt initialization. We only need to do so once.
// But keep track so that it is cleaned up when all usage of it for an editor has
// been removed.
this.refCount++;
// Turn on custom undo for all.
editor.customUndo = true;
if(this.initialized){ return; }
this.initialized = true;
this.editor = editor;
this.editor._tablePluginHandler = this;
//Editor loads async, can't assume doc is ready yet. So, use the deferred of the
//editor to init at the right time.
editor.onLoadDeferred.addCallback(dojo.hitch(this, function(){
this.editorDomNode = this.editor.editNode || this.editor.iframe.document.body.firstChild;
// RichText should have a mouseup connection to recognize drag-selections
// Example would be selecting multiple table cells
this._myListeners = [];
this._myListeners.push(dojo.connect(this.editorDomNode , "mouseup", this.editor, "onClick"));
this._myListeners.push(dojo.connect(this.editor, "onDisplayChanged", this, "checkAvailable"));
this._myListeners.push(dojo.connect(this.editor, "onBlur", this, "checkAvailable"));
this.doMixins();
this.connectDraggable();
}));
},
getTableInfo: function(forceNewData){
// summary:
// Gets the table in focus
// Collects info on the table - see return params
//
if(forceNewData){ this._tempStoreTableData(false); }
if(this.tableData){
// tableData is set for a short amount of time, so that all
// plugins get the same return without doing the method over
//console.log("returning current tableData:", this.tableData);
return this.tableData;
}
var tr, trs, td, tds, tbl, cols, tdIndex, trIndex;
td = this.editor.getAncestorElement("td");
if(td){ tr = td.parentNode; }
tbl = this.editor.getAncestorElement("table");
//console.log("td:", td);console.log("tr:", tr);console.log("tbl:", tbl)
tds = dojo.query("td", tbl);
tds.forEach(function(d, i){
if(td==d){tdIndex = i;}
});
trs = dojo.query("tr", tbl);
trs.forEach(function(r, i){
if(tr==r){trIndex = i;}
});
cols = tds.length/trs.length;
var o = {
tbl:tbl, // focused table
td:td, // focused TD
tr:tr, // focused TR
trs:trs, // rows
tds:tds, // cells
rows:trs.length,// row amount
cols:cols, // column amount
tdIndex:tdIndex,// index of focused cell
trIndex:trIndex, // index of focused row
colIndex:tdIndex%cols
};
//console.log("NEW tableData:",o);
this.tableData = o;
this._tempStoreTableData(500);
return this.tableData;
},
connectDraggable: function(){
// summary:
// Detects drag-n-drop in the editor (could probably be moved to there)
// Currently only checks if item dragged was a TABLE, and removes its align attr
// DOES NOT WORK IN FF - it could - but FF's drag detection is a monster
//
if(!dojo.isIE){
//console.warn("Drag and Drop is currently only detectable in IE.");
return;
}
// IE ONLY
this.editorDomNode.ondragstart = dojo.hitch(this, "onDragStart");
this.editorDomNode.ondragend = dojo.hitch(this, "onDragEnd");
//NOTES:
// FF _ Able to detect the drag-over object (the editor.domNode)
// Not able to detect an item's ondrag() event
// Don't know why - I actually got it working when there was an error
// Something to do with different documents or windows I'm sure
//
//console.log("connectDraggable", tbl);
/*tbl.ondragstart=dojo.hitch(this, "onDragStart");
tbl.addEventListener("dragstart", dojo.hitch(this, "onDragStart"), false);
tbl.addEventListener("drag", dojo.hitch(this, "onDragStart2"), false);
tbl.addEventListener("dragend", dojo.hitch(this, "onDragStart3"), false);
dojo.withGlobal(this.editor.window, "selectElement",dijit._editor.selection, [tbl]);
tbl.ondragstart = function(){
//console.log("ondragstart");
};
tbl.ondrag = function(){
alert("drag")
//console.log("ondrag");
*/
},
onDragStart: function(){
var e = window.event;
if(!e.srcElement.id){
e.srcElement.id = "tbl_"+(new Date().getTime());
}
//console.log("onDragStart", e.srcElement.id);
},
onDragEnd: function(){
// summary:
// Detects that an object has been dragged into place
// Currently, this code is only used for when a table is dragged
// and clears the "align" attribute, so that the table will look
// to be more in the place that the user expected.
// TODO: This code can be used for other things, most
// notably UNDO, which currently is not quite usable.
// This code could also find itself in the Editor code when it is
// complete.
//console.log("onDragEnd");
var e = window.event;
var node = e.srcElement;
var id = node.id;
var win = this.editor.window;
//console.log("NODE:", node.tagName, node.id, dojo.attr(node, "align"));
// clearing a table's align attr
// TODO: when ondrag becomes more robust, this code block
// should move to its own method
if(node.tagName.toLowerCase()=="table"){
setTimeout(function(){
var node = dojo.withGlobal(win, "byId", dojo, [id]);
dojo.removeAttr(node, "align");
//console.log("set", node.tagName, dojo.attr(node, "align"))
}, 100);
}
},
checkAvailable: function(){
// summary:
// For table plugs
// Checking if a table or part of a table has focus so that
// Plugs can change their status
//
if(this.availableCurrentlySet){
// availableCurrentlySet is set for a short amount of time, so that all
// plugins get the same return without doing the method over
//console.log("availableCurrentlySet:", this.availableCurrentlySet, "currentlyAvailable:", this.currentlyAvailable)
return this.currentlyAvailable;
}
//console.log("G - checkAvailable...");
if(!this.editor) {
//console.log("editor not ready")
return false;
}
if(this.alwaysAvailable) {
//console.log(" return always available")
return true;
}
// Only return avalable if the editor is focused.
this.currentlyAvailable = this.editor._focused ? this.editor.hasAncestorElement("table") : false;
if(this.currentlyAvailable){
this.connectTableKeys();
}else{
this.disconnectTableKeys();
}
this._tempAvailability(500);
dojo.publish(this.editor.id + "_tablePlugins", [ this.currentlyAvailable ]);
return this.currentlyAvailable;
},
_prepareTable: function(tbl){
// For IE's sake, we are adding IDs to the TDs if none is there
// We go ahead and use it for other code for convenience
//
var tds = this.editor.query("td", tbl);
console.log("prep:", tds, tbl);
if(!tds[0].id){
tds.forEach(function(td, i){
if(!td.id){
td.id = "tdid"+i+this.getTimeStamp();
}
}, this);
}
return tds;
},
getTimeStamp: function(){
return new Date().getTime(); // Fixed the bug that this method always returns the same timestamp
// return Math.floor(new Date().getTime() * 0.00000001);
},
_tempStoreTableData: function(type){
// caching or clearing table data, depending on the arg
//
if(type===true){
//store indefinitely
}else if(type===false){
// clear object
this.tableData = null;
}else if(type===undefined){
console.warn("_tempStoreTableData must be passed an argument");
}else{
// type is a number/ms
setTimeout(dojo.hitch(this, function(){
this.tableData = null;
}), type);
}
},
_tempAvailability: function(type){
// caching or clearing availability, depending on the arg
if(type===true){
//store indefinitely
this.availableCurrentlySet = true;
}else if(type===false){
// clear object
this.availableCurrentlySet = false;
}else if(type===undefined){
console.warn("_tempAvailability must be passed an argument");
}else{
// type is a number/ms
this.availableCurrentlySet = true;
setTimeout(dojo.hitch(this, function(){
this.availableCurrentlySet = false;
}), type);
}
},
connectTableKeys: function(){
// summary:
// When a table is in focus, start detecting keys
// Mainly checking for the TAB key so user can tab
// through a table (blocking the browser's desire to
// tab away from teh editor completely)
if(this.tablesConnected){ return; }
this.tablesConnected = true;
var node = (this.editor.iframe) ? this.editor.document : this.editor.editNode;
this.cnKeyDn = dojo.connect(node, "onkeydown", this, "onKeyDown");
this.cnKeyUp = dojo.connect(node, "onkeyup", this, "onKeyUp");
this._myListeners.push(dojo.connect(node, "onkeypress", this, "onKeyUp"));
},
disconnectTableKeys: function(){
//console.log("disconnect")
dojo.disconnect(this.cnKeyDn);
dojo.disconnect(this.cnKeyUp);
this.tablesConnected = false;
},
onKeyDown: function(evt){
var key = evt.keyCode;
//console.log(" -> DOWN:", key);
if(key == 16){ this.shiftKeyDown = true;}
if(key == 9) {
var o = this.getTableInfo();
//console.log("TAB ", o.tdIndex, o);
// modifying the o.tdIndex in the tableData directly, because we may save it
// FIXME: tabTo is a global
o.tdIndex = (this.shiftKeyDown) ? o.tdIndex-1 : tabTo = o.tdIndex+1;
if(o.tdIndex>=0 && o.tdIndex
this.editor.selectElement(o.tds[o.tdIndex]);
// we know we are still within a table, so block the need
// to run the method
this.currentlyAvailable = true;
this._tempAvailability(true);
//
this._tempStoreTableData(true);
this.stopEvent = true;
}else{
//tabbed out of table
this.stopEvent = false;
this.onDisplayChanged();
}
if(this.stopEvent) {
dojo.stopEvent(evt);
}
}
},
onKeyUp: function(evt){
var key = evt.keyCode;
//console.log(" -> UP:", key)
if(key == 16){ this.shiftKeyDown = false;}
if(key == 37 || key == 38 || key == 39 || key == 40 ){
// user can arrow or tab out of table - need to recheck
this.onDisplayChanged();
}
if(key == 9 && this.stopEvent){ dojo.stopEvent(evt);}
},
onDisplayChanged: function(){
//console.log("onDisplayChanged")
this.currentlyAvailable = false;
this._tempStoreTableData(false);
this._tempAvailability(false);
this.checkAvailable();