/*
* Copyright 2014 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This class is the central point for moving, copying or referencing objects that will affect the server side.
* The list of supported operations depends on the list of registered extensions.
*/
Ext.define("Ametys.relation.RelationManager",
{
singleton: true,
/**
* @private
* @property {Ametys.relation.RelationHandler[]} _handlers The know relation handlers.
*/
_handlers: [],
/**
* Register a new relation handler, that will handle a new kind of relation .
* @param {Ametys.relation.RelationHandler} handler The instance of the new relation to handle
*/
register: function(handler)
{
this._handlers.push(handler);
},
/**
* This method is a quick test to know if there is a chance that #link would work.
* It can be called often, is fast and synchronous (no server request) in order to display the right drop-ok or drop-ko icon.
* It is based on all Ametys.relation.RelationHandler resgistered.
* @param {Object} sourceCfg The Ametys.relation.RelationPoint object from the drag source.
* @param {Object} targetCfg The Ametys.relation.RelationPoint object from the drop zone.
* @return {Boolean} False if link will fail. True if link can success.
*/
testLink: function(sourceCfg, targetCfg)
{
var sourceRelationTypes = (Ext.isArray(sourceCfg.relationTypes) ? sourceCfg.relationTypes : [sourceCfg.relationTypes || Ametys.relation.Relation.REFERENCE]);
var targetRelationTypes = (Ext.isArray(targetCfg.relationTypes) ? targetCfg.relationTypes : [targetCfg.relationTypes || Ametys.relation.Relation.REFERENCE]);
var possibleRelationTypes = Ext.Array.intersect(sourceRelationTypes, targetRelationTypes);
if (possibleRelationTypes.length == 0)
{
return false;
}
for (var i = 0; i < this._handlers.length; i++)
{
var handler = this._handlers[i];
var possibleRelation = handler.supportedRelations(sourceCfg, targetCfg) || [];
possibleRelation = Ext.isArray(possibleRelation) ? possibleRelation : [possibleRelation];
// Filter available relations with given relation
possibleRelation = Ext.Array.filter(possibleRelation, function(r, index) {
return Ext.Array.contains(possibleRelationTypes, r.getType());
});
if (possibleRelation.length > 0)
{
return true;
}
}
return false;
},
/**
* Effectively do a relation operation by delegating to a Ametys.relation.RelationHandler.
* This is an asynchronous process. Note that a popup to select handler and/or relation may appear.
* @param {Object/Ametys.relation.RelationPoint} source The start point of the relation operation. Can be a Ametys.relation.RelationPoint config or the object itself.
* @param {Object/Ametys.relation.RelationPoint} target The end point of the relation operation. Can be a Ametys.relation.RelationPoint config or the object itself.
* @param {Function} callback The function called when the relation operation is over.
* @param {Boolean/String} callback.success The success. False is a problem occurred, a Ametys.relation.Relation constant else determining which operation was done
* @param {String} [relationType=null] The relation type to establish.
* The default value is null that means to use the default relation of the chosen handler.
* You may transmit a relation (one of the constants Ametys.relation.Relation.MOVE, Ametys.relation.Relation.COPY or Ametys.relation.Relation.REFERENCE) to try to force the relation kind (if available only else it will leads to a popupmenu): this should happens if the user do not select the default UI option (e.g. a drag and drop with shift key maintained would force relation to Ametys.relation.RelationManager.MOVE).
* @param {Boolean} [handlerchoice=false] When true, if necessary a popup will appear to make the user choice the handler and/or relation to use. The user can "remember" its choice. So, when false (most cases), the manager will choose a handler.True should happens when the user do a special operation (e.g. drag and drop using the right button of the mouse). Even when this value is false, the popup may appear if the user did never select a default value.
*/
link: function(source, target, callback, relationType, handlerchoice)
{
this.getLogger().debug("Trying to start link");
if (!source.self)
{
source = Ext.create("Ametys.relation.RelationPoint", Ext.clone(source));
}
if (!target.self)
{
target = Ext.create("Ametys.relation.RelationPoint", Ext.clone(target));
}
if (!source.isReady())
{
source.waitForTargets(Ext.bind(this.link, this, arguments, false));
return;
}
if (!target.isReady())
{
target.waitForTargets(Ext.bind(this.link, this, arguments, false));
return;
}
this.getLogger().debug("Starting link...");
// List compatible relations between source and target
var possibleRelationTypes = Ext.Array.intersect(source.relationTypes, target.relationTypes);
if (possibleRelationTypes.length == 0)
{
this.getLogger().warn("Cannot find compatible relations types");
callback(false);
return;
}
// Loop on handlers to get those handling this source and this target, and filter it by possibleRelationTypes to keep only the possible relations
var possibleRelations = {};
for (var i = 0; i < this._handlers.length; i++)
{
var handler = this._handlers[i];
var possibleRelation = handler.supportedRelations(source, target) || [];
possibleRelation = Ext.isArray(possibleRelation) ? possibleRelation : [possibleRelation];
// Filter available relations with given relation
possibleRelation = Ext.Array.filter(possibleRelation, function(r, index) {
return Ext.Array.contains(possibleRelationTypes, r.getType());
});
if (possibleRelation.length > 0)
{
possibleRelations[i] = possibleRelation;
}
}
var indexes = Ext.Object.getKeys(possibleRelations);
if (indexes.length == 0)
{
Ametys.Msg.alert("{{i18n PLUGINS_CORE_UI_RELATIONS_UNSUPPORTED_MOVE_LABEL}}", "{{i18n PLUGINS_CORE_UI_RELATIONS_UNSUPPORTED_MOVE_DESCRIPTION}}");
this.getLogger().warn("No RelationHandler supports one of the compatible relations");
callback(false);
return;
}
// Now we have to determine which handler and relation will do the job
var choosenHandler = null;
var choosenRelation = null;
if (indexes.length == 1)
{
// A single handler that'it
choosenHandler = indexes[0];
}
else if (!handlerchoice)
{
// Many possible handlers, is there any that was previously chosen?
// TODO
// choosenHandler = ...
}
if (choosenHandler)
{
// The handler is known
if (possibleRelations[choosenHandler].length == 1)
{
// A single possible relation in it. that's it
choosenRelation = possibleRelations[choosenHandler][0];
}
else
{
// Many relation in the chosen handler
if (relationType)
{
// A relation type was imposed
// Let's see if its compatible with the possible relations
var compatible = null;
Ext.Array.every(possibleRelations[choosenHandler], function(item, index, array) {
if (item.getType() == relationType)
{
compatible = index;
return false;
}
return true;
});
if (compatible)
{
// The imposed relation type is possible => choice is done
choosenRelation = possibleRelations[choosenHandler][compatible];
}
}
else if (!handlerchoice)
{
// No relation type was imposed and the user did not wanted to choose, let's take the first one (e.g. the default choice)
choosenRelation = possibleRelations[choosenHandler][0];
}
}
}
if (choosenHandler == null || choosenRelation == null)
{
// Either the handler or the relation in it, is not determined: let's display a menu to make the user choose
// TODO
callback(false);
throw new Error("Not Yet implemented: The user have to choose which handler will do the relation");
}
this.getLogger().debug("Doing link...");
// Finally we have a handler and a relationType: let's link
this._handlers[choosenHandler].link(source, target, Ext.bind(this._linkCb, true, [choosenRelation.getType(), callback], true), choosenRelation.getType());
this.getLogger().debug("Link is running...");
},
/**
* The link callback
* @param {Boolean} success Determine if the link was successfully done
* @param {String} tryedRelation The relation tried (Ametys.relation.Relation constant)
* @param {Function} callback The initial function called when the relation operation is over.
* @param {Boolean/String} callback.success The success. False is a problem occurred, a Ametys.relation.Relation constant else determining which operation was done
*/
_linkCb: function(success, tryedRelation, callback)
{
return callback(success ? tryedRelation : false);
}
}
);