/*
* Copyright 2016 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.
*/
/**
* Dialog box used to select one or more tags
* @private
*/
Ext.define('Ametys.cms.uihelper.ChooseTagHelper', {
singleton: true,
/**
* @property {Object} initialConfig The initial configuration object passed to the #open method:
* @property {Boolean} [initialConfig.multiple=false] True to allow multiple selections
* @property {Boolean} [initialConfig.onlyCustomTags=false] If true, only custom tags (JCR) are displayed
* @property {Boolean} [initialConfig.onlyTagsWithChildren=false] If 'true', only tags with children can be selected.
* @property {Boolean} [initialConfig.allowProviders=false] If 'true', tag providers are also be selected.
* @property {Boolean} [initialConfig.allowCreation=false] If 'true', a link at the dialog bottom proposes to add a new tag (defaults to false).
* @property {String} [initialConfig.targetType] The type of tags to display. If set to a value, display the tags of this target type. If set to null, display all tags.
* @property {String} [initialConfig.filterTarget] Filter on target type. If set to a value, disable the tags of this target type. If set to null, enable all tags.
* @property {String} [initialConfig.url="tags.json"] The url to get the tags informations
* @property {String} [initialConfig.icon] The full path to icon (16x16 pixels) for the dialog box.
* @property {String} [initialConfig.iconCls="ametysicon-label49"] The separated CSS class to apply for dialog icon. Use only if the 'icon' configuration is undefined.
* @property {String} initialConfig.title The title of dialog box
* @property {String} [initialConfig.helpMessage] The help message to display on top of dialog box.
* @property {String[]} [initialConfig.values] the selected tags as an array of id.
* @property {String[]} initialConfig.taggableObjects the ids of current objects to be tagged.
* @property {Function} initialConfig.cbContextParametersFct the function to get context parameters to add to callback parameters
* @property {String[]} initialConfig.callback The callback function invoked when tags are selected. The callback function will received the following parameters:
* @property {Function} initialConfig.callback.tags The names of selected tags
* @property {Object} initialConfig.callback.params The callback additional parameters
*/
/**
* @property {Ametys.window.DialogBox} _box The dialog box
* @private
*/
/**
* @property {Ametys.plugins.cms.tag.TagsTreePanel} _tree The tags tree panel
* @private
*/
/**
* Configure and open the dialog box
* @param {Object} config The configuration options :
* @param {String} [config.url=tags.json] The url to get the tags informations.
* @param {String} [config.iconCls] The CSS class for the icon of the dialog box.
* @param {String} [config.title] The title of the dialog box.
* @param {String} [config.helpMessage] The help message to display on top of dialog box.
* @param {String[]} [config.values] the selected tags as an array of id.
* @param {String[]} [config.taggableObjects] the ids of current objects to be tagged.
* @param {Boolean} [config.multiple=false] `true` to allow selecting multiple tags.
* @param {Boolean} [config.onlyCustomTags=false] `true` to show only custom tags.
* @param {Boolean} [config.allowProviders=false] `true` to allow selecting providers.
* @param {Boolean} [config.allowCreation=false] `true` to allow the creation of new tags from the dialog box.
* @param {Boolean} [config.onlyTagsWithChildren=false] `true` to prevent selection of tags without child.
* @param {String} [config.targetType] The type of tags to display. Set to the type of target to display only tags of this type. Set to null to display all tags.
* @param {String[]} [config.filterTarget] Filter on target type. Set to the type(s) of target to disable tags of this(those) type(s). Set to null to enable all tags.
* @param {Object} [config.contextualParameters] Optional contextual parameters to be passed to the server requests
* @param {Function} [config.cbContextParametersFct] the function to get context parameters to add to callback parameters
* @param {Function} config.callback The callback function invoked when tags are selected. The callback function will received the following parameters:
* @param {Function} config.callback.tags The names of selected tags
* @param {Object} tree the tag panel tree
* @param {Function} createAction the function to create tag
* @param {String} createAction.parentId the parent tag id
* @param {Function} createAction.callback the callback after creation
*/
open: function (config, tree, createAction)
{
this.initialConfig = Ext.applyIf(config || {}, this.getDefaultConfig());
this._clickEventRegistered = false;
this._tree = tree || this.createTree();
this._createAction = createAction || Ametys.plugins.cms.tag.TagActions.create;
this._box = this._createDialogBox();
this.initialize();
this._box.show();
},
/**
* @protected
* Get the default configuration
* @param {Object} config the default values for configuration object
*/
getDefaultConfig: function(config)
{
return {
title: "{{i18n PLUGINS_CMS_HELPER_CHOOSETAG_TITLE}}",
url: "tags.json",
plugin: "cms",
multiple: false,
onlyCustomTags: false,
allowProviders: false,
allowCreation: false,
onlyTagsWithChildren: false,
values: []
}
},
/**
* Function called after creating dialog box and before showing the dialog to initialize the tags tree.
* The default implementation does nothing.
* @protected
*/
initialize: function ()
{
// Nothing to do
},
/**
* @protected
* Creates the tags' tree
*/
createTree: function ()
{
var me = this;
return Ext.create('Ametys.plugins.cms.tag.TagsTreePanel', {
width: 320,
height: 450,
onlyCustomTags: this.initialConfig.onlyCustomTags,
allowProviders: this.initialConfig.allowProviders,
allowCreation: this.initialConfig.allowCreation,
onlyTagsWithChildren: this.initialConfig.onlyTagsWithChildren,
targetType: this.initialConfig.targetType,
filterTarget: this.initialConfig.filterTarget,
contextualParameters: this.initialConfig.contextualParameters,
values: this.initialConfig.values,
taggableObjects: this.initialConfig.taggableObjects,
url: this.initialConfig.url,
plugin: this.initialConfig.plugin,
checkMode: this.initialConfig.multiple
});
},
/**
* Creates the dialog box
* @return {Ametys.window.DialogBox} the dialog box
*/
_createDialogBox: function()
{
me = this;
this._tree.addListener('load', this._onLoad, this, { single: true });
this._tree.addListener('selectionchange', Ext.bind(this._onSelectionChange, this));
this._tree.addListener('checkchange', function(node, checked) {
if (me.multiple === true)
{
me._box.getDockedItems('toolbar[dock="bottom"]')[0].items.get(0).setDisabled(false);
}
});
return Ext.create('Ametys.window.DialogBox', {
title: this.initialConfig.title,
icon: this.initialConfig.icon,
iconCls: this.initialConfig.icon ? null : (this.initialConfig.iconCls || (this.initialConfig.multiple ? 'ametysicon-tag25' : 'ametysicon-label49')),
width: 420,
cls: 'ametys-dialogbox',
bodyPadding: '0',
items: {
xtype: 'panel',
layout: {
type: 'vbox',
align : 'stretch',
pack : 'start'
},
border: false,
items : [{
xtype: 'component',
cls: 'a-text',
padding: '5',
html: this.initialConfig.helpMessage || this._getDefaultHelpMessage()
},
this._tree,
{
xtype: 'component',
cls: 'a-text link',
html: "{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_NO_JCR_TAG_SELECTED}}",
hidden: !this.initialConfig.allowCreation
}]
},
closeAction: 'destroy',
buttons : [{
text :"{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_OKBUTTON}}",
handler : Ext.bind(this._validate, this)
}, {
text :"{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_CANCELBUTTON}}",
handler: Ext.bind(function() {this._box.close();}, this)
}]
});
},
/**
* @private
* Get the default help message to be displayed on top of dialog box
* The default help message depends on 'multiple' and 'onlyTagsWithChildren' configuration's parameters
* @return {String} The default help message
*/
_getDefaultHelpMessage: function ()
{
if (this.initialConfig.multiple)
{
return this.initialConfig.onlyTagsWithChildren ? "{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_SELECT_TAGS_WITH_CHILDREN_HELP_MESSAGE}}" : "{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_SELECT_TAGS_HELP_MESSAGE}}";
}
else
{
return this.initialConfig.onlyTagsWithChildren ? "{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_SELECT_TAG_WITH_CHILDREN_HELP_MESSAGE}}" : "{{i18n plugin.cms:PLUGINS_CMS_HELPER_CHOOSETAG_SELECT_TAG_HELP_MESSAGE}}";
}
},
/**
* This method updates the dialog box according to the current selection
* @param {Ext.selection.Model} sm The selection model
* @param {Ext.data.Model[]} nodes The selected nodes
* @private
*/
_onSelectionChange: function(sm, nodes)
{
if (this.initialConfig.multiple === false)
{
var isValidSelection = this._isSelectionValid();
this._box.getDockedItems('toolbar[dock="bottom"]')[0].items.get(0).setDisabled(!isValidSelection);
}
this._isAllowedToCreateTag();
},
/**
* Initialize the "ok" button of the dialog box on the first load.
* Select and focus the first node of the tree as well
* @param {Ext.data.Store} store The store
* @param {Ext.data.Model[]} records the loaded records
* @private
*/
_onLoad: function(store, records)
{
for (var i = 0; i < records.length; i++)
{
if (records[i].isRoot())
{
this._box.getDockedItems('toolbar[dock="bottom"]')[0].items.get(0).setDisabled(true);
}
}
var firstChild = this._tree.getRootNode().firstChild;
if (firstChild)
{
var view = this._tree.getView();
view.select(firstChild);
view.focusNode(firstChild);
}
},
/**
* Check if selection is valid according to the configuration parameters
* @return {boolean} true if the selection is valid
* @private
*/
_isSelectionValid : function()
{
var selection = this._tree.getSelectionModel().getSelection();
for (var s in selection)
{
if (this.initialConfig.allowProviders === false && selection[s].get('type') == 'provider')
{
return false;
}
if (this.initialConfig.onlyTagsWithChildren === true && selection[s].get('leaf') === true)
{
return false;
}
if (selection[s].get('disabled') === true)
{
return false;
}
}
return true;
},
/**
* This method retrieves the current selection, and set the tag creation link accordingly.
* @private
*/
_isAllowedToCreateTag: function()
{
var selection = this._tree.getSelectionModel().getSelection();
var node = selection[0];
if (node !== null)
{
var createCmp = this._box.items.get(0).child("component[cls~=link]");
var providerNode = this._getParentProviderNode(node);
if (providerNode == null || providerNode.get('creationAllowed') !== true)
{
// Selected node is not part of JCR provider branch
createCmp.update("{{i18n PLUGINS_CMS_HELPER_CHOOSETAG_NO_JCR_TAG_SELECTED}}");
this._clickEventRegistered = false;
}
else if (node.get('creationAllowed') === true)
{
createCmp.update("<a class='action'>{{i18n PLUGINS_CMS_HELPER_CHOOSETAG_ADD_MESSAGE}}</a>");
// add a click event listener on the <a class='action'> dom node to call the #_createTag method.
if (!this._clickEventRegistered)
{
createCmp.mon(createCmp.getEl(), 'click', this._createTag, this, {delegate: 'a.action'});
this._clickEventRegistered = true;
}
}
else
{
this._clickEventRegistered = false;
createCmp.update("{{i18n PLUGINS_CMS_HELPER_CHOOSETAG_ADD_NO_RIGHT}}");
}
}
},
/**
* Get the provider parent node of given node
* @param {Ext.data.NodeInterface} node the node
* @return {Ext.data.NodeInterface} the provider parent node or null if not found
*/
_getParentProviderNode: function (node)
{
var parentNode = node;
while (parentNode != null)
{
if (parentNode.get('type') == 'provider')
{
return parentNode;
}
parentNode = parentNode.parentNode;
}
return null;
},
/**
* Retrieve the current tree values, and call the callback function from the initial configuration sent to #open
* @private
*/
_validate: function ()
{
if (this.initialConfig.multiple === false)
{
var selected = [];
var selection = this._tree.getSelectionModel().getSelection();
for (var s in selection)
{
selected.push(selection[s].data.name);
}
this._box.close();
if (Ext.isFunction(this.initialConfig.callback))
{
this.initialConfig.callback(selected, this._getCbContextParameters());
}
}
else
{
this._box.close();
if (Ext.isFunction(this.initialConfig.callback))
{
this.initialConfig.callback(this._tree.values, this._getCbContextParameters());
}
}
},
/**
* Get callback context parameters
* @private
*/
_getCbContextParameters: function()
{
if (Ext.isFunction(this.initialConfig.cbContextParametersFct))
{
return this.initialConfig.cbContextParametersFct();
}
return {};
},
/**
* Create a tag from the dialog box
* @private
*/
_createTag: function()
{
var selection = this._tree.getSelectionModel().getSelection();
if (selection && selection.length > 0)
{
var id = selection[0].get('id');
this._createAction(id, Ext.bind(this._onTagCreated, this));
}
},
/**
* Callback function after tag creation
* @param {String} tagId The tag's id
* @param {String} tagName The tag's name
* @param {String} tagParentId The tag's parent id
* @private
*/
_onTagCreated: function (tagId, tagName, tagParentId)
{
var node = this._tree.getStore().getNodeById(tagParentId);
if (node != null)
{
node.set('leaf', false);
node.commit();
this._tree.getStore().load({
node : node,
callback : function()
{
Ext.defer(this._tree.expandNode, 200, this._tree, [ node, false, null ]);
},
scope : this
});
}
}
});