368 lines
11 KiB
JavaScript
368 lines
11 KiB
JavaScript
/**
|
|
* @defgroup js_classes
|
|
*/
|
|
|
|
/**
|
|
* @file js/classes/Helper.js
|
|
*
|
|
* Copyright (c) 2014-2021 Simon Fraser University
|
|
* Copyright (c) 2000-2021 John Willinsky
|
|
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
|
*
|
|
* @class Helper
|
|
* @ingroup js_controllers
|
|
*
|
|
* @brief PKP helper methods
|
|
*/
|
|
(function($) {
|
|
|
|
// Create PKP namespaces.
|
|
/** @type {Object} */
|
|
$.pkp = $.pkp || { };
|
|
|
|
|
|
/** @type {Object} */
|
|
$.pkp.classes = $.pkp.classes || { };
|
|
|
|
|
|
/** @type {Object} */
|
|
$.pkp.controllers = $.pkp.controllers || { };
|
|
|
|
|
|
/** @type {Object} */
|
|
$.pkp.plugins = $.pkp.plugins || {};
|
|
|
|
|
|
/** @type {Object} */
|
|
$.pkp.plugins.blocks = $.pkp.plugins.blocks || {};
|
|
|
|
|
|
/** @type {Object} */
|
|
$.pkp.plugins.generic = $.pkp.plugins.generic || {};
|
|
|
|
|
|
/** @type {Object} */
|
|
$.pkp.plugins.pubIds = $.pkp.plugins.pubIds || {};
|
|
|
|
|
|
/** @type {Object} */
|
|
$.pkp.plugins.importexport = $.pkp.plugins.importexport || {};
|
|
|
|
|
|
|
|
/**
|
|
* Helper singleton
|
|
* @constructor
|
|
*
|
|
* @extends $.pkp.classes.ObjectProxy
|
|
*/
|
|
$.pkp.classes.Helper = function() {
|
|
throw new Error('Trying to instantiate the Helper singleton!');
|
|
};
|
|
|
|
|
|
//
|
|
// Private class constants
|
|
//
|
|
/**
|
|
* Characters available for UUID generation.
|
|
* @const
|
|
* @private
|
|
* @type {Array}
|
|
*/
|
|
$.pkp.classes.Helper.CHARS_ = ['0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
'abcdefghijklmnopqrstuvwxyz'].join('').split('');
|
|
|
|
|
|
//
|
|
// Public static helper methods
|
|
//
|
|
/**
|
|
* Generate a random UUID.
|
|
*
|
|
* Original code thanks to Robert Kieffer <robert@broofa.com>,
|
|
* http://www.broofa.com, adapted by PKP.
|
|
*
|
|
* Copyright (c) 2010 Robert Kieffer
|
|
* Copyright (c) 2014-2021 Simon Fraser University
|
|
* Copyright (c) 2010-2021 John Willinsky
|
|
* Distributed under the GNU GPL v3 and MIT licenses. For full
|
|
* terms see the file docs/COPYING.
|
|
*
|
|
* See discussion of randomness versus uniqueness:
|
|
* http://www.broofa.com/2008/09/javascript-uuid-function/
|
|
*
|
|
* @return {string} an RFC4122v4 compliant UUID.
|
|
*/
|
|
$.pkp.classes.Helper.uuid = function() {
|
|
var chars = $.pkp.classes.Helper.CHARS_, uuid = new Array(36), rnd = 0, r, i;
|
|
for (i = 0; i < 36; i++) {
|
|
if (i == 8 || i == 13 || i == 18 || i == 23) {
|
|
uuid[i] = '-';
|
|
} else if (i == 14) {
|
|
uuid[i] = '4';
|
|
} else {
|
|
/*jslint bitwise: true*/
|
|
if (rnd <= 0x02) {
|
|
rnd = 0x2000000 + (Math.random() * 0x1000000) | 0;
|
|
}
|
|
r = rnd & 0xf;
|
|
rnd = rnd >> 4;
|
|
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
|
|
/*jslint bitwise: false*/
|
|
}
|
|
}
|
|
return uuid.join('');
|
|
};
|
|
|
|
|
|
/**
|
|
* Let one object inherit from another.
|
|
*
|
|
* Example:
|
|
* $.pkp.classes.Parent = function() {...};
|
|
* $.pkp.classes.Child = function() {...};
|
|
* $.pkp.classes.Helper.inherits($.pkp.classes.Child, $.pkp.classes.Parent);
|
|
*
|
|
* @param {Function} Child Constructor of the child object.
|
|
* @param {Function} Parent Constructor of the parent object.
|
|
*/
|
|
$.pkp.classes.Helper.inherits = function(Child, Parent) {
|
|
// Use an empty temporary object to avoid
|
|
// calling a potentially costly constructor
|
|
// on the parent object which also may have
|
|
// undesired side effects. Also avoids instantiating
|
|
// a potentially big object.
|
|
/** @constructor */ var Temp = function() {};
|
|
Temp.prototype = Parent.prototype;
|
|
|
|
// Provide a way to reach the parent's
|
|
// method implementations even after
|
|
// overriding them in the child object.
|
|
Child.parent_ = Parent.prototype;
|
|
|
|
// Let the child object inherit from
|
|
// the parent object.
|
|
Child.prototype = new Temp();
|
|
|
|
// Need to fix the child constructor because
|
|
// it get's lost when setting the prototype
|
|
// to an object instance.
|
|
Child.prototype.constructor = Child;
|
|
|
|
// Make sure that we can always call the parent object's
|
|
// constructor without coupling the child constructor
|
|
// to it. This should work even when the parent inherits
|
|
// directly from an Object instance (i.e. the parent's
|
|
// prototype was set like this: Parent.prototype = {...})
|
|
// which wipes out the original constructor.
|
|
if (Parent.prototype.constructor == Object.prototype.constructor) {
|
|
Parent.prototype.constructor = Parent;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Introduce a central object factory that maintains some
|
|
* level of indirection so that we can enrich objects, e.g.
|
|
* with aspects, provide different runtime-implementations
|
|
* of objects, distinguish between singletons and prototypes
|
|
* or even implement dependency injection if we want to.
|
|
*
|
|
* The standard implementation has a 'convention over
|
|
* configuration' approach that assumes that an object's
|
|
* name corresponds to the name of a constructor within
|
|
* the global jQuery namespace ($).
|
|
*
|
|
* The factory also helps us to avoid the common pitfall to
|
|
* use a constructor without the 'new' keyword.
|
|
*
|
|
* @param {string} objectName The name of an object.
|
|
* @param {Array} args The arguments to be passed
|
|
* into the object's constructor.
|
|
* @return {$.pkp.classes.ObjectProxy} the instantiated object.
|
|
*/
|
|
$.pkp.classes.Helper.objectFactory = function(objectName, args) {
|
|
var ObjectConstructor, ObjectProxyInstance, objectInstance;
|
|
|
|
// Resolve the object name.
|
|
ObjectConstructor = $.pkp.classes.Helper.resolveObjectName(objectName);
|
|
|
|
// Create a new proxy constructor instance.
|
|
ObjectProxyInstance = $.pkp.classes.Helper.getObjectProxyInstance();
|
|
|
|
// Copy static members over from the object proxy. (This may
|
|
// overwrite the proxy constructor's prototype in some
|
|
// browsers but we don't care because we'll replace the prototype
|
|
// anyway when we inherit.)
|
|
$.extend(true, ObjectProxyInstance, $.pkp.classes.ObjectProxy);
|
|
|
|
// Let the proxy inherit from the proxied object.
|
|
$.pkp.classes.Helper.inherits(ObjectProxyInstance, ObjectConstructor);
|
|
|
|
// Enrich the new proxy constructor prototype with proxy object
|
|
// prototype members.
|
|
$.extend(true, ObjectProxyInstance.prototype,
|
|
$.pkp.classes.ObjectProxy.prototype);
|
|
|
|
// Instantiate the proxy with the proxied object.
|
|
objectInstance = new ObjectProxyInstance(objectName, args);
|
|
return objectInstance;
|
|
};
|
|
|
|
|
|
/**
|
|
* Resolves the given object name to an object implementation
|
|
* (or better to it's constructor).
|
|
* @param {string} objectName The object name to resolve.
|
|
* @return {Function} The constructor of the object.
|
|
*/
|
|
$.pkp.classes.Helper.resolveObjectName = function(objectName) {
|
|
var objectNameParts, i, functionName, ObjectConstructor;
|
|
|
|
// Currently only objects in the $ namespace are
|
|
// supported.
|
|
objectNameParts = objectName.split('.');
|
|
if (objectNameParts.shift() != '$') {
|
|
throw new Error(['Namespace "', objectNameParts[0], '" for object "',
|
|
objectName, '" is currently not supported!'].join(''));
|
|
}
|
|
|
|
// Make sure that we actually have a constructor name
|
|
// (starts with an upper case letter).
|
|
functionName = objectNameParts[objectNameParts.length - 1];
|
|
if (functionName.charAt(0).toUpperCase() !== functionName.charAt(0)) {
|
|
throw new Error(['The name "', objectName, '" does not point to a',
|
|
'constructor which must always be upper case!'].join(''));
|
|
}
|
|
|
|
// Run through the namespace and identify the constructor.
|
|
ObjectConstructor = $;
|
|
for (i in objectNameParts) {
|
|
ObjectConstructor = ObjectConstructor[objectNameParts[i]];
|
|
if (ObjectConstructor === undefined) {
|
|
throw new Error(['Constructor for object "', objectName, '" not found!']
|
|
.join(''));
|
|
}
|
|
}
|
|
|
|
// Check that the constructor actually is a function.
|
|
if (!$.isFunction(ObjectConstructor)) {
|
|
throw new Error(['The name "', objectName, '" does not point to a',
|
|
'constructor which must always be a function!'].join());
|
|
}
|
|
|
|
return ObjectConstructor;
|
|
};
|
|
|
|
|
|
/**
|
|
* Create a new instance of a proxy constructor.
|
|
*
|
|
* NB: We do this in a separate closure to avoid
|
|
* memory leaks.
|
|
*
|
|
* @return {Function} a new proxy instance.
|
|
*/
|
|
$.pkp.classes.Helper.getObjectProxyInstance = function() {
|
|
// Create a new proxy constructor so that proxies
|
|
// do not interfere with each other.
|
|
/**
|
|
* @constructor
|
|
*
|
|
* @param {string} objectName The name of the proxied
|
|
* object.
|
|
* @param {Array} args The arguments to be passed to
|
|
* the constructor of the proxied object.
|
|
*/
|
|
var proxyConstructor = function(objectName, args) {
|
|
// Set the internal object name.
|
|
this.objectName_ = objectName;
|
|
|
|
// Call the constructor of the proxied object.
|
|
this.parent.apply(this, args);
|
|
};
|
|
|
|
// Declare properties/methods used in the constructor for the
|
|
// closure compiler. These will later be overwritten by the
|
|
// true implementation.
|
|
/**
|
|
* @private
|
|
* @type {string} The object name of this object.
|
|
*/
|
|
proxyConstructor.objectName_ = '';
|
|
|
|
/**
|
|
* @param {*=} opt_methodName The name of the method to
|
|
* be found. Do not set when calling this method from a
|
|
* constructor!
|
|
* @param {...*} var_args Arguments to be passed to the
|
|
* parent method.
|
|
* @return {*} The return value of the parent method.
|
|
*/
|
|
proxyConstructor.prototype.parent = function(opt_methodName, var_args) {};
|
|
|
|
return proxyConstructor;
|
|
};
|
|
|
|
|
|
/**
|
|
* Inject (mix in) an interface into an object.
|
|
* @param {Function} Constructor The target object's constructor.
|
|
* @param {string} mixinObjectName The object name of interface
|
|
* that can be resolved to an interface implementation by the
|
|
* object factory.
|
|
*/
|
|
$.pkp.classes.Helper.injectMixin = function(Constructor, mixinObjectName) {
|
|
// Retrieve an instance of the mix-in interface implementation.
|
|
var mixin = $.pkp.classes.Helper.objectFactory(mixinObjectName, []);
|
|
|
|
// Inject the mix-in into the target constructor.
|
|
$.extend(true, Constructor, mixin);
|
|
};
|
|
|
|
|
|
/**
|
|
* A function currying implementation borrowed from Google Closure.
|
|
* @param {Function} fn A function to partially apply.
|
|
* @param {Object} context Specifies the object which |this| should
|
|
* point to when the function is run. If the value is null or undefined, it
|
|
* will default to the global object.
|
|
* @param {...*} var_args Additional arguments that are partially
|
|
* applied to the function.
|
|
* @return {!Function} A partially-applied form of the function bind() was
|
|
* invoked as a method of.
|
|
*/
|
|
$.pkp.classes.Helper.curry = function(fn, context, var_args) {
|
|
if (arguments.length > 2) {
|
|
var boundArgs, newArgs;
|
|
boundArgs = Array.prototype.slice.call(arguments, 2);
|
|
return function() {
|
|
// Prepend the bound arguments to the current arguments.
|
|
newArgs = Array.prototype.slice.call(arguments);
|
|
Array.prototype.unshift.apply(newArgs, boundArgs);
|
|
return fn.apply(context, newArgs);
|
|
};
|
|
} else {
|
|
return function() {
|
|
return fn.apply(context, arguments);
|
|
};
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* A function that takes care of escaping @ character which could be interpreted
|
|
* as CSS notation. This is due to the fact that jQuery uses CSS syntax for
|
|
* selecting elements. These characters must be escaped by placing two
|
|
* backslashes in front of them.
|
|
* @param {string} elementId jQuery element selector
|
|
* @return {string}
|
|
*/
|
|
$.pkp.classes.Helper.escapeJQuerySelector = function(elementId) {
|
|
return elementId.replace('@', '\\@');
|
|
};
|
|
|
|
|
|
}(jQuery));
|