1 /**
  2  * @class Ozone.layout.ManageWidgetsContainer
  3  * @extends Ext.form.Panel
  4  */
  5 Ext.define('Ozone.layout.ManageWidgetsContainer', {
  6     extend: 'Ext.form.Panel', /** @lends Ozone.layout.ManageWidgetsContainer */
  7     alias: ['widget.owfManageWidgetsContainer', 'widget.Ozone.layout.ManageWidgetsContainer'],
  8     requires: [
  9         'Ext.layout.container.VBox'
 10     ],
 11 
 12     layout: {
 13       type: 'vbox',
 14       align: 'stretch'
 15     },
 16 
 17     border: false,
 18 
 19     cls: 'mwc-manageWidgetsContainer',
 20 
 21     mixins: {
 22         focus: 'Ozone.components.focusable.CircularFocus'
 23     },
 24 
 25     loadGridData: function(){
 26     this.setLoading(true);
 27     var existingWidgetStore = this.dashboardContainer.widgetStore;
 28 
 29         // Add removed flag for delete column
 30         var storeData = [];
 31         for (var i = 0; i < existingWidgetStore.getCount(); i++) {
 32             var widget = existingWidgetStore.getAt(i).data;
 33             widget["removed"] = false;
 34             if (widget.definitionVisible && widget.widgetTypes != null) {
 35               //loop through widget types and find 'standard'
 36               var widgetTypes = widget.widgetTypes;
 37               var isStandard = false;
 38               for (var j = 0 ; j < widgetTypes.length ; j++) {
 39                 var widgetType = widgetTypes[j];
 40                 if (widgetType != null && widgetType.name == 'standard') {
 41                   isStandard = true;
 42                   break;
 43                 }
 44               }
 45 
 46               //only show standard and visible widgetdefs
 47               if (isStandard) {
 48                 storeData.push(widget);
 49               }
 50             }
 51         }
 52 
 53         this.store.loadData(storeData);
 54         this.setLoading(false);
 55     },
 56 
 57     refreshOriginalWidgetNames: function(params) {
 58         this.setLoading(true);
 59         var scope = this;
 60         Ozone.pref.PrefServer.findWidgets({
 61           onSuccess: function(records) {
 62                 // Update originalWidgetNames
 63                 for (var i = 0; i < records.length; i++) {
 64                     scope.dashboardContainer.originalWidgetNames[records[i].path] = records[i].value.namespace;
 65                 }
 66 
 67                 if (params.reloadGrid == true) {
 68                     scope.loadGridData();
 69                 } else {
 70                     scope.setLoading(false);
 71                 }
 72                 params.callback();
 73             },
 74             
 75             onFailure: function() {
 76                 scope.setLoading(false);
 77                 params.callback();
 78             }
 79         });
 80     },
 81     initComponent: function() {
 82         var scope = this;
 83           
 84           // Set up a model to use in our Store
 85         var widgetJsonModel = Ext.define('widgetJsonModel', {
 86             extend: 'Ext.data.Model',
 87             fields: ['widgetGuid', {name:'name', sortType:Ext.data.SortTypes.asUCString}, 'visible', 'removed', 'headerIcon','tags', 'editable', '_tags','_editable']
 88         });
 89           
 90           // Create data store
 91         this.store = Ext.create('Ext.data.JsonStore', {
 92             model:'widgetJsonModel',
 93             sorters: [{
 94                 property : 'name',
 95                 direction: 'ASC' // or 'DESC' (case sensitive for local sorting)
 96             }],
 97             listeners : {
 98                 datachanged : {
 99                     fn : function(s) {
100                         //preprocess store
101                         var store = s;
102                         store.suspendEvents();
103                         for (var i = 0 ; i < store.getCount() ; i++) {
104                             var rec = store.getAt(i);
105                             var recData = rec.data;
106                             if (Ext.isArray(recData.tags) && recData.tags.length > 0) {
107                                 var _tags = '';
108                                 var editable = true;
109                                 for (var j = 0 ; j < recData.tags.length ; j++) {
110                                     _tags +=recData.tags[j].name;
111                                     if (j < recData.tags.length -1) {
112                                         _tags+=', ';
113                                     }
114                                     if (recData.tags[j].editable === false) {
115                                         editable = false;
116                                     }
117                                 }
118                                 rec.set('_tags',_tags);
119                                 rec.set('_editable',editable);
120                                 rec.commit();
121                             }
122                             else {
123                                 rec.set('_tags','');
124                                 rec.set('_editable',true);
125                                 rec.commit();
126                             }
127                         }
128                         store.resumeEvents();
129                     },
130                     scope : this
131                 }
132             }
133         });
134 
135         // Create editable Dashboard Name field
136         this.nameField = Ext.create('Ext.form.field.Text', {
137             maxLength: 200,
138             allowBlank: false,
139             blankText: Ozone.layout.DialogMessages.view_dashboardNameField_blankText,
140             validator: function(value) {
141                 if (this.value) {
142                     if (this.value.toUpperCase() != value.toUpperCase()) {                  
143                         for (var i = 0; i < store.data.items.length; i++) {
144                             if(value.charAt(0) === ' ' || value.charAt(value.length-1) === ' ') {
145                                 return Ozone.util.ErrorMessageString.invalidDashboardNameMsg;
146                             }
147                         }
148                     } 
149                     else {
150                         if(this.value.charAt(0) === ' ' || this.value.charAt(this.value.length-1) === ' ') {
151                             return Ozone.util.ErrorMessageString.invalidDashboardNameMsg;
152                         }                    
153                     }
154                     return true;
155                 }
156                 return true;
157             }
158         });
159             
160         this.tagsField = Ext.create('Ext.form.field.Text', {
161             allowBlank: true,
162             validator: function(val) {
163                 if (val.match(Ozone.config.carousel.restrictedTagGroupsRegex) != null) {
164                 return '"'+val+'"'+Ozone.util.ErrorMessageString.restrictedTagError;
165                 }
166                 else {
167                 return true;
168                 }
169             }
170         });
171           
172         var resetTitleAction = Ext.create('Ext.Action', {
173             text: 'Reset Title',
174             handler: function(widget, event) {
175                 var rec = scope.widgetGrid.getSelectionModel().getSelection()[0];
176                 if (rec) {
177                     scope.refreshOriginalWidgetNames({
178                         reloadGrid: false,
179                         callback: function() {
180                             var originalName = rec.get('originalName');
181                             rec.set('name', originalName);
182                         }
183                     });
184                 }
185             }
186         });
187         var resetAllTitlesAction = Ext.create('Ext.Action', {
188             text: 'Reset All Titles',
189             handler: function(widget, event) {
190                 var records = scope.widgetGrid.getStore().getRange();
191                 scope.refreshOriginalWidgetNames({
192                     reloadGrid: false,
193                     callback: function() {
194                         for (var i = 0; i < records.length; i++) {
195                             if (records[i].data.editable) {
196                                 // Reset titles
197                                 var originalName = records[i].get('originalName');
198                                 records[i].data.name = originalName;
199 
200                                 // Update tags
201                                 var tags = [];
202                                 if (records[i].data._tags.length > 0 && records[i].data._tags != '') {
203                                     var splits = records[i].data._tags.split(',');
204                                     for (var j = 0 ; j < splits.length ; j++) {
205                                         var name = Ext.String.trim(splits[j]);
206                                         if (name != '') {
207                                             tags.push({
208                                                 name: name,
209                                                 visible: true,
210 
211                                                 //todo use position to order groups for now just set to -1
212                                                 position: -1,
213                                                 editable: records[i].data._editable
214                                             });
215                                         }
216                                     }
217                                 }
218                                 records[i].data.tags = tags;
219                             }
220                         }
221                         scope.store.loadRecords(records, {
222                             addRecords: false
223                         });
224                     }
225                 });
226             }
227         });
228 
229         var contextMenu = Ext.create('Ext.menu.Menu', {
230             items: [
231                 resetTitleAction,
232                 resetAllTitlesAction
233             ]
234         });
235 
236         this.widgetGrid = Ext.create('Ext.grid.GridPanel', {
237             ddGroup: 'ddWidgets',
238             cls:'mwc-widgetGridPanel',
239             store: this.store,
240             autoScroll: true,
241             scroll: true,
242             viewConfig: {
243                 listeners: {
244                     itemcontextmenu: {
245                         fn: function (view, rec, node, index, e) {
246                             e.stopEvent();
247                             this.dashboardContainer.modalWindowManager.register(contextMenu);
248                             contextMenu.showAt(e.getXY());
249                             this.dashboardContainer.modalWindowManager.bringToFront(contextMenu);
250                             return false;
251                         },
252                         scope: this
253                     },
254                     itemkeydown: {
255                         fn: function (view, record, item, index, evt) {
256                             switch (evt.getKey()) {
257                                 case Ext.EventObject.S:
258                                     record.set('visible', !record.get('visible'));
259                                     view.refreshNode(index);
260                                     
261                                     //Fire the column's appropriate checked/unchecked method
262                                     var col = this.down('#showCheckColumn');
263                                     record.get('visible') ? col.onChecked(view, record) : col.onUnchecked(view, record);
264                                     break;
265 
266                                 case Ext.EventObject.D:
267                                     record.set('removed', !record.get('removed'));
268                                     view.refreshNode(index);
269                                     
270                                     //Fire the column's appropriate checked/unchecked method
271                                     var col = this.down('#deleteCheckColumn');
272                                     record.get('removed') ? col.onChecked(view, record) : col.onUnchecked(view, record);
273                                     break;
274 
275                                 case Ext.EventObject.R:
276                                     if(!Ext.EventObject.hasModifier()) {
277                                         //Get the 3rd cell (in the Tags column) of the selected row to get starting point
278                                         //for XY coordinates to place the context menu inside the selected row appropriately.
279                                         var thirdCellInSelectedRow = Ext.select('tr[class*=x-grid-row-focused]:first td').item(2);
280                                         var coordinates = thirdCellInSelectedRow.getXY();
281                                         coordinates[0] -= 5;
282                                         coordinates[1] += 10; // Approximates a good location for the context menu to appear
283 
284                                         this.dashboardContainer.modalWindowManager.register(contextMenu);
285                                         contextMenu.showAt(coordinates);
286                                         this.dashboardContainer.modalWindowManager.bringToFront(contextMenu);
287 
288                                         //Add a listener to the context menu so that when it is closed with ESC it will 
289                                         //move focus back to the widget grid
290                                         contextMenu.addListener('hide', function(view, record, item) {
291                                             //Set on a defer to wait until focus is moved to the banner, then pulls focus back
292                                             Ext.defer(function() {
293                                                 //Set focus on the widget grid, which is 3 levels down from the manageWidgetsWindow
294                                                 Ext.getCmp("manageWidgetsWindowId").getComponent(0).getComponent(0).getComponent(0).focus();
295                                             }, 100, this);
296 
297                                             //Remove this listener after the context menu is hidden
298                                             contextMenu.removeListener('hide', arguments.callee);
299                                         });
300                                     }
301                                     break;
302 
303                                 case Ext.EventObject.ENTER:
304                                     var header = view.headerCt.getHeaderAtIndex(1);
305                                     view.editingPlugin.startEdit(record, header);
306                             }
307                         },
308                         scope: this
309                     },
310                     afterrender: function (cmp) {
311                         cmp.getEl().on('keyup', function(evt) {
312                             //prevent ESC key from escaping to the 
313                             //body.  Since ESC keydown is stopped
314                             //internally to the grid, we need to to
315                             //stop the keyup as well
316                             if (evt.getKey() === evt.ESC)
317                                 evt.stopPropagation();
318                         });
319                     }
320                 }
321             },
322             columns: [
323                 {
324                     xtype: 'gridcolumn',
325                     text: '<span class="gridHdr">Icon</span>', 
326                     dataIndex: 'headerIcon', 
327                     width: 36,
328                     sortable: false,
329                     resizable: false,
330                     menuDisabled: true,
331                     renderer: function (val, metaData, record, rowIndex, colIndex, store) {
332                         if (record.get('editable') == false) {
333                             metaData.attr = 'ext:qtip="This widget belongs to a Group and may not be edited or deleted"';
334                             metaData.tdCls += ' x-item-disabled';
335                         }
336                 
337                         var retVal = '<div><img width=\"24px\" height=\"24px\" src="'+val+'">';
338                         retVal += '</div>';
339 
340                         return  retVal;
341                     }
342                 },
343                 {
344                     xtype: 'textcolumn',
345                     text: '<span class="gridHdr">Widget</span>', 
346                     dataIndex: 'name',
347                     field: this.nameField,
348                     width: 176,
349                     resizable: false,
350                     menuDisabled: true,
351                     renderer: function(value, metaData, record, rowIndex, colIndex, store) {
352                         if(record.get('editable') != false && record.get('_editable')!= false) {
353                             metaData.tdCls += ' manage-editable';
354                         }
355                         if (record.get('editable') == false) {
356                             metaData.tdAttr = 'data-qtip="This widget belongs to a Group and may not be edited or deleted"';
357                             metaData.tdCls += ' x-item-disabled';
358                         }
359                         return Ext.util.Format.htmlEncode(value);
360                     }
361                 },
362                 {
363                     xtype:'textcolumn',
364                     menuDisabled: true,
365                     text: '<span class="gridHdr">Tags</span>',
366                     field : this.tagsField,
367                     dataIndex: '_tags',
368                     width: 170,
369                     sortable: false,
370                     resizable: false,
371                     renderer: function(value, metaData, record, rowIndex, colIndex, store) {
372                         metaData.tdCls = 'hd-tags';
373                         if (record.get('_editable') == false) {
374                             metaData.tdCls += ' x-item-disabled';
375                         }
376                         if (record.get('editable') == false) {
377                             metaData.tdAttr = 'data-qtip="This widget belongs to a Group and may not be edited or deleted"';
378                             metaData.tdCls += ' x-item-disabled';
379                         }
380                         if(record.get('editable') != false && record.get('_editable')!= false)
381                         {
382                             metaData.tdCls += ' manage-editable';
383                             var encValue = Ext.util.Format.htmlEncode(value);
384                             //work-around to html-encode html tags - can't just directly htmlEncode stuff
385                             //so we inject an XTemplate to handle it
386                             metaData.tdAttr = 'data-qtip="'+new Ext.XTemplate('{foo:htmlEncode}').apply({
387                                 foo:encValue
388                             })+'"';
389                         }
390                         return Ext.util.Format.htmlEncode(value);  
391                     }
392                 },
393                 {
394                     xtype:'checkcolumn',
395                     itemId: 'showCheckColumn',
396                     text: '<span class="gridHdrShow"><span class="x-grid-column-checkbox-off"></span><span class="underline">S</span>how</span>',
397                     dataIndex: 'visible',
398                     width: 90,
399                     sortable: false,
400                     tdCls:'gridCellShowWidgets-chkbox',
401                     listeners: {
402                         afterrender: {
403                             scope: this,
404                             single: true,
405                             fn: function(cmp) {
406                                 this.store.mon(this.store, 'datachanged', function() {
407                                     if(this.find('visible', false) === -1) {
408                                         //All rows are checked on open, so check the column's overall checkbox
409                                         cmp.toggleHeader(true);
410                                     }
411                                 });
412                             }
413                         }
414                     }
415                 },
416                 {
417                     xtype:'checkcolumn',
418                     itemId: 'deleteCheckColumn',
419                     text: '<span class="gridHdrDelete"><span class="x-grid-column-checkbox-off"></span><span class="underline">D</span>elete</span>',
420                     dataIndex: 'removed',
421                     width: 90,
422                     sortable: false,
423                     tdCls:'gridCellDeleteWidgets-chkbox'
424                 }
425             ],
426             selType:'rowmodel',
427             frame: false,
428             enableDragDrop: true,
429             enableHdMenu: false,
430             enableColumnResize: false,
431             plugins: [
432                 Ext.create('Ext.grid.plugin.CellEditing', {
433                     clicksToEdit: 1
434                 }),
435                 new Ozone.components.focusable.FocusableGridPanel()
436             ],
437             flex: 1,
438             listeners: {
439                 //Handle Before Edit has taken place
440                 beforeedit: function(e) {
441                     var isCellEditable = function(record) {
442                         if (record.get('removed') || record.get('_editable') === false ||  record.get('editable') === false) {
443                             return false;
444                         }
445                         return true;
446                     };
447                     return isCellEditable(e.record);
448                 },
449                 //Handle After Edit has taken place
450                 edit: function(editor, e) {
451                     if(editor.editors.items[e.column.id]) {
452                         e.record.set('_tags',editor.editors.items[e.column.id].field.value.toLowerCase());
453                     }
454                 }
455             }
456         });
457 
458         this.items = [
459             this.widgetGrid
460         ];
461 
462         /***
463          * IMPLEMENT HEADER CLICKED.
464          */
465         this.widgetGrid.headerCt.on('headerclick', function(ct, column, evt, htmlElem) {
466                 if(typeof column.onGridHeaderCtClick == 'function') {
467                     column.onGridHeaderCtClick(this.widgetGrid, evt, htmlElem);
468                 }
469             }, 
470             this
471         );
472 
473         this.keys = [];
474         this.keys.push({
475             key: Ext.EventObject.ENTER,
476             fn: function (k,e) {
477                 var button = this.getFooterToolbar().getComponent('saveButton');
478                 if (button) {
479                     e.button = 0;
480                     button.onClick(e);
481                 }
482             },
483             scope: this
484         });
485           
486         this.dockedItems = [{
487             xtype: 'toolbar',
488             dock: 'bottom',
489             defaults: {
490                 minWidth: this.minButtonWidth
491             },
492             layout: {
493                 type: 'hbox',
494                 pack: 'end'
495             },
496             items: [
497                 {
498                     xtype:'button',
499                     scale: 'small',
500                     text: Ozone.layout.MessageBoxButtonText.ok,
501                     itemId: 'saveButton',
502                     //                iconCls: 'okSaveBtnIcon',
503                     handler: function() {
504                         scope.onSaveClick();
505                     },
506                     listeners: {
507                         // afterrender: function(button) {
508                         //     button.getEl().on('keydown', function(e) {
509                         //         e.stopPropagation();
510                         //     });
511                         // }
512                     }
513                 },
514                 {
515                     xtype:'button',
516                     scale: 'small',
517                     itemId: 'cancelBtn',
518                     text: Ozone.layout.MessageBoxButtonText.cancel,
519                     // iconCls: 'cancelBtnIcon',
520                     handler: function() {
521                         Ext.getCmp(scope.winId).close();
522                     }
523                 }
524             ]
525         }];
526           
527         //Need this to get rid of destory errors with ExtJS
528         this.on('beforedestroy', function(cmp) {
529             cmp.dockedItems = null;
530         });
531 
532         this.on({
533             afterrender: {
534                 fn: function(cmp) {
535                     var winCmp = Ext.getCmp(cmp.winId);
536 
537                     winCmp.on('show', function() {
538                         cmp.loadGridData();
539                     });
540 
541                     winCmp.setupFocus(cmp.widgetGrid.getView().getEl(), 
542                     cmp.down('#cancelBtn').getFocusEl());
543 
544                 },
545                 scope: this
546             }
547         });
548 
549         //Ensure the shadow follows the window in IE
550         this.on('resize', function() {
551             this.syncShadow();
552         });
553 
554         this.callParent();
555     },
556 
557     onSaveClick: function() {
558         // Create array of widgets
559         var gridData = this.widgetGrid.store.data.items;
560         var widgetsToUpdate = [];
561         var widgetsToDelete = [];
562         var widgetGuidsToDelete = [];
563         //this.dashboardContainer.widgetNames = {};
564         for (var i = 0; i < gridData.length; i++) {
565 
566             if (gridData[i].data.editable) {
567                 if (!gridData[i].data.removed) {
568                     var recData = gridData[i].data;
569                     var tags = [];
570                     if (recData._tags.length > 0 && recData._tags != '') {
571                         var splits = recData._tags.split(',');
572                         for (var j = 0 ; j < splits.length ; j++) {
573                             var name = Ext.String.trim(splits[j]);
574                             if (name != '') {
575                                 tags.push({
576                                     name: name,
577                                     visible: true,
578 
579                                     //todo use position to order groups for now just set to -1
580                                     position: -1,
581                                     editable: recData._editable
582                                 });
583                             }
584                         }
585                     }
586 
587                     var widget = {
588                         guid: gridData[i].data.widgetGuid,
589                         visible: gridData[i].data.visible,
590                         tags: tags
591                     };
592                     var originalName = gridData[i].data.originalName;
593                     if (gridData[i].data.name !== originalName) {
594                         widget.name = gridData[i].data.name;
595                     }
596                     widgetsToUpdate.push(widget);
597 
598                 } else {
599                     widgetGuidsToDelete.push(gridData[i].data.widgetGuid);
600                     widgetsToDelete.push({
601                         guid: gridData[i].data.widgetGuid,
602                         name: gridData[i].data.name,
603                         headerIcon: gridData[i].data.headerIcon,
604                         image: gridData[i].data.image
605                     });
606                 }
607             }
608         }
609 
610         var scope = this;
611         function onSuccess() {
612             //Call method to refresh the 'widget launch menu' widgets.
613             scope.dashboardContainer.retrieveUpdatedWidgets();
614         }
615         
616         function onFailure() {
617             Ozone.Msg.alert(Ozone.util.ErrorMessageString.saveUpdatedWidgets, Ozone.util.ErrorMessageString.saveUpdatedWidgetsMsg,
618                 null, null, null, scope.dashboardContainer.modalWindowManager);
619         }
620         
621         Ext.getCmp(this.winId).close();
622         Ozone.pref.PrefServer.updateAndDeleteWidgets({
623             widgetsToUpdate:widgetsToUpdate,
624             widgetGuidsToDelete:[], 
625             updateOrder:false,
626             onSuccess:onSuccess, 
627             onFailure:onFailure
628         });
629       
630         if (widgetGuidsToDelete.length > 0) {
631           
632             Ext.create('Ext.window.Window', {
633                 title: 'Deleting Widgets',
634                 cls: 'delete-widgets-window',
635                 height: 550,
636                 ownerCt: scope.dashboardContainer,
637                 dashboardContainer: scope.dashboardContainer,
638                 constrain: Ext.isIE,
639                 constrainHeader: true,
640                 width: 600,
641                 layout: 'fit',
642                 resizable: false,
643                 modal: true,
644                 items: {
645                     xtype: 'deletewidgetspanel',
646                     delWidgets: widgetsToDelete,
647                     dashboardContainer: scope.dashboardContainer
648                 },
649                 listeners: {
650                     show: function (cmp) {
651                         //Ensure its on top
652                         cmp.dashboardContainer.modalWindowManager.register(cmp);
653                         cmp.dashboardContainer.modalWindowManager.bringToFront(cmp);
654                         
655                         /*
656                          * Needs to be deferred or it will happen before the
657                          * window close in IE
658                          */
659                         Ext.defer(function() {
660                             this.focus();
661                         }, 100, cmp.getComponent('topdeletepanel').okBtn);
662                     }
663                 }
664             }).show();
665             
666         }
667     }
668 });