|
1 /** |
|
2 * Plugin.js |
|
3 * |
|
4 * Copyright, Moxiecode Systems AB |
|
5 * Released under LGPL License. |
|
6 * |
|
7 * License: http://www.tinymce.com/license |
|
8 * Contributing: http://www.tinymce.com/contributing |
|
9 */ |
|
10 |
|
11 /** |
|
12 * This class contains all core logic for the table plugin. |
|
13 * |
|
14 * @class tinymce.tableplugin.Plugin |
|
15 * @private |
|
16 */ |
|
17 define("tinymce/tableplugin/Plugin", [ |
|
18 "tinymce/tableplugin/TableGrid", |
|
19 "tinymce/tableplugin/Quirks", |
|
20 "tinymce/tableplugin/CellSelection", |
|
21 "tinymce/tableplugin/Dialogs", |
|
22 "tinymce/util/Tools", |
|
23 "tinymce/dom/TreeWalker", |
|
24 "tinymce/Env", |
|
25 "tinymce/PluginManager" |
|
26 ], function(TableGrid, Quirks, CellSelection, Dialogs, Tools, TreeWalker, Env, PluginManager) { |
|
27 var each = Tools.each; |
|
28 |
|
29 function Plugin(editor) { |
|
30 var clipboardRows, self = this, dialogs = new Dialogs(editor); |
|
31 |
|
32 function cmd(command) { |
|
33 return function() { |
|
34 editor.execCommand(command); |
|
35 }; |
|
36 } |
|
37 |
|
38 function insertTable(cols, rows) { |
|
39 var y, x, html, tableElm; |
|
40 |
|
41 html = '<table id="__mce"><tbody>'; |
|
42 |
|
43 for (y = 0; y < rows; y++) { |
|
44 html += '<tr>'; |
|
45 |
|
46 for (x = 0; x < cols; x++) { |
|
47 html += '<td>' + (Env.ie ? " " : '<br>') + '</td>'; |
|
48 } |
|
49 |
|
50 html += '</tr>'; |
|
51 } |
|
52 |
|
53 html += '</tbody></table>'; |
|
54 |
|
55 editor.undoManager.transact(function() { |
|
56 editor.insertContent(html); |
|
57 |
|
58 tableElm = editor.dom.get('__mce'); |
|
59 editor.dom.setAttrib(tableElm, 'id', null); |
|
60 |
|
61 editor.dom.setAttribs(tableElm, editor.settings.table_default_attributes || {}); |
|
62 editor.dom.setStyles(tableElm, editor.settings.table_default_styles || {}); |
|
63 }); |
|
64 |
|
65 return tableElm; |
|
66 } |
|
67 |
|
68 function handleDisabledState(ctrl, selector) { |
|
69 function bindStateListener() { |
|
70 ctrl.disabled(!editor.dom.getParent(editor.selection.getStart(), selector)); |
|
71 |
|
72 editor.selection.selectorChanged(selector, function(state) { |
|
73 ctrl.disabled(!state); |
|
74 }); |
|
75 } |
|
76 |
|
77 if (editor.initialized) { |
|
78 bindStateListener(); |
|
79 } else { |
|
80 editor.on('init', bindStateListener); |
|
81 } |
|
82 } |
|
83 |
|
84 function postRender() { |
|
85 /*jshint validthis:true*/ |
|
86 handleDisabledState(this, 'table'); |
|
87 } |
|
88 |
|
89 function postRenderCell() { |
|
90 /*jshint validthis:true*/ |
|
91 handleDisabledState(this, 'td,th'); |
|
92 } |
|
93 |
|
94 function generateTableGrid() { |
|
95 var html = ''; |
|
96 |
|
97 html = '<table role="grid" class="mce-grid mce-grid-border" aria-readonly="true">'; |
|
98 |
|
99 for (var y = 0; y < 10; y++) { |
|
100 html += '<tr>'; |
|
101 |
|
102 for (var x = 0; x < 10; x++) { |
|
103 html += '<td role="gridcell" tabindex="-1"><a id="mcegrid' + (y * 10 + x) + '" href="#" ' + |
|
104 'data-mce-x="' + x + '" data-mce-y="' + y + '"></a></td>'; |
|
105 } |
|
106 |
|
107 html += '</tr>'; |
|
108 } |
|
109 |
|
110 html += '</table>'; |
|
111 |
|
112 html += '<div class="mce-text-center" role="presentation">1 x 1</div>'; |
|
113 |
|
114 return html; |
|
115 } |
|
116 |
|
117 function selectGrid(tx, ty, control) { |
|
118 var table = control.getEl().getElementsByTagName('table')[0]; |
|
119 var x, y, focusCell, cell, active; |
|
120 var rtl = control.isRtl() || control.parent().rel == 'tl-tr'; |
|
121 |
|
122 table.nextSibling.innerHTML = (tx + 1) + ' x ' + (ty + 1); |
|
123 |
|
124 if (rtl) { |
|
125 tx = 9 - tx; |
|
126 } |
|
127 |
|
128 for (y = 0; y < 10; y++) { |
|
129 for (x = 0; x < 10; x++) { |
|
130 cell = table.rows[y].childNodes[x].firstChild; |
|
131 active = (rtl ? x >= tx : x <= tx) && y <= ty; |
|
132 |
|
133 editor.dom.toggleClass(cell, 'mce-active', active); |
|
134 |
|
135 if (active) { |
|
136 focusCell = cell; |
|
137 } |
|
138 } |
|
139 } |
|
140 |
|
141 return focusCell.parentNode; |
|
142 } |
|
143 |
|
144 if (editor.settings.table_grid === false) { |
|
145 editor.addMenuItem('inserttable', { |
|
146 text: 'Insert table', |
|
147 icon: 'table', |
|
148 context: 'table', |
|
149 onclick: dialogs.table |
|
150 }); |
|
151 } else { |
|
152 editor.addMenuItem('inserttable', { |
|
153 text: 'Insert table', |
|
154 icon: 'table', |
|
155 context: 'table', |
|
156 ariaHideMenu: true, |
|
157 onclick: function(e) { |
|
158 if (e.aria) { |
|
159 this.parent().hideAll(); |
|
160 e.stopImmediatePropagation(); |
|
161 dialogs.table(); |
|
162 } |
|
163 }, |
|
164 onshow: function() { |
|
165 selectGrid(0, 0, this.menu.items()[0]); |
|
166 }, |
|
167 onhide: function() { |
|
168 var elements = this.menu.items()[0].getEl().getElementsByTagName('a'); |
|
169 editor.dom.removeClass(elements, 'mce-active'); |
|
170 editor.dom.addClass(elements[0], 'mce-active'); |
|
171 }, |
|
172 menu: [ |
|
173 { |
|
174 type: 'container', |
|
175 html: generateTableGrid(), |
|
176 |
|
177 onPostRender: function() { |
|
178 this.lastX = this.lastY = 0; |
|
179 }, |
|
180 |
|
181 onmousemove: function(e) { |
|
182 var target = e.target, x, y; |
|
183 |
|
184 if (target.tagName.toUpperCase() == 'A') { |
|
185 x = parseInt(target.getAttribute('data-mce-x'), 10); |
|
186 y = parseInt(target.getAttribute('data-mce-y'), 10); |
|
187 |
|
188 if (this.isRtl() || this.parent().rel == 'tl-tr') { |
|
189 x = 9 - x; |
|
190 } |
|
191 |
|
192 if (x !== this.lastX || y !== this.lastY) { |
|
193 selectGrid(x, y, e.control); |
|
194 |
|
195 this.lastX = x; |
|
196 this.lastY = y; |
|
197 } |
|
198 } |
|
199 }, |
|
200 |
|
201 onclick: function(e) { |
|
202 var self = this; |
|
203 |
|
204 if (e.target.tagName.toUpperCase() == 'A') { |
|
205 e.preventDefault(); |
|
206 e.stopPropagation(); |
|
207 self.parent().cancel(); |
|
208 |
|
209 editor.undoManager.transact(function() { |
|
210 insertTable(self.lastX + 1, self.lastY + 1); |
|
211 }); |
|
212 |
|
213 editor.addVisual(); |
|
214 } |
|
215 } |
|
216 } |
|
217 ] |
|
218 }); |
|
219 } |
|
220 |
|
221 editor.addMenuItem('tableprops', { |
|
222 text: 'Table properties', |
|
223 context: 'table', |
|
224 onPostRender: postRender, |
|
225 onclick: dialogs.tableProps |
|
226 }); |
|
227 |
|
228 editor.addMenuItem('deletetable', { |
|
229 text: 'Delete table', |
|
230 context: 'table', |
|
231 onPostRender: postRender, |
|
232 cmd: 'mceTableDelete' |
|
233 }); |
|
234 |
|
235 editor.addMenuItem('cell', { |
|
236 separator: 'before', |
|
237 text: 'Cell', |
|
238 context: 'table', |
|
239 menu: [ |
|
240 {text: 'Cell properties', onclick: cmd('mceTableCellProps'), onPostRender: postRenderCell}, |
|
241 {text: 'Merge cells', onclick: cmd('mceTableMergeCells'), onPostRender: postRenderCell}, |
|
242 {text: 'Split cell', onclick: cmd('mceTableSplitCells'), onPostRender: postRenderCell} |
|
243 ] |
|
244 }); |
|
245 |
|
246 editor.addMenuItem('row', { |
|
247 text: 'Row', |
|
248 context: 'table', |
|
249 menu: [ |
|
250 {text: 'Insert row before', onclick: cmd('mceTableInsertRowBefore'), onPostRender: postRenderCell}, |
|
251 {text: 'Insert row after', onclick: cmd('mceTableInsertRowAfter'), onPostRender: postRenderCell}, |
|
252 {text: 'Delete row', onclick: cmd('mceTableDeleteRow'), onPostRender: postRenderCell}, |
|
253 {text: 'Row properties', onclick: cmd('mceTableRowProps'), onPostRender: postRenderCell}, |
|
254 {text: '-'}, |
|
255 {text: 'Cut row', onclick: cmd('mceTableCutRow'), onPostRender: postRenderCell}, |
|
256 {text: 'Copy row', onclick: cmd('mceTableCopyRow'), onPostRender: postRenderCell}, |
|
257 {text: 'Paste row before', onclick: cmd('mceTablePasteRowBefore'), onPostRender: postRenderCell}, |
|
258 {text: 'Paste row after', onclick: cmd('mceTablePasteRowAfter'), onPostRender: postRenderCell} |
|
259 ] |
|
260 }); |
|
261 |
|
262 editor.addMenuItem('column', { |
|
263 text: 'Column', |
|
264 context: 'table', |
|
265 menu: [ |
|
266 {text: 'Insert column before', onclick: cmd('mceTableInsertColBefore'), onPostRender: postRenderCell}, |
|
267 {text: 'Insert column after', onclick: cmd('mceTableInsertColAfter'), onPostRender: postRenderCell}, |
|
268 {text: 'Delete column', onclick: cmd('mceTableDeleteCol'), onPostRender: postRenderCell} |
|
269 ] |
|
270 }); |
|
271 |
|
272 var menuItems = []; |
|
273 each("inserttable tableprops deletetable | cell row column".split(' '), function(name) { |
|
274 if (name == '|') { |
|
275 menuItems.push({text: '-'}); |
|
276 } else { |
|
277 menuItems.push(editor.menuItems[name]); |
|
278 } |
|
279 }); |
|
280 |
|
281 editor.addButton("table", { |
|
282 type: "menubutton", |
|
283 title: "Table", |
|
284 menu: menuItems |
|
285 }); |
|
286 |
|
287 // Select whole table is a table border is clicked |
|
288 if (!Env.isIE) { |
|
289 editor.on('click', function(e) { |
|
290 e = e.target; |
|
291 |
|
292 if (e.nodeName === 'TABLE') { |
|
293 editor.selection.select(e); |
|
294 editor.nodeChanged(); |
|
295 } |
|
296 }); |
|
297 } |
|
298 |
|
299 self.quirks = new Quirks(editor); |
|
300 |
|
301 editor.on('Init', function() { |
|
302 self.cellSelection = new CellSelection(editor); |
|
303 }); |
|
304 |
|
305 editor.on('PreInit', function() { |
|
306 // Remove internal data attributes |
|
307 editor.serializer.addAttributeFilter( |
|
308 'data-mce-cell-padding,data-mce-border,data-mce-border-color', |
|
309 function(nodes, name) { |
|
310 |
|
311 var i = nodes.length; |
|
312 |
|
313 while (i--) { |
|
314 nodes[i].attr(name, null); |
|
315 } |
|
316 }); |
|
317 }); |
|
318 |
|
319 // Register action commands |
|
320 each({ |
|
321 mceTableSplitCells: function(grid) { |
|
322 grid.split(); |
|
323 }, |
|
324 |
|
325 mceTableMergeCells: function(grid) { |
|
326 var cell; |
|
327 |
|
328 cell = editor.dom.getParent(editor.selection.getStart(), 'th,td'); |
|
329 |
|
330 if (!editor.dom.select('td.mce-item-selected,th.mce-item-selected').length) { |
|
331 dialogs.merge(grid, cell); |
|
332 } else { |
|
333 grid.merge(); |
|
334 } |
|
335 }, |
|
336 |
|
337 mceTableInsertRowBefore: function(grid) { |
|
338 grid.insertRow(true); |
|
339 }, |
|
340 |
|
341 mceTableInsertRowAfter: function(grid) { |
|
342 grid.insertRow(); |
|
343 }, |
|
344 |
|
345 mceTableInsertColBefore: function(grid) { |
|
346 grid.insertCol(true); |
|
347 }, |
|
348 |
|
349 mceTableInsertColAfter: function(grid) { |
|
350 grid.insertCol(); |
|
351 }, |
|
352 |
|
353 mceTableDeleteCol: function(grid) { |
|
354 grid.deleteCols(); |
|
355 }, |
|
356 |
|
357 mceTableDeleteRow: function(grid) { |
|
358 grid.deleteRows(); |
|
359 }, |
|
360 |
|
361 mceTableCutRow: function(grid) { |
|
362 clipboardRows = grid.cutRows(); |
|
363 }, |
|
364 |
|
365 mceTableCopyRow: function(grid) { |
|
366 clipboardRows = grid.copyRows(); |
|
367 }, |
|
368 |
|
369 mceTablePasteRowBefore: function(grid) { |
|
370 grid.pasteRows(clipboardRows, true); |
|
371 }, |
|
372 |
|
373 mceTablePasteRowAfter: function(grid) { |
|
374 grid.pasteRows(clipboardRows); |
|
375 }, |
|
376 |
|
377 mceTableDelete: function(grid) { |
|
378 grid.deleteTable(); |
|
379 } |
|
380 }, function(func, name) { |
|
381 editor.addCommand(name, function() { |
|
382 var grid = new TableGrid(editor); |
|
383 |
|
384 if (grid) { |
|
385 func(grid); |
|
386 editor.execCommand('mceRepaint'); |
|
387 self.cellSelection.clear(); |
|
388 } |
|
389 }); |
|
390 }); |
|
391 |
|
392 // Register dialog commands |
|
393 each({ |
|
394 mceInsertTable: dialogs.table, |
|
395 mceTableProps: function() { |
|
396 dialogs.table(true); |
|
397 }, |
|
398 mceTableRowProps: dialogs.row, |
|
399 mceTableCellProps: dialogs.cell |
|
400 }, function(func, name) { |
|
401 editor.addCommand(name, function(ui, val) { |
|
402 func(val); |
|
403 }); |
|
404 }); |
|
405 |
|
406 // Enable tab key cell navigation |
|
407 if (editor.settings.table_tab_navigation !== false) { |
|
408 editor.on('keydown', function(e) { |
|
409 var cellElm, grid, delta; |
|
410 |
|
411 if (e.keyCode == 9) { |
|
412 cellElm = editor.dom.getParent(editor.selection.getStart(), 'th,td'); |
|
413 |
|
414 if (cellElm) { |
|
415 e.preventDefault(); |
|
416 |
|
417 grid = new TableGrid(editor); |
|
418 delta = e.shiftKey ? -1 : 1; |
|
419 |
|
420 editor.undoManager.transact(function() { |
|
421 if (!grid.moveRelIdx(cellElm, delta) && delta > 0) { |
|
422 grid.insertRow(); |
|
423 grid.refresh(); |
|
424 grid.moveRelIdx(cellElm, delta); |
|
425 } |
|
426 }); |
|
427 } |
|
428 } |
|
429 }); |
|
430 } |
|
431 |
|
432 self.insertTable = insertTable; |
|
433 } |
|
434 |
|
435 PluginManager.add('table', Plugin); |
|
436 }); |