/**
* Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* This widget allows an array of [items]{@link module:alfresco/dnd/DragAndDropItems#items} to
* be rendered which can be dragged and dropped (e.g. onto a [DragAndDropTargetControl]{@link module:alfresco/form/controls/DragAndDropTargetControl}).
*
* @module alfresco/dnd/DragAndDropItems
* @extends external:dijit/_WidgetBase
* @mixes external:dojo/_TemplatedMixin
* @mixes module:alfresco/core/ObjectProcessingMixin
* @mixes module:alfresco/core/Core
* @author Dave Draper
*/
define(["dojo/_base/declare",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"alfresco/core/CoreWidgetProcessing",
"dojo/text!./templates/DragAndDropItems.html",
"alfresco/core/Core",
"alfresco/dnd/Constants",
"dojo/dnd/Source",
"dojo/_base/lang",
"dojo/_base/array",
"dojo/dom",
"dojo/on",
"dojo/string",
"dojo/dom-construct",
"dojo/dom-class"],
function(declare, _Widget, _Templated, CoreWidgetProcessing, template, AlfCore, Constants,
Source, lang, array, dom, on, stringUtil, domConstruct, domClass) {
return declare([_Widget, _Templated, CoreWidgetProcessing, AlfCore], {
/**
* An array of the CSS files to use with this widget.
*
* @instance
* @type {Array}
*/
cssRequirements: [{cssFile:"./css/DragAndDropItems.css"}],
/**
* The HTML template to use for the widget.
* @instance
* @type {String}
*/
templateString: template,
/**
* @instance
* @type {boolean}
* @default
*/
dragWithHandles: false,
/**
* If this is configured to be true then once an item has been used it will be removed
* so that it cannot be used again. If the item is deleted it will be reinstated.
*
* @instance
* @type {boolean}
* @default
*/
useItemsOnce: false,
/**
* When [items can only be used once]{@link module:alfresco/dnd/DragAndDropItems#useItemsOnce}
* this is the dot-notation property to compare deleted items against the configured
* [items]{@link module:alfresco/dnd/DragAndDropItems#items} to see if a deleted item should
* be re-instated.
*
* @instance
* @type {string}
* @default
*/
useItemsOnceComparisonKey: "name",
/**
* Indicates that the items should be rendered immediately. This is a configurable option because
* the [DragAndDropItemsListView]{@link module:alfresco/dnd/DragAndDropItemsListView} needs to be able
* to prevent immediately rendering the item data.
*
* @instance
* @type {boolean}
* @default
*/
immediateRender: true,
/**
* Indicates whether or not items displayed can be re-arranged. Defaults to false.
*
* @instance
* @type {boolean}
* @default
*/
selfAccept: false,
/**
* The array of types that this can be dropped if this is to be used as a target. Note that this has been added
* purely to support configurablility and extensions. If this is left as the default of null then it means
* that this cannot be used as a drag-and-drop target.
*
* @instance
* @type {array}
* @default
*/
acceptTypes: null,
/**
* Creates a palette of items that can be dragged (and dropped).
*
* @instance
*/
postCreate: function alfresco_dnd_DragAndDropItems__postCreate() {
// Listen for items that are selected through key presses so that the currently selected item
// can be inserted into a drop target when the drop target is actioned by the keyboard
on(this.paletteNode, Constants.itemSelectedEvent, lang.hitch(this, this.onItemSelected));
if (this.immediateRender)
{
this.renderData();
}
this.alfSubscribe(Constants.requestItemToAddTopic, lang.hitch(this, this.onItemToAddRequest));
this.alfSubscribe(Constants.itemSelectedTopic, lang.hitch(this, this.onExternalItemSelected));
if (this.useItemsOnce === true)
{
this.alfSubscribe(Constants.itemDeletedTopic, lang.hitch(this, this.onItemDeleted));
this.alfSubscribe(Constants.itemAddedTopic, lang.hitch(this, this.onItemAdded));
}
},
/**
* This function can be called to render the drag-and-drop items. It has this specific name so that
* it can be used as the renderer in the [DragAndDropItemsListView]{@link module:alfresco/dnd/DragAndDropItemsListView}
*
* @instance
*/
renderData: function alfresco_dnd_DragAndDropItems__renderData() {
if (this.sourceTarget)
{
this.sourceTarget.destroy();
}
this.sourceTarget = new Source(this.paletteNode, {
copyOnly: !this.useItemsOnce,
selfCopy: false,
accept: lang.clone(this.acceptTypes) || [],
selfAccept: this.selfAccept,
creator: lang.hitch(this, this.creator),
withHandles: this.dragWithHandles
});
this.sourceTarget.insertNodes(false, this.items);
},
/**
* Handles items being deleted. If the item deleted is a deleted item from this widget then it will
* be re-instated.
*
* @instance
* @param {object} payload A payload containing a deleted item
*/
onItemDeleted: function alfresco_dnd_DragAndDropItems__onItemDeleted(payload) {
if (payload && payload.item && this.useItemsOnceComparisonKey)
{
var a = lang.getObject(this.useItemsOnceComparisonKey, false, payload.item);
if (a)
{
// NOTE: Using some to exit as soon as match is found
array.some(this.items, function(item) {
var b = lang.getObject(this.useItemsOnceComparisonKey, false, item.value);
if (a === b)
{
this.sourceTarget.insertNodes(false, [item]);
return true;
}
return false;
}, this);
}
}
},
/**
* Handles items being deleted. If the item deleted is a deleted item from this widget then it will
* be re-instated.
*
* @instance
* @param {object} payload A payload containing a deleted item
*/
onItemAdded: function alfresco_dnd_DragAndDropItems__onItemAdded(payload) {
if (payload && payload.value && this.useItemsOnceComparisonKey)
{
var a = lang.getObject(this.useItemsOnceComparisonKey, false, payload.value);
if (a)
{
var _this = this;
this.sourceTarget.forInItems(function(i, id, map) {
var _item = map.getItem(id);
var b = lang.getObject(_this.useItemsOnceComparisonKey, false, _item.data.value);
if (a === b)
{
map.delItem(id);
domConstruct.destroy(dom.byId(id));
}
});
}
}
},
/**
* Handles the selection of an item. When an item is selected it will returned as the item to
* insert into a drag target when requested.
*
* @instance
* @param {object} evt The selection event
*/
onItemSelected: function alfresco_dnd_DragAndDropItems__onItemToAddRequest(evt) {
if (evt && evt.target && evt.target.parentNode)
{
var item = this.sourceTarget.getItem(evt.target.parentNode.id);
if (item)
{
this._selectedItem = item;
this._selectedNode = evt.target.parentNode;
array.forEach(this.sourceTarget.getAllNodes(), function(node) {
domClass.remove(node.firstChild, "selected");
});
domClass.add(evt.target, "selected");
// Publish the selected item so that other DragAndDropItems widgets can de-select
// any selected items...
this.alfPublish(Constants.itemSelectedTopic, {
widget: this
});
}
}
},
/**
* Handles publications indicating that another [DragAndDropItems]{@link module:alfresco/dnd/DragAndDropItems}
* widget publishing at the same scope has had an item selected. It checks that the publication didn't
* originate from the current instance and if not deselects all the items.
*
* @instance
* @param {object} payload The payload containing the details of the widget that has had an item selected.
*/
onExternalItemSelected: function alfresco_dnd_DragAndDropItems__onExternalItemSelected(payload) {
if (payload && payload.widget !== this)
{
array.forEach(this.sourceTarget.getAllNodes(), function(node) {
domClass.remove(node.firstChild, "selected");
});
this._selectedItem = null;
}
},
/**
* Handles requests to provide an item to insert into a [DragAndDropTarget]{@link module:alfresco/dnd/DragAndDropTarget}
*
* @instance
* @param {object} payload The payload of the request to add an item.
*/
onItemToAddRequest: function alfresco_dnd_DragAndDropItems__onItemToAddRequest(payload) {
if (payload.promise && typeof payload.promise.resolve === "function")
{
if (this._selectedItem)
{
payload.promise.resolve({
item: lang.clone(this._selectedItem.data),
addCallback: this.onItemAddedByKeyboard,
addCallbackScope: this
});
// See AKU-379 - ensure use once items can only be added via the keyboard once...
if (this.useItemsOnce)
{
this._selectedItem = null;
}
}
}
},
/**
* Handles items being added via the keyboard. This checks to see whether
* [items can only be used once]{@link module:alfresco/dnd/DragAndDropItems#useItemsOnce}
* and if this is the case it will remove the selected item.
*
* @instance
* @param {object} item The item selected
*/
onItemAddedByKeyboard: function alfresco_dnd_DragAndDropItems__onItemAddedByKeyboard(/*jshint unused:false*/ item) {
if (this.useItemsOnce === true && this._selectedNode)
{
this.sourceTarget.delItem(this._selectedNode.id);
domConstruct.destroy(this._selectedNode);
this._selectedNode = null;
}
},
/**
* The widgets model to render as a drag-and-drop item.
*
* @instance
* @type {array}
*/
widgets: [
{
name: "alfresco/dnd/DragAndDropItem",
config: {
iconClass: "{iconClass}",
title: "{title}"
}
}
],
/**
* Handles the creation of drag and drop avatars. This could check the supplied hint parameter
* to see if an avatar is required, but since the source doesn't allow self-copying and is not
* a target in itself then this is not necessary.
*
* @instance
* @param {object} item The configuration for the dragged item.
*/
creator: function alfresco_dnd_DragAndDropItems__creator(item, hint) {
// jshint unused: false
var node = domConstruct.create("div");
var clonedItem = lang.clone(item);
this.currentItem = {};
this.currentItem.title = "";
if (clonedItem.label)
{
this.currentItem.title = this.message(clonedItem.label);
}
this.currentItem.iconClass = clonedItem.iconClass || "";
var widgetModel = lang.clone(this.widgets);
this.processObject(["processCurrentItemTokens"], widgetModel);
this.processWidgets(widgetModel, node);
return {node: node, data: clonedItem, type: clonedItem.type};
},
/**
* The array of items to render as draggable entities. Each item should have a type array (indicating
* what targets it can be dropped onto), a label (to indicate its purpose) and a value (which can
* be any complex object).
*
* @instance
* @type {array}
* @default
*/
items: null
});
});