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 });