TablePlugins.js 38 KB

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