/**
* 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 mixin in provides all the functions required for widgets that wish to process sub-widgets.
*
* @module alfresco/core/CoreWidgetProcessing
* @extends module:alfresco/core/Core
* @mixes module:alfresco/core/ObjectProcessingMixin
* @mixinSafe
* @author Dave Draper
*/
define(["dojo/_base/declare",
"alfresco/core/Core",
"alfresco/core/ObjectProcessingMixin",
"alfresco/forms/controls/utilities/RulesEngineMixin",
"alfresco/core/ObjectTypeUtils",
"dijit/registry",
"dojo/_base/array",
"dojo/_base/lang",
"dojo/dom-attr",
"dojo/dom-construct",
"dojo/dom-class",
"dojo/dom-style",
"dojo/Deferred",
"service/constants/Default",
"alfresco/debug/WidgetInfo"],
function(declare, AlfCore, ObjectProcessingMixin, RulesEngineMixin, ObjectTypeUtils, registry, array, lang, domAttr, domConstruct, domClass, domStyle, Deferred, AlfConstants, WidgetInfo) {
return declare([AlfCore, ObjectProcessingMixin, RulesEngineMixin], {
/**
* An array of the CSS files to use with this widget.
*
* @instance
* @type {object[]}
* @default [{cssFile:"./css/CoreWidgetProcessing.css"}]
*/
cssRequirements: [{cssFile:"./css/CoreWidgetProcessing.css"}],
/**
* The current item to act upon
*
* @instance
* @type {object}
* @default
*/
currentItem: null,
/**
* This string is used to identify locations of counts of widgets that are being processed.
*
* @instance
* @type {string}
* @default
* @since 1.0.36
*/
_countDownLocationPrefix: "_processingCountDown",
/**
* This string is used to identify locations where processed widgets can be referenced. This location
* will either be populated with an array of widgets or with a promise that will be resolved once all the
* widgets have been created. This should not be set or configured.
*
* @instance
* @type {string}
* @default
* @since 1.0.36
*/
_processedWidgetsLocationPrefix: "_processedWidgets",
/**
* This string is used to identify locations of arrays where widgets that are being created will be stored.
* THis should not be set or configured.
*
* @instance
* @type {string}
* @default
* @since 1.0.36
*/
_processingWidgetsLocationPrefix: "_processingWidgets",
/**
* Used to keep track of all the widgets created as a result of a call to the
* [processWidgets]{@link module:alfresco/core/Core#processWidgets} function. This should not be referenced directly,
* instead the [getProcessedWidgets]{@link module:alfresco/core/Core#getProcessedWidgets} should be called.
*
* @instance
* @type {Array}
* @default
*/
_processedWidgets: null,
/**
* As of 1.0.36 this is no longer used and it should not be referenced
*
* @instance
* @type {number}
* @default
* @deprecated Since 1.0.36
*/
_processedWidgetCountdown: null,
/**
* This function can be used to retrieve the widgets that have been processed by a call to
* [processWidgets]{@link module:alfresco/core/CoreWidgetProcessing#processWidgets}. It return a promise
* of the widgets that will be created an as such calls to this function should be protected through
* the use of dojo/when. It is not recommended to use this function, instead it is better to extend
* the [allWidgetsProcessed]{@link module:alfresco/core/CoreWidgetProcessing#allWidgetsProcessed} function
* as this will only be called when widget processing has completed and is passed an argument of the
* widgets that were created.
*
* @instance
* @param {string} processWidgetsId The ID mapped to the original processWidgets call
* @return {object[]|promise} The array of processed widgets or a promise of them
* @since 1.0.36
*/
getProcessedWidgets: function alfresco_core_CoreWidgetProcessing__getProcessedWidgets(processWidgetsId) {
var widgets;
var processedWidgets = lang.getObject(this.getWidgetProcessingLocation(processWidgetsId, this._processedWidgetsLocationPrefix), false, this);
if (processedWidgets)
{
widgets = processedWidgets.promise;
}
else
{
widgets = [];
}
return widgets;
},
/**
* This function is used to get the dot-notation property of the key attributes used when processing widgets. When
* a processWidgetsId is provided, the location will be a location within a map. The supplied prefix indicates
* the type of property required - this will either be the remaining count of widgets to process, the array of
* widgets processed so far or the promise of all the widgets when processing is complete.
*
* @instance
* @param {string} processWidgetsId The ID mapped to the original processWidgets call
* @param {string} prefix Expected to be either [_processedWidgetsLocationPrefix]{@link module:alfresco/core/CoreWidgetProcessing#_processedWidgetsLocationPrefix}
* or [_processingWidgetsLocationPrefix]{@link module:alfresco/core/CoreWidgetProcessing#_processingWidgetsLocationPrefix}
* @return {string} A dot-notation property to set or retrieve the processed widgets
* @since 1.0.36
*/
getWidgetProcessingLocation: function alfresco_core_CoreWidgetProcessing__getWidgetProcessingLocation(processWidgetsId, prefix) {
var location = prefix;
if (processWidgetsId)
{
location = prefix + "Map." + processWidgetsId;
}
return location;
},
/**
* This function can be used to instantiate an array of widgets. Each widget configuration in supplied
* widgets array is passed to the [processWidget]{@link module:alfresco/core/Core#processWidget} function
* to handle it's creation.
*
* @callable
* @instance
* @param {array} widgets An array of the widget definitions to instantiate
* @param {element} rootNode The DOM node which should be used to add instantiated widgets to
* @param {string} processWidgetsId An optional ID that might have been provided to map the results of the call to
*/
processWidgets: function alfresco_core_CoreWidgetProcessing__processWidgets(widgets, rootNode, processWidgetsId) {
// There are two options for providing configuration, either via a JSON object or
// via a URL to asynchronously retrieve the configuration. The configuration object
// takes precedence as it will be faster by virtue of not requiring a remote call.
try
{
// For the moment we'll just ignore handling the configUrl...
if (widgets && widgets instanceof Array)
{
// Reset the processing complete flag... NOTE: THIS CAN NO LONGER BE RELIED ON UNLESS ONLY USED FOR SINGLE REQUESTS
this.widgetProcessingComplete = false;
// Reset all the data for this call... we create a Deferred object for anyone trying to get the widgets before
// they've all be created, a countdown to track all the widgets that need to be created and an array to add widgets
// to as they are created...
lang.setObject(this.getWidgetProcessingLocation(processWidgetsId, this._countDownLocationPrefix), widgets.length, this);
lang.setObject(this.getWidgetProcessingLocation(processWidgetsId, this._processedWidgetsLocationPrefix), new Deferred(), this);
lang.setObject(this.getWidgetProcessingLocation(processWidgetsId, this._processingWidgetsLocationPrefix), [], this);
// Iterate over all the widgets in the configuration object and add them...
array.forEach(widgets, lang.hitch(this, this.processWidget, rootNode, processWidgetsId));
}
else
{
throw "widgets is missing or not an array";
}
}
catch(e)
{
this.alfLog("error", "The following error occurred processing widgets", e);
}
},
/**
* Creates a widget from the supplied configuration. The creation of each widgets DOM node
* is delegated to the [createWidgetDomNode]{@link module:alfresco/core/Core#createWidgetDomNode} function
* and the actual instantiation of the widget is handled by the [createWidget]{@link module:alfresco/core/Core#createWidget} function.
* Before creation of the widget begins the [filterWidget]{@link module:alfresco/core/Core#filterWidget} function is
* called to confirm that the widget should be created. This allows extending classes the opportunity filter
* out widget creation in specific circumstances.
*
* @instance
* @param {element} rootNode The DOM node where the widget should be created.
* @param {string} processWidgetsId An optional ID that might have been provided to map the results of multiple calls to [processWidgets]{@link module:alfresco/core/Core#processWidgets}
* @param {object} widgetConfig The configuration for the widget to be created
* @param {number} index The index of the widget configuration in the array that it was taken from
*/
processWidget: function alfresco_core_CoreWidgetProcessing__processWidget(rootNode, processWidgetsId, widgetConfig, index) {
if (widgetConfig)
{
if (this.filterWidget(widgetConfig, index, processWidgetsId))
{
var domNode = this.createWidgetDomNode(widgetConfig, rootNode, widgetConfig.className || "");
this.createWidget(widgetConfig, domNode, this._registerProcessedWidget, this, index, processWidgetsId);
}
}
else
{
this.alfLog("warn", "Could not process widget because it was missing configuration - check 'widgets' array for empty elements", this, index);
var location = this.getWidgetProcessingLocation(processWidgetsId, this._countDownLocationPrefix);
var countdown = lang.getObject(location, false, this);
lang.setObject(location, countdown - 1, this);
}
},
/**
* This function registers the creation of a widget. It decrements the
* [_processedWidgetCountdown]{@link module:alfresco/core/Core#_processedWidgetCountdown} attribute
* and calls the [allWidgetsProcessed]{@link module:alfresco/core/Core#allWidgetsProcessed} function when it reaches zero.
*
* @instance
* @param {object} widget The widget that has just been processed.
* @param {number} index The target index of the widget
* @param {string} processWidgetsId An optional ID that might have been provided to map the results of multiple calls to [processWidgets]{@link module:alfresco/core/Core#processWidgets}
*/
_registerProcessedWidget: function alfresco_core_CoreWidgetProcessing___registerProcessedWidget(widget, index, processWidgetsId) {
// Decrement the count as another widget is registered...
var countDownLocation = this.getWidgetProcessingLocation(processWidgetsId, this._countDownLocationPrefix);
var countdown = lang.getObject(countDownLocation, false, this) - 1;
lang.setObject(countDownLocation, countdown, this);
this.alfLog("log", "Widgets expected: ", countdown, this.id);
// 1.0.35 UPDATE
// If an "processWidgetsId" attribute is provided then we want to make sure that multiple calls to processWidgets
// will not result in a _processedWidgets attribute containing results different calls. Therefore we want to map each
// call to its own array... We still retain the original _processedWidgets object for backwards compatibility. The reason
// for this is to ensure that in the event of an XHR request being made for a dependency that the asynchronous processing
// is handled correctly.
var location = this.getWidgetProcessingLocation(processWidgetsId, this._processingWidgetsLocationPrefix);
var processedWidgets = lang.getObject(location, false, this);
if (widget)
{
if (!index || index === 0 || isNaN(index))
{
processedWidgets.push(widget);
}
else
{
processedWidgets[index] = widget;
}
// Handle any dynamic visibility and invisibility rules...
this.setupVisibilityConfigProcessing(widget, false);
this.setupVisibilityConfigProcessing(widget, true);
}
else
{
this.alfLog("warn", "No widget supplied following registration", this);
}
if (countdown === 0)
{
// Double-check that no empty elements are in the array of processed widgets...
// This could still be possible when indices have been used to set array contents...
processedWidgets = array.filter(processedWidgets, function(item) {
return (item !== null && typeof item !== "undefined");
}, this);
// IMPORTANT: We need to reset the processedWidgets with the filtered version...
var promise = lang.getObject(this.getWidgetProcessingLocation(processWidgetsId, this._processedWidgetsLocationPrefix), false, this);
promise.resolve(processedWidgets);
this.allWidgetsProcessed(processedWidgets, processWidgetsId);
this.widgetProcessingComplete = true; // NOTE: Not safe to refer to when calling processWidgets multiple times from a single widget
this.alfPublish("ALF_WIDGET_PROCESSING_COMPLETE", {}, true);
}
},
/**
* This function is called when form style visibility configuration is used.
*
* @instance
* @param {boolean} status Whether or not the widget should be displayed or not.
* @param {object} widget The widget to toggle the visibility of
* @since 1.0.69
*/
_visibilitySetting: function alfresco_core_CoreWidgetProcessing___visibilitySetting(status, widget) {
domStyle.set(widget.domNode, "display", status ? "" : "none");
},
/**
* Sets up the dynamic visibility handling for the supplied widget.
*
* @instance
* @param {object} widget The widget to process the config of
* @param {string} configAttribute The attribute to use in the widget config for rules
* @param {boolean} negate Whether or not to negate the evaluated rule
*/
setupVisibilityConfigProcessing: function alfresco_core_CoreWidgetProcessing__setupVisibilityConfigProcessing(widget, negate) {
/*jshint eqnull:true*/
// Set a default for negation if not provided...
negate = (negate != null) ? negate : false;
// Based on the negate value we'll determine which configuration attribute to look at...
var configAttribute = negate ? "invisibilityConfig" : "visibilityConfig";
// If the widget has dynamic visibility behaviour configured then we need to set up the necessary
// subscriptions to handle the rules that have been defined. We will set the initial visibility
// as requested and then set up the subcriptions...
if (widget[configAttribute])
{
// See AKU-974 - support for "form" style visibility config...
var useState = lang.getObject(configAttribute + ".useState", false, widget);
if (useState)
{
this.processConfig("_visibilitySetting", widget.visibilityConfig, widget);
}
else
{
var initialValue = lang.getObject(configAttribute + ".initialValue", false, widget);
initialValue = negate ? !initialValue : initialValue;
if (initialValue === false)
{
// Hide the widget if requested to initially...
domStyle.set(widget.domNode, "display", "none");
}
var rules = lang.getObject(configAttribute + ".rules", false, widget);
if (rules)
{
array.forEach(rules, function(rule) {
var topic = rule.topic,
attribute = rule.attribute,
is = rule.is,
isNot = rule.isNot,
strict = rule.strict != null ? rule.strict : true,
useCurrentItem = rule.useCurrentItem != null ? rule.useCurrentItem : false;
if (topic && attribute && (is || isNot))
{
var rulesObj = {
attribute: attribute,
lookupObject: useCurrentItem && widget.currentItem,
is: is,
isNot: isNot,
negate: negate,
strict: strict
},
successCallback = lang.hitch(this, this.onVisibilityProcessedSuccess, widget),
failureCallback = lang.hitch(this, this.onVisibilityProcessedFailure, widget);
widget.alfConditionalSubscribe(topic,
rulesObj,
successCallback,
failureCallback,
rule.subscribeGlobal,
rule.subscribeParent,
rule.subscribeScope);
}
}, this);
}
}
}
},
/**
* Called when visibility config on a widget has passed rule-evaluation.
*
* @instance
* @param {object} widget The widget under test
* @since 1.0.44
*/
onVisibilityProcessedSuccess: function alfresco_core_CoreWidgetProcessing__onVisibilityProcessedSuccess(widget) {
domStyle.set(widget.domNode, "display", "");
if (typeof widget.alfPublishResizeEvent === "function")
{
widget.alfPublishResizeEvent(widget.domNode);
}
},
/**
* Called when visibility config on a widget has failed rule-evaluation.
*
* @instance
* @param {object} widget The widget under test
* @since 1.0.44
*/
onVisibilityProcessedFailure: function alfresco_core_CoreWidgetProcessing__onVisibilityProcessedFailure(widget) {
domStyle.set(widget.domNode, "display", "none");
},
/**
* This function is called whenever a widget configured with a dynamic visibility rule is triggered
* by a publication. The configured rules are processed and the widget is displayed or hidden
* accordingly
*
* @instance
* @param {object} widget The widget to control the visibility of
* @param {array} is The values that the payload value can be for the widget to be visible
* @param {array} isNot The values that the payload value must not be for the widget to be visible
* @param {string} attribute The dot-notation attribute to retrieve from the payload
* @param {boolean} negate Whether or not the to negate the evaluated rule (e.g evaluated visible become invisible)
* @param {object} payload The publication payload triggering the visibility processing
* @deprecated Since 1.0.44 - See [setupVisibilityConfigProcessing]{@link module:alfresco/core/CoreWidgetProcessing#setupVisibilityConfigProcessing} source code for current technique for evaluating visibility
*/
processVisibility: function alfresco_core_CoreWidgetProcessing__processVisibility(widget, is, isNot, attribute, negate, strict, useCurrentItem, payload) {
var target = lang.getObject(attribute, false, payload);
// Assume that its NOT valid value (we'll only do the actual test if its not set to an INVALID value)...
// UNLESS there are no valid values specified (in which case any value is valid apart form those in the invalid list)
var isValidValue = (typeof is === "undefined" || is.length === 0);
// Initialise the invalid value to be false if no invalid values have been declared (and only check values if defined)...
var isInvalidValue = (typeof isNot !== "undefined" && isNot.length > 0);
if (isInvalidValue)
{
// Check to see if the current value is set to an invalid value (i.e. a value that negates the rule)
isInvalidValue = array.some(isNot, lang.hitch(this, "visibilityRuleComparator", target, widget, useCurrentItem));
}
// Check to see if the current value is set to a valid value...
if (!isInvalidValue && typeof is !== "undefined" && is.length > 0)
{
isValidValue = array.some(is, lang.hitch(this, "visibilityRuleComparator", target, widget, useCurrentItem));
}
var evaluationPassed = isValidValue && !isInvalidValue;
if (evaluationPassed)
{
// Handle successful evaluation, the widget will be displayed or hidden depending on the
// negate value (e.g. if negated the evaluated position is hidden, not displayed)...
if (negate)
{
domStyle.set(widget.domNode, "display", "none");
}
else
{
domStyle.set(widget.domNode, "display", "");
if (typeof widget.alfPublishResizeEvent === "function")
{
widget.alfPublishResizeEvent(widget.domNode);
}
}
}
else if (strict)
{
// If evaluation has failed and we're running in "strict" mode then we need to show
// or hide the widget as dictated by the "negate" variable...
if (negate)
{
domStyle.set(widget.domNode, "display", "");
if (typeof widget.alfPublishResizeEvent === "function")
{
widget.alfPublishResizeEvent(widget.domNode);
}
}
else
{
domStyle.set(widget.domNode, "display", "none");
}
}
},
/**
* This function compares the supplied values for equality. It is called from the
* [processVisibility function]{@link module:alfresco/core/CoreWidgetProcessing#processVisibility} to compare
* the current value with the configured rules for dynamically hiding and displaying a widget
* triggered by publications
*
* @instance
* @param {string} targetValue The target value supplied
* @param {boolean} useCurrentItem Indicates whether or not the values to check are attributes of the "currentItem"
* @param {string} currValue The value from the current rule being processed
* @returns {boolean} true if the values match and false otherwise
* @deprecated Since 1.0.44 - See [setupVisibilityConfigProcessing]{@link module:alfresco/core/CoreWidgetProcessing#setupVisibilityConfigProcessing} source code for current technique for evaluating visibility
*/
visibilityRuleComparator: function alfresco_core_CoreWidgetProcessing__visibilityRuleComparator(targetValue, widget, useCurrentItem, currValue) {
/*jshint eqnull:true*/
if (targetValue == null && currValue == null)
{
return true;
}
else if (targetValue != null &&
typeof targetValue.toString === "function" &&
currValue != null &&
typeof currValue.toString === "function")
{
if (useCurrentItem === true && widget.currentItem)
{
var c = lang.getObject(currValue.toString(), false, widget.currentItem);
return c === targetValue.toString();
}
else
{
return currValue.toString() === targetValue.toString();
}
}
else
{
return false;
}
},
/**
* This is set from false to true after the [allWidgetsProcessed]{@link module:alfresco/core/Core#allWidgetsProcessed}
* extension point function is called. It can be used to check whether or not widget processing is complete.
* This is to allow for checks that widget processing has been completed BEFORE attaching a listener to the
* [allWidgetsProcessed]{@link module:alfresco/core/Core#allWidgetsProcessed} function.
*
* @instance
* @type {boolean}
* @default
*/
widgetProcessingComplete: false,
/**
* This is an extension point for handling the completion of calls to [processWidgets]{@link module:alfresco/core/Core#processWidgets}
*
* @extensionPoint
* @instance
* @param {Array} widgets An array of all the widgets that have been processed
* @param {string} processWidgetsId An optional ID that might have been provided to map the results of multiple calls to [processWidgets]{@link module:alfresco/core/Core#processWidgets}
*/
allWidgetsProcessed: function alfresco_core_CoreWidgetProcessing__allWidgetsProcessed(widgets, processWidgetsId) {
// jshint unused:false
this.alfLog("log", "All widgets processed");
},
/**
* Creates a new DOM node for a widget to use. The DOM node contains a child <div> element
* that the widget will be attached to and an outer <div> element that additional CSS classes
* can be applied to.
*
* @instance
* @param {object} widget The widget definition to create the DOM node for
* @param {element} rootNode The DOM node to create the new DOM node as a child of
* @param {string} rootClassName A string containing one or more space separated CSS classes to set on the DOM node
*/
createWidgetDomNode: function alfresco_core_CoreWidgetProcessing__createWidgetDomNode(widget, rootNode, rootClassName) {
// Add a new <div> element to the "main" domNode (defined by the "data-dojo-attach-point"
// in the HTML template)...
var tmp = rootNode;
if (rootClassName)
{
tmp = domConstruct.create("div", { className: rootClassName}, rootNode);
}
return domConstruct.create("div", {}, tmp);
},
/**
* This function is used to build the configuration used to instantiate a widget.
*
* @instance
* @param {object} widget The widget configuration build configuration for
* @return {object} The arguments that can be used when instantiating the widget configuration processed
*/
processWidgetConfig: function alfresco_core_CoreWidgetProcessing__processWidgetConfig(widget) {
// jshint maxcomplexity:false
// Make sure we have an instantiation args object...
var initArgs = (widget && widget.config && (typeof widget.config === "object")) ? widget.config : {};
// Ensure that each widget has a unique id. Without this Dojo seems to occasionally
// run into trouble trying to re-use an existing id...
if (typeof initArgs.id === "undefined")
{
// Attempt to use the model ID as the DOM ID if available, but if not just generate an ID
// based on the module name...
if (!widget.id || lang.trim(widget.id) === "")
{
initArgs.id = widget.name.replace(/\//g, "_") + "___" + this.generateUuid();
}
else
{
initArgs.id = widget.id;
}
}
if (initArgs.generatePubSubScope === true)
{
// Generate a new pubSubScope if requested to...
initArgs.pubSubScope = this.generateUuid();
}
else if (initArgs.pubSubScope === undefined)
{
// ...otherwise inherit the callers pubSubScope if one hasn't been explicitly configured...
initArgs.pubSubScope = this.pubSubScope;
}
// Pass on the pub/sub scope from the parent...
if (initArgs.pubSubScope === this.pubSubScope)
{
// If the scope is inherited then also inherit the parent scope...
if (!this.parentPubSubScope)
{
// ...set as global if not set already
initArgs.parentPubSubScope = "";
}
else
{
// ...but try to inherit...
initArgs.parentPubSubScope = this.parentPubSubScope;
}
}
else
{
// If the scope has changed then inherit the my scope...
initArgs.parentPubSubScope = this.pubSubScope;
}
if (initArgs.dataScope === undefined)
{
initArgs.dataScope = this.dataScope;
}
if (initArgs.currentItem === undefined)
{
initArgs.currentItem = this.currentItem;
}
if (initArgs.currentMetadata === undefined)
{
initArgs.currentMetadata = this.currentMetadata;
}
if (initArgs.groupMemberships === undefined)
{
initArgs.groupMemberships = this.groupMemberships;
}
return initArgs;
},
/**
* This method will instantiate a new widget having requested that its JavaScript resource and
* dependent resources be downloaded. In principle all of the required resources should be available
* if the widget is being processed in the context of the Surf framework and dependency analysis of
* the page has been completed. However, if this is being performed as an asynchronous event it may
* be necessary for Dojo to request additional modules. This is why the callback function is required
* to ensure that successfully instantiated modules can be kept track of.
*
* @instance
* @param {object} widget The configuration for the widget
* @param {element} domNode The DOM node to attach the widget to
* @param {function} callback A function to call once the widget has been instantiated
* @param {object} callbackScope The scope with which to call the callback
* @param {number} index The index of the widget to create (this will effect it's location in the
* [_processedWidgets]{@link module:alfresco/core/Core#_processedWidgets} array)
* @return {object|promise} Either the created widget or the promise of a widget
*/
createWidget: function alfresco_core_CoreWidgetProcessing__createWidget(widget, domNode, callback, callbackScope, index, processWidgetsId) {
var _this = this;
this.alfLog("log", "Creating widget: ",widget);
var initArgs = this.processWidgetConfig(widget);
// In certain circumstances there is the possibility that Surf will not have been able to correctly
// identify all the dependencies for the widget that is being created. In this case we will return
// the promise of a widget and only register the widget once all the dependencies have been retrieved
// and the widget has been instantiated. This ensures that the allWidgetsProcessed function is never
// called until all widgets have truly been created.
var promisedWidget = new Deferred();
promisedWidget.then(lang.hitch(this, function(resolvedWidget) {
if (callback)
{
callback.call((callbackScope || this), resolvedWidget, index, processWidgetsId);
}
}));
// Create a reference for the widget to be added to. Technically the require statement
// will need to asynchronously request the widget module - however, assuming the widget
// has been included in such a way that it will have been included in the generated
// module cache then the require call will actually process synchronously and the widget
// variable will be returned with an assigned value...
var instantiatedWidget;
// Dynamically require the specified widget
// The use of indirection is done so modules will not rolled into a build (should we do one)
var requires = [widget.name];
require(requires, function(WidgetType) {
/* jshint maxcomplexity:false,maxstatements:false*/
// Just to be sure, check that no widget doesn't already exist with that id and
// if it does, generate a new one...
if (typeof WidgetType === "function")
{
try
{
var preferredDomNodeId;
if (registry.byId(initArgs.id))
{
preferredDomNodeId = initArgs.id;
initArgs.id = widget.name.replace(/\//g, "_") + "___" + _this.generateUuid();
}
// Instantiate the new widget
// This is an asynchronous response so we need a callback method...
instantiatedWidget = new WidgetType(initArgs, domNode);
if (!_this.widgetsToDestroy)
{
_this.widgetsToDestroy = [];
_this.widgetsToDestroy.push(widget);
}
if (preferredDomNodeId)
{
domAttr.set(instantiatedWidget.domNode, "id", preferredDomNodeId);
instantiatedWidget._alfPreferredWidgetId = preferredDomNodeId;
}
_this.alfLog("log", "Created widget", instantiatedWidget);
if (typeof instantiatedWidget.startup === "function")
{
instantiatedWidget.startup();
}
var assignToScope = widget.assignToScope || _this;
if (widget.assignTo)
{
assignToScope[widget.assignTo] = instantiatedWidget;
}
// Set any additional style attributes...
if (initArgs.style && instantiatedWidget.domNode)
{
domStyle.set(instantiatedWidget.domNode, initArgs.style);
}
// Create a node for debug mode...
if (AlfConstants.DEBUG && instantiatedWidget.domNode)
{
domClass.add(instantiatedWidget.domNode, "alfresco-debug-Info highlight");
var infoWidget = new WidgetInfo({
displayId: widget.id || "",
displayType: widget.name,
displayConfig: initArgs
}).placeAt(instantiatedWidget.domNode);
domConstruct.place(infoWidget.domNode, instantiatedWidget.domNode, "first");
}
// Look to see if we can add any additional CSS classes configured onto the instantiated widgets
// This should cover any widgets created by a call to the processWidgets function but will
// not capture widgets instantiated directly (which we should look to phase out) but this is
// why "additionalCssClasses" may still be defined and set explicitly in some widgets.
if (instantiatedWidget.domNode && initArgs.additionalCssClasses)
{
domClass.add(instantiatedWidget.domNode, initArgs.additionalCssClasses);
}
promisedWidget.resolve(instantiatedWidget);
}
catch (e)
{
_this.alfLog("error", "The following error occurred creating a widget", e, _this);
promisedWidget.resolve(null);
return null;
}
}
else
{
_this.alfLog("error", "The following widget could not be found, so is not included on the page '" + widget.name + "'. Please correct the use of this widget in your page definition", _this);
promisedWidget.resolve(null);
}
});
if (!widget)
{
this.alfLog("warn", "A widget was not declared so that it's modules were included in the loader cache", widget, this);
}
return instantiatedWidget || promisedWidget.promise;
},
/**
* Overrides [filterWidget]{@link module:alfresco/core/Core#filterWidget} to check for a "renderFilter" attribute
* included in the supplied widget configuration. This is then used to determine whether or not the widget
* should be created or not.
*
* @instance
* @param {object} widgetConfig The configuration for the widget to be created
* @returns {boolean} The result of the filter evaluation or true if no "renderFilter" is provided
*/
filterWidget: function alfresco_core_CoreWidgetProcessing__filterWidget(widgetConfig, index, processWidgetsId) {
var shouldRender = this.processAllFilters(widgetConfig.config);
if (!shouldRender)
{
// It is not always necessary to call the _registerProcessedWidget. This is relevant for widgets
// that work through an entire model before performing any processing (e.g. alfresco/core/FilteredPage)
this._registerProcessedWidget(null, index, processWidgetsId);
}
return shouldRender;
},
/**
* Processes filter configuration. This looks for either "renderFilters" (e.g. a filter containing
* sub-filters) or "renderFilter" (i.e. a single filter containing one or more rules to evaluate).
* It then delegates processing to the appropriate function
*
* @instance
* @param {object} filterConfig The configuration to inspect
* @return {boolean} True if all filters have evaluated successfully and false otherwise.
*/
processAllFilters: function alfresco_core_CoreWidgetProcessing__processAllFilters(filterConfig) {
var shouldRender = true;
if (filterConfig && filterConfig.renderFilters)
{
// If "renderFilters" (i.e. more than one "renderFilter" - see following else/if block)
var renderFiltersConfig = filterConfig.renderFilters;
var renderFiltersMethod = lang.getObject("renderFilterMethod", false, filterConfig);
shouldRender = this.processMultipleFilters(renderFiltersConfig, renderFiltersMethod);
}
else if (filterConfig && filterConfig.renderFilter)
{
// If filter configuration is provided, then switch the default so that rendering will NOT occur...
// Check that the object has a the supplied property...
var renderFilterConfig = filterConfig.renderFilter;
var renderFilterMethod = lang.getObject("renderFilterMethod", false, filterConfig);
shouldRender = this.processSingleFilter(renderFilterConfig, renderFilterMethod);
}
return shouldRender;
},
/**
* This function is used to to determine whether or not a filter containing multiple sub-filters evaluates to true.
* The sub-filters themselves can contain further nested filters.
*
* @instance
* @param {object[]} renderFilterConfig The configuration for the filter array
* @param {string} renderFilterMethod Either ANY or ALL
* @return {boolean} True if the filter passes and false otherwise
*/
processMultipleFilters: function alfresco_core_CoreWidgetProcessing__processMultipleFilters(renderFiltersConfig, renderFilterMethod) {
var shouldRender = true;
if (!ObjectTypeUtils.isArray(renderFiltersConfig))
{
// Invalid configuration counts as being allowed to render...
this.alfLog("warn", "A request was made to filter a widget, but the 'renderFilters' configuration was not an array", this, renderFiltersConfig, renderFilterMethod);
shouldRender = true;
}
else
{
// Check that the widget passes all the filter checks...
if (!renderFilterMethod || lang.trim(renderFilterMethod) === "ALL")
{
// Handle AND logic (all filters must pass)
shouldRender = array.every(renderFiltersConfig, lang.hitch(this, this.processAllFilters));
}
else
{
// Handle OR logic (only one filter needs to pass)
shouldRender = array.some(renderFiltersConfig, lang.hitch(this, this.processAllFilters));
}
}
return shouldRender;
},
/**
* This function is used to to determine whether or not a single filter evaluates to true. Note that a single
* filter can consist of multiple rules where all rules or just one rule must evaluate to true in order for
* the filter to pass.
*
* @instance
* @param {object[]} renderFilterConfig The configuration for the filter array
* @param {string} renderFilterMethod Either ANY or ALL
* @return {boolean} True if the filter passes and false otherwise
*/
processSingleFilter: function alfresco_core_CoreWidgetProcessing__processSingleFilter(renderFilterConfig, renderFilterMethod) {
var shouldRender = true;
if (!ObjectTypeUtils.isArray(renderFilterConfig))
{
this.alfLog("warn", "A request was made to filter a widget, but the 'renderFilter' configuration was not an array", this, renderFilterConfig, renderFilterMethod);
shouldRender = true;
}
else
{
// Check that the widget passes all the filter checks...
if (!renderFilterMethod || lang.trim(renderFilterMethod) === "ALL")
{
// Handle AND logic (all filters must pass)
shouldRender = array.every(renderFilterConfig, lang.hitch(this, this.processFilterConfig));
}
else
{
// Handle OR logic (only one filter needs to pass)
shouldRender = array.some(renderFilterConfig, lang.hitch(this, this.processFilterConfig));
}
}
return shouldRender;
},
/**
* @instance
* @param {object} renderFilterConfig The filter configuration to process
* @param {number} index The index of the filter configuration
* @returns {boolean} True if the filter criteria have been met and false otherwise.
*/
processFilterConfig: function alfresco_core_CoreWidgetProcessing__processFilterConfig(renderFilterConfig, /*jshint unused:false*/ index) {
// jshint maxcomplexity:false
var passesFilter = false;
if (renderFilterConfig.comparator === "currentUser")
{
passesFilter = AlfConstants.USERNAME === renderFilterConfig.value;
}
else if (this.filterPropertyExists(renderFilterConfig))
{
// Compare the property value against the applicable values...
var renderFilterProperty = this.getRenderFilterPropertyValue(renderFilterConfig);
if (renderFilterConfig.value)
{
// See AKU-781 - We now allow client-side properties to be compared...
switch (renderFilterConfig.comparator) {
case "lessThan":
passesFilter = renderFilterProperty < renderFilterConfig.value;
break;
case "greaterThan":
passesFilter = renderFilterProperty > renderFilterConfig.value;
break;
default:
passesFilter = renderFilterProperty === renderFilterConfig.value;
}
}
else if (renderFilterConfig.values)
{
// Check that the target property matches one of the supplied values...
var renderFilterValues = this.getRenderFilterValues(renderFilterConfig);
passesFilter = array.some(renderFilterValues, lang.hitch(this, this.processFilter, renderFilterConfig, renderFilterProperty));
}
else if (renderFilterConfig.contains)
{
// Check that the target property is an array containing ONE of the target values...
passesFilter = array.some(renderFilterConfig.contains, lang.hitch(this, this.processFilterArray, renderFilterConfig, renderFilterProperty));
}
else if (renderFilterConfig.containsAll)
{
// Check that the target property is an array containing ALL of the target values...
passesFilter = array.every(renderFilterConfig.containsAll, lang.hitch(this, this.processFilterArray, renderFilterConfig, renderFilterProperty));
}
}
else if (renderFilterConfig.renderOnAbsentProperty === true)
{
passesFilter = true;
}
else
{
passesFilter = false;
}
if (renderFilterConfig.negate === true)
{
// Negate the result...
passesFilter = !passesFilter;
}
else
{
// No action, leave result as it is...
}
this.alfLog("log", "Render filter result", passesFilter, this.currentItem, renderFilterConfig);
return passesFilter;
},
/**
* This function is used to compare the value of all the elements in the supplied targetArray against the
* value of the supplied currValue. If a match is found then this will return true.
*
* @instance
* @param {object} renderFilterConfig The complete configuration for the render filter
* @param {array} targetArray The array of values to compare against the supplied currValue
* @param {string|boolean} currValue The value to compare against all the elements in the targetArray
* @return {boolean} Indicates whether or not the supplied currValue has been found in the targetArray
*/
processFilterArray: function alfresco_core_CoreWidgetProcessing__processFilterArray(renderFilterConfig, targetArray, currValue) {
var foundCurrValue = false;
if (ObjectTypeUtils.isArray(targetArray))
{
if (typeof currValue === "boolean")
{
currValue = currValue.toString();
}
// Substitute any tokens in the current value to search for...
if (renderFilterConfig.substituteTokens === true)
{
currValue = this.substituteFilterTokens(currValue);
}
foundCurrValue = array.some(targetArray, function(arrayValue) {
if (typeof arrayValue === "boolean")
{
arrayValue = arrayValue.toString();
}
return arrayValue === currValue;
});
}
return foundCurrValue;
},
/**
* This function is called from both [processFilter]{@link module:alfresco/core/CoreWidgetProcessing#processFilter}
* and [processFilterArray]{@link module:alfresco/core/CoreWidgetProcessing#processFilterArray} to substitute
* any tokens found in the target values with matching dot-notation properties found in the currentItem and
* currentMetadata objects if they are available.
*
* @instance
* @param {*} value The value to look for tokens in
* @return {string} The value with any tokens substituted
* @since 1.0.43
*/
substituteFilterTokens: function alfresco_core_CoreWidgetProcessing__substituteFilterTokens(value) {
if (this.currentItem)
{
value = this.processCurrentItemTokens(value);
}
if (this.currentMetadata)
{
value = this.processCurrentMetadataTokens(value);
}
return value;
},
/**
* This is called from the [filterWidget]{@link module:alfresco/core/WidgetsProcessingFilterMixin#filterWidget} function
* for each acceptable filter value and compares it against the supplied target value.
*
* @instance
* @param {object} renderFilterConfig The configuration for the filter
* @param {string|boolean|number} target The target object to match (ideally this should be a string, boolean or a number
* @returns {boolean} true If the supplied value matches the target value and false otherwise.
*/
processFilter: function alfresco_core_CoreWidgetProcessing__processFilter(renderFilterConfig, target, currValue) {
if (ObjectTypeUtils.isString(currValue))
{
currValue = lang.trim(currValue);
}
// Substitute any tokens in the current value to search for...
if (renderFilterConfig.substituteTokens === true)
{
currValue = this.substituteFilterTokens(currValue);
}
// Convert booleans to strings for simple comparison...
// This is necessary because when creating pages dynamically the boolean values
// will end up as strings so the comparison won't work.
if (typeof target === "boolean")
{
target = target.toString();
}
if (typeof currValue === "boolean")
{
currValue = currValue.toString();
}
return currValue === target;
},
/**
* Checks to see whether or not the supplied filter property is a genuine attribute of the
* [currentItem]{@link module:alfresco/core/WidgetsProcessingFilterMixin#currentItem}.
*
* @instance
* @param {{property: string, values: string[]|string}} renderFilterConfig The filter configuration to process.
* @returns {boolean} true if the property exists and false if it doesn't.
*/
filterPropertyExists: function alfresco_core_CoreWidgetProcessing__filterPropertyExists(renderFilterConfig) {
var targetObject = this.currentItem;
if (renderFilterConfig.target && lang.exists(renderFilterConfig.target))
{
targetObject = lang.getObject(renderFilterConfig.target);
}
else if (renderFilterConfig.target && this[renderFilterConfig.target])
{
targetObject = this[renderFilterConfig.target];
}
return (ObjectTypeUtils.isString(renderFilterConfig.property) && ObjectTypeUtils.isObject(targetObject) && lang.exists(renderFilterConfig.property, targetObject));
},
/**
* Processes the "filterProperty" attribute defined in the filter configuration (which is expected to be a dot notation path to an attribute
* of the [currentItem]{@link module:alfresco/core/WidgetsProcessingFilterMixin#currentItem}. This
* property is then retrieved from [currentItem]{@link module:alfresco/core/WidgetsProcessingFilterMixin#currentItem}
* and returned so that it can be compared against the "values" configuration. Retrieval of the
*
* @instance
* @param {{property: string, values: string[]|string}} renderFilter The filter configuration to process.
* @returns {object} The property of [currentItem]{@link module:alfresco/core/WidgetsProcessingFilterMixin#currentItem} defined
* by the "property" attribute of the filter configuration.
*/
getRenderFilterPropertyValue: function alfresco_core_CoreWidgetProcessing__getRenderFilterPropertyValue(renderFilterConfig) {
var targetObject = this.currentItem;
if (renderFilterConfig.target && lang.exists(renderFilterConfig.target))
{
targetObject = lang.getObject(renderFilterConfig.target);
}
else if (renderFilterConfig.target && this[renderFilterConfig.target])
{
targetObject = this[renderFilterConfig.target];
}
return lang.getObject(renderFilterConfig.property, false, targetObject);
},
/**
*
* @instance
* @param {{property: string, values: string[]|string}} renderFilter The filter configuration to process.
* @returns {string} The name of the filter
*/
getCustomRenderFilterProperty: function alfresco_core_CoreWidgetProcessing__getCustomRenderFilterProperty(currentItem) {
var result = null;
if (currentItem instanceof Boolean || typeof currentItem === "boolean")
{
result = currentItem ? "folder" : "document";
}
return result;
},
/**
* Attempt to convert the supplied filter value into an array. Filter values should be configured as an array of
* strings but this also allows single strings to be used (which are converted into a single element array) but
* if all else fails then an empty array will be returned.
*
* @instance
* @param {{property: string, values: string[]|string}} renderFilter The filter configuration to process.
* @returns {string[]} An array (assumed to be of strings) that is either empty, the same array supplied as an argument or a single
* string element supplied as an argument.
*/
getRenderFilterValues: function alfresco_core_CoreWidgetProcessing__getRenderFilterValues(renderFilter) {
var result = null;
if (ObjectTypeUtils.isArray(renderFilter.values))
{
result = renderFilter.values;
}
else if (ObjectTypeUtils.isString(renderFilter.values))
{
result = [renderFilter.values];
}
else
{
result = [];
}
return result;
}
});
});