/*
* Copyright 2013 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 UI helper provides a dialog to choose a content attachment.
* Also allow the user to add a attachment to the content on-the-fly.
* See #open method.
*/
Ext.define('Ametys.cms.uihelper.ChooseAttachmentFile', {
singleton: true,
/**
* @property {String} ownerId The identifier of the attachments' owner
*/
/**
* @property {String} ownerMessageTargetType The type of message target of attachments' owner
*/
/**
* @property {Function} cbFn The callback function to call after an attachment has been chosen.
*/
/**
* @property {Function} insertAttachmentCb The callback function to call after an new attachment has been uploaded.
*/
/**
* @property {Boolean} [allowCreation=false] True to allow to upload attachment file.
*/
/**
* @property {Boolean} [creationRightId=null] The right to check to allow attachment insertion. Assume that owner target contains 'right'
*/
/**
* @property {Function} [filter] Tree node's filter function. See {@link Ametys.explorer.tree.ExplorerTree}.
*/
/**
* @property {Ametys.window.DialogBox} _box The dialog box (containing the resource explorer tree).
* @private
*/
/**
* @property {Ametys.plugins.cms.content.tree.AttachmentsExplorerTree} _tree The attachments explorer tree.
* @private
*/
/**
* @property {String[]} allowedExtensions The allowed extensions. Such as ["pdf", "zip"].
* @private
*/
/**
* Open a dialog box to choose a attachment
* @param {Object} config The configuration object
* @param {String} [config.icon] The full path to icon (16x16) for the dialog box
* @param {String} [config.iconCls] The CSS classes to apply to icon
* @param {String} config.title The title of the dialog box.
* @param {String} config.helpmessage The message displayed at the top of the dialog box.
* @param {String} config.ownerId (required) The identifier of the attachments' owner.
* @param {String} [config.ownerMessageTargetType=Ametys.message.MessageTarget.CONTENT] The attachments' owner message target type
* @param {String} [config.attachmentHelperRole=org.ametys.cms.repository.ContentDAO] the role of component used to get attachments' root node
* @param {String} [config.attachmentHelperMethodName=getAttachmentsRootNode] the name of method used to get attachments' root node
* @param {String} [config.value] The currently selected attachment.
* @param {String[]} [config.allowedExtensions] The allowed extensions. Such as ["pdf", "zip"].
* @param {Function} [config.filter] A node filter. Choose one in {@link Ametys.explorer.tree.ExplorerTree}. (One argument, the tree node and reply true or false)
* @param {String} [config.creationRightId=Plugin_Explorer_File_Add] The right to check to allow attachment insertion. Assume that owner target contains 'right'. 'allowCreation' configuration parameter has to be set to true.
* @param {String} [config.allowCreation=false] Set to 'true' to allow attachment insertion from dialog box
* @param {Function} config.callback The method that will be called when the dialog box is closed. The method can return false to cancel the closing action (you might display an error message in this case). Callback parameters are :
* @param {String} config.callback.id The id of the file
* @param {String} config.callback.filename: The name of the file
* @param {String} config.callback.size The size in byte of the file
* @param {String} config.callback.viewHref The url to VIEW the file
* @param {String} config.callback.downloadHref The url to DOWNLOAD the file
* @param {Function} [config.insertAttachmentCallback] Function to call after inserting a new attachment
*/
open: function(config)
{
this.cbFn = config.callback || Ext.emptyFn;
this.insertAttachmentCb = config.insertAttachmentCallback || Ext.emptyFn;
this.ownerId = config.ownerId;
this.ownerMessageTargetType = config.ownerMessageTargetType || Ametys.message.MessageTarget.CONTENT;
this.attachmentHelperRole = config.role || 'org.ametys.cms.repository.ContentDAO';
this.attachmentHelperMethodName = config.methodName || 'getAttachmentsRootNode';
this.value = config.value;
this.allowCreation = config.allowCreation || false;
this.rightId = config.rightId || "Plugin_Explorer_File_Add";
this.filter = config.filter;
this.allowedExtensions = config.allowedExtensions;
var allowDragAndDrop = true;
if (config.hasOwnProperty("allowDragAndDrop"))
{
allowDragAndDrop = config.allowDragAndDrop;
}
this._delayedInitialize(config.icon, config.iconCls || 'ametysicon-clip26', config.title, config.helpmessage || '', config.filter, config.allowedExtensions, allowDragAndDrop);
this._initTree();
this._box.show();
},
/**
* Creates the dialog box if it is not already created
* @param {String} icon The full path to icon (16x16 pixels) for the dialog box
* @param {String} iconCls A classname of a glyph css class
* @param {String} title The title of the dialog box.
* @param {String} helpmessage The message displayed at the top of the dialog box.
* @param {Function} [filter] A optional node filter. Choose one in {@link Ametys.explorer.tree.ExplorerTree}. (One argument, the tree node and reply true or false)
* @param {String[]} [allowedExtensions] The allowed extensions. Such as ["pdf", "zip"].
* @param {Boolean} [allowDragAndDrop] True to allow drag and drop
* @private
*/
_delayedInitialize: function(icon, iconCls, title, helpmessage, filter, allowedExtensions, allowDragAndDrop)
{
if (!this._initialized)
{
this._tree = Ext.create('Ametys.cms.attach.AttachmentsExplorerTree', {
flex: 1,
itemId: 'attachments-tree',
inlineEditionEnable: false,
border: true,
filter: filter,
allowedExtensions: allowedExtensions,
allowDragAndDrop: allowDragAndDrop,
rightId: this.rightId,
ownerMessageTargetType: this.ownerMessageTargetType,
height: 290,
listeners: {
selectionchange: {fn: this._onSelectionChange, scope: this},
filterupdated: {fn: this._onFilterUpdated, scope: this}
}
});
this._box = Ext.create('Ametys.window.DialogBox', {
title: title,
iconCls: icon ? null : iconCls,
icon: icon,
width: 410,
scrollable: false,
layout: {
type: 'vbox',
align: 'stretch'
},
items: [{
xtype: 'component',
cls: 'a-text',
html: helpmessage
},
this._tree,
{
xtype: 'component',
cls: 'a-text link',
html: "{{i18n PLUGINS_CMS_HELPER_CHOOSEATTACHMENTSFILE_NO_SELECTION}}",
hidden: !this.allowCreation
}
],
closeAction: 'hide',
referenceHolder: true,
defaultButton: 'validate',
buttons : [{
reference: 'validate',
text :"{{i18n PLUGINS_CMS_HELPER_CHOOSEATTACHMENTSFILE_OKBUTTON}}",
itemId: 'ok-btn',
disabled: true,
handler: Ext.bind(this._validate, this)
}, {
text :"{{i18n PLUGINS_CMS_HELPER_CHOOSEATTACHMENTSFILE_CANCELBUTTON}}",
handler: Ext.bind(this._cancel, this)
}]
});
this._initialized = true;
}
else
{
this._tree.updateFilter (filter || Ametys.explorer.tree.ExplorerTree.DEFAULT_FILTER, allowedExtensions);
if (icon)
{
this._box.setIcon(icon);
this._box.setIconCls(null);
}
else
{
this._box.setIconCls(iconCls);
this._box.setIcon(null);
}
this._box.setTitle(title);
this._box.items.getAt(0).update(helpmessage);
}
},
/**
* @private
* Get the root node configuration
* @param {String} id The root's id
*/
_getRootNodeConfiguration: function (id)
{
Ametys.data.ServerComm.callMethod({
role: this.attachmentHelperRole,
methodName: this.attachmentHelperMethodName,
parameters: [this.ownerId],
callback: {
scope: this,
handler: this._getAttachmentsRootNodeCb,
ignoreOnError: false
},
errorMessage: {
msg: "{{i18n PLUGINS_CMS_HELPER_CHOOSEATTACHMENTSFILE_ERROR}}",
category: this.self.getName()
}
});
},
/**
* @private
* Callback function invoked after retrieving the content's attachment root node
* @param {Object} response The root properties
* @param {Object[]} args the callback arguments
*/
_getAttachmentsRootNodeCb: function (response, args)
{
this._tree.setRootNodes([{
id: response.id,
allowDrag: false,
allowDrop: false,
path: '/dummy/attachments',
iconCls: 'ametysicon-clip26',
type: 'collection',
name: 'attachments',
text: "{{i18n PLUGINS_CMS_HELPER_CHOOSEATTACHMENTSFILE_ROOT_NODE}}" + ' (' + response.title + ')',
canCreateChild: response.canCreateChild,
hasChildNodes: response.hasChildNodes,
hasResources: response.hasResources
}], Ext.bind(this._setValue, this));
},
/**
* This method retrieves the current selection, and set the upload file link accordingly.
* @private
*/
_updateLinkCreation: function (selection)
{
var tree = this._box.items.get(1);
var selection = this._tree.getSelectionModel().getSelection();
var createLink = this._box.child("component[cls~=link]");
var node = selection[0];
if (node != null)
{
this._checkUserRight(node);
}
else
{
// No node is selected
this._clickEventRegistered = false;
createLink.update("{{i18n PLUGINS_CMS_HELPER_CHOOSEATTACHMENTSFILE_NO_SELECTION}}");
}
},
/**
* @private
* Check if user has the right to insert a new file
* @param node The selected node
*/
_checkUserRight: function (node)
{
var createLink = this._box.child("component[cls~=link]");
var ownerTarget = Ametys.message.MessageBus.getCurrentSelectionMessage().getTarget(this.ownerMessageTargetType);
// The right is test on current content
var hasRight = this.allowCreation && ownerTarget && (!this.rightId || Ext.Array.contains(ownerTarget.getParameters().rights, this.rightId));
if (hasRight)
{
createLink.update("<a class='action'>{{i18n PLUGINS_CMS_HELPER_CHOOSEATTACHMENTSFILE_UPLOAD_FILE}}</a>");
// add a click event listener on the <a class='action'> dom node to call the #insertAttachment method.
if (!this._clickEventRegistered)
{
createLink.mon(createLink.getEl(), 'click', Ext.bind(this.insertAttachment, this), this, {delegate: 'a.action'});
this._clickEventRegistered = true;
}
}
else
{
// No right to insert file
this._clickEventRegistered = false;
createLink.update("{{i18n PLUGINS_CMS_HELPER_CHOOSEATTACHMENTSFILE_UPLOAD_FILE_NORIGHT}}");
}
},
/**
* Initialize the attachment tree.
* @private
*/
_initTree: function ()
{
this._getRootNodeConfiguration();
},
/**
* @private
* Select the #value attachment in tree
*/
_setValue: function ()
{
this._tree.getRootNode().expandChildren();
if (this.value)
{
var me = this;
Ametys.explorer.ExplorerNodeDAO.getExplorerNode(this.value, function(resource) {
if (resource)
{
me._tree.selectByPath(me._tree.getRootNode().firstChild.getPath('name') + resource.getPath());
var selectedNode = me._tree.getStore().getNodeById(resource.getId());
if (selectedNode)
{
me._tree.getView().focusNode(selectedNode);
}
}
});
}
else
{
var rootNode = this._tree.getRootNode();
if (rootNode.id == 'dummy')
{
rootNode = rootNode.firstChild;
}
if (rootNode)
{
this._tree.getSelectionModel().select(rootNode);
this._tree.getView().focusNode(rootNode);
}
}
},
/**
* @private
* Handler when 'Ok' button is clicked.
* Calls the callback function passed in {@link #method-open} and hide the dialog box.
*/
_validate: function()
{
var node = this._tree.getSelectionModel().getSelection()[0];
var id = node.getId();
var text = node.get('text');
var size = node.get('size');
var viewHref = Ametys.explorer.tree.ExplorerTree.getViewHref(id);
var downloadHref = Ametys.explorer.tree.ExplorerTree.getDownloadHref(id);
if (this.cbFn(id, text, size, viewHref, downloadHref) !== false)
{
this._box.hide();
}
},
/**
* @private
* Handler when 'Ok' button is clicked.
* Calls the callback function passed in {@link #method-open} with null value and close the dialog box.
*/
_cancel: function ()
{
this._box.close();
if (Ext.isFunction (this.cbFn))
{
this.cbFn(null);
}
},
/**
* @private
* Listener on selection change in the tree.
* @param {Ext.selection.Model} sm The selection model
* @param {Ext.data.NodeInterface[]} nodes the selected nodes
*/
_onSelectionChange: function(sm, nodes)
{
var isValidSelection = nodes.length > 0 && nodes[0].get('type') == Ametys.explorer.tree.ExplorerTree.RESOURCE_TYPE;
this._box.down("button[itemId='ok-btn']").setDisabled(!isValidSelection);
if (this.allowCreation)
{
this._updateLinkCreation();
}
},
/**
* Insert a new attachment file under current folder selection in the tree
*/
insertAttachment: function()
{
var node = this._tree.getSelectionModel().getSelection()[0] || this._tree.getRootNode().firstChild;
if (node.get('type') == Ametys.explorer.tree.ExplorerTree.RESOURCE_TYPE)
{
// Up to parent folder
node = node.parentNode;
}
Ametys.explorer.resources.actions.File.add(node.getId(), this._insertAttachmentCb, this);
},
/**
* @private
* {@link #insertAttachment} callback.
* @param {Object[]} files The files
* @param {String} files.id The id of the new resource
* @param {String} files.name The name of the new resource
* @param {Boolean} files.reload true if a reload is needed.
* @param {String} parentId The id of parent folder
* @param {Boolean} reload true if a reload is needed.
* @param {Boolean} unzip true if the uploaded file was uploaded
* Store and filter management.
*/
_insertAttachmentCb: function(files, parentId, reload, unzip)
{
var multipleFileUploaded = unzip || files.length > 1;
// Reload the parent node
this._tree.refreshNode(parentId, Ext.bind(this._insertAttachmentCb2, this, [files, multipleFileUploaded]));
},
/**
* @private
* {@link #_insertAttachmentCb} callback.
*/
_insertAttachmentCb2: function(files, multipleFileUploaded)
{
if (multipleFileUploaded)
{
// Do nothing: let user choose his file among unzipped files/multiple files inserted.
return;
}
var id = files[0].id;
if (Ext.isFunction(this.insertAttachmentCb))
{
this.insertAttachmentCb (id);
}
var node = this._tree.getStore().getNodeById(id);
if (node)
{
this._tree.selectNode(node);
this._validate();
}
else
{
Ametys.Msg.alert(
"{{i18n PLUGINS_CMS_HELPER_CHOOSEATTACHMENTSFILE_ERRORFILETYPE_TITLE}}",
"{{i18n PLUGINS_CMS_HELPER_CHOOSEATTACHMENTSFILE_ERRORFILETYPE_DESCRIPTION}}"
);
}
}
});