/** * Project  : AMDA-NG * Name : TimeTableUI.js * @class amdaUI.TimeTableUI * @extends Ext.container.Container * @brief Time Table Module UI definition (View) * @author Myriam * @version $Id: TimeTableUI.js 2075 2014-02-11 11:30:14Z elena $ */ Ext.define('amdaUI.TimeTableUI', { extend: 'Ext.container.Container', alias: 'widget.panelTimeTable', requires: [ 'Ext.ux.grid.FiltersFeature', 'Ext.ux.grid.filter.DateFilter', 'Ext.ux.grid.filter.NumericFilter', 'amdaUI.OperationsTT', 'amdaUI.StatisticalPlug', 'Ext.grid.plugin.BufferedRenderer' ], statics: { COL_TO_HIDE: 'colToHide' }, status: null, constructor: function (config) { this.init(config); this.callParent(arguments); // load object into view this.loadObject(); }, /** * set the current editing object * this method will be used on timetable edition when this win is already opened */ setObject: function (object) { // set object this.object = object; // load object into view this.loadObject(); // show the default duration column this.TTGrid.headerCt.getGridColumns(); Ext.Array.each(this.TTGrid.headerCt.getGridColumns(), function (item, index, all) { // if item is the default duration column if (item.id == amdaUI.TimeTableUI.COL_TO_HIDE + '2') { // show this column item.show(); } }); // fire the refresh event (to statistical plugin) this.fireEvent("refresh"); // global event myDesktopApp.EventManager.fireEvent("refresh"); }, /** * load object timetable into this view */ loadObject: function () { this.object.set('created', this.convertUTCDateToLocalDate(this.object.get('created'))); // load object into form this.formPanel.getForm().loadRecord(this.object); this.status = null; // var me = this; var onAfterInit = function (result, e) { if (!result || !result.success) { if (result.message) myDesktopApp.errorMsg(result.message); else myDesktopApp.errorMsg('Unknown error during cache initialisation'); return; } me.TTGrid.getSelectionModel().deselectAll(); // clear filters me.TTGrid.getStore().clearFilter(true); me.TTGrid.filters.clearFilters(); //clear sort me.TTGrid.getStore().sorters.clear(); //me.TTGrid.getStore().sorters = new Ext.util.MixedCollection(); //set cache token to the Time Table object me.object.set('cacheToken', result.token); me.TTGrid.getStore().load(); me.status = result.status; //Statistical plugin me.fireEvent("refresh"); }; if (this.object.get('fromPlugin')) { if (this.object.get('objFormat') && this.object.get('objFormat') != '') { //From uploaded file AmdaAction.initObjectCacheFromUploadedFile(this.object.get('objName'), this.object.get('objFormat'), false, onAfterInit); } else { //From tmp object (ie Search result) AmdaAction.initObjectCacheFromTmpObject(this.object.get('folderId'), this.object.get('objName'), false, onAfterInit); } } else if (this.object.get('relatedCatalogId') != '') { var pathern = this.object.get('relatedCatalogId').split('_')[0]; catType = ''; if (pathern == 'cat') catType = ' catalog'; if (pathern == 'sharedcatalog') catType = 'sharedcatalog'; AmdaAction.initObjectCacheFromCatalog(this.object.get('relatedCatalogId'), catType, onAfterInit); } else { var typeTT = myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.tt.id).linkedNode.data.nodeType; if (this.object.get('id') == '') { //Init empty cache AmdaAction.initObjectCache(false, 0, onAfterInit); } else { //From existing TT file AmdaAction.initObjectCacheFromObject(this.object.get('id'), typeTT, onAfterInit); } } }, /** * update this.object from form */ updateObject: function () { this.updateCount(); // get the basic form var basicForm = this.formPanel.getForm(); var updateStatus = true; var fieldsWithoutName = basicForm.getFields().items; Ext.Array.each(fieldsWithoutName, function (item, index, allItems) { if (item !== this.fieldName) { if (!item.isValid()) { // set update isn't allowed updateStatus = false; } } }, this); // if the update is allowed if (updateStatus) { /// real object update // update TimeTable object with the content of form basicForm.updateRecord(this.object); } // return the update status return updateStatus; }, updateCount: function () { this.object.set('nbIntervals', this.TTGrid.getStore().getTotalCount()); this.formPanel.getForm().findField('nbIntervals').setValue(this.object.get('nbIntervals')); }, /* * save method called by Save button */ saveProcess: function (toRename, onAfterSave, notDisplayMsg) { var timeTableModule = myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.tt.id); // if the name has been modified this is a creation if (timeTableModule.contextNode && (timeTableModule.contextNode.data.id == 'sharedtimeTable-treeRootNode')) { timeTableModule.linkedNode = null; timeTableModule.createLinkedNode(); timeTableModule.createObject(this.object.getJsonValues()); var ttobj = timeTableModule.linkedNode.get('object'); // synchronisation of objects this.object = ttobj; timeTableModule.linkedNode.create(); } else if (this.fclose() || this.status && (this.status.nbFiltered > 0)) /*TimeTable object has been modified*/ { if (this.object.isModified('name') || this.object.get('fromPlugin')) { // if object already has an id : it's a 'rename' of an existing TimeTable if (this.object.get('id')) { // the context Node is the parent node of current edited one var contextNode = timeTableModule.linkedNode.parentNode; // link a new node to the TimeTableModule timeTableModule.createLinkedNode(); // set the contextNode timeTableModule.linkedNode.set('contextNode', contextNode); // create a new object linked timeTableModule.createObject(this.object.getJsonValues()); var ttobj = timeTableModule.linkedNode.get('object'); // synchronisation of objects this.object = ttobj; if (toRename) timeTableModule.linkedNode.toRename = true; } timeTableModule.linkedNode.create({callback: function ($action) { if (timeTableModule.linkedNode.get('object').get('fromPlugin')) timeTableModule.linkedNode.get('object').set('fromPlugin', false); timeTableModule.linkedNode.update({callback: function () { if (onAfterSave) onAfterSave(); }, scope: this}); }, scope: this}); } else { //update timeTableModule.linkedNode.update({callback: function () { if (onAfterSave) onAfterSave(); }, scope: this}); } } }, /** * overwrite metod called by Save button */ overwriteProcess: function (btn) { if (btn == 'cancel') return; this.fieldName.clearInvalid(); this.saveProcess(true); }, addInterval: function (start, stop) { var row = this.TTGrid.getStore().getTotalCount(); var me = this; this.TTGrid.getSelectionModel().deselectAll(); AmdaAction.addCacheInterval({'start': start, 'stop': stop, 'index': row}, function (result, e) { this.status = result.status; if (!this.TTGrid.getStore().loading) { this.TTGrid.getStore().reload({ callback: function (records, options, success) { me.TTGrid.getView().bufferedRenderer.scrollTo(row, false, function () { me.TTGrid.getView().select(row); }, me); } }); } }, this); }, generateCAT: function (id) { if (this.fclose()) { Ext.Msg.confirm('Generate CAT', 'Current Time Table has been modified.\nDo you want to save it to include these changes in the generated Catalog ?', function (btn, text) { if (btn == 'yes') { // mark this.closed as true before the call to close() as that will fire the beforeclose event again if (this.object.get('id') == "") { // case of creation of catalog this.saveTT(this.createCAT(this.object.get('id'))); } else { // case existing catalog this.saveProcess(false, this.createCAT(this.object.get('id'))); } return; } }, this); } else { this.createCAT(this.object.get('id')); return; } }, createCAT: function (id) { Ext.Msg.prompt('Create catalog', 'Enter the number of columns:', function (btn, text) { if (btn == 'ok') { var catObj = Ext.create('amdaModel.Catalog'); var catNode = Ext.create('amdaModel.CatalogNode', {leaf: true}); catObj.set('relatedTimeTableId', id); creatDate = new Date(this.object.get('created')); date = Ext.Date.format(creatDate, 'Y-m-d\\TH:i:s.u'); descr = 'Generated by CDPP/Amda Time Table Module \n' + 'From Time Table: ' + this.object.get('name') + ' which was generated on: ' + date + '\n'; catObj.set('description', descr + this.object.get('description')); var nbParam = parseInt(text); if ((nbParam <= 0) || (nbParam > 100)) { nbParam = 1; } catObj.set('nbParameters', nbParam); catNode.set('object', catObj); var explorerTree = Ext.getCmp(amdaUI.ExplorerUI.RESRC_TAB.TREE_ID); var catRootNode = explorerTree.getRootNode().findChild('id', 'catalog-treeRootNode', true); amdaModel.InteractiveNode.preloadNodes(catRootNode.getRootNode(), function () { // edit newNode into Parameter Module with node as contextNode catNode.editInModule(); }); } }, this); }, saveTT: function (OnAfterSave) { if (this.updateObject()) { var basicForm = this.formPanel.getForm(); // if there's at least one record in the store of TTGrid if (this.TTGrid.getStore().getTotalCount() > 0) { // update TimeTable object which the content of form basicForm.updateRecord(this.object); var me = this; this.checkIntervalsStatusForSave(function () { //Name validation var ttModule = myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.tt.id); if (!ttModule) return; ttModule.linkedNode.isValidName(me.fieldName.getValue(), function (res) { if (!res) { me.fieldName.validFlag = 'Error during object validation'; myDesktopApp.errorMsg(me.fieldName.validFlag); me.fieldName.validate(); return; } if (!res.valid) { if (res.error) { if (res.error.search('subtree') != -1) { Ext.MessageBox.show({title: 'Warning', msg: res.error + '
Do you want to overwrite it?', width: 300, buttons: Ext.MessageBox.OKCANCEL, fn: me.overwriteProcess, icon: Ext.MessageBox.WARNING, scope: me }); me.fieldName.validFlag = true; } else me.fieldName.validFlag = res.error; } else { me.fieldName.validFlag = 'Invalid object name'; myDesktopApp.errorMsg(me.fieldName.validFlag); } me.fieldName.validate(); return; } me.fieldName.validFlag = true; me.fieldName.validate(); me.saveProcess(false); }); }); } else { // warning: Ext.Msg.alert('No intervals', 'Your time table is invalid,
you must have at least one interval'); } } }, // Convert UTC date to client local date convertUTCDateToLocalDate: function (date) { if (date == null) { return date; }; var newDate = new Date(date.getTime() + date.getTimezoneOffset() * 60 * 1000); var offset = date.getTimezoneOffset() / 60; var hours = date.getHours(); newDate.setHours(hours - offset); return newDate; }, init: function (config) { this.object = config.object; this.fieldName = new Ext.form.field.Text({ fieldLabel: 'Name*', allowBlank: false, stripCharsRe: /(^\s+|\s+$)/g, emptyText: 'Please no spaces!', name: 'name', anchor: '100%', validateOnChange: false, validateOnBlur: false, validFlag: false, validator: function () { return this.validFlag; } }); this.formPanel = new Ext.form.Panel({ bodyStyle: {background: '#dfe8f6'}, id: 'formTimeTable', flex: 4, model: 'amdaModel.TimeTable', trackResetOnLoad: true, // reset to the last loaded record border: false, fieldDefaults: {labelWidth: 80}, items: [ this.fieldName, { xtype: 'fieldcontainer', layout: 'hbox', fieldLabel: 'Creation date', items: [ { xtype: 'datefield', width: 180, name: 'created', disabled: true, hideTrigger: true, format: 'Y/m/d H:i:s' }, {xtype: 'component', width: 20}, {xtype: 'displayfield', value: 'Intervals:', width: 50}, {xtype: 'component', width: 8}, {xtype: 'textfield', name: 'nbIntervals', disabled: true, width: 70} ] }, { xtype: 'textarea', name: 'description', fieldLabel: 'Description', anchor: '100% 50%' }, { xtype: 'textarea', name: 'history', fieldLabel: 'Operation log', anchor: '100% 30%' } ] }); var store = Ext.create('Ext.data.Store', { model: 'amdaModel.Interval', autoDestroy: false, pageSize: 200, buffered: true, autoLoad: true, purgePageCount: 0, remoteSort: true, listeners: { load: function (store, records) { // alert('nb of records in store:'+records.length ); myDesktopApp.EventManager.fireEvent('refresh'); this.TTGrid.getView().refresh(); this.TTGrid.getSelectionModel().refresh(); this.updateCount(); //Statistical plugin this.fireEvent("refresh"); }, prefetch: function (store, records, successful, operation, eOpts) { if (operation && (operation.action == 'read')) { if (operation.response && operation.response.result && operation.response.result.success) this.status = operation.response.result.status; } }, remove: function (store) { this.updateCount(); //Statistical plugin this.fireEvent("refresh"); }, add: function (store) { this.updateCount(); //Statistical plugin this.fireEvent("refresh"); }, datachanged: function (store) { this.updateCount(); //Statistical plugin this.fireEvent("refresh"); }, scope: this } }); var filters = { ftype: 'filters', encode: true, // json encode the filter query local: false, // defaults to false (remote filte filters: [ {type: 'numeric', dataIndex: 'durationDay'}, {type: 'numeric', dataIndex: 'durationHour'}, {type: 'numeric', dataIndex: 'durationMin'}, {type: 'numeric', dataIndex: 'durationSec'}, {type: 'date', dataIndex: 'start', dateFormat: 'Y-m-d'}, {type: 'date', dataIndex: 'stop', dateFormat: 'Y-m-d'} ] }; var cellEditing = Ext.create('Ext.grid.plugin.CellEditing', { onEditComplete: function (ed, value, startValue) { var me = this, activeColumn = me.getActiveColumn(), context = me.context, record; if (activeColumn) { record = context.record; me.setActiveEditor(null); me.setActiveColumn(null); me.setActiveRecord(null); context.value = value; if (!me.validateEdit()) { me.editing = false; return; } // Only update the record if the new value is different than the // startValue. When the view refreshes its el will gain focus if (!record.isEqual(value, startValue)) { var obj = { 'cacheId': record.get('cacheId'), 'isCatalog': false, 'data': {} }; obj['data'][activeColumn.dataIndex] = value; //context.grid.getSelectionModel().deselectAll(); //Interval is modified on the server side me.editing = true; AmdaAction.modifyCacheInterval(obj, function (result, e) { var ttModule = myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.tt.id); if (ttModule) ttModule.getUiContent().status = result.status; if (!context.store.loading) { context.grid.getSelectionModel().deselectAll(); context.store.reload({ callback: function (records, options, success) { context.view.bufferedRenderer.scrollTo(context.rowIdx, true, function () { me.fireEvent('edit', me, context); me.editing = false; }, me); } }); } else { me.editing = false; } }, this); } else me.editing = false; } } }); this.TTGrid = Ext.create('Ext.grid.Panel', { store: store, features: [filters], columnLines: true, selModel: {pruneRemoved: false}, countDecimals: function (value) { if (Math.floor(value) === value) return 0; return value.toString().split(".")[1].length || 0; }, dateToString: function (value) { ndegits = this.countDecimals(value); if (ndegits <= 3) { return Ext.util.Format.number(value, '0.000'); } else if (value < 0.1) { return value.toExponential(3); } else { return Ext.util.Format.number(value, '0.000000'); } }, columns: [ { xtype: 'rownumberer', width: 50, renderer: function (value, metaData, record, row, col, store, gridView) { var msg = record.index + 1; if (record.get('isNew') || record.get('isModified')) { msg += ' *'; metaData.style = 'font-weight: bold' } return msg; } }, { header: 'Start Time', dataIndex: 'start', width: 120, editor: {xtype: 'datefield', allowBlank: false, hideTrigger: true, format: 'Y-m-d\\TH:i:s.u'}, renderer: function (value) { if (value != null) { if (Ext.isDate(value)) { return Ext.Date.format(value, 'Y-m-d\\TH:i:s.u'); } else { return Ext.Date.format(new Date(value), 'Y-m-d\\TH:i:s.u'); } } else { return value; } } }, { header: 'Stop Time', dataIndex: 'stop', width: 120, editor: {xtype: 'datefield', allowBlank: false, hideTrigger: true, format: 'Y-m-d\\TH:i:s.u'}, renderer: function (value) { if (value != null) { if (Ext.isDate(value)) { return Ext.Date.format(value, 'Y-m-d\\TH:i:s.u'); } else { return Ext.Date.format(new Date(value), 'Y-m-d\\TH:i:s.u'); } } else { return value; } } }, { header: 'Duration (day)', width: 120, dataIndex: 'durationDay', id: amdaUI.TimeTableUI.COL_TO_HIDE + '1', hidden: true, renderer: function (value) { return this.dateToString(value); }, listeners: { beforeshow: function () { Ext.Array.each(this.ownerCt.getGridColumns(), function (item, index, all) { // if item is a column to hide automatically if (Ext.util.Format.substr(item.id, 0, amdaUI.TimeTableUI.COL_TO_HIDE.length) == amdaUI.TimeTableUI.COL_TO_HIDE) { // if item isn't the column which is being declared and is not hidden if (item.id != amdaUI.TimeTableUI.COL_TO_HIDE + '1' && !item.isHidden()) { // hide this column item.hide(); } } }); } } }, { header: 'Duration (hour)', width: 120, dataIndex: 'durationHour', id: amdaUI.TimeTableUI.COL_TO_HIDE + '2', hidden: true, renderer: function (value) { return this.dateToString(value); }, listeners: { beforeshow: function () { Ext.Array.each(this.ownerCt.getGridColumns(), function (item, index, all) { // if item is a column to hide automatically if (Ext.util.Format.substr(item.id, 0, amdaUI.TimeTableUI.COL_TO_HIDE.length) == amdaUI.TimeTableUI.COL_TO_HIDE) { // if item isn't the column which is being declared and is not hidden if (item.id != amdaUI.TimeTableUI.COL_TO_HIDE + '2' && !item.isHidden()) { // hide this column item.hide(); } } }); } } }, { header: 'Duration (min)', width: 120, dataIndex: 'durationMin', id: amdaUI.TimeTableUI.COL_TO_HIDE + '3', renderer: function (value) { return this.dateToString(value); }, listeners: { beforeshow: function () { Ext.Array.each(this.ownerCt.getGridColumns(), function (item, index, all) { // if item is a column to hide automatically if (Ext.util.Format.substr(item.id, 0, amdaUI.TimeTableUI.COL_TO_HIDE.length) == amdaUI.TimeTableUI.COL_TO_HIDE) { // if item isn't the column which is being declared and is not hidden if (item.id != amdaUI.TimeTableUI.COL_TO_HIDE + '3' && !item.isHidden()) { // hide this column item.hide(); } } }); } } }, { header: 'Duration (sec)', width: 120, dataIndex: 'durationSec', id: amdaUI.TimeTableUI.COL_TO_HIDE + '4', hidden: true, renderer: function (value) { return Ext.util.Format.number(value, '0.000'); }, listeners: { beforeshow: function () { Ext.Array.each(this.ownerCt.getGridColumns(), function (item, index, all) { // if item is a column to hide automatically if (Ext.util.Format.substr(item.id, 0, amdaUI.TimeTableUI.COL_TO_HIDE.length) == amdaUI.TimeTableUI.COL_TO_HIDE) { // if item isn't the column which is being declared and is not hidden if (item.id != amdaUI.TimeTableUI.COL_TO_HIDE + '4' && !item.isHidden()) { // hide this column item.hide(); } } }); } } } ], frame: true, dockedItems: [{ xtype: 'toolbar', items: [{ iconCls: 'icon-add', scope: this, handler: function () { cellEditing.cancelEdit(); var selection = this.TTGrid.getView().getSelectionModel().getSelection()[0]; var row = 0; if (selection) row = store.indexOf(selection) + 1; this.TTGrid.getSelectionModel().deselectAll(); var me = this; AmdaAction.addCacheInterval({'index': row}, function (result, e) { this.status = result.status; if (!this.TTGrid.getStore().loading) { this.TTGrid.getStore().reload({ callback: function (records, options, success) { me.TTGrid.getView().bufferedRenderer.scrollTo(row, false, function () { me.TTGrid.getView().select(row); cellEditing.startEditByPosition({row: row, column: 1}); }, me); } }); } }, this); } }, { iconCls: 'icon-delete', disabled: true, itemId: 'delete', scope: this, handler: function () { var selection = this.TTGrid.getView().getSelectionModel().getSelection()[0]; if (selection) { var rowId = selection.get('cacheId'); this.TTGrid.getSelectionModel().deselectAll(); AmdaAction.removeTTCacheIntervalFromId(rowId, false, function (result, e) { this.status = result.status; if (!this.TTGrid.getStore().loading) { this.TTGrid.getStore().reload(); } }, this); } } }, '->', { text: 'Clear Filters', scope: this, handler: function () { this.TTGrid.getStore().clearFilter(true); this.TTGrid.filters.clearFilters(); } } ] }], plugins: [cellEditing, {ptype: 'bufferedrenderer'}], listeners: { scope: this, edit: function (editor, e) { if (e.record.get('stop') != null && e.record.get('start') != null) { e.record.set('durationHour', (e.record.get('stop') - e.record.get('start')) / 3600000.0); e.record.set('durationMin', (e.record.get('stop') - e.record.get('start')) / 60000.0); e.record.set('durationSec', (e.record.get('stop') - e.record.get('start')) / 1000.0); e.record.set('durationDay', (e.record.get('stop') - e.record.get('start')) / 3600000.0 / 24.0); // send refresh event to statistical plugin this.fireEvent("refresh"); } } } }); this.TTGrid.getSelectionModel().on('selectionchange', function (selModel, selections) { this.TTGrid.down('#delete').setDisabled(selections.length === 0); }, this); var myConf = { layout: 'border', defaults: {layout: 'fit', border: false}, items: [ { xtype: 'form', region: 'center', buttonAlign: 'left', bodyStyle: {background: '#dfe8f6'}, padding: '5 5 5 5', layout: {type: 'hbox', pack: 'start', align: 'stretch'}, items: [ { xtype: 'container', flex: 3.6, layout: {type: 'vbox', pack: 'start', align: 'stretch'}, items: [ this.formPanel, { xtype: 'operationsTT', parent: this, flex: 2.5, id: 'operation' } ] }, { xtype: 'container', border: false, padding: '0 0 5 15', flex: 4, layout: 'fit', items: [this.TTGrid] } ], fbar: [ { xtype: 'button', text: 'Save', width: 65, scope: this, handler: function () { this.saveTT(); } }, { xtype: 'button', text: 'Reset', width: 65, scope: this, handler: function () { var ttModule = myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.tt.id); ttModule.createLinkedNode(); ttModule.createObject(); this.setObject(ttModule.getLinkedNode().get('object')); } }, { xtype: 'button', text: 'Generate Catalog', width: 120, scope: this, handler: function () { this.generateCAT(this.object.get('id')); } } ] }, { xtype: 'panel', region: 'south', title: 'Information', collapsible: true, collapseMode: 'header', height: 100, autoHide: false, iconCls: 'icon-information', bodyStyle: 'padding:5px', loader: { autoLoad: true, url: helpDir + 'timetableHOWTO' } } ], plugins: [{ptype: 'statisticalPlugin'}] }; Ext.apply(this, Ext.apply(arguments, myConf)); }, checkIntervalsStatusForSave: function (onStatusOk) { if (this.status == null) return; if (this.status.nbValid <= 0) { myDesktopApp.errorMsg('Your time table is invalid,
you must have at least one valid interval'); return; } var msg = ''; if (this.status.nbInvalid > 0) msg += 'There are some invalid intervals. Only valid intervals will be saved!
'; if (this.status.nbFiltered > 0) msg += 'There are some filtered intervals. Filtered intervals will not be saved!
'; if (msg != '') { msg += 'Do you want to continue?'; Ext.Msg.show({ title: 'Warning!', msg: msg, buttons: Ext.Msg.OKCANCEL, fn: function (btnId) { if (btnId === 'cancel') { // cancel the save action } else { onStatusOk(); } }, scope: this, icon: Ext.Msg.WARNING }); return; } onStatusOk(); }, /** * Check if changes were made before closing window * @return true if changes */ fclose: function () { if (this.status == null) return false; var isDirty = this.formPanel.getForm().isDirty() || (this.status.isModified) || (this.status.nbModified > 0) || (this.status.nbNew > 0); return isDirty; } });