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.floatingWindowManager.register(contextMenu);
248                             contextMenu.showAt(e.getXY());
249                             this.dashboardContainer.floatingWindowManager.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                                     break;
261 
262                                 case Ext.EventObject.D:
263                                     record.set('removed', !record.get('removed'));
264                                     view.refreshNode(index);
265                                     break;
266 
267                                 case Ext.EventObject.R:
268                                     if(!Ext.EventObject.hasModifier()) {
269                                         //Get the 3rd cell (in the Tags column) of the selected row to get starting point
270                                         //for XY coordinates to place the context menu inside the selected row appropriately.
271                                         var thirdCellInSelectedRow = Ext.select('tr[class*=x-grid-row-focused]:first td').item(2);
272                                         var coordinates = thirdCellInSelectedRow.getXY();
273                                         coordinates[0] -= 5;
274                                         coordinates[1] += 10; // Approximates a good location for the context menu to appear
275                                         contextMenu.showAt(coordinates);
276 
277                                         //Add a listener to the context menu so that when it is closed with ESC it will 
278                                         //move focus back to the widget grid
279                                         contextMenu.addListener('hide', function(view, record, item) {
280                                             //Set on a defer to wait until focus is moved to the banner, then pulls focus back
281                                             Ext.defer(function() {
282                                                 //Set focus on the widget grid, which is 3 levels down from the manageWidgetsWindow
283                                                 Ext.getCmp("manageWidgetsWindowId").getComponent(0).getComponent(0).getComponent(0).focus();
284                                             }, 100, this);
285 
286                                             //Remove this listener after the context menu is hidden
287                                             contextMenu.removeListener('hide', arguments.callee);
288                                         });
289                                     }
290                                     break;
291 
292                                 case Ext.EventObject.ENTER:
293                                     var header = view.headerCt.getHeaderAtIndex(1);
294                                     view.editingPlugin.startEdit(record, header);
295                             }
296                         },
297                         scope: this
298                     },
299                     afterrender: function (cmp) {
300                         cmp.getEl().on('keyup', function(evt) {
301                             //prevent ESC key from escaping to the 
302                             //body.  Since ESC keydown is stopped
303                             //internally to the grid, we need to to
304                             //stop the keyup as well
305                             if (evt.getKey() === evt.ESC)
306                                 evt.stopPropagation();
307                         });
308                     }
309                 }
310             },
311             columns: [
312                 {
313                     xtype: 'gridcolumn',
314                     text: '<span class="gridHdr">Icon</span>', 
315                     dataIndex: 'headerIcon', 
316                     width: 36,
317                     sortable: false,
318                     resizable: false,
319                     menuDisabled: true,
320                     renderer: function (val, metaData, record, rowIndex, colIndex, store) {
321                         if (record.get('editable') == false) {
322                             metaData.attr = 'ext:qtip="This widget belongs to a Group and may not be edited or deleted"';
323                             metaData.tdCls += ' x-item-disabled';
324                         }
325                 
326                         var retVal = '<div><img width=\"24px\" height=\"24px\" src="'+val+'">';
327                         retVal += '</div>';
328 
329                         return  retVal;
330                     }
331                 },
332                 {
333                     xtype: 'textcolumn',
334                     text: '<span class="gridHdr">Widget</span>', 
335                     dataIndex: 'name',
336                     field: this.nameField,
337                     width: 176,
338                     resizable: false,
339                     menuDisabled: true,
340                     renderer: function(value, metaData, record, rowIndex, colIndex, store) {
341                         if(record.get('editable') != false && record.get('_editable')!= false) {
342                             metaData.tdCls += ' manage-editable';
343                         }
344                         if (record.get('editable') == false) {
345                             metaData.tdAttr = 'data-qtip="This widget belongs to a Group and may not be edited or deleted"';
346                             metaData.tdCls += ' x-item-disabled';
347                         }
348                         return Ext.util.Format.htmlEncode(value);
349                     }
350                 },
351                 {
352                     xtype:'textcolumn',
353                     menuDisabled: true,
354                     text: '<span class="gridHdr">Tags</span>',
355                     field : this.tagsField,
356                     dataIndex: '_tags',
357                     width: 170,
358                     resizable: false,
359                     renderer: function(value, metaData, record, rowIndex, colIndex, store) {
360                         metaData.tdCls = 'hd-tags';
361                         if (record.get('_editable') == false) {
362                             metaData.tdCls += ' x-item-disabled';
363                         }
364                         if (record.get('editable') == false) {
365                             metaData.tdAttr = 'data-qtip="This widget belongs to a Group and may not be edited or deleted"';
366                             metaData.tdCls += ' x-item-disabled';
367                         }
368                         if(record.get('editable') != false && record.get('_editable')!= false)
369                         {
370                             metaData.tdCls += ' manage-editable';
371                             var encValue = Ext.util.Format.htmlEncode(value);
372                             //work-around to html-encode html tags - can't just directly htmlEncode stuff
373                             //so we inject an XTemplate to handle it
374                             metaData.tdAttr = 'data-qtip="'+new Ext.XTemplate('{foo:htmlEncode}').apply({
375                                 foo:encValue
376                             })+'"';
377                         }
378                         return Ext.util.Format.htmlEncode(value);  
379                     }
380                 },
381                 {
382                     xtype:'checkcolumn',
383                     text: '<span class="gridHdrShow"><span class="x-grid-column-checkbox-off"></span><span class="underline">S</span>how</span>',
384                     dataIndex: 'visible',
385                     width: 90,
386                     menuDisabled: true,
387                     sortable: false,
388                     resizable: false,
389                     tdCls:'gridCellShowWidgets-chkbox',
390                     cellDisabled: function(record) {
391                         return record.data['removed'] || record.get('editable') === false;
392                     },
393                     renderer : function(value, metaData, record, rowIndex, colIndex, store) {
394                         var thisColumn = this.columns[colIndex];
395                         var cls = [thisColumn._checkBoxCls];
396                         if (!value || 'false' == value) {
397                             cls.push(thisColumn._checkBoxClsOff);
398                         } else {
399                             cls.push(thisColumn._checkBoxClsOn);
400                         }
401                         if (record.get('editable') == false) {
402                             metaData.tdAttr = 'data-qtip="This widget belongs to a Group and may not be edited or deleted"';
403                             metaData.tdCls += ' x-item-disabled';
404                         }
405                         return '<div class="' + cls.join(' ') + '"> </div>';
406                     }
407                 },
408                 {
409                     xtype:'checkcolumn',
410                     text: '<span class="gridHdrDelete"><span class="x-grid-column-checkbox-off"></span><span class="underline">D</span>elete</span>',
411                     dataIndex: 'removed',
412                     width: 90,
413                     menuDisabled: true,
414                     sortable: false,
415                     resizable: false,
416                     tdCls:'gridCellDeleteWidgets-chkbox',
417                     cellDisabled : function(record) {
418                         if (record.get('_editable') === false || record.get('editable') === false) {
419                             return true;
420                         }
421                         else {
422                             return false;
423                         }
424                     },
425                     onChecked: function(view, record) {
426                         view.addRowCls(record.index, "x-item-disabled");
427                     },
428                     onUnchecked: function(view, record) {
429                         view.removeRowCls(record.index, "x-item-disabled");
430                     },
431                     renderer : function(value, metaData, record, rowIndex, colIndex, store) {
432                         var thisColumn = this.columns[colIndex];
433                         var cls = [thisColumn._checkBoxCls];
434                         if (!value || 'false' == value) {
435                             cls.push(thisColumn._checkBoxClsOff);
436                         } else {
437                             cls.push(thisColumn._checkBoxClsOn);
438                         }
439                         if (record.get('editable') == false) {
440                             metaData.tdAttr = 'data-qtip="This widget belongs to a Group and may not be edited or deleted"';
441                             metaData.tdCls += ' x-item-disabled';
442                         }
443                         return '<div class="' + cls.join(' ') + '"> </div>';
444                     }
445                 }
446             ],
447             selType:'rowmodel',
448             frame: false,
449             enableDragDrop: true,
450             enableHdMenu: false,
451             enableColumnResize: false,
452             plugins: [
453                 Ext.create('Ext.grid.plugin.CellEditing', {
454                     clicksToEdit: 1
455                 }),
456                 new Ozone.components.focusable.FocusableGridPanel()
457             ],
458             flex: 1,
459             listeners: {
460                 //Handle Before Edit has taken place
461                 beforeedit: function(e) {
462                     var isCellEditable = function(record) {
463                         if (record.get('removed') || record.get('_editable') === false ||  record.get('editable') === false) {
464                             return false;
465                         }
466                         return true;
467                     };
468                     return isCellEditable(e.record);
469                 },
470                 //Handle After Edit has taken place
471                 edit: function(editor, e) {
472                     if(editor.editors.items[e.column.id]) {
473                         e.record.set('_tags',editor.editors.items[e.column.id].field.value.toLowerCase());
474                     }
475                 }
476             }
477         });
478 
479         this.items = [
480             this.widgetGrid
481         ];
482 
483         /***
484          * IMPLEMENT HEADER CLICKED.
485          */
486         this.widgetGrid.headerCt.on('headerclick', function(ct, column, evt, htmlElem) {
487                 if(typeof column.onGridHeaderCtClick == 'function') {
488                     column.onGridHeaderCtClick(this.widgetGrid, evt, htmlElem);
489                 }
490             }, 
491             this
492         );
493 
494         this.keys = [];
495         this.keys.push({
496             key: Ext.EventObject.ENTER,
497             fn: function (k,e) {
498                 var button = this.getFooterToolbar().getComponent('saveButton');
499                 if (button) {
500                     e.button = 0;
501                     button.onClick(e);
502                 }
503             },
504             scope: this
505         });
506           
507         this.dockedItems = [{
508             xtype: 'toolbar',
509             dock: 'bottom',
510             defaults: {
511                 minWidth: this.minButtonWidth
512             },
513             layout: {
514                 type: 'hbox',
515                 pack: 'end'
516             },
517             items: [
518                 {
519                     xtype:'button',
520                     scale: 'small',
521                     text: Ozone.layout.MessageBoxButtonText.ok,
522                     itemId: 'saveButton',
523                     //                iconCls: 'okSaveBtnIcon',
524                     handler: function() {
525                         scope.onSaveClick();
526                     },
527                     listeners: {
528                         // afterrender: function(button) {
529                         //     button.getEl().on('keydown', function(e) {
530                         //         e.stopPropagation();
531                         //     });
532                         // }
533                     }
534                 },
535                 {
536                     xtype:'button',
537                     scale: 'small',
538                     itemId: 'cancelBtn',
539                     text: Ozone.layout.MessageBoxButtonText.cancel,
540                     // iconCls: 'cancelBtnIcon',
541                     handler: function() {
542                         Ext.getCmp(scope.winId).close();
543                     }
544                 }
545             ]
546         }];
547           
548         //Need this to get rid of destory errors with ExtJS
549         this.on('beforedestroy', function(cmp) {
550             cmp.dockedItems = null;
551         });
552 
553         this.on({
554             afterrender: {
555                 fn: function(cmp) {
556                     var winCmp = Ext.getCmp(cmp.winId);
557 
558                     winCmp.on('show', function() {
559                         cmp.loadGridData();
560                     });
561 
562                     winCmp.setupFocus(cmp.widgetGrid.getView().getEl(), 
563                     cmp.down('#cancelBtn').getFocusEl());
564 
565                 },
566                 scope: this
567             }
568         });
569 
570         //Ensure the shadow follows the window in IE
571         this.on('resize', function() {
572             this.syncShadow();
573         });
574 
575         this.callParent();
576     },
577 
578     onSaveClick: function() {
579         // Create array of widgets
580         var gridData = this.widgetGrid.store.data.items;
581         var widgetsToUpdate = [];
582         var widgetsToDelete = [];
583         var widgetGuidsToDelete = [];
584         //this.dashboardContainer.widgetNames = {};
585         for (var i = 0; i < gridData.length; i++) {
586 
587             if (gridData[i].data.editable) {
588                 if (!gridData[i].data.removed) {
589                     var recData = gridData[i].data;
590                     var tags = [];
591                     if (recData._tags.length > 0 && recData._tags != '') {
592                         var splits = recData._tags.split(',');
593                         for (var j = 0 ; j < splits.length ; j++) {
594                             var name = Ext.String.trim(splits[j]);
595                             if (name != '') {
596                                 tags.push({
597                                     name: name,
598                                     visible: true,
599 
600                                     //todo use position to order groups for now just set to -1
601                                     position: -1,
602                                     editable: recData._editable
603                                 });
604                             }
605                         }
606                     }
607 
608                     var widget = {
609                         guid: gridData[i].data.widgetGuid,
610                         visible: gridData[i].data.visible,
611                         tags: tags
612                     };
613                     var originalName = gridData[i].data.originalName;
614                     if (gridData[i].data.name !== originalName) {
615                         widget.name = gridData[i].data.name;
616                     }
617                     widgetsToUpdate.push(widget);
618 
619                 } else {
620                     widgetGuidsToDelete.push(gridData[i].data.widgetGuid);
621                     widgetsToDelete.push({
622                         guid: gridData[i].data.widgetGuid,
623                         name: gridData[i].data.name,
624                         headerIcon: gridData[i].data.headerIcon,
625                         image: gridData[i].data.image
626                     });
627                 }
628             }
629         }
630 
631         var scope = this;
632         function onSuccess() {
633             //Call method to refresh the 'widget launch menu' widgets.
634             scope.dashboardContainer.retrieveUpdatedWidgets();
635         }
636         
637         function onFailure() {
638             Ozone.Msg.alert(Ozone.util.ErrorMessageString.saveUpdatedWidgets, Ozone.util.ErrorMessageString.saveUpdatedWidgetsMsg,
639                 null, null, null, scope.dashboardContainer.floatingWindowManager);
640         }
641         
642         Ext.getCmp(this.winId).close();
643         Ozone.pref.PrefServer.updateAndDeleteWidgets({
644             widgetsToUpdate:widgetsToUpdate,
645             widgetGuidsToDelete:[], 
646             updateOrder:false,
647             onSuccess:onSuccess, 
648             onFailure:onFailure
649         });
650       
651         if (widgetGuidsToDelete.length > 0) {
652           
653             Ext.create('Ext.window.Window', {
654                 title: 'Deleting Widgets',
655                 cls: 'delete-widgets-window',
656                 height: 550,
657                 ownerCt: scope.dashboardContainer,
658                 dashboardContainer: scope.dashboardContainer,
659                 constrain: Ext.isIE,
660                 constrainHeader: true,
661                 width: 600,
662                 layout: 'fit',
663                 resizable: false,
664                 modal: true,
665                 items: {
666                     xtype: 'deletewidgetspanel',
667                     delWidgets: widgetsToDelete,
668                     dashboardContainer: scope.dashboardContainer
669                 },
670                 listeners: {
671                     show: function (cmp) {
672                         //Ensure its on top
673                         cmp.dashboardContainer.floatingWindowManager.register(cmp);
674                         cmp.dashboardContainer.floatingWindowManager.bringToFront(cmp);
675                         
676                         /*
677                          * Needs to be deferred or it will happen before the
678                          * window close in IE
679                          */
680                         Ext.defer(function() {
681                             this.focus();
682                         }, 100, cmp.getComponent('topdeletepanel').okBtn);
683                     }
684                 }
685             }).show();
686             
687         }
688     }
689 });