/**
* 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 is used as the default root object when instantiating a page. There should be no need
* to ever instantiate this widget directly.
*
* @module alfresco/core/Page
* @extends module:alfresco/core/ProcessWidgets
* @mixes module:alfresco/core/ResizeMixin
* @author Dave Draper
* @author Martin Doyle
*/
define(["alfresco/core/ProcessWidgets",
"alfresco/core/ResizeMixin",
"alfresco/core/topics",
"alfresco/enums/urlTypes",
"alfresco/util/urlUtils",
"service/constants/Default",
"dojo/string",
"dojo/_base/declare",
"dojo/dom-construct",
"dojo/_base/array",
"dojo/_base/lang",
"dojo/dom-class",
"dojo/_base/window",
"alfresco/core/PubQueue",
"jquery", // NOTE: Need to include JQuery at root page to prevent XHR require request for first module that uses it
"jqueryui", // NOTE: Need to include JQuery UI at root page to prevent XHR require request for first module that uses it
"alfresco/core/shims"],
function(ProcessWidgets, ResizeMixin, topics, urlTypes, urlUtils, AlfConstants, string, declare, domConstruct, array,
lang, domClass, win, PubQueue, $, jqueryui, shims) {
return declare([ProcessWidgets, ResizeMixin], {
/**
* This is the base class for the page
*
* @instance
* @type {string}
* @default
*/
baseClass: "alfresco-core-Page",
/**
* This is the callback handler for any require based errors that may occur during page load.
*
* @instance
* @param {object} error Details of the error that has occurred
*/
onError: function alfresco_core_Page__onError(error) {
/* global console */
console.error("The following AMD module loading error occurred", error);
this._showPage();
},
/**
* Overrides the superclass implementation to call [processServices]{@link module:alfresco/core/Core#processServices}
* and [processWidgets]{@link module:alfresco/core/Core#processWidgets} as applicable.
*
* @instance
*/
postCreate: function alfresco_core_Page__postCreate() {
if ((this.services && this.services.length) ||
(this.widgets && this.widgets.length))
{
// See AKU-956 / AKU-998- register a new page with with the PubQueue...
PubQueue.getSingleton().registerPage();
}
/*jshint devel:true*/
shims.apply();
try
{
if (AlfConstants.DEBUG)
{
require.on("error", lang.hitch(this, this.onError));
}
// If we're in debug mode, then we should add some DOM elements for the Developer View. This will
// allow developers to see which WebScript has generated the page and to click a link to generate
// a sample JAR to customize the page.
if (AlfConstants.DEBUG && this.domNode && this.webScriptId)
{
this.webScriptLabel = "WebScript ID:";
this.extensionDownloadUrl = urlUtils.convertUrl("generator/extension?webscriptId=" + this.webScriptId, urlTypes.PAGE_RELATIVE);
this.extensionDownloadLabel = "(Click to generate extension JAR)";
var pageInfoTemplate = "<div class=\"alfresco-debug-PageInfo\">" +
"<span class=\"label\">${webScriptLabel}</span>" +
"<span class=\"value\">${webScriptId}</span>" +
"<a href=\"${extensionDownloadUrl}\">${extensionDownloadLabel}</a>" +
"</div>";
var pageInfo = string.substitute(pageInfoTemplate, this);
var pageInfoDom = domConstruct.toDom(pageInfo);
domConstruct.place(pageInfoDom, this.domNode, "first");
}
// See AKU-891 - Prevent all standard drag and drop events from working if they bubble as
// far as the html element. This decision has been taken to prevent users from inadvertently
// dropping files onto areas of any page that can't handle them (and subsequently having those
// files rendered by the browser). JQuery used here as Dojo approach proved problematic and we
// have JQuery loaded anyway. All drag events need to be subscribed to to prevent the drop
// being fired, see: http://stackoverflow.com/questions/14674349/why-preventdefault-does-not-work
$("html").on("dragenter dragstart dragend dragleave dragover drag drop", function (e) {
e.preventDefault();
});
if (this.services && this.services.length)
{
this.processServices(this.services);
}
else if (this.widgets && this.widgets.length)
{
// Make sure to process widgets if there are no services...
// Otherwise they will be processed once all the services are instantiated...
this.processWidgets(this.widgets, this.containerNode);
}
}
catch (e)
{
this.alfLog("error", "The following error occurred building the page", e);
this._showPage();
PubQueue.getSingleton().release();
}
},
/**
* @instance
*/
onReadyPublish: function alfresco_core_Page__onReadyPublish(publicationDetails) {
if (publicationDetails && publicationDetails.publishTopic)
{
this.alfLog("log", "Onload publication", publicationDetails);
this.alfPublish(publicationDetails.publishTopic, publicationDetails.publishPayload);
}
else
{
this.alfLog("warn", "The page was configured with an onload publication but no 'publishTopic' was provided", publicationDetails, this);
}
},
/**
* Handles the dependency management and instantiation of services required.
*
* @instance
* @param {Array} services An array of the services to be instantiated.
* @param {function} callback A function to call once the service has been instantiated
* @param {object} callbackScope The scope with which to call the callback
* @param {number} index The index of the service to create
*/
processServices: function alfresco_core_Page__processServices(services, callback, callbackScope, index) {
if (services)
{
if (!this.servicesToDestroy)
{
this.servicesToDestroy = [];
}
// Reset the processing complete flag (this is to support multiple invocations of widget processing)...
this.serviceProcessingComplete = false;
// TODO: Using these attributes will not support multiple calls to processWidgets from within the same object instance
this._processedServiceCountdown = services.length;
this._processedServices = [];
// Iterate over all the services in the configuration object and add them...
array.forEach(services, function(serviceConfig) {
this.createService(serviceConfig, this._registerProcessedService, this, index);
}, this);
}
},
/**
* This method will instantiate a new service having requested that its JavaScript resource and
* dependent resources be downloaded. In principle all of the required resources should be available
* if the service 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} config The configuration for the service
* @param {element} domNode The DOM node to attach the service to
* @param {function} callback A function to call once the service has been instantiated
* @param {object} callbackScope The scope with which to call the callback
* @param {number} index The index of the service to create (this will effect it's location in the
* [_processedServices]{@link module:alfresco/core/Core#_processedServices} array)
*/
createService: function alfresco_core_Page__createService(config, callback, callbackScope, index) {
var dep = null,
serviceConfig = {};
if (typeof config === "string")
{
dep = config;
}
else if (typeof config === "object" && config.name)
{
dep = config.name;
if (typeof config.config === "object")
{
serviceConfig = config.config;
}
}
if (serviceConfig.pubSubScope === undefined)
{
// ...otherwise inherit the callers pubSubScope if one hasn't been explicitly configured...
serviceConfig.pubSubScope = this.pubSubScope;
}
var _this = this;
var requires = [dep];
require(requires, function(ServiceType) {
if (typeof ServiceType === "function")
{
try
{
// We need to add the dependency name of the service into the constructor arguments
// in order for the BaseService (if extended, which really it should be) to be able
// to use that dependency name to register the service instance (see AKU-531)...
serviceConfig.alfServiceName = dep;
var service = new ServiceType(serviceConfig);
_this.servicesToDestroy.push(service);
}
catch (e)
{
_this.alfLog("error", "The following error occurred creating a service", e);
_this._showPage();
}
}
else
{
_this._showPage();
_this.alfLog("error", "The following service could not be found, so is not included on the page '" + dep + "'. Please correct the use of this service in your page definition");
}
if (callback)
{
// If there is a callback then call it with any provided scope (but default to the
// "this" as the scope if one isn't provided).
callback.call((callbackScope || _this), service, index);
}
});
},
/**
* Used to keep track of all the services created as a result of a call to the [processWidgets]{@link module:alfresco/core/Core#processWidgets} function
*
* @instance
* @type {Array}
* @default
*/
_processedServices: null,
/**
* This is used to countdown the services that are still waiting to be created. It is initialised to the size
* of the services array supplied to the [processServices]{@link module:alfresco/core/Core#processServices} function.
*
* @instance
* @type {number}
* @default
*/
_processedServiceCountdown: null,
/**
* This function registers the creation of a service. It decrements the
* [_processedServiceCountdown]{@link module:alfresco/core/Core#_processedServiceCountdown} attribute
* and calls the [allServicesProcessed]{@link module:alfresco/core/Core#allServicesProcessed} function when it reaches zero.
*
* @instance
* @param {object} service The service that has just been processed.
* @param {number} index The target index of the service
*/
_registerProcessedService: function alfresco_core_Core___registerProcessedService(service, index) {
this._processedServiceCountdown--;
if (index !== 0 && (!index || isNaN(index)))
{
this._processedServices.push(service);
}
else
{
this._processedServices[index] = service;
}
if (this._processedServiceCountdown === 0)
{
this.allServicesProcessed(this._processedServices);
this.serviceProcessingComplete = true;
}
},
/**
* This is set from false to true after the [allServicesProcessed]{@link module:alfresco/core/Core#allServicesProcessed}
* extension point function is called. It can be used to check whether or not service processing is complete.
* This is to allow for checks that service processing has been completed BEFORE attaching a listener to the
* [allServicesProcessed]{@link module:alfresco/core/Core#allServicesProcessed} function.
*
* @instance
* @type {boolean}
* @default
*/
serviceProcessingComplete: false,
/**
* This is an extension point for handling the completion of calls to [processWidgets]{@link module:alfresco/core/Core#processWidgets}
*
* @instance
* @param {Array} services An array of all the services that have been processed
*/
allServicesProcessed: function alfresco_core_Page__allServicesProcessed(services) {
/*jshint unused:false*/
this.alfLog("log", "All services processed");
if (this.widgets && this.widgets.length)
{
// Make sure to process widgets if there are no services...
// Otherwise they will be processed once all the services are instantiated...
this.processWidgets(this.widgets, this.containerNode);
}
else
{
PubQueue.getSingleton().release();
}
},
/**
* @instance
*/
allWidgetsProcessed: function alfresco_core_Page__allWidgetsProcessed(widgets) {
/*jshint unused:false*/
this.alfLog("log", "All page widgets processed");
// TODO: Need to be able to notify widgets that they can start publications in the knowledge that other widgets are available
// to respond...
if (this.publishOnReady)
{
array.forEach(this.publishOnReady, lang.hitch(this, this.onReadyPublish));
}
// Release all publications that have been queued awaiting all the widgets to have finished being
// created...
PubQueue.getSingleton().release();
this.alfPublish(topics.PAGE_WIDGETS_READY, {});
// Once everything has completed loading we want to publish a resize event to give all of the widgets
// a chance to set their dimensions correctly. This is somewhat using a sledgehammer to crack a nut but
// is a simple and effective means of ensuring the correct layout on page load. In particular this was
// prompted by issues with the AlfTabContainer in Chrome not handling client height correctly (see AKU-506)
this.alfPublishResizeEvent(this.domNode);
// Add a class to indicate that the page is ready. This is primarily for testing purposes.
domClass.add(this.domNode, "allWidgetsProcessed");
this._showPage();
},
/**
* Un-hide the body of the page
*
* @instance
* @since 1.0.59
*/
_showPage: function alfresco_core_Page___showPage() {
domClass.add(document.body, "aikau-reveal");
}
});
});