/**
* Project : AMDA-NG4
* Name : InteractiveNode.js
* @class amdaModel.InteractiveNode
* @extends amdaModel.AmdaNode
* @brief Generic Model of Interactive Node
* @author CDA
* @version $Id: InteractiveNode.js 2683 2014-12-02 10:58:53Z elena $
*/
Ext.define('amdaModel.InteractiveNode', {
extend: 'amdaModel.AmdaNode',
fields: [
{name: 'contextNode', type: 'amdaModel.AmdaNode', persist: false},
{name: 'objectDataModel', type: 'string', persist: false},
{name: 'object', type: 'object', persist: false},
{name: 'moduleId', type: 'string', persist: false},
{name: 'filtered', type: 'boolean', defaultValue: false, persist: false},
{name: 'disable', type: 'boolean', defaultValue: false, persist: false}
],
statics: {
preloadNodes : function(node,onready)
{
var me = this;
var nodesToLoad = new Array();
nodesToLoad.push(node);
this.preloadTreeNode(node, nodesToLoad, function (node)
{
var isFinish = true;
nodesToLoad.forEach(function (element, index, array)
{
if (!element.isLoaded())
isFinish = false;
});
if (isFinish && onready)
onready.call();
});
},
preloadTreeNode : function(node, nodesToLoad, onloaded)
{
var me = this;
if (node.isLoaded())
{
node.eachChild(function(n)
{
if (!n.isLoaded() && !n.isLeaf())
{
nodesToLoad.push(n);
me.preloadTreeNode(n,nodesToLoad,onloaded);
}
});
if (onloaded)
onloaded.call(me,node);
return;
}
node.store.load({
node : node,
callback : function(records, operation, successful)
{
records.forEach(function (record)
{
if (!record.isLoaded() && !record.isLeaf())
{
nodesToLoad.push(record);
me.preloadTreeNode(record,nodesToLoad,onloaded);
}
});
if (onloaded)
onloaded.call(me,node);
}
});
}
},
constructor : function(config)
{
this.callParent(arguments);
this.set('nodeType',this.self.nodeType);
this.set('ownerTreeId',amdaUI.ExplorerUI.RESRC_TAB.TREE_ID);
// if id of this node have root category suffix
if (Ext.util.Format.substr(this.get('id'), -(amdaUI.ExplorerUI.CAT_SUFFIX.length), this.get('id').length) === amdaUI.ExplorerUI.CAT_SUFFIX)
{
// set the expanded property to true
this.set('expanded',true);
}
},
/**
* this method is overriden into ExecutableNode to return true
*/
isExecutable: function()
{
return false;
},
/**
* open Module with THIS NODE
*/
editInModule : function (contextNode)
{
// set the contextNode of this node
this.set('contextNode',contextNode);
// parameter module
var me = this;
myDesktopApp.getLoadedModule(this.get('moduleId'),true, function (module) {
// If the node to edit is not already linked to this module
if (module.getLinkedNode() != me)
{
// set relative node into parameter Module
module.setLinkedNode(me);
if (contextNode==null)
{
// set the root node as contextNode
contextNode = me.getRootNode();
}
module.setContextNode(contextNode);
} else if (module.getLinkedNode() != null){
//TODO the node to edit is already edited
// myDesktopApp.warningMsg('This object is being edited');
//Sol1: msg alert: "warning this node is already edited! If you want to get the original, please press the 'reset' button"->'OK'
//Sol2: msg with user choice: "warning this node is already edited! Would you confirm this action and lost your modification?"->'Confirm','Cancel'
}
// Opening parameter window
module.createWindow();
});
},
/**
* Method to rename the workspace node
*/
rename: function(value,callBackFn)
{
var dataToSend = {id : this.get('id'), old_name: this.modified.text, name: value, parent : this.parentNode.get('id'), leaf: this.isLeaf(), nodeType: this.get('nodeType')};
AmdaAction.renameObject(dataToSend, callBackFn);
},
/**
* Method to rename the workspace node when D&D
*/
renameDD: function(parentId, callBackFn)
{
var dataToSend = {id : this.get('id'), old_name: this.get('name'), name: this.get('name'), parent : parentId, leaf: this.isLeaf(), nodeType: this.get('nodeType')};
AmdaAction.renameObject(dataToSend, callBackFn);
},
/**
* validation method on name (used in module forms)
* @param name the name to validate
* @returns
*/
isValidName : function(name, callBackFn)
{
var dataToSend = {name: name, nodeType: this.get('nodeType'), leaf: this.isLeaf()};
AmdaAction.validNameObject(dataToSend, callBackFn);
/*if (name.match(/[a-z,0-9]/gi)) {
var subTreeRoot = this.getRootNode();
//TODO - check to be sure that preload is already applied before to use the isValidName function (normally, this is the case)
var nodeWithSameName = subTreeRoot.findChild('text', name, true);
if (nodeWithSameName !== null && this !== nodeWithSameName) {
return "This name already exists in this subtree!
Do you want to overwrite it?";
} else return true;
} else {
return "Name is required";
}*/
},
/**
* Method to persist modifications of an AmdaObject by Server side and update the workspace
* node linked to a Module
*/
update : function(opt)
{
AmdaAction.modifyObject(this.get('object').getJsonValues(true), function(res,e){
if(e.status) {
if (res.id) {
if (!this.get('contextNode')) {
// set the root node of 'Derived Parameters' tree as contextNode
this.set('contextNode',this.getRootNode());
}
this.get('contextNode').expand(false,false);
this.myGetOwnerTree().getSelectionModel().select(this);
if (opt)
{
var scope = opt.scope ? opt.scope : this;
if (opt.callback)
opt.callback.call(scope,'update');
}
Ext.Msg.alert('Complete', 'Object '+this.get('object').get('name')+' has been modified');
// fix the modifications for object
this.get('object').commit();
if (res.info) {
this.set('info',res.info);
}
if (this.get('nodeType') == 'myDataParam') {
if (res.isSpectra) {
this.set('iconCls', 'icon-spectra');
}
else {
if (res.size > 1) this.set('iconCls', 'icon-unknowntype');
}
}
// update my data on possibble mask change
if (res.updateMyData) {
this.updateMyData();
this.updateMask(res.mask);
}
// reload object into the view of corresponding Module
var me = this;
myDesktopApp.getLoadedModule(this.get('moduleId'), true, function (module) {
module.getUiContent().setObject(me.get('object'));
});
}
else {
//TODO proper error message handling
// error code from server; but e.status==true
// revert all modifications since last load or commit
this.get('object').reject();
myDesktopApp.errorMsg(res.error);
}
}
else {
// revert all modifications since last load or commit
this.get('object').reject();
myDesktopApp.errorMsg(e.message);
}
},this);
},
/**
* Method to create a new AmdaObject by server side and create the workspace node linked to a Module
* under its contextNode or the root node corresponding to this nodeType category
*/
create : function(opt)
{
if (!this.get('contextNode') || (this.get('contextNode').data.id == 'sharedtimeTable-treeRootNode')) {
// set the root node of 'Derived Parameters' tree as contextNode
this.set('contextNode',this.getRootNode());
}
// call the Ext.Direct method to create parameter
AmdaAction.createObject(this.get('object').getJsonValues(false), this.get('contextNode').get('id'), function(res,e){
//success
if(e.status)
{
// if correct response received
if (res.id) { //if (res.id || res.error == 'NAME_EXISTS') {
// 'save as' case ; delete old node if it exists
if (this.toRename)
{
this.toRename = false;
var myRoot = this.getRootNode();
// search the same named node to override
var updateNode = myRoot.findChild('text',this.get('object').get('name'),true);
// destroy the overrided node
updateNode.parentNode.removeChild(updateNode);//TODO ??if destroy==true => too many recursions....
updateNode.destroy();
}
// set text of this node
this.set('text',this.get('object').get('name'));
//set id of this node
this.set('id',res.id);
this.internalId = res.id;
// set id of node's object
this.get('object').set('id',res.id);
if (res.created){
// set the created date
this.get('object').set('created',res.created);
}
if (res.info){
// set the tooltip
this.set('info',res.info);
//set globalStart & global Stop to be used for time selection
if (this.get('nodeType') == 'myDataParam')
{
var startStop = res.info.split("
");
var globalStart = startStop[1].substr(0,16);
var globalStop = startStop[1].substr(17,16);
this.set('globalStart', globalStart);
this.set('globalStop', globalStop);
if (res.mask)
this.set('linkedMask', res.mask);
if (res.size)
this.set('size', res.size);
if (res.isSpectra) {
this.set('iconCls', 'icon-spectra');
}
else {
if (res.size > 1) this.set('iconCls', 'icon-unknowntype');
}
}
}
//TODO do we need this commission ???
// fix the modifications for object
this.get('object').commit();
// if ownerTree panel is not active
if (this.myGetOwnerTree().ownerCt.getActiveTab()!==this.myGetOwnerTree())
{
// set ownerTree panel as the active tab - to enable selection of this node his ownerTree must have a view
this.myGetOwnerTree().ownerCt.setActiveTab(this.myGetOwnerTree());
}
Ext.Msg.alert('Complete', 'New object '+this.get('object').get('name')+' has been created');
// expand the contextNode
this.get('contextNode').expand(false, function()
{
if (!this.get('contextNode').findChild('text',this.get('text'))) {
// create node in tree as child of contextNode
this.get('contextNode').appendChild(this);
}
// select the new node
this.myGetOwnerTree().getSelectionModel().select(this);
if (opt)
{
var scope = opt.scope ? opt.scope : this;
if (opt.callback)
opt.callback.call(scope,'create');
}
}, this);
// myDataParamNode - update MyData subtree
//TODO put this in mydataparamnode
if (res.updateMyData) {
this.updateMyData();
this.updateMask(res.mask);
}
}
// error code from server; but e.status==true
else {
myDesktopApp.errorMsg(res.error);
// revert all modifications since last load or commit
this.get('object').reject();
}
}
// failure: e.status == false
else {
// revert all modifications since last load or commit
this.get('object').reject();
//TODO: this.destroy();
myDesktopApp.errorMsg(e.message);
}
},this);
},
/**
* Generic part of Context Menu
*
*/
allMenuItems : function() {
var src = this.self.objectName;
var menuItems =
[ {
fnId : 'root-createLeaf',
text : 'Create '+ src
}, {
fnId : 'root-createDir',
text : 'Create Folder'
}, {
fnId : 'dire-createLeaf',
text : 'Create ' + src
}, {
fnId : 'dire-createDir',
text : 'Create Folder'
}, {
fnId : 'dire-renameNode',
text : 'Rename Folder'
}, {
fnId : 'dire-deleteNode',
text : 'Delete Folder'
}, {
fnId : 'leaf-editLeaf',
text : 'Edit ' + src
}, {
fnId : 'leaf-renameNode',
text : 'Rename ' + src
}, {
fnId : 'leaf-deleteNode',
text : 'Delete '+ src
} ];
return menuItems;
},
allMenuMultiItems : function() {
var menuMulti = [
{
fnId : 'mult-deleteMulti',
text : 'Delete selected ' + this.self.objectName + 's'
}
];
return menuMulti;
},
getAllContextMenuItems: function(){
return this.allMenuItems();
},
getMultiContextMenuItems: function(){
return this.allMenuMultiItems();
},
/**
* default implementation
* no menu display if there's no override of this function
*/
getMultiContextMenuItems: function(){
return null;
},
/**
* Context Menu Actions
*
*/
onMenuItemClick : function(menu,item,event) {
// fnId parsing :
var fnId = Ext.util.Format.substr(item.fnId, 5, item.fnId.length);
switch (fnId) {
case 'deleteNode':
this.deleteNode();
break;
case 'createDir':
this.createDir();
break;
case 'createLeaf':
this.createLeaf(this);
break;
case 'renameNode':
this.renameNode();
break;
case 'editLeaf':
this.editLeaf(this);
break;
case 'deleteMulti':
this.deleteMulti();
break;
case 'plotParam':
this.createPlot(this);
break;
case 'downParam':
this.createDownload(this);
break;
default:
break;
} // switch end
},
getTimeFromNode: function(node) {
var startString = new String(node.get('globalStart'));
var stopString = new String(node.get('globalStop'));
var startDate = new Date(startString.replace(/\-/g,'\/').replace(/[T|Z]/g,' '));
var stopDate = new Date(stopString.replace(/\-/g,'\/').replace(/[T|Z]/g,' '));
if (stopDate - startDate > 86400000 ) {
var startTime = Ext.Date.add(stopDate, Ext.Date.DAY, -1);
var timeObj = {start: Ext.Date.format(startTime, 'Y/m/d'), stop: node.get('globalStop')};
}
else {
var timeObj = {start: node.get('globalStart'), stop: node.get('globalStop')};
}
return timeObj;
},
createPlot: function(node)
{
if (node.get('disable')) return;
var me = this;
myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.plot.id, true, function (module) {
if (!myDesktopApp.desktop.getWindow(myDesktopApp.dynamicModules.plot.id)) {
var request = Ext.create(amdaPlotObj.PlotRequestObject.$className);
var newNode = Ext.create(amdaModel.PlotNode.$className, { object : request });
// edit newNode into Plot Module with node as contextNode
newNode.editInModule();
if(node.get('globalStart') != 'depending on mission' && ( node.get('nodeType') == 'localParam' ||
node.get('nodeType') == 'myDataParam')) {
module.getUiContent().setTimeFromData(me.getTimeFromNode(node));
}
}
module.getUiContent().addParameter(node);
});
},
createDownload: function(node)
{
if (node.get('disable')) return;
if (node.get('notyet')) {
myDesktopApp.warningMsg('Sorry! access to this parameter is restricted.');
return;
}
var me = this;
myDesktopApp.getLoadedModule(myDesktopApp.dynamicModules.download.id, true, function (module) {
if (!myDesktopApp.desktop.getWindow(myDesktopApp.dynamicModules.download.id)) {
var request = Ext.create(amdaModel.Download.$className);
amdaModel.DownloadNode.set('object',request);
// singleton!
amdaModel.DownloadNode.editInModule();
if(node.get('globalStart') != 'depending on mission' && ( node.get('nodeType') == 'localParam' ||
node.get('nodeType') == 'myDataParam')) {
module.getUiContent().setTimeFromData(me.getTimeFromNode(node));
}
}
if (node.get('needsArgs') && !node.get('isSpectra')) {
module.getUiContent().fireEvent('openParamEditor',node.get('id'));
}
else {
var paramName;
switch (node.$className) {
case 'amdaModel.AliasNode' :
paramName = "#"+node.get('text');
break;
case 'amdaModel.DerivedParamNode' :
paramName = "ws_"+node.get('text');
break;
case 'amdaModel.MyDataParamNode' :
paramName = 'wsd_'+node.get('text');
break;
default :
if (node.get('alias')!= "" )
paramName = "#"+node.get('alias');
else
paramName = node.get('id');
}
module.addParam(paramName,true);
}
});
},
deleteNode: function() {
// if the target is a directory
if (!this.isLeaf()) {
// determine if this directory is empty before launching the delete confirmation method
this.isNotEmptyDir(this.confirmDirectoryDeletion);
// else (the target is a leaf)
} else {
// no confirmation prompt for leaves
this.confirmDirectoryDeletion(false);
}
},
/**
* this method return if node has Childs even if it was not already loaded
*/
isNotEmptyDir : function(callbackFn) {
var hasChilds;
// if node not already loaded
if (!this.isLoaded()){
// call directFunction to load this node
AmdaAction.getTree({node:this.get('id'),nodeType:this.get('nodeType')},function(res,e){
callbackFn.call(this,res.length>0?true:false);
},this);
}
else {
callbackFn.call(this,this.hasChildNodes());
}
},
/**
* this method is used to display a confirmation message
*/
confirmDirectoryDeletion : function(isNotEmptyDir)
{
// if this is a non-empty directory
if (isNotEmptyDir) {
// Prompt to the user if he also wants to delete its content
Ext.Msg.confirm('non-empty directory', 'The target is a non-empty directory!
Do you want to continue and also delete its content?', function(btn, text){
if (btn == 'yes'){
// do delete
this.realDelete();
}
},this);
} else {
this.realDelete();
}
},
/*
* Call the extDirect method to delete parameter
* Callback method needed to execute node deletion in tree if id in result or to show error msg
*/
realDelete : function()
{
AmdaAction.deleteObject({id: this.get('id'), leaf: this.isLeaf(), nodeType: this.get('nodeType')}, function(res,e){
//TODO proper errors handling
// node deletion in tree
if (res) { // if success
if (res.id) {
//Ext.Msg.show({title:'Warning', msg: 'Requests with parameter '+node.data.text+' are deleted', icon: Ext.MessageBox.ERROR, buttons: Ext.Msg.OK});
if (this.parentNode) {
if (this.isLeaf()){
var moduleId = this.get('moduleId');
// if really interactive node
if (moduleId) {
var me = this;
myDesktopApp.getLoadedModule(moduleId, true, function (module) {
var editedNode = module.getLinkedNode();
// file node is not linked directly to the module
var isThisFile = false;
if (editedNode && editedNode.$className == 'amdaModel.MyDataParamNode')
if (editedNode.get('fileObject').get('fileName') == me.get('text'))
isThisFile = true;
if (me.$className == 'amdaModel.DerivedParamNode')
{
var obj = {
paramId : 'ws_'+me.get('text')
};
AmdaAction.compilParamDelete(obj);
}
if (editedNode === me || isThisFile){
var newNode = Ext.ModelManager.create({leaf : true}, me.$className);
// several tabs could be connected to one node
if (moduleId === myDesktopApp.dynamicModules.plot.id) {
var linkedNodes = module.linkedNodes;
if (linkedNodes) {
linkedNodes.each(function(key, value){
if (value === me) {
linkedNodes.replace(key,newNode);
var tabPanel = module.getUiContent().tabPanel.items.getByKey(key);
tabPanel.setObject(Ext.create(amdaModel.Plot.$className, {}));
}
}, me);
}
}
newNode.editInModule();
}
});
}
}
//update mask info in myData
if (res.maskDesc && !res.maskDeleted) {
this.parentNode.set('info', res.maskDesc);
this.updateMyDataParam(res.mask, res.maskDesc);
}
this.remove();
}
//TODO Several special node-dependent actions - to move to node functions..
// nodes of another nodeType to be deleted as they depend on deleted node
if (res.params) {
this.deleteDependence(res.params);
//TODO reset
}
// mask was deleted or updated - to update mydata tree
if (res.maskDeleted) {
this.updateMyData();
}
}
else {
myDesktopApp.warningMsg(res.error);
}
}
else {
myDesktopApp.errorMsg(e.message);
}
}, this);
},
/*
* Delete musti selection
*/
deleteMulti: function()
{
var selection = this.myGetOwnerTree().getSelectionModel().selected.items;
alert(selection.length +' to delete!');
Ext.Array.each(selection,function(item,index,allItems){
item.deleteNode();
})
},
/*
* Create Folder
*/
createDir: function() {
var me = this;
amdaModel.InteractiveNode.preloadNodes(this.getRootNode(),
function()
{
var newNode = Ext.create(me.$className,
{
leaf : false, nodeType : me.get('nodeType'),
text : amdaModel.AmdaNode.NEW_DIR_NAME,
children : []
});
// insert the new node as a child of node
newNode = me.insertChild(0, newNode);
// start text edition on this new directory node
me.expand(false);
newNode.expand(false);
// select the new node
me.myGetOwnerTree().getSelectionModel().select(newNode);
// call the renameNode method for this new node
newNode.renameNode();
});
},
/*
*
*/
createLeaf: function(contextNode) {
// create new node with the same type than the contextNode
var newNode = Ext.create(contextNode.$className, {leaf : true});
// load the rootNode and recursively all its child nodes
amdaModel.InteractiveNode.preloadNodes(contextNode.getRootNode(),
function()
{
// edit newNode into Parameter Module with node as contextNode
newNode.editInModule(contextNode);
});
},
renameNode: function() {
if (this.myGetOwnerTree())
{
// load the rootNode and recursively all its child nodes if not already loaded
var me = this;
amdaModel.InteractiveNode.preloadNodes(this.getRootNode(),
function()
{
// fire the edition event on tree
me.myGetOwnerTree().fireEvent('edition',me.myGetOwnerTree().view, me.myGetOwnerTree().getSelectionModel().selected.items[0]);
});
}
else
{
myDesktopApp.errorMsg('tree is undefined');
//TODO: log error... tree is undefined
}
},
/*
* load the rootNode and recursively all its child nodes
* to know all names of DerivedParameters
*/
editLeaf: function()
{
var me = this;
amdaModel.InteractiveNode.preloadNodes(this.getRootNode(),
function()
{
if (me.get('object'))
{
// launch edition of parameter into parameter module
me.editInModule();
}
else
{
// call the ext method to get the details of parameter
// the edition of real parameter is done into callback method getObjectCallback
AmdaAction.getObject(me.get('id'), me.get('nodeType'), me.getObjectCallback, me);
}
});
},
/*
*
*/
getObjectCallback : function(result,remoteEvent)
{
var t = remoteEvent.getTransaction();
if (result) {
/*TODO why Caro used this???
var reader = Ext.create('Ext.data.reader.Json',{
model: this.get('objectDataModel')
});
var resultSet = reader.read(result);
var paramObj = resultSet.records[0];//
*/
var paramObj = Ext.create(this.get('objectDataModel'), result);
// set parameter into node
this.set('object',paramObj);
// Edition of parameter into parameter Module
this.editInModule();
}
else {
// EXCEPTION : parameter not found !?
myDesktopApp.errorMsg(t.action + "." + t.method + " : No parameter '"
+ this.get('name') + "' found!");
}
}
});