/**
* 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/>.
*/
/**
* @module alfresco/navigation/Tree
* @extends external:dijit/_WidgetBase
* @mixes external:dojo/_TemplatedMixin
* @mixes module:alfresco/renderers/_PublishPayloadMixin
* @mixes module:alfresco/core/Core
* @mixes module:alfresco/documentlibrary/_AlfDocumentListTopicMixin
* @mixes module:alfresco/services/_NavigationServiceTopicMixin
* @author Dave Draper
*/
define(["dojo/_base/declare",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"dojo/text!./templates/Tree.html",
"alfresco/renderers/_PublishPayloadMixin",
"alfresco/core/Core",
"alfresco/core/CoreWidgetProcessing",
"alfresco/core/topics",
"service/constants/Default",
"alfresco/documentlibrary/_AlfDocumentListTopicMixin",
"alfresco/services/_NavigationServiceTopicMixin",
"dojo/dom-construct",
"dojo/_base/lang",
"dojo/_base/array",
"dojo/aspect",
"dojo/dom-class",
"alfresco/navigation/TreeStore",
"dijit/tree/ObjectStoreModel",
"dijit/Tree"],
function(declare, _Widget, _Templated, template, _PublishPayloadMixin, AlfCore, CoreWidgetProcessing, topics, AlfConstants, _AlfDocumentListTopicMixin,
_NavigationServiceTopicMixin, domConstruct, lang, array, aspect, domClass, TreeStore, ObjectStoreModel, Tree) {
// Extend the standard Dijit tree to support better identification of nodes (primarily for the purpose of unit testing)...
var AikauTree = declare([Tree], {
_createTreeNode: function alfresco_navigation_Tree_AikauTree___createTreeNode(){
var id = lang.getObject("item.id", false, arguments[0]);
if (id)
{
declare.safeMixin(arguments[0], {
id: this.id + "_" + id.replace("://", "_").replace("/", "_") // Clean up NodeRef IDs
});
}
var treeNode = this.inherited(arguments);
// When a tree node is added it is possible that it can be disabled if it meets
// configured disablement rules. This has been implemented to support the use case
// required for Cloud Sync where existing sync folders cannot be reused.
if (this.tree.treeNodeDisablementConfig && this.tree.treeNodeDisablementConfig.rules)
{
var item = arguments[0];
var disabled = array.some(this.tree.treeNodeDisablementConfig.rules, lang.hitch(this, this.processDisablementRule, item));
if (disabled)
{
domClass.add(treeNode.domNode, "alfresco-navigation-Tree__node--disabled");
}
}
return treeNode;
},
/**
* Extended to ignore clicks on disabled nodes.
*
* @param {object} nodeWidget The tree node widget clicked
* @param {object} e The click event
* @param {boolean} doOpen Whether or not to open
* @param {string} func A click callback function.
* @since 1.0.94
*/
__click: function alfresco_navigation_Tree_AikauTree____click(/*jshint unused:false*/ nodeWidget, e, doOpen, func) {
if (nodeWidget && nodeWidget.domNode && domClass.contains(nodeWidget.domNode, "alfresco-navigation-Tree__node--disabled"))
{
e.stopPropagation();
e.preventDefault();
}
else
{
this.inherited(arguments);
}
},
/**
* Processes any configured disablement rules for tree nodes. Please note that at present only
* the "contains" rule is supported. If other rules are required then please raise an issue
* for them to be implemented.
*
* @instance
* @param {object} node The node item to evaluate the data of
* @param {object} rule The rule configuration to process
* @return {boolean} Whether or not the tree node should be disabled
* @since 1.0.94
*/
processDisablementRule: function alfresco_navigation_Tree_AikauTree__processDisablementRule(node, rule) {
var disabled = false;
if (rule.property && rule.contains)
{
var targetArray = lang.getObject(rule.property, false, node);
if (targetArray)
{
disabled = array.some(targetArray, function(target) {
return array.some(rule.contains, function(containsItem) {
return containsItem === target;
});
});
}
}
return disabled;
}
});
return declare([_Widget, _Templated, _PublishPayloadMixin, AlfCore, CoreWidgetProcessing, _AlfDocumentListTopicMixin, _NavigationServiceTopicMixin], {
/**
* An array of the i18n files to use with this widget.
*
* @instance
* @type {object[]}
* @default [{i18nFile: "./i18n/Tree.properties"}]
*/
i18nRequirements: [{i18nFile: "./i18n/Tree.properties"}],
/**
* An array of the CSS files to use with this widget.
*
* @instance
* @type {object[]}
* @default [{cssFile:"./css/Tree.css"}]
*/
cssRequirements: [{cssFile:"./css/Tree.css"}],
/**
* Additional space separated CSS classes to be applied to the root DOM node of the widget.
*
* @instance
* @type {string}
* @default
*/
customCssClasses: "",
/**
* The HTML template to use for the widget.
* @instance
* @type {string}
*/
templateString: template,
/**
* @instance
* @type {string}
* @default
*/
siteId: null,
/**
* This should be set when the current context is a site, typically this will be set to "documentlibrary"
*
* @instance
* @type {string}
* @default
*/
containerId: null,
/**
* This should be set if "siteId" is not set.
*
* @instance
* @type {string}
* @default
*/
rootNode: null,
/**
* @instance
* @type {string}
* @default
*/
rootLabel: "documentlibrary.root.label",
/**
* This is the value that should be given to the root node. This node is not actually requested
* but is simply generated on instantiation. The value assigned to it is important because it will
* be included as the filter data when it is clicked.
*
* @instance
* @type {string}
* @default
*/
rootValue: "",
/**
* @instance
* @type {string}
* @default
*/
filterPrefsName: "docListTreePref",
/**
* This is an array of Regular Expressions that should match pathes to show. At least one of the
* Regular Expressions in the array needs to pass true for the tree node to be displayed. To show
* all paths this should be left as null (the default value).
*
* @instance
* @type {array}
* @default
*/
filterPaths: null,
/**
* Indicates whether or not the root node of the tree should be displayed or not.
*
* @instance
* @type {boolean}
* @default
*/
showRoot: true,
/**
* This is a topic that can be published to request child data.
*
* @instance
* @type {string}
* @default
* @since 1.0.39
* @event
*/
childRequestPublishTopic: null,
/**
* This is the payload that will be published to request child data when a
* [publishTopic]{@link module:alfresco/navigation/Tree#childRequestPublishTopic} has been configured.
*
* @instance
* @type {object}
* @default
* @since 1.0.39
*/
childRequestPublishPayload: null,
/**
* Indicates whether or not requests to get child data will be published globally using the
* [publishTopic]{@link module:alfresco/navigation/Tree#childRequestPublishTopic} that has been configured.
*
* @instance
* @type {boolean}
* @default
* @since 1.0.39
*/
childRequestPublishGlobal: true,
/**
* Configuration rules that determine when a tree node should be disabled.
*
* @instance
* @type {object}
* @default
* @since 1.0.94
*/
treeNodeDisablementConfig: null,
/**
* @instance
* @return {string} The root of the URL to use when requesting child nodes.
*/
getTargetUrl: function alfresco_navigation_Tree__getTargetUrl() {
var url = null;
if (this.siteId && this.containerId)
{
url = AlfConstants.PROXY_URI + "slingshot/doclib/treenode/site/" + this.siteId + "/documentlibrary";
}
else if (this.rootNode)
{
url = AlfConstants.PROXY_URI + "slingshot/doclib/treenode/node/alfresco/company/home";
}
else if (!this.childRequestPublishTopic)
{
this.alfLog("error", "Cannot create a tree without 'siteId' and 'containerId' or 'rootNode' attributes", this);
}
return url;
},
/**
* Gets an object containing the query parameters to include when requesting child nodes. The object returned
* will differ slightly depending upon whether the tree is configured to represent a site or a general node.
* A general node query will include a library root parameter (this will typically map to either company home,
* user home or shared files but can be any valid node).
*
* @instance
* @return {object} The query object to include when requesting child nodes.
*/
getTargetQueryObject: function alfresco_navigation_Tree__getTargetQueryObject() {
var query = {
perms: "false",
children: "false",
max: "-1"
};
if (this.siteId && this.containerId)
{
// No changes to the default query
}
else if (this.rootNode)
{
query.max = "500";
query.libraryRoot = this.rootNode;
}
return query;
},
/**
* Creates the a dijit/Tree widget.
*
* @instance
*/
postCreate: function alfresco_navigation_Tree__postCreate() {
this.showRoot = this.showRoot !== null ? this.showRoot : true;
// Create a new tree store using the siteId as part of the URL
this.treeStore = this.createWidget({
name: "alfresco/navigation/TreeStore",
config: {
publishTopic: this.childRequestPublishTopic,
publishPayload: this.childRequestPublishPayload,
publishGlobal: this.childRequestPublishGlobal,
target: this.getTargetUrl(),
targetQueryObject: this.getTargetQueryObject(),
filterPaths: this.filterPaths
}
});
// Create the object store...
// It is important that we set a root object. This should reflect correct location and label of the
// document library location...
this.treeModel = new ObjectStoreModel({
root: {
id: this.id + "_ROOT",
name: this.message(this.rootLabel),
value: this.rootValue,
path: this.rootValue + "/"
},
store: this.treeStore,
query: {}
});
// Create the tree and add it to our widget...
this.tree = new AikauTree({
id: this.id + "_TREE",
model: this.treeModel,
showRoot: this.showRoot,
onClick: lang.hitch(this, this.onClick),
onOpen: lang.hitch(this, this.onNodeExpand),
onClose: lang.hitch(this, this.onNodeCollapse),
treeNodeDisablementConfig: this.treeNodeDisablementConfig
});
this.tree.placeAt(this.domNode);
this.tree.startup();
// This section of code creates an aspect around the dndController created for the tree
// to prevent it from selecting nodes when the selected node is disabled...
// It works by only calling the original function when a non-disabled node has been selected
if (this.tree.dndController)
{
aspect.around(this.tree.dndController, "setSelection", function(originalFunction) {
return function(newSelection) {
if (newSelection &&
newSelection.length &&
newSelection[0].domNode &&
domClass.contains(newSelection[0].domNode, "alfresco-navigation-Tree__node--disabled"))
{
return null;
}
else
{
return originalFunction.apply(this, arguments);
}
};
});
}
},
/**
* This is the topic that is published when a node on the tree is clicked. The data applied
* to the filter is the value of the node clicked. By default it is expected that the
* tree represents a path so is set to the [path changed topic]{@link module:alfresco/core/topics#PATH_CHANGED}.
*
* @instance
* @type {string}
* @default [topics.PATH_CHANGED]{@link module:alfresco/core/topics#PATH_CHANGED}
*/
publishTopic: topics.PATH_CHANGED,
/**
* By default the payload type will the current item. This will automatically be set
* to be the tree node clicked.
*
* @instance
* @type {string}
* @default
*/
publishPayloadType: "CURRENT_ITEM",
/**
* @instance
* @param {object} item The store item represented by the clicked node
* @param {object} node The node on the tree that was clicked
* @param {object} evt The click event
*/
onClick: function alfresco_navigation_Tree__onClick(item, node, evt) {
if (node && node.domNode && domClass.contains(node.domNode, "alfresco-navigation-Tree__node--disabled"))
{
// Ignore clicks on nodes where the tree node is disabled...
this.alfLog("log", "Ignoring click on disabled tree node", item, node, evt);
}
else
{
this.alfLog("log", "Tree Node clicked", item, node, evt);
// Assign the clicked item as the currentItem value so that it can be used in payload generation...
this.currentItem = item;
if (!this.currentItem.nodeRef)
{
this.currentItem.nodeRef = this.rootNode;
}
this.publishPayload = lang.clone(this.publishPayload);
var generatedPayload = this.getGeneratedPayload(true);
this.alfPublish(this.publishTopic, generatedPayload, this.publishGlobal);
}
},
/**
* @instance
* @param {object} item The store item represented by the opened node
* @param {object} node The node on the tree that was opened
*/
onNodeExpand: function alfresco_navigation_Tree__onNodeExpand(item, node) {
if (node !== null && node._loadDeferred !== null && node._loadDeferred !== undefined)
{
this.alfLog("log", "Wait for node expand before rezize", node._loadDeferred);
node._loadDeferred.then(lang.hitch(this, this.requestSidebarResize));
}
},
/**
* @instance
* @param {object} item The store item represented by the collapsed node
* @param {object} node The node on the tree that was collapsed
*/
onNodeCollapse: function alfresco_navigation_Tree__onNodeExpand(item, node) {
if (node !== null && node._collapseDeferred !== null && node._collapseDeferred !== undefined)
{
this.alfLog("log", "Wait for node collapse before rezize", node._collapseDeferred);
node._loadDeferred.then(lang.hitch(this, this.requestSidebarResize));
}
},
/**
* Publishes a request to resize the side bar (if the tree is not in a side bar then this will
* have no effect).
*
* @instance
*/
requestSidebarResize: function alfresco_navigation_Tree__requestSidebarResize() {
this.alfPublish("ALF_RESIZE_SIDEBAR", {});
}
});
});