f9a4cc2e29b74cddaf8ade770398183400ad74df
[roojs1] / Roo / htmleditor / BlockTd.js
1 /**
2  *
3  * editing a TD?
4  *
5  * since selections really work on the table cell, then editing really should work from there
6  *
7  * The original plan was to support merging etc... - but that may not be needed yet..
8  *
9  * So this simple version will support:
10  *   add/remove cols
11  *   adjust the width +/-
12  *   reset the width...
13  *   
14  *
15  */
16
17
18  
19
20 /**
21  * @class Roo.htmleditor.BlockTable
22  * Block that manages a table
23  * 
24  * @constructor
25  * Create a new Filter.
26  * @param {Object} config Configuration options
27  */
28
29 Roo.htmleditor.BlockTd = function(cfg)
30 {
31     if (cfg.node) {
32         this.readElement(cfg.node);
33         this.updateElement(cfg.node);
34     }
35     Roo.apply(this, cfg);
36      
37     
38     
39 }
40 Roo.extend(Roo.htmleditor.BlockTd, Roo.htmleditor.Block, {
41  
42     node : false,
43     
44     width: '',
45     textAlign : 'left',
46     
47     colspan : 1,
48     rowspan : 1,
49     
50     
51     // used by context menu
52     friendly_name : 'Table Cell',
53     deleteTitle : 'Delete Table',
54     
55     // context menu is drawn once..
56     
57     contextMenu : function(toolbar)
58     {
59         
60         var cell = function() {
61             return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
62         };
63         
64         var table = function() {
65             return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode.closest('table'));
66         };
67         
68         var lr = false;
69         var saveSel = function()
70         {
71             lr = toolbar.editorcore.getSelection().getRangeAt(0);
72         }
73         var restoreSel = function()
74         {
75             if (lr) {
76                 (function() {
77                     toolbar.editorcore.focus();
78                     var cr = toolbar.editorcore.getSelection();
79                     cr.removeAllRanges();
80                     cr.addRange(lr);
81                     toolbar.editorcore.onEditorEvent();
82                 }).defer(10, this);
83                 
84                 
85             }
86         }
87         
88         var rooui =  typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
89         
90         var syncValue = toolbar.editorcore.syncValue;
91         
92         var fields = {};
93         
94         return [
95             {
96                 xtype : 'Button',
97                 text : 'Edit Table',
98                 listeners : {
99                     click : function() {
100                         var t = toolbar.tb.selectedNode.closest('table');
101                         toolbar.editorcore.selectNode(t);
102                         toolbar.editorcore.onEditorEvent();                        
103                     }
104                 }
105                 
106             },
107               
108            
109              
110             {
111                 xtype : 'TextItem',
112                 text : "Column Width: ",
113                  xns : rooui.Toolbar 
114                
115             },
116             {
117                 xtype : 'Button',
118                 text: '-',
119                 listeners : {
120                     click : function (_self, e)
121                     {
122                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
123                         cell().shrinkColumn();
124                         syncValue();
125                          toolbar.editorcore.onEditorEvent();
126                     }
127                 },
128                 xns : rooui.Toolbar
129             },
130             {
131                 xtype : 'Button',
132                 text: '+',
133                 listeners : {
134                     click : function (_self, e)
135                     {
136                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
137                         cell().growColumn();
138                         syncValue();
139                         toolbar.editorcore.onEditorEvent();
140                     }
141                 },
142                 xns : rooui.Toolbar
143             },
144             {
145                 xtype : 'TextItem',
146                 text : "Merge Cells: ",
147                  xns : rooui.Toolbar 
148                
149             },
150             
151             
152             {
153                 xtype : 'Button',
154                 text: 'Right',
155                 listeners : {
156                     click : function (_self, e)
157                     {
158                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
159                         cell().mergeRight();
160                         //block().growColumn();
161                         syncValue();
162                         toolbar.editorcore.onEditorEvent();
163                     }
164                 },
165                 xns : rooui.Toolbar
166             },
167              
168             {
169                 xtype : 'Button',
170                 text: 'Below',
171                 listeners : {
172                     click : function (_self, e)
173                     {
174                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
175                         cell().mergeBelow();
176                         //block().growColumn();
177                         syncValue();
178                         toolbar.editorcore.onEditorEvent();
179                     }
180                 },
181                 xns : rooui.Toolbar
182             },
183             {
184                 xtype : 'TextItem',
185                 text : "| ",
186                  xns : rooui.Toolbar 
187                
188             },
189             
190             {
191                 xtype : 'Button',
192                 text: 'Split',
193                 listeners : {
194                     click : function (_self, e)
195                     {
196                         //toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
197                         cell().split();
198                         syncValue();
199                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
200                         toolbar.editorcore.onEditorEvent();
201                                              
202                     }
203                 },
204                 xns : rooui.Toolbar
205             },
206             {
207                 xtype : 'TextItem',
208                 text : "| ",
209                  xns : rooui.Toolbar 
210                
211             },
212             {
213                 xtype : 'Button',
214                 text: 'Delete Row',
215                 listeners : {
216                     click : function (_self, e)
217                     {
218                         //toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
219                         cell().deleteRow();
220                         syncValue();
221                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
222                         toolbar.editorcore.onEditorEvent();
223                                              
224                     }
225                 },
226                 xns : rooui.Toolbar
227             },
228             {
229                 xtype : 'Button',
230                 text: 'Delete Column',
231                 listeners : {
232                     click : function (_self, e)
233                     {
234                         //toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
235                         cell().deleteColumn();
236                         syncValue();
237                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
238                         toolbar.editorcore.onEditorEvent();
239                                              
240                     }
241                 },
242                 xns : rooui.Toolbar
243             },
244             // align... << fixme
245             
246         ];
247         
248     },
249     
250     
251   /**
252      * create a DomHelper friendly object - for use with
253      * Roo.DomHelper.markup / overwrite / etc..
254      * ?? should it be called with option to hide all editing features?
255      */
256  /**
257      * create a DomHelper friendly object - for use with
258      * Roo.DomHelper.markup / overwrite / etc..
259      * ?? should it be called with option to hide all editing features?
260      */
261     toObject : function()
262     {
263         
264         var ret = {
265             tag : 'td',
266             contenteditable : 'true', // this stops cell selection from picking the table.
267             'data-block' : 'Td',
268             style : {  
269                 'text-align' :  this.textAlign,
270                 border : 'solid 1px rgb(0, 0, 0)', // ??? hard coded?
271                 'border-collapse' : 'collapse',
272                 padding : '6px' // 8 for desktop / 4 for mobile
273             },
274             html : this.html
275         };
276         if (this.width != '') {
277             ret.width = this.width;
278             ret.style.width = this.width;
279         }
280         
281         
282         if (this.colspan > 1) {
283             ret.colspan = this.colspan ;
284         } 
285         if (this.rowspan > 1) {
286             ret.rowspan = this.rowspan ;
287         }
288         
289            
290         
291         return ret;
292          
293     },
294     
295     readElement : function(node)
296     {
297         node  = node ? node : this.node ;
298         this.width = node.style.width;
299         this.colspan = Math.max(1,1*node.getAttribute('colspan'));
300         this.rowspan = Math.max(1,1*node.getAttribute('rowspan'));
301         this.html = node.innerHTML;
302         
303         
304     },
305      
306     // the default cell object... at present...
307     emptyCell : function() {
308         return {
309             colspan :  1,
310             rowspan :  1,
311             textAlign : 'left',
312             html : "&nbsp;" // is this going to be editable now?
313         };
314      
315     },
316     
317     removeNode : function()
318     {
319         return this.node.closest('table');
320          
321     },
322     
323     cellData : false,
324     
325     colWidths : false,
326     
327     toTableArray  : function()
328     {
329         var ret = [];
330         var tab = this.node.closest('tr').closest('table');
331         Array.from(tab.rows).forEach(function(r, ri){
332             ret[ri] = [];
333         });
334         var rn = 0;
335         this.colWidths = [];
336         var all_auto = true;
337         Array.from(tab.rows).forEach(function(r, ri){
338             
339             var cn = 0;
340             Array.from(r.cells).forEach(function(ce, ci){
341                 var c =  {
342                     cell : ce,
343                     row : rn,
344                     col: cn,
345                     colspan : ce.colSpan,
346                     rowspan : ce.rowSpan
347                 };
348                 if (ce.isEqualNode(this.node)) {
349                     this.cellData = c;
350                 }
351                 // if we have been filled up by a row?
352                 if (typeof(ret[rn][cn]) != 'undefined') {
353                     while(typeof(ret[rn][cn]) != 'undefined') {
354                         cn++;
355                     }
356                     c.col = cn;
357                 }
358                 
359                 if (typeof(this.colWidths[cn]) == 'undefined') {
360                     this.colWidths[cn] =   ce.style.width;
361                     if (this.colWidths[cn] != '') {
362                         all_auto = false;
363                     }
364                 }
365                 
366                 
367                 if (c.colspan < 2 && c.rowspan < 2 ) {
368                     ret[rn][cn] = c;
369                     cn++;
370                     return;
371                 }
372                 for(var j = 0; j < c.rowspan; j++) {
373                     if (typeof(ret[rn+j]) == 'undefined') {
374                         continue; // we have a problem..
375                     }
376                     ret[rn+j][cn] = c;
377                     for(var i = 0; i < c.colspan; i++) {
378                         ret[rn+j][cn+i] = c;
379                     }
380                 }
381                 
382                 cn += c.colspan;
383             }, this);
384             rn++;
385         }, this);
386         
387         // initalize widths.?
388         // either all widths or no widths..
389         if (all_auto) {
390             this.colWidths[0] = false; // no widths flag.
391         }
392         
393         
394         return ret;
395         
396     },
397     
398     
399     
400     
401     mergeRight: function()
402     {
403          
404         // get the contents of the next cell along..
405         var tr = this.node.closest('tr');
406         var i = Array.prototype.indexOf.call(tr.childNodes, this.node);
407         if (i >= tr.childNodes.length - 1) {
408             return; // no cells on right to merge with.
409         }
410         var table = this.toTableArray();
411         
412         if (typeof(table[this.cellData.row][this.cellData.col+this.cellData.colspan]) == 'undefined') {
413             return; // nothing right?
414         }
415         var rc = table[this.cellData.row][this.cellData.col+this.cellData.colspan];
416         // right cell - must be same rowspan and on the same row.
417         if (rc.rowspan != this.cellData.rowspan || rc.row != this.cellData.row) {
418             return; // right hand side is not same rowspan.
419         }
420         
421         
422         
423         this.node.innerHTML += ' ' + rc.cell.innerHTML;
424         tr.removeChild(rc.cell);
425         this.colspan += rc.colspan;
426         this.node.setAttribute('colspan', this.colspan);
427
428     },
429     
430     
431     mergeBelow : function()
432     {
433         var table = this.toTableArray();
434         if (typeof(table[this.cellData.row+this.cellData.rowspan]) == 'undefined') {
435             return; // no row below
436         }
437         if (typeof(table[this.cellData.row+this.cellData.rowspan][this.cellData.col]) == 'undefined') {
438             return; // nothing right?
439         }
440         var rc = table[this.cellData.row+this.cellData.rowspan][this.cellData.col];
441         
442         if (rc.colspan != this.cellData.colspan || rc.col != this.cellData.col) {
443             return; // right hand side is not same rowspan.
444         }
445         this.node.innerHTML =  this.node.innerHTML + rc.cell.innerHTML ;
446         rc.cell.parentNode.removeChild(rc.cell);
447         this.rowspan += rc.rowspan;
448         this.node.setAttribute('rowspan', this.rowspan);
449     },
450     
451     split: function()
452     {
453         if (this.node.rowSpan < 2 && this.node.colSpan < 2) {
454             return;
455         }
456         var table = this.toTableArray();
457         var cd = this.cellData;
458         this.rowspan = 1;
459         this.colspan = 1;
460         
461         for(var r = cd.row; r < cd.row + cd.rowspan; r++) {
462             
463             
464             
465             for(var c = cd.col; c < cd.col + cd.colspan; c++) {
466                 if (r == cd.row && c == cd.col) {
467                     this.node.removeAttribute('rowspan');
468                     this.node.removeAttribute('colspan');
469                     continue;
470                 }
471                  
472                 var ntd = this.node.cloneNode(); // which col/row should be 0..
473                 ntd.removeAttribute('id'); //
474                 //ntd.style.width  = '';
475                 ntd.innerHTML = '';
476                 table[r][c] = { cell : ntd, col : c, row: r , colspan : 1 , rowspan : 1   };
477             }
478             
479         }
480         this.redrawAllCells(table);
481         
482          
483         
484     },
485     
486     
487     
488     redrawAllCells: function(table)
489     {
490         
491          
492         var tab = this.node.closest('tr').closest('table');
493         Array.from(tab.rows).forEach(function(r, ri){
494             Array.from(r.cells).forEach(function(ce, ci){
495                 ce.parentNode.removeChild(ce);
496             });
497         });
498         for(var r = 0 ; r < table.length; r++) {
499             var re = tab.rows[r];
500             for(var c = 0 ; c < table[r].length; c++) {
501                 if (table[r][c].cell === false) {
502                     continue;
503                 }
504                 
505                 re.appendChild(table[r][c].cell);
506                  
507                 table[r][c].cell = false;
508             }
509         }
510         
511     },
512     updateWidths : function(table)
513     {
514         for(var r = 0 ; r < table.length; r++) {
515            
516             for(var c = 0 ; c < table[r].length; c++) {
517                 if (table[r][c].cell === false) {
518                     continue;
519                 }
520                 
521                 if (this.colWidths[0] != false && table[r][c].colspan < 2) {
522                     var el = Roo.htmleditor.Block.factory(table[r][c].cell);
523                     el.width = Math.floor(this.colWidths[c])  +'%';
524                     el.updateElement(el.node);
525                 }
526                 table[r][c].cell = false; // done
527             }
528         }
529     },
530     normalizeWidths : function(table)
531     {
532     
533         if (this.colWidths[0] === false) {
534             var nw = 100.0 / this.colWidths.length;
535             this.colWidths.forEach(function(w,i) {
536                 this.colWidths[i] = nw;
537             },this);
538             return;
539         }
540     
541         var t = 0, missing = [];
542         
543         this.colWidths.forEach(function(w,i) {
544             //if you mix % and
545             this.colWidths[i] = this.colWidths[i] == '' ? 0 : (this.colWidths[i]+'').replace(/[^0-9]+/g,'')*1;
546             var add =  this.colWidths[i];
547             if (add > 0) {
548                 t+=add;
549                 return;
550             }
551             missing.push(i);
552             
553             
554         },this);
555         var nc = this.colWidths.length;
556         if (missing.length) {
557             var mult = (nc - missing.length) / (1.0 * nc);
558             var t = mult * t;
559             var ew = (100 -t) / (1.0 * missing.length);
560             this.colWidths.forEach(function(w,i) {
561                 if (w > 0) {
562                     this.colWidths[i] = w * mult;
563                     return;
564                 }
565                 
566                 this.colWidths[i] = ew;
567             }, this);
568             // have to make up numbers..
569              
570         }
571         // now we should have all the widths..
572         
573     
574     },
575     
576     shrinkColumn : function()
577     {
578         var table = this.toTableArray();
579         this.normalizeWidths(table);
580         var col = this.cellData.col;
581         var nw = this.colWidths[col] * 0.8;
582         if (nw < 5) {
583             return;
584         }
585         var otherAdd = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
586         this.colWidths.forEach(function(w,i) {
587             if (i == col) {
588                  this.colWidths[i] = nw;
589                 return;
590             }
591             this.colWidths[i] += otherAdd
592         }, this);
593         this.updateWidths(table);
594          
595     },
596     growColumn : function()
597     {
598         var table = this.toTableArray();
599         this.normalizeWidths(table);
600         var col = this.cellData.col;
601         var nw = this.colWidths[col] * 1.2;
602         if (nw > 90) {
603             return;
604         }
605         var otherSub = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
606         this.colWidths.forEach(function(w,i) {
607             if (i == col) {
608                 this.colWidths[i] = nw;
609                 return;
610             }
611             this.colWidths[i] -= otherSub
612         }, this);
613         this.updateWidths(table);
614          
615     },
616     deleteRow : function()
617     {
618         // delete this rows 'tr'
619         // if any of the cells in this row have a rowspan > 1 && row!= this row..
620         // then reduce the rowspan.
621         var table = this.toTableArray();
622         // this.cellData.row;
623         for (var i =0;i< table[this.cellData.row].length ; i++) {
624             var c = table[this.cellData.row][i];
625             if (c.row != this.cellData.row) {
626                 c.rowspan--;
627                 continue;
628             }
629             if (c.rowspan > 1) {
630                 c.rowspan--;
631             }
632         }
633         table.splice(this.cellData.row,1);
634         this.redrawAllCells(table);
635         
636     },
637     deleteColumn : function()
638     {
639         var table = this.toTableArray();
640         // this.cellData.row;
641         for (var i =0;i< table.length ; i++) {
642             var c = table[i][this.cellData.col];
643             if (c.col != this.cellData.col) {
644                 table[i][this.cellData.col].colspan--;
645             }
646             table[i].splice(this.cellData.col,1);
647         }
648         
649         this.redrawAllCells(table);
650     }
651     
652     
653     
654     
655 })
656