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