aceae39a24bab2a025bc227ea7f82ba800d0dae1
[Packages/TYPO3.CMS.git] / typo3 / sysext / cms / layout / res / grideditor.js
1 /**
2 * A JavaScript object to handle, edit, draw and export a grid. The grid is basically
3 * a table with some colspan and rowspan. Each cell can additionally hold a name and
4 * column.
5 *
6 * @author Thomas Hempel <thomas@typo3.org>
7 */
8 Ext.namespace('TYPO3.Backend.t3Grid');
9
10 TYPO3.Backend.t3Grid = Ext.extend(Ext.Component, {
11
12 constructor: function(config) {
13
14 config = Ext.apply({
15 colCount: config.colCount,
16 rowCount: config.rowCount,
17 data: config.data,
18 nameLabel: config.nameLabel,
19 columnLabel: config.columnLabel,
20 targetElement: config.targetElement
21 }, config);
22
23 TYPO3.Backend.t3Grid.superclass.constructor.call(this, config);
24 },
25
26 /**
27 * Adds a row below the grid
28 */
29 addRow: function() {
30 var newRow = [];
31 for (var i = 0; i < this.colCount; i++) {
32 newRow[i] = {spanned:false,rowspan:1,colspan:1};
33 }
34 this.data.push(newRow);
35 this.rowCount++;
36 },
37
38 /**
39 * Removes the last row of the grid and adjusts all cells that might be effected
40 * by that change. (Removing colspans)
41 *
42 * @returns void
43 */
44 removeRow: function() {
45 if (this.rowCount <= 1) return false;
46 var newData = [];
47 for (var rowIndex = 0; rowIndex < this.rowCount - 1; rowIndex++) {
48 newData.push(this.data[rowIndex]);
49 }
50
51 // fix rowspan in former last row
52 for (var colIndex = 0; colIndex < this.colCount; colIndex++) {
53 if (this.data[this.rowCount - 1][colIndex].spanned == true) {
54 this.findUpperCellWidthRowspanAndDecreaseByOne(colIndex, this.rowCount - 1);
55 }
56 }
57
58 this.data = newData;
59 this.rowCount--;
60 },
61
62 /**
63 * Takes a cell and looks above it if there are any cells that have colspans that
64 * spanns into the given cell. This is used when a row was removed from the grid
65 * to make sure that no cell with wrong colspans exists in the grid.
66 *
67 * @param col integer
68 * @param row integer
69 * @return void
70 */
71 findUpperCellWidthRowspanAndDecreaseByOne: function(col, row) {
72 var upperCell = this.getCell(col, row - 1);
73 if (!upperCell) return false;
74
75 if (upperCell.spanned == true) {
76 this.findUpperCellWidthRowspanAndDecreaseByOne(col, row - 1);
77 } else {
78 if (upperCell.rowspan > 1) {
79 this.removeRowspan(col, row - 1);
80 }
81 }
82 },
83
84 /**
85 * Removes the outermost right column from the grid.
86 *
87 * @return void
88 */
89 removeColumn: function() {
90 if (this.colCount <= 1) return false;
91 var newData = [];
92
93 for (var rowIndex = 0; rowIndex < this.rowCount; rowIndex++) {
94 var newRow = [];
95 for (colIndex = 0; colIndex < this.colCount - 1; colIndex++) {
96 newRow.push(this.data[rowIndex][colIndex]);
97 }
98 if (this.data[rowIndex][this.colCount - 1].spanned == true) {
99 this.findLeftCellWidthColspanAndDecreaseByOne(this.colCount - 1, rowIndex);
100 }
101 newData.push(newRow);
102 }
103
104 this.data = newData;
105 this.colCount--;
106 },
107
108 /**
109 * Checks if there are any cells on the left side of a given cell with a
110 * rowspan that spans over the given cell.
111 *
112 * @param col integer
113 * @param row integer
114 * @return void
115 */
116 findLeftCellWidthColspanAndDecreaseByOne: function(col, row) {
117 var leftCell = this.getCell(col - 1, row);
118 if (!leftCell) return false;
119
120 if (leftCell.spanned == true) {
121 this.findLeftCellWidthColspanAndDecreaseByOne(col - 1, row);
122 } else {
123 if (leftCell.colspan > 1) {
124 this.removeColspan(col - 1, row);
125 }
126 }
127 },
128
129 /**
130 * Adds a column at the right side of the grid.
131 *
132 * @return void
133 */
134 addColumn: function() {
135 for (var rowIndex = 0; rowIndex < this.rowCount; rowIndex++) {
136 this.data[rowIndex].push({
137 spanned: false,
138 rowspan: 1,
139 colspan: 1,
140 name: this.colCount + 'x' + rowIndex
141 });
142 }
143 this.colCount++;
144 },
145
146 /**
147 * Draws the grid as table into a given container.
148 * It also adds all needed links and bindings to the cells to make it editable.
149 *
150 * @return void
151 */
152 drawTable: function() {
153 var domHelper = Ext.DomHelper;
154 var newTable = {
155 tag: 'table',
156 children: [],
157 id: 'base',
158 border: '0',
159 width: '100%',
160 height: '100%',
161 class: 'editor',
162 cellspacing: '0',
163 cellpadding: '0'
164 };
165
166 var colgroups = {
167 tag: 'colgroup',
168 children: []
169 };
170 for (var col = 0; col < this.colCount; col++) {
171 colgroups.children.push({
172 tag: 'col',
173 style: 'width:' + parseInt(100 / this.colCount, 10) + '%'
174 });
175 }
176 newTable.children.push(colgroups);
177
178 for (var row = 0; row < this.rowCount; row++) {
179 var rowData = this.data[row];
180 if (rowData.length == 0) continue;
181
182 var rowSpec = {tag: 'tr', children:[]};
183
184 for (var col = 0; col < this.colCount; col++) {
185 var cell = this.data[row][col];
186 if (cell.spanned == true) {
187 continue;
188 }
189
190
191 var cellHtml = '<div class="cell_container"><a class="link_editor" id="e_'
192 + col + '_' + row + '" title="' + TYPO3.lang.editCell + '" href="#"><!-- --></a>';
193 if (this.cellCanSpanRight(col, row)) {
194 cellHtml += '<a href="#" id="r_'
195 + col + '_' + row + '" title="' + TYPO3.lang.mergeCell + '" class="link_expand_right"><!-- --></a>';
196 }
197 if (this.cellCanShrinkLeft(col, row)) {
198 cellHtml += '<a href="#" id="l_'
199 + col + '_' + row + '" title="' + TYPO3.lang.splitCell + '" class="link_shrink_left"><!-- --></a>';
200 }
201 if (this.cellCanSpanDown(col, row)) {
202 cellHtml += '<a href="#" id="d_'
203 + col + '_' + row + '" title="' + TYPO3.lang.mergeCell + '" class="link_expand_down"><!-- --></a>';
204 }
205 if (this.cellCanShrinkUp(col, row)) {
206 cellHtml += '<a href="#" id="u_'
207 + col + '_' + row + '" title="' + TYPO3.lang.splitCell + '" class="link_shrink_up"><!-- --></a>';
208 }
209 cellHtml += '</div>';
210
211 cellHtml += '<div class="cell_data">' + TYPO3.lang.name + ': ' + (cell.name ? cell.name : TYPO3.lang.notSet)
212 + '<br />' + TYPO3.lang.column + ': '
213 + (cell.column === undefined ? TYPO3.lang.notSet : parseInt(cell.column, 10)) + '</div>';
214
215 // create cells
216 var child = {
217 tag: 'td',
218 height: parseInt(100 / this.rowCount, 10) * cell.rowspan + '%',
219 width: parseInt(100 / this.colCount, 10) * cell.colspan + '%',
220 html: cellHtml
221 };
222 if (cell.colspan > 1) {
223 child.colspan = cell.colspan;
224 }
225 if (cell.rowspan > 1) {
226 child.rowspan = cell.rowspan;
227 }
228 rowSpec.children.push(child);
229 }
230
231 newTable.children.push(rowSpec);
232
233 }
234
235 domHelper.overwrite(Ext.Element.get(this.targetElement), newTable);
236 this.bindLinks();
237 },
238
239 /**
240 * Sets the name of a certain grid element.
241 *
242 * @param newName string
243 * @param col integer
244 * @param row integer
245 *
246 * @return boolean
247 */
248 setName: function(newName, col, row) {
249 var cell = this.getCell(col, row);
250 if (!cell) return false;
251 cell.name = newName;
252 return true;
253 },
254
255 /**
256 * Sets the column field for a certain grid element. This is NOT the column of the
257 * element itself.
258 *
259 * @param newColumn integer
260 * @param col integer
261 * @param row integer
262 *
263 * @return boolean
264 */
265 setColumn: function(newColumn, col, row) {
266 var cell = this.getCell(col, row);
267 if (!cell) return false;
268 cell.column = newColumn;
269 return true;
270 },
271
272 /**
273 * Searches for all a tags with certain classes and binds some actions to them.
274 *
275 * @return void
276 */
277 bindLinks: function() {
278 for (var row = 0; row < this.rowCount; row++) {
279 for (var col = 0; col < this.colCount; col++) {
280 // span right
281 var el = Ext.Element.get('r_' + col + '_' + row);
282 if (el) {
283 el.addListener('click', function(e, sender, params) {
284 this.addColspan(params.colIndex, params.rowIndex);
285 this.drawTable();
286 }, this, {stopEvent:true, colIndex:col, rowIndex:row});
287 }
288
289 // reduce to left
290 var el = Ext.Element.get('l_' + col + '_' + row);
291 if (el) {
292 el.addListener('click', function(e, sender, params) {
293 this.removeColspan(params.colIndex, params.rowIndex);
294 this.drawTable();
295 }, this, {stopEvent:true, colIndex:col, rowIndex:row});
296 }
297
298 // span down
299 var el = Ext.Element.get('d_' + col + '_' + row);
300 if (el) {
301 el.addListener('click', function(e, sender, params) {
302 this.addRowspan(params.colIndex, params.rowIndex);
303 this.drawTable();
304 }, this, {stopEvent:true, colIndex:col, rowIndex:row});
305 }
306
307 // reduce up
308 var el = Ext.Element.get('u_' + col + '_' + row);
309 if (el) {
310 el.addListener('click', function(e, sender, params) {
311 this.removeRowspan(params.colIndex, params.rowIndex);
312 this.drawTable();
313 }, this, {stopEvent:true, colIndex:col, rowIndex:row});
314 }
315
316 // edit
317 var el = Ext.Element.get('e_' + col + '_' + row);
318 if (el) {
319 el.addListener('click', function(e, sender, params) {
320 this.showOptions(sender, params.colIndex, params.rowIndex);
321 }, this, {stopEvent:true, colIndex:col, rowIndex:row});
322 }
323 }
324 }
325 },
326
327 /**
328 * Creates an ExtJs Window with two input fields and shows it. On save, the data
329 * is written into the grid element.
330 *
331 * @param sender DOM-object (the link)
332 * @param col integer
333 * @param row integer
334 */
335 showOptions: function(sender, col, row) {
336 var win;
337 sender = Ext.get('base');
338 var cell = this.getCell(col, row);
339 if (!cell) return false;
340
341 if (!win) {
342 var fieldName = new Ext.form.TextField({
343 fieldLabel: TYPO3.lang.name,
344 name: 'name',
345 width: 270,
346 value: cell.name,
347 tabIndex: 1,
348 listeners: {
349 render: function(c) {
350 Ext.QuickTips.register({
351 target: c,
352 text: TYPO3.lang.nameHelp
353 });
354 }
355 }
356 });
357
358 var fieldColumn = new Ext.form.NumberField({
359 fieldLabel: TYPO3.lang.column,
360 name: 'column',
361 width: 50,
362 value: cell.column,
363 tabIndex: 2,
364 listeners: {
365 render: function(c) {
366 Ext.QuickTips.register({
367 target: c,
368 text: TYPO3.lang.columnHelp
369 });
370 }
371 }
372 });
373
374
375 win = new Ext.Window({
376 layout: 'fit',
377 title: TYPO3.lang.title,
378 width: 400,
379 modal: true,
380 closable: true,
381 resizable: false,
382
383 items: [
384 {
385 xtype: 'fieldset',
386 autoHeight: true,
387 autoWidth: true,
388 labelWidth: 100,
389 border: false,
390
391 items: [fieldName, fieldColumn]
392 }
393 ],
394
395 buttons: [
396 {
397 iconCls:'save',
398 text: TYPO3.lang.save,
399 handler: function(fieldName, fieldColumn, col, row) {
400 t3Grid.setName(fieldName.getValue(), col, row);
401 t3Grid.setColumn(fieldColumn.getValue(), col, row);
402 win.close();
403 t3Grid.drawTable();
404 }.createDelegate(this, [fieldName, fieldColumn, col, row])
405 }
406 ]
407 });
408 }
409 win.show(this);
410 },
411
412 /**
413 * Returns a cell element from the grid.
414 *
415 * @param col integer
416 * @param row integer
417 * return Object
418 */
419 getCell: function(col, row) {
420 if (col > this.colCount - 1) return false;
421 if (row > this.rowCount - 1) return false;
422 return this.data[row][col];
423 },
424
425 /**
426 * Checks wether a cell can span to the right or not. A cell can span to the right
427 * if it is not in the last column and if there is no cell beside it that is
428 * already overspanned by some other cell.
429 *
430 * @param col integer
431 * @param row integer
432 *
433 * @return boolean
434 */
435 cellCanSpanRight: function(col, row) {
436 if (col == this.colCount - 1) {
437 return false;
438 }
439
440 var cell = this.getCell(col, row);
441 if (cell.rowspan > 1) {
442 for (var rowIndex = row; rowIndex < row + cell.rowspan; rowIndex++) {
443 var checkCell = this.getCell(col + cell.colspan, rowIndex);
444 if (!checkCell || checkCell.spanned == true || checkCell.colspan > 1 || checkCell.rowspan > 1) {
445 return false;
446 }
447 }
448 } else {
449 var checkCell = this.getCell(col + cell.colspan, row);
450 if (!checkCell || cell.spanned == true || checkCell.spanned == true || checkCell.colspan > 1 || checkCell.rowspan > 1) {
451 return false;
452 }
453 }
454
455 return true;
456 },
457
458 /**
459 * Checks wether a cell can span down or not.
460 *
461 * @param col integer
462 * @param row integer
463 *
464 * @return boolean
465 */
466 cellCanSpanDown: function(col, row) {
467 if (row == this.rowCount - 1) {
468 return false;
469 }
470
471 var cell = this.getCell(col, row);
472 if (cell.colspan > 1) {
473 // we have to check all cells on the right side for the complete colspan
474 for (var colIndex = col; colIndex < col + cell.colspan; colIndex++) {
475 var checkCell = this.getCell(colIndex, row + cell.rowspan);
476 if (!checkCell || checkCell.spanned == true || checkCell.colspan > 1 || checkCell.rowspan > 1) {
477 return false;
478 }
479 }
480 } else {
481 var checkCell = this.getCell(col, row + cell.rowspan);
482 if (!checkCell || cell.spanned == true || checkCell.spanned == true || checkCell.colspan > 1 || checkCell.rowspan > 1) {
483 return false;
484 }
485 }
486
487 return true;
488 },
489
490 /**
491 * Checks if a cell can shrink to the left. It can shrink if the colspan of the
492 * cell is bigger than 1.
493 *
494 * @param col integr
495 * @param row integer
496 *
497 * @return boolean
498 */
499 cellCanShrinkLeft: function(col, row) {
500 return (this.data[row][col].colspan > 1);
501 },
502
503 /**
504 * Returns if a cell can shrink up. This is the case if a cell has at least
505 * a rowspan of 2.
506 *
507 * @param col integr
508 * @param row integer
509 *
510 * @return boolean
511 */
512 cellCanShrinkUp: function(col, row) {
513 return (this.data[row][col].rowspan > 1);
514 },
515
516 /**
517 * Adds a colspan to a grid element.
518 *
519 * @param col integr
520 * @param row integer
521 */
522 addColspan: function(col, row) {
523 var cell = this.getCell(col, row);
524 if (!cell || !this.cellCanSpanRight(col, row)) return false;
525
526 for (var rowIndex = row; rowIndex < row + cell.rowspan; rowIndex++) {
527 this.data[rowIndex][col + cell.colspan].spanned = true;
528 }
529 cell.colspan += 1;
530 },
531
532 /**
533 * Adds a rowspan to grid element.
534 *
535 * @param col integr
536 * @param row integer
537 *
538 * @return void
539 */
540 addRowspan: function(col, row) {
541 var cell = this.getCell(col, row);
542 if (!cell || !this.cellCanSpanDown(col, row)) return false;
543
544 for (var colIndex = col; colIndex < col + cell.colspan; colIndex++) {
545 this.data[row + cell.rowspan][colIndex].spanned = true;
546 }
547 cell.rowspan += 1;
548 },
549
550 /**
551 * Removes a colspan from a grid element.
552 *
553 * @param col integr
554 * @param row integer
555 *
556 * @return void
557 */
558 removeColspan: function(col, row) {
559 var cell = this.getCell(col, row);
560 if (!cell || !this.cellCanShrinkLeft(col, row)) return false;
561
562 cell.colspan -= 1;
563 for (var rowIndex = row; rowIndex < row + cell.rowspan; rowIndex++) {
564 this.data[rowIndex][col + cell.colspan].spanned = false;
565 }
566 },
567
568 /**
569 * Removes a rowspan from a grid element.
570 *
571 * @param col integr
572 * @param row integer
573 *
574 * @return void
575 */
576 removeRowspan: function(col, row) {
577 var cell = this.getCell(col, row);
578 if (!cell || !this.cellCanShrinkUp(col, row)) return false;
579
580 cell.rowspan -= 1;
581 for (var colIndex = col; colIndex < col + cell.colspan; colIndex++) {
582 this.data[row + cell.rowspan][colIndex].spanned = false;
583 }
584 },
585
586 /**
587 * Exports the current grid to a TypoScript notation that can be read by the
588 * page module and is human readable.
589 *
590 * @return string
591 */
592 export2LayoutRecord: function() {
593 var result = "backend_layout {\n\tcolCount = " + this.colCount + "\n\trowCount = " + this.rowCount + "\n\trows {\n";
594 for (var row = 0; row < this.rowCount; row++) {
595 result += "\t\t" + (row + 1) + " {\n";
596 result += "\t\t\tcolumns {\n";
597 colIndex = 0;
598 for (var col = 0; col < this.colCount; col++) {
599 var cell = this.getCell(col, row);
600 if (cell && !cell.spanned) {
601 colIndex++;
602 result += "\t\t\t\t" + (colIndex) + " {\n";
603 result += "\t\t\t\t\tname = " + ((!cell.name) ? col + "x" + row : cell.name) + "\n";
604 if (cell.colspan > 1) result += "\t\t\t\t\tcolspan = " + cell.colspan + "\n";
605 if (cell.rowspan > 1) result += "\t\t\t\t\trowspan = " + cell.rowspan + "\n";
606 if (typeof(cell.column) === 'number') result += "\t\t\t\t\tcolPos = " + cell.column + "\n";
607 result += "\t\t\t\t}\n";
608 }
609
610 }
611 result += "\t\t\t}\n";
612 result += "\t\t}\n";
613 }
614
615 result += "\t}\n}\n";
616 return result;
617 }
618 });