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