/**
* @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 <[email protected]>,
* 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));
|