/*
* 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 tool displays the JCR tags in a tree
* @private
*/
Ext.define('Ametys.plugins.cms.tag.tool.TagsTool', {
extend : 'Ametys.tool.Tool',
/**
* @property {Ext.tree.Panel} _tree The tree panel
* @private
*/
/**
* @property {Ext.Template} _tagTooltipTpl The template used for tags' tooltip
*/
_tagTooltipTpl : Ext.create('Ext.XTemplate', [
'<tpl if="description && description != \'\'">',
'{description}<br/>',
'</tpl>',
'<u>{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_VISIBILITY}}</u> : <span style="white-space: nowrap">{visibility}</span><br/>',
'<tpl if="target && target != \'\'">',
'<u>{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_TARGET}}</u> : <span style="white-space: nowrap">{target}</span><br/>',
'</tpl>',
'<tpl if="isColorable">',
'<tpl if="color && color != \'\'">',
'<u>{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_COLOR}}</u> : <span class="{class}-tooltip-{color}" /><br/>',
'<tpl else>',
'<u>{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_COLOR}}</u> : <span class="{class}-tooltip-default" /><br/>',
'</tpl>',
'</tpl>'
]),
/**
* @property {String} _rootNodeLabel The label of the root node
*/
_rootNodeLabel : '{{i18n PLUGINS_CMS_UITOOL_TAGS_ROOT_NODE_LABEL}}',
constructor : function(config)
{
this.callParent(arguments);
Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onTagCreated, this);
Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onTagModified, this);
Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onTagDeleted, this);
},
createPanel : function()
{
var store = this._createStore();
this._tree = Ext.create('Ext.tree.Panel', {
border : false,
scrollable : true,
animate : true,
dockedItems: this._getDockedItems(),
store : store,
root : {
text : this._rootNodeLabel,
expanded: false,
iconCls : "tag-icon-root"
},
rootVisible : true,
selModel: {
mode: "SINGLE"
},
viewConfig: {
plugins: {
ptype: 'ametystreeviewdragdrop',
containerScroll: true,
appendOnly: true,
sortOnDrop: true,
expandDelay: 500,
allowContainerDrops: false,
setAmetysDragInfos: Ext.bind(this.getDragInfo, this),
setAmetysDropZoneInfos: Ext.bind(this.getDropInfo, this)
}
}
});
this._tree.on('itemmouseenter', this._createQtip, this);
this._tree.on('selectionchange', this._onSelectNode, this);
return this._tree;
},
/**
* @template
* @protected
* Get the docked items for the tree panel
* @return {}
*/
_getDockedItems: function()
{
var items = this._getTopToolbarItems();
if (items && items.length > 0)
{
return [{
dock: 'top',
xtype: 'toolbar',
layout: {
type: 'hbox',
align: 'stretch'
},
border: false,
defaults : {
cls: 'ametys',
labelWidth: 55,
labelSeparator: ''
},
items: items
}]
}
},
/**
* @template
* @protected
* Get the toolbars to but on top of the tool.<br/>
* Override this method to add your own toolbars.
*/
_getTopToolbarItems: function ()
{
return [];
},
/**
* @protected
* Get the store that will return the tags
* @return {Ext.data.TreeStore}
*/
_createStore: function()
{
var store = Ext.create('Ext.data.TreeStore', {
model : this.getTagModel(),
autoLoad : false,
sorters: [{
property: 'priority',
direction: 'DESC'
}, {
property: 'text',
direction: 'ASC'
}],
proxy : {
type : 'ametys',
plugin : 'cms',
url : 'tags/repository.json',
reader : {
type : 'json'
},
extraParams: {
contextualParameters: this.getContextualParameters(),
tagProviderId: this.getTagProviderId(),
tagProviderEPId: this.getTagProviderEPId()
}
}
});
return store;
},
/**
* @template
* @protected
* Get the contextual parameters.<br/>
* Override this method to set your own parameters.
*/
getContextualParameters: function ()
{
return {};
},
/**
* @template
* @protected
* Get the id of tag provider.<br/>
* Returns 'org.ametys.cms.tag.jcr.CMSJCRTagProvider' by default<br/>
* Override this method to get tags of provider of your choice.
* @return the id of tag provider
*/
getTagProviderId: function()
{
return 'org.ametys.cms.tag.jcr.CMSJCRTagProvider';
},
/**
* @template
* @protected
* Get the id of tag provider extension point.<br/>
* Returns 'org.ametys.cms.tag.jcr.CMSJCRTagProvider' by default<br/>
* Override this method to get tags of provider extension point of your choice.
* @return the id of tag provider extension point
*/
getTagProviderEPId: function()
{
return 'org.ametys.cms.tag.TagProviderExtensionPoint';
},
/**
* @template
* @protected
* Get the id of the tag DAO.<br/>
* Returns 'org.ametys.cms.tag.jcr.JCRTagsDAO' by default<br/>
* Override this method to get DAO link to you provider
* @return the id of the tag DAO
*/
getTagDAO: function()
{
return 'org.ametys.cms.tag.jcr.JCRTagsDAO';
},
/**
* @template
* @protected
* Get the tag model.<br/>
* Returns 'Ametys.plugins.cms.tag.TagsTreePanel.TagNode' by default<br/>
* Override this method to get different tag model
* @return the tag model
*/
getTagModel: function()
{
return 'Ametys.plugins.cms.tag.TagsTreePanel.TagNode';
},
/**
* @template
* @protected
* Get the tag target message id.<br/>
* Returns 'Ametys.plugins.cms.tag.TagsTreePanel.TagNode' by default<br/>
* Override this method to get different tag target message id
* @return the tag target message id
*/
getTagTargetMessage: function()
{
return Ametys.message.MessageTarget.TAG;
},
/**
* @template
* @protected
* Get the root tag target message id.<br/>
* Returns 'Ametys.plugins.cms.tag.TagsTreePanel.TagNode' by default<br/>
* Override this method to get different root tag target message id
* @return the root tag target message id
*/
getRootTagTargetMessage: function()
{
return Ametys.message.MessageTarget.TAG_ROOT;
},
getMBSelectionInteraction : function()
{
return Ametys.tool.Tool.MB_TYPE_ACTIVE;
},
sendCurrentSelection : function()
{
var me = this;
var targets = [];
Ext.each (this._tree.getSelectionModel().getSelection(), function (node) {
targets.push(me.getMessageTargetConfiguration(node));
});
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.SELECTION_CHANGED,
targets: targets
});
},
setParams: function (params)
{
this.callParent(arguments);
this.refresh();
},
refresh : function()
{
this.showRefreshing();
//Ask the server the root ID
Ametys.data.ServerComm.callMethod({
role: this.getTagDAO(),
methodName: "getTagRootNode",
parameters: [this.getTagProviderId(), this.getContextualParameters()],
callback: {
scope: this,
handler: this._getRootIdCb
},
errorMessage: {
msg: "{{i18n PLUGINS_CMS_HANDLE_TAGS_TAG_ERROR}}",
category: this.self.getName()
}
});
},
/**
* Callback function called after retrieving the root node's ID
* @param {Object} result The server response
* @param {Object} args The callback arguments
* @private
*/
_getRootIdCb: function (result, args)
{
this._tree.setRootNode({
id : result.id,
editable : false,
allowDrag : false,
allowDrop : true,
text : this._rootNodeLabel,
name : 'tags',
path : 'tags',
expanded : true,
iconCls : "tag-icon-root"
});
this._tree.getSelectionModel().select(0);
this.showRefreshed();
},
/**
* This function is called when a node is selected.
* @param {Ext.selection.Model} sm The selection model
* @param {Ext.data.NodeInterface[]} selected The selected nodes
* @private
*/
_onSelectNode : function(sm, selected)
{
this.sendCurrentSelection();
},
/**
* Destroy and create the node tooltip when the mouse enters the node
* @param {Ext.view.View} view The tree view
* @param {Ext.data.Model} node The tree node
* @param {HTMLElement} el The node's element
*/
_createQtip: function (view, node, el)
{
// If it's the root node, no Qtip
if (!node.parentNode)
{
return;
}
Ext.QuickTips.unregister(el);
Ext.QuickTips.register(Ext.apply({target: el, id: el.id + '-tooltip'}, this._getTooltip(node)));
},
/**
* Get the tooltip configuration
* @param {Ext.data.Model} node The tree node
* @returns {Object} The tooltip configuration. See Ametys.ui.fluent.Tooltip.
*/
_getTooltip: function(node)
{
var title = node.get('name');
if (node.get("title") != null)
{
title = Ext.String.escapeHtml(node.get('title')) + " (" + title + ")";
}
var text = this._tagTooltipTpl.applyTemplate ({
description: Ext.String.escapeHtml(node.get('description')).replace(/\n/g, "<br/>"),
visibility : (node.get('visibility') == 'PRIVATE' ? "{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_VISIBILITY_PRIVATE}}" : "{{i18n PLUGINS_CMS_UITOOL_TAG_TOOLTIP_VISIBILITY_PUBLIC}}"),
target: node.get('target-info') && Ext.isObject(node.get('target-info')) ? node.get('target-info').label || node.get('target-info').name : null,
color: node.get('color'),
class: node.get('class'),
isColorable: node.get('isColorable')
});
return {
title: title,
glyphIcon: node.get('tooltipIconCls'),
imageWidth: 48,
imageHeight: 48,
text: text,
inribbon: false
};
},
/**
* This function reload the given node
* @param {String} id The ID of the node to reload
* @param {Function} callback The callback function to call after reload. Can be null. Has the following parameters:
* @param {Ext.data.Model} callback.node The refreshed node
* @private
*/
_refreshNode : function(id, callback)
{
var node;
if (id == null)
{
var selection = this._tree.getSelectionModel().getSelection();
node = selection.length > 0 ? selection[0] : null;
}
else
{
node = id != '' ? this._tree.getStore().getNodeById(id) : this._tree.getRootNode();
}
if (node != null)
{
this._tree.getStore().load({
node : node,
callback : function()
{
Ext.defer(this._expandNode, 200, this, [ node, callback ]);
},
scope : this
});
}
},
/**
* Expand the given node
* @param {String} node The node to expand.
* @param {Function} callback The callback function to call after reload. Can be null. Has the following parameters:
* @param {Ext.data.Model} callback.node The expanded node
* @private
*/
_expandNode : function(node, callback)
{
if (node == null)
{
var selection = this.getSelectionModel().getSelection();
node = selection.length > 0 ? selection[0] : null;
}
if (node != null)
{
callback = callback ? Ext.bind(callback, null, [ node ]) : null;
this._tree.expandNode(node, false, callback);
}
},
/**
* Deletes a node from the tree and selects the parent node.
* @param {String} id The ID of the node to delete.
* @private
*/
_deleteNode: function (id)
{
var node = this._tree.getStore().getNodeById(id);
if (node != null)
{
var parentNode = node.parentNode;
this._tree.getSelectionModel().select([parentNode]);
node.remove();
}
},
/**
* Listener on {@link Ametys.message.Message#CREATED} message.
* If the tool is concerned by the created object, the parent node will be refreshed.
* @param {Ametys.message.Message} message The created message.
* @private
*/
_onTagCreated: function (message)
{
var tagTarget = message.getTarget(this.getTagTargetMessage());
if (tagTarget != null)
{
var tagParentId = tagTarget.getParameters().parentId;
var node = this._tree.getStore().getNodeById(tagParentId);
if (node != null)
{
node.set('leaf', false);
node.commit();
}
this._refreshNode(tagParentId);
}
},
/**
* Listener on {@link Ametys.message.Message#MODIFIED} message.
* If the tool is concerned by the created object, the node will be refreshed.
* @param {Ametys.message.Message} message The created message.
* @private
*/
_onTagModified: function (message)
{
var tagTarget = message.getTarget(this.getTagTargetMessage());
if (tagTarget != null)
{
var tagId = tagTarget.getParameters().id;
Ametys.data.ServerComm.callMethod({
role: this.getTagDAO(),
methodName: "getTag",
parameters: [tagId],
callback: {
scope: this,
handler: this._updateTag
},
errorMessage: {
msg: "{{i18n PLUGINS_CMS_HANDLE_TAGS_TAG_ERROR}}",
category: this.self.getName()
}
});
if (message.getParameters().major)
{
// Reload node
var node = this._tree.getStore().getNodeById(tagId);
if (node !== null)
{
node.set('expanded', true)
node.commit();
this._tree.getStore().load({node: node});
}
}
}
var tagRootTarget = message.getTarget(this.getRootTagTargetMessage());
if (tagRootTarget != null)
{
var tagRootNode = this._tree.getStore().getNodeById(tagRootTarget.getParameters().id);
if (tagRootNode != null)
{
// Reload whole tree
this._tree.getStore().load({ node: tagRootNode });
}
}
},
/**
* Update a tag which has been modified
* @param {Object} response The JSON server response
* @private
*/
_updateTag : function (response)
{
var id = response['id'];
var node = this._tree.getStore().getNodeById(id);
if (node !== null)
{
node.beginEdit();
node.set('name', response['name']);
node.set('title', response['title']);
node.set('description', response['description']);
node.set('priority', response['priority']);
this._updateAdditionalInfo(node, response);
node.endEdit();
node.commit();
this._tree.getStore().sort();
}
},
/**
* @template
* @protected
* Update a additional info of the tag which has been modified
* @param {Object} node The node to update
* @param {Object} response The JSON server response
*/
_updateAdditionalInfo: function (node, response)
{
node.set('visibility', response['visibility']);
node.set('target', response['target']);
node.set('target-info', response['target-info']);
node.set('color', response['color']);
node.set('class', response['class']);
node.set('isColorable', response['isColorable']);
},
/**
* Listener on {@link Ametys.message.Message#DELETED} message.
* If the tool is concerned by the created object, the node will be refreshed.
* @param {Ametys.message.Message} message The deleted message.
* @private
*/
_onTagDeleted : function(message)
{
var tagTarget = message.getTarget(this.getTagTargetMessage());
if (tagTarget != null)
{
this._deleteNode(tagTarget.getParameters().id)
}
},
/**
* @private
* This event is thrown by the getDragData to add the 'source' of the drag.
* @param {Object} item The default drag data that will be transmitted. You have to add a 'source' item in it:
* @param {Ametys.relation.RelationPoint} item.source The source (in the relation way) of the drag operation.
*/
getDragInfo: function(item)
{
var targets = [];
for (var i = 0; i < item.records.length; i++)
{
var cfg = this.getMessageTargetConfiguration(item.records[i]);
if (cfg != null)
{
targets.push(cfg);
}
}
if (targets.length > 0)
{
item.source = {
relationTypes: [Ametys.relation.Relation.MOVE, Ametys.relation.Relation.REFERENCE],
targets: targets
};
}
},
/**
* @private
* This event is thrown before the beforeDrop event and create the target of the drop operation relation.
* @param {Ext.data.Model[]} targetRecords The target records of the drop operation.
* @param {Object} item The default drag data that will be transmitted. You have to add a 'target' item in it:
* @param {Object} item.target The target (in the relation way) of the drop operation. A Ametys.relation.RelationPoint config.
*/
getDropInfo: function(targetRecords, item)
{
var targets = [];
for (var i = 0; i < targetRecords.length; i++)
{
var cfg = this.getMessageTargetConfiguration(targetRecords[i]);
if (cfg != null)
{
targets.push(cfg);
}
}
if (targets.length > 0)
{
item.target = {
relationTypes: [Ametys.relation.Relation.MOVE],
targets: targets
};
}
},
/**
* @private
* Called by #getDragInfo, #getDragInfo or by tools to send the current selection
* @param {Ext.data.Model} record The tree record to convert to its Ametys.message.MessageTarget configuration
* @return {Object} The configuration to create a Ametys.message.MessageTarget. Can be null, if the record is null or not relevant to be a messagetarget.
*/
getMessageTargetConfiguration: function (record)
{
if (record == null)
{
// Empty selection
return null;
}
else
{
return {
id: record.isRoot() ? this.getRootTagTargetMessage() : this.getTagTargetMessage(),
parameters: {
name: record.isRoot() ? '' : record.get('name'),
id: record.getId()
}
}
}
}
});
Ext.define("Ametys.message.TagMessageTarget",
{
override: "Ametys.message.MessageTarget",
statics:
{
/**
* @member Ametys.message.MessageTarget
* @readonly
* @property {String} TAG The target type is a tag. The expected parameters are:
* @property {String} TAG.id The id of tag
* @property {String} TAG.parentId The id of the parent of the tag
* @property {String} TAG.name The name of tag
*/
TAG: "tag",
/**
* @member Ametys.message.MessageTarget
* @readonly
* @property {String} TAG_ROOT The target type is a tag root. The expected parameters are:
* @property {String} TAG_ROOT.id The id of root tag
* @property {String} TAG_ROOT.name The name of root tag
*/
TAG_ROOT: "tag-root"
}
}
);