first commit
This commit is contained in:
@@ -0,0 +1,880 @@
|
||||
/**
|
||||
* @file js/classes/Handler.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 Handler
|
||||
* @ingroup js_classes
|
||||
*
|
||||
* @brief Base class for handlers bound to a DOM HTML element.
|
||||
*/
|
||||
/*global _, pkp */
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.ObjectProxy
|
||||
*
|
||||
* @param {jQueryObject} $element A DOM element to which
|
||||
* this handler is bound.
|
||||
* @param {Object} options Handler options.
|
||||
*/
|
||||
$.pkp.classes.Handler = function($element, options) {
|
||||
var $parents, self, i;
|
||||
|
||||
// Check whether a single element was passed in.
|
||||
if ($element.length > 1) {
|
||||
throw new Error('jQuery selector contained more than one handler!');
|
||||
}
|
||||
|
||||
// Save a pointer to the bound element in the handler.
|
||||
this.$htmlElement_ = $element;
|
||||
|
||||
// Check whether a handler has already been bound
|
||||
// to the element.
|
||||
if (this.data('handler') !== undefined) {
|
||||
throw new Error(['The handler "', this.getObjectName(),
|
||||
'" has already been bound to the selected element!'].join(''));
|
||||
}
|
||||
|
||||
// Initialize object properties.
|
||||
this.eventBindings_ = { };
|
||||
this.dataItems_ = { };
|
||||
this.publishedEvents_ = { };
|
||||
this.handlerChildren_ = [];
|
||||
this.globalEventListeners_ = { };
|
||||
|
||||
// Register this handler with a parent handler if one is found. This
|
||||
// allows global events to be de-registered when a parent handler is
|
||||
// refreshed.
|
||||
$parents = this.$htmlElement_.parents();
|
||||
self = this;
|
||||
$parents.each(function(i) {
|
||||
if ($.pkp.classes.Handler.hasHandler($($parents[i]))) {
|
||||
$.pkp.classes.Handler.getHandler($($parents[i]))
|
||||
.handlerChildren_.push(self);
|
||||
return; // only attach to the closest parent handler
|
||||
}
|
||||
});
|
||||
|
||||
if (options.eventBridge) {
|
||||
// Configure the event bridge.
|
||||
this.eventBridge_ = options.eventBridge;
|
||||
}
|
||||
|
||||
// The "publishChangeEvents" option can be used to specify
|
||||
// a list of event names that will also be published upon
|
||||
// content change.
|
||||
if (options.publishChangeEvents) {
|
||||
this.publishChangeEvents_ = options.publishChangeEvents;
|
||||
for (i = 0; i < this.publishChangeEvents_.length; i++) {
|
||||
this.publishEvent(this.publishChangeEvents_[i]);
|
||||
}
|
||||
} else {
|
||||
this.publishChangeEvents_ = [];
|
||||
}
|
||||
|
||||
// Bind the handler to the DOM element.
|
||||
this.data('handler', this);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* Optional list of publication events.
|
||||
* @private
|
||||
* @type {Array}
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.publishChangeEvents_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The HTML element this handler is bound to.
|
||||
* @private
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.$htmlElement_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A list of event bindings for this handler.
|
||||
* @private
|
||||
* @type {Object.<string, Array>}
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.eventBindings_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A list of data items bound to the DOM element
|
||||
* managed by this handler.
|
||||
* @private
|
||||
* @type {Object.<string, boolean>}
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.dataItems_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A list of published events.
|
||||
* @private
|
||||
* @type {Object.<string, boolean>}
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.publishedEvents_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* An HTML element id to which we'll forward all handler events.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.eventBridge_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Global event bindings. These are tracked so they can be deregistered when
|
||||
* the handler is destroyed.
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.globalEventListeners_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Public static methods
|
||||
//
|
||||
/**
|
||||
* Retrieve the bound handler from the jQuery element.
|
||||
* @param {jQueryObject} $element The element to which the
|
||||
* handler was attached.
|
||||
* @return {Object} The retrieved handler.
|
||||
*/
|
||||
$.pkp.classes.Handler.getHandler = function($element) {
|
||||
// Retrieve the handler. We cannot do this with our own
|
||||
// data() method because this method cannot be called
|
||||
// in the context of the handler if it's purpose is to
|
||||
// retrieve the handler. This should be the only place
|
||||
// at all where we have to do access element data
|
||||
// directly.
|
||||
var handler = $element.data('pkp.handler');
|
||||
|
||||
// Check whether the handler exists.
|
||||
if (!(handler instanceof $.pkp.classes.Handler)) {
|
||||
throw new Error('There is no handler bound to this element!');
|
||||
}
|
||||
|
||||
return handler;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if a jQuery element has a handler bound to it
|
||||
*
|
||||
* @param {jQueryObject} $element The element to check for a handler
|
||||
* @return {boolean}
|
||||
*/
|
||||
$.pkp.classes.Handler.hasHandler = function($element) {
|
||||
return $element.data('pkp.handler') instanceof $.pkp.classes.Handler;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Returns the HTML element this handler is bound to.
|
||||
*
|
||||
* @return {jQueryObject} The element this handler is bound to.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.getHtmlElement = function() {
|
||||
$.pkp.classes.Handler.checkContext_(this);
|
||||
|
||||
// Return the HTML element.
|
||||
return this.$htmlElement_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Publish change events. (See options.publishChangeEvents.)
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.publishChangeEvents = function() {
|
||||
var i;
|
||||
for (i = 0; i < this.publishChangeEvents_.length; i++) {
|
||||
this.trigger(this.publishChangeEvents_[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A generic event dispatcher that will be bound to
|
||||
* all handler events. See bind() above.
|
||||
*
|
||||
* @this {HTMLElement}
|
||||
* @param {jQuery.Event} event The jQuery event object.
|
||||
* @return {boolean} Return value to be passed back
|
||||
* to jQuery.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.handleEvent = function(event) {
|
||||
var $callingElement, handler, boundEvents, args, returnValue, i, l;
|
||||
|
||||
// This handler is always called out of the
|
||||
// handler context.
|
||||
$callingElement = $(this);
|
||||
|
||||
// Identify the targeted handler.
|
||||
handler = $.pkp.classes.Handler.getHandler($callingElement);
|
||||
|
||||
// Make sure that we really got the right element.
|
||||
if ($callingElement[0] !== handler.getHtmlElement.call(handler)[0]) {
|
||||
throw new Error(['An invalid handler is bound to the calling ',
|
||||
'element of an event!'].join(''));
|
||||
}
|
||||
|
||||
// Retrieve the event handlers for the given event type.
|
||||
boundEvents = handler.eventBindings_[event.type];
|
||||
if (boundEvents === undefined) {
|
||||
// We have no handler for this event but we also
|
||||
// don't allow bubbling of events outside of the
|
||||
// GUI widget!
|
||||
return false;
|
||||
}
|
||||
|
||||
// Call all event handlers.
|
||||
args = $.makeArray(arguments);
|
||||
returnValue = true;
|
||||
args.unshift(this);
|
||||
for (i = 0, l = boundEvents.length; i < l; i++) {
|
||||
// Invoke the event handler in the context
|
||||
// of the handler object.
|
||||
if (boundEvents[i].apply(handler, args) === false) {
|
||||
// False overrides true.
|
||||
returnValue = false;
|
||||
}
|
||||
|
||||
// Stop immediately if one of the handlers requests this.
|
||||
if (event.isImmediatePropagationStopped()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We do not allow bubbling of events outside of the GUI widget!
|
||||
event.stopPropagation();
|
||||
|
||||
// Return the event handler status.
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a closure that calls the callback in the
|
||||
* context of the handler object.
|
||||
*
|
||||
* NB: Always make sure that the callback is properly
|
||||
* unbound and freed for garbage collection. Otherwise
|
||||
* you might create a memory leak. If you want to bind
|
||||
* an event to the HTMLElement handled by this handler
|
||||
* then always use the above bind() method instead which
|
||||
* is safer.
|
||||
*
|
||||
* @param {Function} callback The callback to be wrapped.
|
||||
* @param {Object=} opt_context Specifies the object which
|
||||
* |this| should point to when the function is run.
|
||||
* If the value is not given, the context will default
|
||||
* to the handler object.
|
||||
* @return {Function} The wrapped callback.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.callbackWrapper =
|
||||
function(callback, opt_context) {
|
||||
|
||||
$.pkp.classes.Handler.checkContext_(this);
|
||||
|
||||
// Create a closure that calls the event handler
|
||||
// in the right context.
|
||||
if (!opt_context) {
|
||||
opt_context = this;
|
||||
}
|
||||
return function() {
|
||||
var args;
|
||||
args = $.makeArray(arguments);
|
||||
args.unshift(this);
|
||||
return callback.apply(opt_context, args);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This callback can be used to handle simple remote server requests.
|
||||
*
|
||||
* @param {Object} ajaxOptions AJAX options.
|
||||
* @param {Object} jsonData A JSON object.
|
||||
* @return {Object|boolean} The parsed JSON data if no error occurred,
|
||||
* otherwise false.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.remoteResponse =
|
||||
function(ajaxOptions, jsonData) {
|
||||
|
||||
return this.handleJson(jsonData);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Completely remove all traces of the handler from the
|
||||
* HTML element to which it is bound and leave the element in
|
||||
* its previous state.
|
||||
*
|
||||
* Subclasses should override this method if necessary but
|
||||
* should always call this implementation.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.remove = function() {
|
||||
$.pkp.classes.Handler.checkContext_(this);
|
||||
var $element, key;
|
||||
|
||||
// Remove all event handlers in our namespace.
|
||||
$element = this.getHtmlElement();
|
||||
$element.unbind('.pkpHandler');
|
||||
|
||||
// Remove all our data items except for the
|
||||
// handler itself.
|
||||
for (key in this.dataItems_) {
|
||||
if (key !== 'pkp.handler') {
|
||||
$element.removeData(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger the remove event, then delete it.
|
||||
$element.trigger('pkpRemoveHandler');
|
||||
$element.unbind('.pkpHandlerRemove');
|
||||
|
||||
// Delete the handler.
|
||||
$element.removeData('pkp.handler');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This function should be used to pre-process a JSON response
|
||||
* from the server.
|
||||
*
|
||||
* @param {Object} jsonData The returned server response data.
|
||||
* @return {Object|boolean} The returned server response data or
|
||||
* false if an error occurred.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.handleJson = function(jsonData) {
|
||||
var key, eventData;
|
||||
|
||||
if (!jsonData) {
|
||||
throw new Error('Server error: Server returned no or invalid data!');
|
||||
}
|
||||
|
||||
if (jsonData.status === true) {
|
||||
// Trigger events passed from the server
|
||||
for (key in jsonData.events) {
|
||||
eventData = jsonData.events[key].hasOwnProperty('data') ?
|
||||
jsonData.events[key].data : null;
|
||||
if (eventData !== null && eventData.isGlobalEvent) {
|
||||
eventData.handler = this;
|
||||
pkp.eventBus.$emit(jsonData.events[key].name, eventData);
|
||||
} else {
|
||||
this.trigger(jsonData.events[key].name, eventData);
|
||||
}
|
||||
}
|
||||
return jsonData;
|
||||
} else {
|
||||
// If we got an error message then display it.
|
||||
if (jsonData.content) {
|
||||
alert(jsonData.content);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* Sets the HTML element this handler is bound to.
|
||||
*
|
||||
* @protected
|
||||
* @param {jQueryObject} $htmlElement The element this handler should be bound
|
||||
* to.
|
||||
* @return {jQueryObject} Passes through the supplied parameter.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.setHtmlElement = function($htmlElement) {
|
||||
$.pkp.classes.Handler.checkContext_(this);
|
||||
|
||||
// Return the HTML element.
|
||||
this.$htmlElement_ = $htmlElement;
|
||||
return $htmlElement;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind an event to a handler operation.
|
||||
*
|
||||
* This will be done with a generic event handler
|
||||
* to make sure that we get a chance to re-set
|
||||
* 'this' to the handler before we call the actual
|
||||
* handler method.
|
||||
*
|
||||
* @protected
|
||||
* @param {string} eventName The name of the event
|
||||
* to be bound. See jQuery.bind() for event names.
|
||||
* @param {Function} handler The event handler to
|
||||
* be called when the even is triggered.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.bind = function(eventName, handler) {
|
||||
$.pkp.classes.Handler.checkContext_(this);
|
||||
|
||||
if (!this.eventBindings_[eventName]) {
|
||||
// Initialize the event store for this event.
|
||||
this.eventBindings_[eventName] = [];
|
||||
|
||||
// Determine the event namespace.
|
||||
var eventNamespace;
|
||||
eventNamespace = '.pkpHandler';
|
||||
if (eventName === 'pkpRemoveHandler') {
|
||||
// We have a special namespace for the remove event
|
||||
// because it needs to be triggered when all other
|
||||
// events have already been removed.
|
||||
eventNamespace = '.pkpHandlerRemove';
|
||||
}
|
||||
|
||||
// Bind the generic event handler to the event within our namespace.
|
||||
this.getHtmlElement().bind(eventName + eventNamespace, this.handleEvent);
|
||||
}
|
||||
|
||||
// Store the event binding internally
|
||||
this.eventBindings_[eventName].push(handler);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unbind an event from a handler operation.
|
||||
*
|
||||
* @protected
|
||||
* @param {string} eventName The name of the event
|
||||
* to be bound. See jQuery.bind() for event names.
|
||||
* @param {Function} handler The event handler to
|
||||
* be called when the even is triggered.
|
||||
* @return {boolean} True, if a handler was found and
|
||||
* removed, otherwise false.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.unbind = function(eventName, handler) {
|
||||
$.pkp.classes.Handler.checkContext_(this);
|
||||
|
||||
// Remove the event from the internal event cache.
|
||||
if (!this.eventBindings_[eventName]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var i, length;
|
||||
for (i = 0, length = this.eventBindings_[eventName].length; i < length; i++) {
|
||||
if (this.eventBindings_[eventName][i] === handler) {
|
||||
this.eventBindings_[eventName].splice([i], 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.eventBindings_[eventName].length === 0) {
|
||||
// If this was the last event then unbind the generic event handler.
|
||||
delete this.eventBindings_[eventName];
|
||||
this.getHtmlElement().unbind(eventName, this.handleEvent);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind a global event to a handler operation.
|
||||
*
|
||||
* Binds a callback function to fire when a global event is triggered on
|
||||
* the global event router.
|
||||
*
|
||||
* @param {string} eventName The name of the event to bind to.
|
||||
* @param {Function} callback The function to firewhen the event is triggered
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.bindGlobal = function(eventName, callback) {
|
||||
if (typeof this.globalEventListeners_[eventName] === 'undefined') {
|
||||
this.globalEventListeners_[eventName] = [];
|
||||
}
|
||||
var wrapper = this.callbackWrapper(callback);
|
||||
this.globalEventListeners_[eventName].push(wrapper);
|
||||
pkp.eventBus.$on(eventName, wrapper);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unbind a global event from a handler operation.
|
||||
*
|
||||
* If passing a `null` callback, all callbacks bound to eventName by this
|
||||
* handler will be unbound. See: http://backbonejs.org/#Events-off
|
||||
*
|
||||
* @see $.pkp.classes.Handler.prototype.bindGlobal()
|
||||
* @param {string} eventName The name of the event to bind to
|
||||
* @param {Function} callback The function to fire when event is triggered
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.unbindGlobal = function(eventName, callback) {
|
||||
var wrapper = this.callbackWrapper(callback),
|
||||
globalEventListeners = [];
|
||||
if (typeof this.globalEventListeners_[eventName] !== 'undefined') {
|
||||
this.globalEventListeners.forEach(function(callback) {
|
||||
if (callback !== wrapper) {
|
||||
globalEventListeners.push(callback);
|
||||
}
|
||||
});
|
||||
this.globalEventListeners = globalEventListeners;
|
||||
}
|
||||
pkp.eventBus.$off(eventName, wrapper);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unbind all global event listeners on this handler and any child handlers
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.unbindGlobalAll = function() {
|
||||
var event, callback;
|
||||
if (typeof this.globalEventListeners_ !== 'undefined') {
|
||||
for (event in this.globalEventListeners_) {
|
||||
for (callback in this.globalEventListeners_[event]) {
|
||||
pkp.eventBus.$off(event, this.globalEventListeners_[event][callback]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.globalEventListeners = null;
|
||||
this.unbindGlobalChildren();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unbind all global event listeners on child handlers
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.unbindGlobalChildren = function() {
|
||||
this.handlerChildren_.forEach(function(childHandler) {
|
||||
// Handler in legacy JS framework
|
||||
if (typeof childHandler.unbindGlobalAll !== 'undefined') {
|
||||
childHandler.unbindGlobalAll();
|
||||
// Handler in new Vue.js framework
|
||||
} else if (typeof childHandler.$destroy !== 'undefined') {
|
||||
delete pkp.registry._instances[childHandler.id];
|
||||
childHandler.$destroy();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add or retrieve a data item to/from the DOM element
|
||||
* this handler is managing.
|
||||
*
|
||||
* Always use this method if you want to store data
|
||||
* items. It makes sure that your items will be properly
|
||||
* namespaced and it also guarantees correct garbage
|
||||
* collection of your items once the handler is removed.
|
||||
*
|
||||
* @protected
|
||||
* @param {string} key The name of the item to be stored
|
||||
* or retrieved.
|
||||
* @param {Object=} opt_value The data item to be stored. If no item
|
||||
* is given then the existing value for the given key
|
||||
* will be returned.
|
||||
* @return {Object} The cached data item.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.data = function(key, opt_value) {
|
||||
$.pkp.classes.Handler.checkContext_(this);
|
||||
|
||||
// Namespace the key.
|
||||
key = 'pkp.' + key;
|
||||
|
||||
if (opt_value !== undefined) {
|
||||
// Add the key to the list of data items
|
||||
// that need to be garbage collected.
|
||||
this.dataItems_[key] = true;
|
||||
}
|
||||
|
||||
// Add/retrieve the data to/from the
|
||||
// element's data cache.
|
||||
if (arguments.length > 1) {
|
||||
return this.getHtmlElement().data(key, opt_value);
|
||||
} else {
|
||||
return this.getHtmlElement().data(key);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This function should be used to let the element emit events
|
||||
* that bubble outside the widget and are published over the
|
||||
* event bridge.
|
||||
*
|
||||
* @protected
|
||||
* @param {string} eventName The event to be triggered.
|
||||
* @param {Array=} opt_data Additional event data.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.trigger =
|
||||
function(eventName, opt_data) {
|
||||
|
||||
if (opt_data === undefined) {
|
||||
opt_data = null;
|
||||
}
|
||||
|
||||
// Trigger the event on the handled element.
|
||||
var $handledElement = this.getHtmlElement();
|
||||
$handledElement.triggerHandler(eventName, opt_data);
|
||||
|
||||
// Trigger the event publicly if it's not
|
||||
// published anyway.
|
||||
if (!this.publishedEvents_[eventName]) {
|
||||
this.triggerPublicEvent_(eventName, opt_data);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Publish an event triggered by a nested widget. This event
|
||||
* will bubble outside the widget and will also be published
|
||||
* over the event bridge.
|
||||
*
|
||||
* @param {string} eventName The event name.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.publishEvent = function(eventName) {
|
||||
// If the event has been published before then do nothing.
|
||||
if (this.publishedEvents_[eventName]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the event to the published event list.
|
||||
this.publishedEvents_[eventName] = true;
|
||||
|
||||
this.bind(eventName, function(context, privateEvent, var_args) {
|
||||
// Retrieve additional event data.
|
||||
var eventData = null;
|
||||
if (arguments.length > 2) {
|
||||
eventData = Array.prototype.slice.call(arguments, 2);
|
||||
}
|
||||
|
||||
// Re-trigger the private event publicly.
|
||||
this.triggerPublicEvent_(eventName, eventData);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the "show more" and "show less" clicks triggered by the
|
||||
* links in longer text items.
|
||||
*
|
||||
* @param {Event} event The event.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.switchViz = function(event) {
|
||||
var eventElement = event.currentTarget;
|
||||
$(eventElement).parent().parent().find('span').toggle();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initialize TinyMCE instances.
|
||||
*
|
||||
* There are instances where TinyMCE is not initialized with the call to
|
||||
* init(). These occur when content is loaded after the fact (via AJAX).
|
||||
*
|
||||
* In these cases, search for richContent fields and initialize them.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.initializeTinyMCE =
|
||||
function() {
|
||||
|
||||
if (typeof tinyMCE !== 'undefined') {
|
||||
var $element = this.getHtmlElement(),
|
||||
elementId = $element.attr('id'),
|
||||
settings = tinyMCE.EditorManager.settings;
|
||||
|
||||
settings.defaultToolbar = settings.toolbar;
|
||||
|
||||
$('#' + elementId).find('.richContent').each(function() {
|
||||
var id = /** @type {string} */ ($(this).attr('id')),
|
||||
icon = $('<div></div>'),
|
||||
iconParent = $('<div></div>'),
|
||||
classes, i, editor,
|
||||
settings = tinyMCE.EditorManager.settings;
|
||||
|
||||
// Set the extended toolbar, if requested
|
||||
if ($(this).hasClass('extendedRichContent')) {
|
||||
settings.toolbar = settings.richToolbar;
|
||||
} else {
|
||||
settings.toolbar = settings.defaultToolbar;
|
||||
}
|
||||
|
||||
editor = tinyMCE.EditorManager.createEditor(id, settings).render();
|
||||
|
||||
// For localizable text fields add globe and flag icons
|
||||
if ($(this).hasClass('localizable') || $(this).hasClass('flag')) {
|
||||
icon.addClass('mceLocalizationIcon localizable');
|
||||
icon.attr('id', 'mceLocalizationIcon-' + id);
|
||||
$(this).wrap(iconParent);
|
||||
$(this).parent().append(icon);
|
||||
|
||||
if ($(this).hasClass('localizable')) {
|
||||
// Add a globe icon to localizable TinyMCE textareas
|
||||
icon.addClass('mceGlobe');
|
||||
} else if ($(this).hasClass('flag')) {
|
||||
// Add country flag icon to localizable TinyMCE textareas
|
||||
classes = $(this).attr('class').split(' ');
|
||||
if (classes.length) {
|
||||
for (i = 0; i < classes.length; i++) {
|
||||
if (classes[i].match(/^flag_[a-z]{2}_[A-Z]{2}$/)) {
|
||||
icon.addClass(classes[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Trigger a public event.
|
||||
*
|
||||
* Public events will bubble outside the widget and will
|
||||
* also be forwarded through the event bridge if one has
|
||||
* been configured.
|
||||
*
|
||||
* @private
|
||||
* @param {string} eventName The event to be triggered.
|
||||
* @param {Array=} opt_data Additional event data.
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.triggerPublicEvent_ =
|
||||
function(eventName, opt_data) {
|
||||
|
||||
// Publish the event.
|
||||
var $handledElement = this.getHtmlElement();
|
||||
$handledElement.parent().trigger(eventName, opt_data);
|
||||
|
||||
// If we have an event bridge configured then re-trigger
|
||||
// the event on the target object.
|
||||
if (this.eventBridge_) {
|
||||
$('[id^="' + this.eventBridge_ + '"]').trigger(eventName, opt_data);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for the jQuery .replaceWith() function.
|
||||
*
|
||||
* This unbinds all global events before replacing the HTML content, to
|
||||
* ensure there are no orphaned event listeners lingering from handlers
|
||||
* which may have been destroyed when the HTML was replaced.
|
||||
*
|
||||
* This function can only be used when the entire handler is replaced. For
|
||||
* replacing parts of a handler, see replacePartialWith().
|
||||
*
|
||||
* @param {string|jQueryObject} html The HTML content to replace the
|
||||
* current element with
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.replaceWith = function(html) {
|
||||
this.unbindGlobalAll();
|
||||
this.getHtmlElement().replaceWith(html);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for the jQuery .replaceWith() function.
|
||||
*
|
||||
* This function works like the .replaceWith() wrapper above, except it
|
||||
* allows you to pass a specific dom element to replace within the Handler.
|
||||
*
|
||||
* This function loops over any handlers found within the $partial dom
|
||||
* element, unbinding global events to ensure there are no orphaned event
|
||||
* listeners when the HTML element is replaced.
|
||||
*
|
||||
* The .replaceWith() function is preferred in most cases. This should only
|
||||
* been used when you _need_ to replace part of a Handler's HTML content.
|
||||
* Full handler refreshes are preferred to keep things simple. Also, this
|
||||
* function isn't very performant, because it requires looping over every
|
||||
* child DOM element.
|
||||
*
|
||||
* @param {string|jQueryObject} html The HTML content to inject into
|
||||
* the $partial
|
||||
* @param {jQueryObject} $partial The HTML element to unbind
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.replacePartialWith =
|
||||
function(html, $partial) {
|
||||
|
||||
// Check if the $partial already has a handler bound to it on which
|
||||
// we can call .unbindGlobalAll() instead
|
||||
if ($.pkp.classes.Handler.hasHandler($partial)) {
|
||||
$.pkp.classes.Handler.getHandler($partial).replaceWith(html);
|
||||
return;
|
||||
}
|
||||
|
||||
this.unbindPartial($partial);
|
||||
$partial.replaceWith(html);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for the jQuery .html() function.
|
||||
*
|
||||
* This unbinds all global events before replacing the inner HTML content.
|
||||
* It differs from the .replaceWith() wrapper function in that the handler's
|
||||
* element is not removed. This means the handler isn't re-initialized, and
|
||||
* so only child handler events need to be unbound.
|
||||
*
|
||||
* @param {string} html The HTML content to inject into the $partial
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.html = function(html) {
|
||||
this.unbindGlobalChildren();
|
||||
this.getHtmlElement().html(html);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This function loops over any handlers found within the $partial dom
|
||||
* element, unbinding global events to ensure there are no orphaned event
|
||||
* listeners when the HTML element is replaced.
|
||||
*
|
||||
* This function isn't very performant. It requires looping over every
|
||||
* element in scope, which could potentially be hundreds or thousands.
|
||||
* This should only be used as a last resort for some handlers which need
|
||||
* to empty out partial content, such as tabs and grids.
|
||||
*
|
||||
* @param {jQueryObject} $partial The HTML element to unbind
|
||||
*/
|
||||
$.pkp.classes.Handler.prototype.unbindPartial =
|
||||
function($partial) {
|
||||
|
||||
$('*', $partial).each(function() {
|
||||
if ($.pkp.classes.Handler.hasHandler($(this))) {
|
||||
var handler = $.pkp.classes.Handler.getHandler($(this));
|
||||
handler.callbackWrapper(handler.unbindGlobalAll());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private static methods
|
||||
//
|
||||
/**
|
||||
* Check the context of a method invocation.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} context The function context
|
||||
* to be tested.
|
||||
*/
|
||||
$.pkp.classes.Handler.checkContext_ = function(context) {
|
||||
if (!(context instanceof $.pkp.classes.Handler)) {
|
||||
throw new Error('Trying to call handler method in non-handler context!');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,367 @@
|
||||
/**
|
||||
* @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));
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* @file js/classes/ObjectProxy.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 ObjectProxy
|
||||
* @ingroup js_classes
|
||||
*
|
||||
* @brief Proxy that will be added to every object before
|
||||
* instantiation.
|
||||
*
|
||||
* This proxy allows us to use a generic object factory. It'll
|
||||
* also be a good place to intercept objects and implement
|
||||
* cross-cutting concerns if required.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* The constructor must remain empty because it will
|
||||
* be replaced on instantiation of the proxy.
|
||||
*/
|
||||
$.pkp.classes.ObjectProxy = function() {};
|
||||
|
||||
|
||||
//
|
||||
// Private instance variables
|
||||
//
|
||||
/**
|
||||
* @private
|
||||
* @type {string} The object name of this object.
|
||||
*/
|
||||
$.pkp.classes.ObjectProxy.prototype.objectName_ = '';
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* Find a static property in the constructor hierarchy.
|
||||
*
|
||||
* NB: If the property is a function then it will be executed
|
||||
* in the current context with the additional arguments given.
|
||||
* If it is any other type then the property will be returned.
|
||||
*
|
||||
* @param {string} propertyName The name of the static
|
||||
* property to be found.
|
||||
* @param {...*} var_args Arguments to be passed to the
|
||||
* static method (if any).
|
||||
* @return {*} The property or undefined if the property
|
||||
* was not found.
|
||||
*/
|
||||
$.pkp.classes.ObjectProxy.prototype.self =
|
||||
function(propertyName, var_args) {
|
||||
var ctor, foundProperty, args;
|
||||
|
||||
// Loop through the inheritance hierarchy to find the property.
|
||||
for (ctor = this.constructor; ctor;
|
||||
ctor = ctor.parent_ && ctor.parent_.constructor) {
|
||||
|
||||
// Try to find the property in the current constructor.
|
||||
if (ctor.hasOwnProperty(propertyName)) {
|
||||
foundProperty = ctor[propertyName];
|
||||
if ($.isFunction(foundProperty)) {
|
||||
// If the property is a function then call it.
|
||||
args = Array.prototype.slice.call(arguments, 1);
|
||||
return foundProperty.apply(this, args);
|
||||
} else {
|
||||
// Return the property itself.
|
||||
return foundProperty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The property was not found on any of the functions
|
||||
// in the constructor hierarchy.
|
||||
throw new Error(['Static property "', propertyName, '" not found!'].join(''));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Find the parent constructor or method in the prototype
|
||||
* hierarchy.
|
||||
*
|
||||
* NB: If the method is found then it will be executed in the
|
||||
* context of the me parameter with the given arguments.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
$.pkp.classes.ObjectProxy.prototype.parent =
|
||||
function(opt_methodName, var_args) {
|
||||
var caller, args, foundCaller, ctor;
|
||||
|
||||
// Retrieve a reference to the function that called us.
|
||||
caller = $.pkp.classes.ObjectProxy.prototype.parent.caller;
|
||||
|
||||
// 1) Check whether the caller is a constructor.
|
||||
if (caller.parent_) {
|
||||
// We were called from within a constructor and
|
||||
// therefore the methodName parameter is not set.
|
||||
args = Array.prototype.slice.call(arguments);
|
||||
|
||||
// Call the constructor.
|
||||
return caller.parent_.constructor.apply(this, args);
|
||||
}
|
||||
|
||||
// Assume that we were called from within a method and that
|
||||
// therefore the methodName parameter is set.
|
||||
args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
// 2) Look for the caller in the top-level instance methods.
|
||||
if (this.hasOwnProperty(opt_methodName) && this[opt_methodName] === caller) {
|
||||
return this.constructor.parent_[opt_methodName].apply(this, args);
|
||||
}
|
||||
|
||||
// 3) Look for the caller in the prototype chain.
|
||||
foundCaller = false;
|
||||
for (ctor = this.constructor; ctor;
|
||||
ctor = ctor.parent_ && ctor.parent_.constructor) {
|
||||
if (ctor.prototype.hasOwnProperty(opt_methodName) &&
|
||||
ctor.prototype[opt_methodName] === caller) {
|
||||
foundCaller = true;
|
||||
} else if (foundCaller) {
|
||||
return ctor.prototype[opt_methodName].apply(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
// 4) This method was not called by the right caller.
|
||||
throw new Error(['Trying to call parent from a method of one name ',
|
||||
'to a method of a different name'].join(''));
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Return the object name of this object
|
||||
* @return {string} The object name of this object.
|
||||
*/
|
||||
$.pkp.classes.ObjectProxy.prototype.getObjectName = function() {
|
||||
return this.objectName_;
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @file js/classes/TinyMCEHelper.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 TinyMCEHelper
|
||||
* @ingroup js_classes
|
||||
*
|
||||
* @brief TinyMCE helper methods
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* Helper singleton
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.ObjectProxy
|
||||
*/
|
||||
$.pkp.classes.TinyMCEHelper = function() {
|
||||
throw new Error('Trying to instantiate the TinyMCEHelper singleton!');
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public static methods.
|
||||
//
|
||||
/**
|
||||
* Get the list of variables and their descriptions for a specified field.
|
||||
* @param {string} selector The textarea field's selector.
|
||||
* @return {?Object} Map of variableName: variableDisplayName entries.
|
||||
*/
|
||||
$.pkp.classes.TinyMCEHelper.prototype.getVariableMap =
|
||||
function(selector) {
|
||||
|
||||
var variablesEncoded = $(selector).attr('data-variables'),
|
||||
variablesParsed;
|
||||
|
||||
// If we found the data attribute, decode and return it.
|
||||
if (variablesEncoded !== undefined) {
|
||||
return $.parseJSON(decodeURIComponent(
|
||||
/** @type {string} */ (variablesEncoded)));
|
||||
}
|
||||
|
||||
// If we could not find the data attribute, return an empty list.
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the list of variables and their types for a specified field.
|
||||
* @param {string} selector The textarea field's selector.
|
||||
* @return {?Object} Map of variableName: variableType entries.
|
||||
*/
|
||||
$.pkp.classes.TinyMCEHelper.prototype.getVariableTypesMap =
|
||||
function(selector) {
|
||||
|
||||
var variablesTypeEncoded = $(selector).attr('data-variablesType');
|
||||
|
||||
// If we found the data attribute, decode and return it.
|
||||
if (variablesTypeEncoded !== undefined) {
|
||||
return $.parseJSON(decodeURIComponent(
|
||||
/** @type {string} */(variablesTypeEncoded)));
|
||||
}
|
||||
|
||||
// If we could not find the data attribute, return an empty list.
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generate an element to represent a PKP variable (e.g. primary contact name
|
||||
* in setup) within the TinyMCE editor.
|
||||
* @param {string} variableSymbolic The variable symbolic name.
|
||||
* @param {string} variableName The human-readable name for the variable.
|
||||
* @param {string} selector The selector to use for the element.
|
||||
* @return {jQueryObject} JQuery DOM representing the PKP variable.
|
||||
*/
|
||||
$.pkp.classes.TinyMCEHelper.prototype.getVariableElement =
|
||||
function(variableSymbolic, variableName, selector) {
|
||||
var variableType, variableTypes =
|
||||
$.pkp.classes.TinyMCEHelper.prototype.getVariableTypesMap(selector);
|
||||
|
||||
// Check if there is a variable type that should be treated otherwise
|
||||
if (variableTypes[variableSymbolic] != undefined) {
|
||||
variableType = variableTypes[variableSymbolic];
|
||||
if (variableType == $.pkp.cons.INSERT_TAG_VARIABLE_TYPE_PLAIN_TEXT) {
|
||||
return $('<div/>').append($('<span/>').text(variableName));
|
||||
}
|
||||
}
|
||||
|
||||
return $('<div/>').append($('<span/>')
|
||||
.addClass('pkpTag mceNonEditable')
|
||||
.attr('data-symbolic', variableSymbolic)
|
||||
.text(variableName));
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @file js/classes/VueRegistry.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 VueRegistry
|
||||
* @ingroup js_classes
|
||||
*
|
||||
* @brief Registry and initialization class for Vue.js handlers
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Registry of all active vue instances
|
||||
*/
|
||||
_instances: {},
|
||||
|
||||
/**
|
||||
* Initialize a Vue controller
|
||||
*
|
||||
* This method is often called directly from a <script> tag in a template
|
||||
* file to spin up a Vue controller on-demand. This allows the Vue component
|
||||
* lifecycle to be compatible with the legacy JS framework.
|
||||
*
|
||||
* @param string id Element ID to attach this controller to
|
||||
* @param string type The type of controller to initialize
|
||||
* @param object The data object to pass to the controller. Can include
|
||||
* configuration parameters, translatable strings and initial data.
|
||||
*/
|
||||
init: function (id, type, data) {
|
||||
if (pkp.controllers[type] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var baseData = {};
|
||||
if (typeof pkp.controllers[type].data === 'function') {
|
||||
baseData = pkp.controllers[type].data();
|
||||
}
|
||||
|
||||
var args = $.extend(true, {}, pkp.controllers[type], {
|
||||
el: '#' + id,
|
||||
data: $.extend(true, {}, baseData, data, {id: id}),
|
||||
});
|
||||
|
||||
pkp.registry._instances[id] = new pkp.Vue(args);
|
||||
|
||||
pkp.eventBus.$emit('root:mounted', id, pkp.registry._instances[id]);
|
||||
|
||||
// Register with a parent handler from the legacy JS framework, so that
|
||||
// those componments can destroy a Vue instance when removing HTML code
|
||||
var $parents = $(pkp.registry._instances[id].$el).parents();
|
||||
$parents.each(function (i) {
|
||||
if ($.pkp.classes.Handler.hasHandler($($parents[i]))) {
|
||||
$.pkp.classes.Handler.getHandler($($parents[i])).handlerChildren_.push(
|
||||
pkp.registry._instances[id]
|
||||
);
|
||||
return false; // only attach to the closest parent handler
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* @file js/classes/features/CollapsibleGridFeature.js
|
||||
*
|
||||
* Copyright (c) 2016-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 CollapsibleGridFeature
|
||||
* @ingroup js_classes_features
|
||||
*
|
||||
* @brief Adds collapse/expand functionality to grids.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @inheritDoc
|
||||
* @extends $.pkp.classes.features.Feature
|
||||
*/
|
||||
$.pkp.classes.features.CollapsibleGridFeature =
|
||||
function(gridHandler, options) {
|
||||
this.parent(gridHandler, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.features.CollapsibleGridFeature,
|
||||
$.pkp.classes.features.Feature);
|
||||
|
||||
|
||||
//
|
||||
// Getter and setters.
|
||||
//
|
||||
/**
|
||||
* Get the collapse/expand control link selector.
|
||||
* @return {string}
|
||||
*/
|
||||
$.pkp.classes.features.CollapsibleGridFeature.prototype.getControlSelector =
|
||||
function() {
|
||||
return "a[id^='collapsibleGridControl-expandGridControlLink-button-']";
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.CollapsibleGridFeature.prototype.init =
|
||||
function() {
|
||||
$(this.getControlSelector(), this.getGridHtmlElement()).
|
||||
click(this.callbackWrapper(this.toggleGridClickHandler_, this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.CollapsibleGridFeature.prototype.
|
||||
addFeatureHtml = function($gridElement, options) {
|
||||
var castOptions = /** @type {{collapsibleLink: string?}} */ (options);
|
||||
$gridElement.find('div.grid_header_bar').prepend(castOptions.collapsibleLink);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Collapse/expand grid.
|
||||
* @private
|
||||
* @param {Object} callingContext The calling element or object.
|
||||
* @param {Event=} opt_event The triggering event.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.classes.features.CollapsibleGridFeature.prototype.
|
||||
toggleGridClickHandler_ = function(callingContext, opt_event) {
|
||||
var $control = this.getGridHtmlElement().find(this.getControlSelector());
|
||||
|
||||
this.getGridHtmlElement().find('div.grid_header').siblings().toggle();
|
||||
$control.toggleClass('expand_all').toggleClass('collapse_all');
|
||||
|
||||
// Hide the search controls, if they are visible.
|
||||
this.getGridHtmlElement().
|
||||
find('div.grid_header_bar .search_extras_collapse').click();
|
||||
|
||||
// Toggle all grid actions.
|
||||
this.getGridHtmlElement().find('div.grid_header span.options').toggle();
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* @defgroup js_classes_features
|
||||
*/
|
||||
/**
|
||||
* @file js/classes/features/Feature.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 Feature
|
||||
* @ingroup js_classes_features
|
||||
*
|
||||
* @brief Base grid feature class.
|
||||
* @see lib/pkp/classes/controllers/grid/feature/GridFeature.php
|
||||
*
|
||||
* We use the features concept of the ext js framework:
|
||||
* http://docs.sencha.com/ext-js/4-0/#!/api/Ext.grid.feature.Feature
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.classes.features = $.pkp.classes.features || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends $.pkp.classes.ObjectProxy
|
||||
* @param {$.pkp.controllers.grid.GridHandler} gridHandler The grid
|
||||
* handler object.
|
||||
* @param {Array} options Associated options.
|
||||
*/
|
||||
$.pkp.classes.features.Feature =
|
||||
function(gridHandler, options) {
|
||||
this.gridHandler = gridHandler;
|
||||
this.options_ = options;
|
||||
this.addFeatureHtml(this.getGridHtmlElement(), options);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected properties.
|
||||
//
|
||||
/**
|
||||
* The grid that this feature is attached to.
|
||||
* @protected
|
||||
* @type {$.pkp.controllers.grid.GridHandler}
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.gridHandler = null;
|
||||
|
||||
|
||||
//
|
||||
// Private properties.
|
||||
//
|
||||
/**
|
||||
* This feature configuration options.
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.options_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Setters and getters.
|
||||
//
|
||||
/**
|
||||
* @param {Object} options The feature options.
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.setOptions =
|
||||
function(options) {
|
||||
this.options_ = options;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Object} The feature options.
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.getOptions =
|
||||
function() {
|
||||
return this.options_;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public template methods.
|
||||
//
|
||||
/**
|
||||
* Initialize this feature. Needs to be extended to implement
|
||||
* specific initialization. This method will always be called
|
||||
* by the components that this feature is attached to, in the
|
||||
* moment of the attachment.
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.init =
|
||||
function() {
|
||||
throw new Error('Abstract method!');
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Template methods (hooks into grid widgets).
|
||||
//
|
||||
/**
|
||||
* Hook into the add new element grid functionality.
|
||||
* @param {jQueryObject} $newElement The new element to be added.
|
||||
* @return {boolean} Always returns false.
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.addElement =
|
||||
function($newElement) {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hook into the replace element content grid functionality.
|
||||
* @param {jQueryObject} $newContent The element new content to be shown.
|
||||
* @return {boolean} Always returns false.
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.replaceElement =
|
||||
function($newContent) {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hook into the resequence rows grid functionality.
|
||||
* @param {Object} sequenceMap The grid rows sequence.
|
||||
* @return {boolean} Always returns false.
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.resequenceRows =
|
||||
function(sequenceMap) {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hook into the refresh grid functionality. Called just before
|
||||
* the fetch (grid or row) call is done.
|
||||
* @param {number|Object=} opt_elementId
|
||||
* @return {boolean} Always returns false.
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.refreshGrid =
|
||||
function(opt_elementId) {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hook into the replace element response handler. Called after the
|
||||
* response is handled.
|
||||
* @param {Object} handledJsonData Object with the response content handled
|
||||
* by the grid.
|
||||
* @return {boolean} Always returns false.
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.replaceElementResponseHandler =
|
||||
function(handledJsonData) {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods.
|
||||
//
|
||||
/**
|
||||
* Use the grid handler object and call the
|
||||
* callback wrapper method there.
|
||||
* @see $.pkp.classes.Handler.callbackWrapper()
|
||||
* @return {Function} Callback function.
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.callbackWrapper =
|
||||
function(callback, opt_context) {
|
||||
return this.gridHandler.callbackWrapper(callback, opt_context);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Extend to add extra html elements in the component
|
||||
* that this feature is attached to.
|
||||
* @param {jQueryObject} $gridElement Grid element to add elements to.
|
||||
* @param {Object} options Feature options.
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.addFeatureHtml =
|
||||
function($gridElement, options) {
|
||||
// Default implementation does nothing.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the html element of the grid that this feature
|
||||
* is attached to.
|
||||
*
|
||||
* @return {jQueryObject} Return the grid's HTML element.
|
||||
*/
|
||||
$.pkp.classes.features.Feature.prototype.getGridHtmlElement =
|
||||
function() {
|
||||
return this.gridHandler.getHtmlElement();
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @file js/classes/features/GeneralPagingFeature.js
|
||||
*
|
||||
* Copyright (c) 2016-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 GeneralPagingFeature
|
||||
* @ingroup js_classes_features
|
||||
*
|
||||
* @brief Base class that implements general functionalities for features
|
||||
* that handles paging on grids.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @inheritDoc
|
||||
* @extends $.pkp.classes.features.Feature
|
||||
*/
|
||||
$.pkp.classes.features.GeneralPagingFeature =
|
||||
function(gridHandler, options) {
|
||||
options.defaultItemsPerPage = parseInt(options.defaultItemsPerPage, 10);
|
||||
options.currentItemsPerPage = parseInt(options.currentItemsPerPage, 10);
|
||||
if (!options.itemsTotal) {
|
||||
options.itemsTotal = 0;
|
||||
} else {
|
||||
options.itemsTotal = parseInt(options.itemsTotal, 10);
|
||||
}
|
||||
options.currentPage = parseInt(options.currentPage, 10);
|
||||
this.parent(gridHandler, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.features.GeneralPagingFeature,
|
||||
$.pkp.classes.features.Feature);
|
||||
|
||||
|
||||
//
|
||||
// Getters and setters.
|
||||
//
|
||||
/**
|
||||
* @return {{itemsPerPageParamName: string,
|
||||
* defaultItemsPerPage: number,
|
||||
* currentItemsPerPage: number,
|
||||
* itemsTotal: number,
|
||||
* pageParamName: string,
|
||||
* currentPage: number,
|
||||
filter: string,
|
||||
* pagingMarkup: string }}
|
||||
* @override
|
||||
*/
|
||||
$.pkp.classes.features.GeneralPagingFeature.prototype.getOptions =
|
||||
function() {
|
||||
var castOptions = /** @type {{itemsPerPageParamName: string,
|
||||
defaultItemsPerPage: number,
|
||||
currentItemsPerPage: number,
|
||||
itemsTotal: number,
|
||||
pageParamName: string,
|
||||
currentPage: number,
|
||||
filter: string,
|
||||
pagingMarkup: string }} */
|
||||
(this.parent('getOptions'));
|
||||
|
||||
return castOptions;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods.
|
||||
//
|
||||
/**
|
||||
* Set grid requests extra parameters.
|
||||
* @param {Object} params
|
||||
*/
|
||||
$.pkp.classes.features.GeneralPagingFeature.prototype.setGridParams =
|
||||
function(params) {
|
||||
var options = this.getOptions(), filter;
|
||||
|
||||
// Add the filter data, if any.
|
||||
if (options.hasOwnProperty('filter')) {
|
||||
filter = $.parseJSON(options.filter);
|
||||
$.extend(true, params, filter);
|
||||
}
|
||||
|
||||
this.gridHandler.setFetchExtraParams(params);
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,339 @@
|
||||
/**
|
||||
* @file js/classes/features/InfiniteScrollingFeature.js
|
||||
*
|
||||
* Copyright (c) 2016-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 InfiniteScrollingFeature
|
||||
* @ingroup js_classes_features
|
||||
*
|
||||
* @brief Feature that implements infinite scrolling on grids.
|
||||
* It doesn't support category grids.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @inheritDoc
|
||||
* @extends $.pkp.classes.features.GeneralPagingFeature
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature =
|
||||
function(gridHandler, options) {
|
||||
this.parent(gridHandler, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.features.InfiniteScrollingFeature,
|
||||
$.pkp.classes.features.GeneralPagingFeature);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The scrollable element.
|
||||
* @private
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
||||
$scrollableElement_ = $();
|
||||
|
||||
|
||||
/**
|
||||
* The scrolling observer callback function.
|
||||
* @private
|
||||
* @type {Function}
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
||||
observeScrollCallback_ = function() {};
|
||||
|
||||
|
||||
//
|
||||
// Extended methods from GeneralPagingFeature
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.init =
|
||||
function() {
|
||||
var $scrollableElement = $('div.scrollable', this.getGridHtmlElement());
|
||||
if (!$scrollableElement.length) {
|
||||
this.gridHandler.publishEvent('pkpObserveScrolling');
|
||||
this.gridHandler.publishEvent('pkpRemoveScrollingObserver');
|
||||
}
|
||||
this.$scrollableElement_ = $scrollableElement;
|
||||
this.observeScrollCallback_ = this.gridHandler.callbackWrapper(
|
||||
this.observeScroll_, this);
|
||||
this.addScrollHandler_();
|
||||
this.fixGridHeight_();
|
||||
this.addPagingDataToRows_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.addFeatureHtml =
|
||||
function($gridElement, options) {
|
||||
var castOptions = /** @type {{pagingMarkup: string?,
|
||||
loadingContainer: string?}} */ (options);
|
||||
$gridElement.append(castOptions.pagingMarkup);
|
||||
$gridElement.find('.pkp_linkaction_moreItems')
|
||||
.click(this.gridHandler.callbackWrapper(this.loadMoreItems_, this));
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Hooks implementation.
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.refreshGrid =
|
||||
function(opt_elementId) {
|
||||
var options = this.getOptions(), params, $firstRow, $lastRow, page, $gridRow,
|
||||
elementId;
|
||||
|
||||
params = this.gridHandler.getFetchExtraParams();
|
||||
params[options.pageParamName] = options.currentPage;
|
||||
|
||||
if (opt_elementId && opt_elementId !==
|
||||
$.pkp.controllers.grid.GridHandler.FETCH_ALL_ROWS_ID) {
|
||||
// We need to make sure we pass the right page for the element.
|
||||
elementId = (/** @type {number} */ (opt_elementId));
|
||||
$gridRow = this.gridHandler.getRowByDataId(elementId);
|
||||
if ($gridRow.length == 1) {
|
||||
params[options.pageParamName] = Number($gridRow.attr('data-paging'));
|
||||
}
|
||||
}
|
||||
|
||||
params[options.itemsPerPageParamName] = options.currentItemsPerPage;
|
||||
|
||||
this.setGridParams(params);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
||||
replaceElementResponseHandler = function(handledJsonData) {
|
||||
var pagingInfo, options, castJsonData, rowMarkup;
|
||||
options = this.getOptions();
|
||||
castJsonData = /** @type {{pagingInfo: Object,
|
||||
deletedRowReplacement: string}} */
|
||||
(handledJsonData);
|
||||
|
||||
if (castJsonData.deletedRowReplacement != undefined) {
|
||||
rowMarkup = handledJsonData.deletedRowReplacement;
|
||||
this.gridHandler.insertOrReplaceElement(rowMarkup);
|
||||
this.updatePagingDataInAllRows_();
|
||||
}
|
||||
|
||||
this.addScrollHandler_();
|
||||
|
||||
if (castJsonData.pagingInfo != undefined) {
|
||||
pagingInfo = handledJsonData.pagingInfo;
|
||||
this.setOptions(pagingInfo);
|
||||
|
||||
if (pagingInfo.pagingMarkup != undefined) {
|
||||
$('div.gridPagingScrolling', this.getGridHtmlElement()).
|
||||
replaceWith(pagingInfo.pagingMarkup);
|
||||
}
|
||||
}
|
||||
|
||||
this.addPagingDataToRows_();
|
||||
|
||||
this.toggleLoadingContainer_();
|
||||
|
||||
this.getGridHtmlElement().find('.pkp_linkaction_moreItems').
|
||||
click(this.gridHandler.callbackWrapper(this.loadMoreItems_, this));
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Scroll handler to detect when it's time to request more rows.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {HTMLElement} sourceElement
|
||||
* @param {Event} event
|
||||
* @return {boolean}
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.observeScroll_ =
|
||||
function(sourceElement, event) {
|
||||
var options = this.getOptions(), sourceElementHeight,
|
||||
bottomLimit, windowDimensions;
|
||||
if (options.itemsTotal == this.gridHandler.getRows().length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.getGridHtmlElement().is(':visible')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($(sourceElement).hasClass('scrollable')) {
|
||||
sourceElementHeight = $(sourceElement).height();
|
||||
bottomLimit = sourceElement.scrollHeight;
|
||||
} else {
|
||||
windowDimensions = $.pkp.controllers.SiteHandler.
|
||||
prototype.getWindowDimensions();
|
||||
sourceElementHeight = windowDimensions.height;
|
||||
bottomLimit = this.getGridHtmlElement().offset().top +
|
||||
this.getGridHtmlElement().height();
|
||||
}
|
||||
|
||||
if (sourceElementHeight + $(sourceElement).scrollTop() >= bottomLimit) {
|
||||
// Avoid multiple rows requests.
|
||||
if (this.$scrollableElement_.length) {
|
||||
this.$scrollableElement_.unbind('scroll');
|
||||
} else {
|
||||
this.getGridHtmlElement().trigger('pkpRemoveScrollingObserver',
|
||||
[this.observeScrollCallback_]);
|
||||
}
|
||||
|
||||
this.loadMoreItems_();
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fix the grid height to acomodate the number of initial visible rows.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.fixGridHeight_ =
|
||||
function() {
|
||||
var $scrollableDivs = $('div.scrollable', this.getGridHtmlElement()),
|
||||
index, limit, $div, timer, length;
|
||||
|
||||
if ($scrollableDivs.length > 0) {
|
||||
timer = setInterval(function() {
|
||||
if ($scrollableDivs.is(':visible')) {
|
||||
clearInterval(timer);
|
||||
length = $scrollableDivs.length;
|
||||
for (index = 0, limit = length; index < limit; index++) {
|
||||
$div = $($scrollableDivs[index]);
|
||||
if ($div.get(0).scrollHeight > $div.height()) {
|
||||
$div.css('max-height', $div.get(0).scrollHeight - 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
},300);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add paging data to the respective rows.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.addPagingDataToRows_ =
|
||||
function() {
|
||||
var $rows, options = this.getOptions();
|
||||
$rows = this.gridHandler.getRows().filter('tr:not([data-paging])');
|
||||
$rows.attr('data-paging', options.currentPage);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update paging data in all grid rows.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
||||
updatePagingDataInAllRows_ = function() {
|
||||
var $rows, options = this.getOptions(), index, limit, page = 1,
|
||||
itemsCount = 1;
|
||||
$rows = this.gridHandler.getRows();
|
||||
$rows.removeAttr('data-paging');
|
||||
|
||||
for (index = 0, limit = $rows.length; index < limit; index++) {
|
||||
$($rows[index]).attr('data-paging', page);
|
||||
itemsCount++;
|
||||
if (itemsCount > options.currentItemsPerPage) {
|
||||
itemsCount = 1;
|
||||
page++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add scroll handler to the grid element.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.addScrollHandler_ =
|
||||
function() {
|
||||
var $scrollableElement = this.$scrollableElement_;
|
||||
if ($scrollableElement.length) {
|
||||
$scrollableElement.
|
||||
scroll(this.observeScrollCallback_);
|
||||
} else {
|
||||
this.getGridHtmlElement().trigger('pkpObserveScrolling',
|
||||
[this.observeScrollCallback_]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Toggle the scrolling loading element.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {boolean=} opt_show
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
||||
toggleLoadingContainer_ = function(opt_show) {
|
||||
var $loadingElement =
|
||||
this.getGridHtmlElement().find('div.gridPagingScrolling div.pkp_loading'),
|
||||
$scrollableElement = this.$scrollableElement_,
|
||||
scrollTop,
|
||||
loadingHeight = $loadingElement.height(),
|
||||
scrollTarget;
|
||||
|
||||
if (opt_show) {
|
||||
this.getGridHtmlElement().addClass('loading');
|
||||
scrollTop = $scrollableElement.scrollTop();
|
||||
scrollTarget = /** @type {number} */ (scrollTop + loadingHeight);
|
||||
$scrollableElement.scrollTop(scrollTarget);
|
||||
} else {
|
||||
this.getGridHtmlElement().removeClass('loading');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Trigger necessary actions for the grid to
|
||||
* load next page items.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
*/
|
||||
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
||||
loadMoreItems_ = function() {
|
||||
var options = this.getOptions();
|
||||
|
||||
// Show the loading icon.
|
||||
this.toggleLoadingContainer_(true);
|
||||
|
||||
options.currentPage = Number($('tr.gridRow',
|
||||
this.getGridHtmlElement()).last().attr('data-paging')) + 1;
|
||||
this.getGridHtmlElement().trigger('dataChanged',
|
||||
[$.pkp.controllers.grid.GridHandler.FETCH_ALL_ROWS_ID]);
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* @file js/classes/features/OrderCategoryGridItemsFeature.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 OrderCategoryGridItemsFeature
|
||||
* @ingroup js_classes_features
|
||||
*
|
||||
* @brief Feature for ordering category grid items.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @inheritDoc
|
||||
* @extends $.pkp.classes.features.OrderGridItemsFeature
|
||||
*/
|
||||
$.pkp.classes.features.OrderCategoryGridItemsFeature =
|
||||
function(gridHandler, options) {
|
||||
this.parent(gridHandler, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.features.OrderCategoryGridItemsFeature,
|
||||
$.pkp.classes.features.OrderGridItemsFeature);
|
||||
|
||||
|
||||
//
|
||||
// Extended methods from OrderItemsFeature.
|
||||
//
|
||||
/**
|
||||
* Setup the sortable plugin.
|
||||
*/
|
||||
$.pkp.classes.features.OrderCategoryGridItemsFeature.prototype.
|
||||
setupSortablePlugin = function() {
|
||||
|
||||
var $categories, index, limit, $category, userAgent;
|
||||
|
||||
this.applySortPlgOnElements(
|
||||
this.getGridHtmlElement(), 'tbody.orderable', null);
|
||||
|
||||
// FIXME *7610*: IE8 can't handle well ordering in both categories and
|
||||
// category rows.
|
||||
userAgent = navigator.userAgent.toLowerCase();
|
||||
if (/msie/.test(userAgent) &&
|
||||
parseInt(userAgent.substr(userAgent.indexOf('msie') + 5, 1), 10) <= 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
$categories = this.gridHandler.getCategories();
|
||||
for (index = 0, limit = $categories.length; index < limit; index++) {
|
||||
$category = $($categories[index]);
|
||||
this.applySortPlgOnElements($category, 'tr.orderable', null);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderCategoryGridItemsFeature.prototype.
|
||||
saveOrderHandler = function() {
|
||||
this.gridHandler.updateEmptyPlaceholderPosition();
|
||||
this.parent('saveOrderHandler');
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderCategoryGridItemsFeature.prototype.
|
||||
cancelOrderHandler = function() {
|
||||
|
||||
var categorySequence = this.getCategorySequence_(this.itemsOrder);
|
||||
this.parent('cancelOrderHandler');
|
||||
this.gridHandler.resequenceCategories(categorySequence);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderCategoryGridItemsFeature.prototype.
|
||||
toggleItemsDragMode = function() {
|
||||
this.parent('toggleItemsDragMode');
|
||||
|
||||
var isOrdering = this.isOrdering,
|
||||
$categories = this.gridHandler.getCategories(),
|
||||
index, limit, $category;
|
||||
|
||||
for (index = 0, limit = $categories.length; index < limit; index++) {
|
||||
$category = $($categories[index]);
|
||||
this.toggleCategoryDragMode_($category);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderCategoryGridItemsFeature.prototype.
|
||||
addOrderingClassToRows = function() {
|
||||
|
||||
var options = this.getOptions(),
|
||||
type = parseInt(options.type, 10), $categories;
|
||||
|
||||
if (type == $.pkp.cons.ORDER_CATEGORY_GRID_CATEGORIES_ONLY ||
|
||||
type == $.pkp.cons.ORDER_CATEGORY_GRID_CATEGORIES_AND_ROWS) {
|
||||
$categories = this.gridHandler.getCategories();
|
||||
$categories.addClass('orderable');
|
||||
}
|
||||
|
||||
if (type == $.pkp.cons.ORDER_CATEGORY_GRID_CATEGORIES_ROWS_ONLY ||
|
||||
type == $.pkp.cons.ORDER_CATEGORY_GRID_CATEGORIES_AND_ROWS) {
|
||||
this.parent('addOrderingClassToRows');
|
||||
}
|
||||
|
||||
// We don't want to order category rows tr elements, so
|
||||
// remove any style that might be added by calling parent.
|
||||
this.gridHandler.getCategoryRow().removeClass('orderable');
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Overriden method from OrderGridItemsFeature
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderCategoryGridItemsFeature.prototype.getItemsDataId =
|
||||
function() {
|
||||
var categoriesSeq = this.getCategorySequence_(this.itemsOrder),
|
||||
itemsDataId = [],
|
||||
index, limit,
|
||||
$category, categoryRowsDataId, categoryDataId;
|
||||
|
||||
for (index = 0, limit = categoriesSeq.length; index < limit; index++) {
|
||||
$category = $('#' + categoriesSeq[index]);
|
||||
categoryRowsDataId = this.getRowsDataId($category);
|
||||
categoryDataId = this.gridHandler.getCategoryDataId($category);
|
||||
itemsDataId.push(
|
||||
{'categoryId': categoryDataId, 'rowsId': categoryRowsDataId });
|
||||
}
|
||||
|
||||
return itemsDataId;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Enable/disable category drag mode.
|
||||
* @param {jQueryObject} $category Category to set mode on.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.OrderCategoryGridItemsFeature.prototype.
|
||||
toggleCategoryDragMode_ = function($category) {
|
||||
var isOrdering = this.isOrdering,
|
||||
$categoryRow = this.gridHandler.getCategoryRow($category),
|
||||
$categoryRowColumn = $('td:first', $categoryRow),
|
||||
moveClasses = this.getMoveItemClasses();
|
||||
|
||||
if (isOrdering) {
|
||||
$categoryRowColumn.addClass(moveClasses);
|
||||
} else {
|
||||
$categoryRowColumn.removeClass(moveClasses);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the categories sequence, based on the passed items order.
|
||||
* @param {Array} itemsOrder Items order.
|
||||
* @return {Array} A sequence array with the category data id as values.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.OrderCategoryGridItemsFeature.prototype.
|
||||
getCategorySequence_ = function(itemsOrder) {
|
||||
var index, limit, categorySequence = [], categoryDataId, categoryId;
|
||||
for (index = 0, limit = itemsOrder.length; index < limit; index++) {
|
||||
categoryDataId = this.gridHandler
|
||||
.getCategoryDataIdByRowId(itemsOrder[index]);
|
||||
categoryId = this.gridHandler.getCategoryIdPrefix() + categoryDataId;
|
||||
if ($.inArray(categoryId, categorySequence) > -1) {
|
||||
continue;
|
||||
}
|
||||
categorySequence.push(categoryId);
|
||||
}
|
||||
|
||||
return categorySequence;
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @file js/classes/features/OrderGridItemsFeature.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 OrderGridItemsFeature
|
||||
* @ingroup js_classes_features
|
||||
*
|
||||
* @brief Feature for ordering grid items.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @inheritDoc
|
||||
* @extends $.pkp.classes.features.OrderItemsFeature
|
||||
*/
|
||||
$.pkp.classes.features.OrderGridItemsFeature =
|
||||
function(gridHandler, options) {
|
||||
this.parent(gridHandler, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.features.OrderGridItemsFeature,
|
||||
$.pkp.classes.features.OrderItemsFeature);
|
||||
|
||||
|
||||
//
|
||||
// Extended methods from OrderItemsFeature.
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderGridItemsFeature.prototype.setupSortablePlugin =
|
||||
function() {
|
||||
this.applySortPlgOnElements(
|
||||
this.getGridHtmlElement(), 'tr.orderable', null);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderGridItemsFeature.prototype.saveOrderHandler =
|
||||
function() {
|
||||
|
||||
var stringifiedData, saveOrderCallback,
|
||||
options = /** @type {{saveItemsSequenceUrl: string}} */
|
||||
(this.getOptions()),
|
||||
returner;
|
||||
|
||||
this.parent('saveOrderHandler');
|
||||
|
||||
stringifiedData = JSON.stringify(this.getItemsDataId());
|
||||
saveOrderCallback = this.callbackWrapper(
|
||||
this.saveOrderResponseHandler_, this);
|
||||
$.post(options.saveItemsSequenceUrl,
|
||||
{data: stringifiedData, csrfToken: options.csrfToken},
|
||||
saveOrderCallback, 'json');
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods to be overriden by subclasses
|
||||
//
|
||||
/**
|
||||
* Get all items data id in a sequence array.
|
||||
* @return {Array} List of all items data.
|
||||
*/
|
||||
$.pkp.classes.features.OrderGridItemsFeature.prototype.getItemsDataId =
|
||||
function() {
|
||||
return this.getRowsDataId(this.getGridHtmlElement());
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Save order response handler.
|
||||
* @private
|
||||
*
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
*/
|
||||
$.pkp.classes.features.OrderGridItemsFeature.prototype.
|
||||
saveOrderResponseHandler_ = function(ajaxContext, jsonData) {
|
||||
var processedJsonData = this.gridHandler.handleJson(jsonData);
|
||||
this.toggleState(false);
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,639 @@
|
||||
/**
|
||||
* @file js/classes/features/OrderItemsFeature.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 OrderItemsFeature
|
||||
* @ingroup js_classes_features
|
||||
*
|
||||
* @brief Base feature class for ordering grid items.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @param {jQueryObject} gridHandler The handler of
|
||||
* the grid element that this feature is attached to.
|
||||
* @param {Object} options Configuration of this feature.
|
||||
* @extends $.pkp.classes.features.Feature
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature =
|
||||
function(gridHandler, options) {
|
||||
this.parent(gridHandler, options);
|
||||
|
||||
this.$orderButton_ = $('.pkp_linkaction_orderItems',
|
||||
this.getGridHtmlElement());
|
||||
this.$finishControl_ = $('.order_finish_controls', this.getGridHtmlElement());
|
||||
|
||||
if (this.$orderButton_.length === 0) {
|
||||
// No order button, it will always stay in ordering mode.
|
||||
this.isOrdering = true;
|
||||
}
|
||||
|
||||
this.itemsOrder = [];
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.features.OrderItemsFeature,
|
||||
$.pkp.classes.features.Feature);
|
||||
|
||||
|
||||
//
|
||||
// Protected properties
|
||||
//
|
||||
/**
|
||||
* Item sequence.
|
||||
* @protected
|
||||
* @type {Array}
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.itemsOrder = null;
|
||||
|
||||
|
||||
/**
|
||||
* Flag to control if user is ordering items.
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.isOrdering = false;
|
||||
|
||||
|
||||
//
|
||||
// Private properties.
|
||||
//
|
||||
/**
|
||||
* Initiate ordering state button.
|
||||
* @private
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.$orderButton_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Cancel ordering state button.
|
||||
* @private
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.$cancelButton_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Save ordering state button.
|
||||
* @private
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.$saveButton_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Ordering finish control.
|
||||
* @private
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.$finishControl_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Getters and setters.
|
||||
//
|
||||
/**
|
||||
* Get the order button.
|
||||
* @return {jQueryObject} The order button JQuery object.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.getOrderButton =
|
||||
function() {
|
||||
return this.$orderButton_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the finish control.
|
||||
* @return {jQueryObject} The JQuery "finish" control.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.getFinishControl =
|
||||
function() {
|
||||
return this.$finishControl_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get save order button.
|
||||
*
|
||||
* @return {jQueryObject} The "save order" JQuery object.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.getSaveOrderButton =
|
||||
function() {
|
||||
return this.getFinishControl().find('.saveButton');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get cancel order link.
|
||||
*
|
||||
* @return {jQueryObject} The "cancel order" JQuery control.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.getCancelOrderButton =
|
||||
function() {
|
||||
return this.getFinishControl().find('.cancelFormButton');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the move item row action element selector.
|
||||
* @return {string} Return the element selector.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.
|
||||
getMoveItemRowActionSelector = function() {
|
||||
return '.orderable .pkp_linkaction_moveItem';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the css classes used to stylize the ordering items.
|
||||
* @return {string} CSS classes.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.getMoveItemClasses =
|
||||
function() {
|
||||
return 'pkp_helpers_moveicon ordering';
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public template methods.
|
||||
//
|
||||
/**
|
||||
* Called every time user start dragging an item.
|
||||
* @param {jQueryObject} contextElement The element this event occurred for.
|
||||
* @param {Event} event The drag/drop event.
|
||||
* @param {Object} ui Object with data related to the event elements.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.dragStartCallback =
|
||||
function(contextElement, event, ui) {
|
||||
// The default implementation does nothing.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called every time user stop dragging an item.
|
||||
* @param {jQueryObject} contextElement The element this event occurred for.
|
||||
* @param {Event} event The drag/drop event.
|
||||
* @param {Object} ui Object with data related to the event elements.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.dragStopCallback =
|
||||
function(contextElement, event, ui) {
|
||||
// The default implementation does nothing.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called every time sequence is changed.
|
||||
* @param {jQueryObject} contextElement The element this event occurred for.
|
||||
* @param {Event} event The drag/drop event.
|
||||
* @param {Object} ui Object with data related to the event elements.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.updateOrderCallback =
|
||||
function(contextElement, event, ui) {
|
||||
// The default implementation does nothing.
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Extended methods from Feature
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.init =
|
||||
function() {
|
||||
|
||||
this.addOrderingClassToRows();
|
||||
this.toggleMoveItemRowAction(this.isOrdering);
|
||||
this.getGridHtmlElement().find('div.order_message').hide();
|
||||
|
||||
this.toggleOrderLink_();
|
||||
if (this.isOrdering) {
|
||||
this.setupSortablePlugin();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.addFeatureHtml =
|
||||
function($gridElement, options) {
|
||||
var castOptions = /** @type {{orderFinishControls: string?,
|
||||
orderMessage: string?}} */ (options),
|
||||
$orderFinishControls, orderMessageHtml, $gridRows;
|
||||
if (castOptions.orderFinishControls !== undefined) {
|
||||
$orderFinishControls = $(castOptions.orderFinishControls);
|
||||
$gridElement.find('table').last().after($orderFinishControls);
|
||||
$orderFinishControls.hide();
|
||||
}
|
||||
|
||||
if (castOptions.orderMessage !== undefined) {
|
||||
orderMessageHtml = castOptions.orderMessage;
|
||||
$gridRows = $gridElement.find('.gridRow').filter(function(index, element) {
|
||||
return !Boolean($(this).find('a.pkp_linkaction_moveItem').length);
|
||||
});
|
||||
$gridRows.find('td:first-child').prepend(orderMessageHtml);
|
||||
}
|
||||
|
||||
this.updateOrderLinkVisibility_();
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected template methods.
|
||||
//
|
||||
/**
|
||||
* Add orderable class to grid rows.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.addOrderingClassToRows =
|
||||
function() {
|
||||
// Add ordering class to grid rows.
|
||||
var $gridRows = this.gridHandler.getRows().filter(function(index, element) {
|
||||
return $(this).find('a.pkp_linkaction_moveItem').length;
|
||||
});
|
||||
$gridRows.addClass('orderable');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Setup the sortable plugin. Must be implemented in subclasses.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.setupSortablePlugin =
|
||||
function() {
|
||||
// Default implementation does nothing.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called every time storeOrder is called. This is a chance to subclasses
|
||||
* execute operations with each row that has their sequence being saved.
|
||||
* @param {number} index The current row index position inside the rows
|
||||
* jQuery object.
|
||||
* @param {jQueryObject} $row Row for which to store the sequence.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.storeRowOrder =
|
||||
function(index, $row) {
|
||||
// The default implementation does nothing.
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods.
|
||||
//
|
||||
/**
|
||||
* Initiate ordering button click event handler.
|
||||
* @return {boolean} Always returns false.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.clickOrderHandler =
|
||||
function() {
|
||||
this.gridHandler.hideAllVisibleRowActions();
|
||||
this.storeOrder(this.gridHandler.getRows());
|
||||
this.toggleState(true);
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Save order handler.
|
||||
* @return {boolean} Return false to stop click event processing.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.saveOrderHandler =
|
||||
function() {
|
||||
var $rows;
|
||||
|
||||
this.gridHandler.updateControlRowsPosition();
|
||||
this.unbindOrderFinishControlsHandlers_();
|
||||
$rows = this.gridHandler.getRows();
|
||||
this.storeOrder($rows);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cancel ordering action click event handler.
|
||||
* @return {boolean} Always returns false.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.cancelOrderHandler =
|
||||
function() {
|
||||
this.gridHandler.resequenceRows(this.itemsOrder);
|
||||
this.toggleState(false);
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Execute all operations necessary to change the state of the
|
||||
* ordering process (enabled or disabled).
|
||||
* @param {boolean} isOrdering Is ordering process active?
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.toggleState =
|
||||
function(isOrdering) {
|
||||
this.isOrdering = isOrdering;
|
||||
this.toggleGridLinkActions_();
|
||||
this.toggleOrderLink_();
|
||||
this.toggleFinishControl_();
|
||||
this.toggleItemsDragMode();
|
||||
this.setupSortablePlugin();
|
||||
this.setupNonOrderableMessage_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set rows sequence store, using
|
||||
* the sequence of the passed items.
|
||||
*
|
||||
* @param {jQueryObject} $rows The rows to be used to get the sequence
|
||||
* information.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.storeOrder =
|
||||
function($rows) {
|
||||
var index, limit, $row, elementId;
|
||||
this.itemsOrder = [];
|
||||
for (index = 0, limit = $rows.length; index < limit; index++) {
|
||||
$row = $($rows[index]);
|
||||
elementId = $row.attr('id');
|
||||
|
||||
this.itemsOrder.push(elementId);
|
||||
|
||||
// Give a chance to subclasses do extra operations to store
|
||||
// the current row order.
|
||||
this.storeRowOrder(index, $row);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enable/disable the items drag mode.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.toggleItemsDragMode =
|
||||
function() {
|
||||
var isOrdering = this.isOrdering,
|
||||
$rows = this.gridHandler.getRows(),
|
||||
$orderableRows = $rows.filter('.orderable'),
|
||||
moveClasses = this.getMoveItemClasses();
|
||||
|
||||
if (isOrdering) {
|
||||
$orderableRows.addClass(moveClasses);
|
||||
} else {
|
||||
$orderableRows.removeClass(moveClasses);
|
||||
}
|
||||
|
||||
this.toggleMoveItemRowAction(isOrdering);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Apply (disabled or enabled) the sortable plugin on passed elements.
|
||||
* @param {jQueryObject} $container The element that contain all the orderable
|
||||
* items.
|
||||
* @param {string} itemsSelector The jQuery selector for orderable items.
|
||||
* @param {Object?} extraParams Optional set of extra parameters for sortable.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.applySortPlgOnElements =
|
||||
function($container, itemsSelector, extraParams) {
|
||||
var isOrdering = this.isOrdering,
|
||||
dragStartCallback = this.gridHandler.callbackWrapper(
|
||||
this.dragStartCallback, this),
|
||||
dragStopCallback = this.gridHandler.callbackWrapper(
|
||||
this.dragStopCallback, this),
|
||||
orderItemCallback = this.gridHandler.callbackWrapper(
|
||||
this.updateOrderCallback, this),
|
||||
config = {
|
||||
disabled: !isOrdering,
|
||||
items: itemsSelector,
|
||||
activate: dragStartCallback,
|
||||
deactivate: dragStopCallback,
|
||||
update: orderItemCallback,
|
||||
tolerance: 'pointer'};
|
||||
|
||||
if (typeof extraParams === 'object') {
|
||||
config = $.extend(true, config, extraParams);
|
||||
}
|
||||
|
||||
$container.sortable(config);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the data element id of all rows inside the passed
|
||||
* container, in the current order.
|
||||
* @param {jQueryObject} $rowsContainer The element that contains the rows
|
||||
* that will be used to retrieve the id.
|
||||
* @return {Array} A sequence array with data element ids as values.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.getRowsDataId =
|
||||
function($rowsContainer) {
|
||||
var index, rowDataIds = [], $row, rowDataId;
|
||||
for (index in this.itemsOrder) {
|
||||
$row = $('#' + this.itemsOrder[index], $rowsContainer);
|
||||
if ($row.length < 1) {
|
||||
continue;
|
||||
}
|
||||
rowDataId = this.gridHandler.getRowDataId($row);
|
||||
rowDataIds.push(rowDataId);
|
||||
}
|
||||
|
||||
return rowDataIds;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show/hide the move item row action (position left).
|
||||
* @param {boolean} enable New enable state.
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.toggleMoveItemRowAction =
|
||||
function(enable) {
|
||||
var $grid = this.getGridHtmlElement(),
|
||||
$actionsContainer = $('div.row_actions', $grid),
|
||||
allLinksButMoveItemSelector = 'a:not(' +
|
||||
this.getMoveItemRowActionSelector() + ')',
|
||||
$actions = $actionsContainer.find(allLinksButMoveItemSelector),
|
||||
$moveItemRowAction = $(this.getMoveItemRowActionSelector(), $grid),
|
||||
$rowActionsContainer, $rowActions;
|
||||
|
||||
if (enable) {
|
||||
$actions.addClass('pkp_helpers_display_none');
|
||||
$moveItemRowAction.show();
|
||||
// Make sure row actions div is visible.
|
||||
this.gridHandler.showRowActionsDiv();
|
||||
} else {
|
||||
$actions.removeClass('pkp_helpers_display_none');
|
||||
|
||||
$rowActionsContainer = $('.gridRow div.row_actions', $grid);
|
||||
$rowActions = $rowActionsContainer.
|
||||
find(allLinksButMoveItemSelector);
|
||||
if ($rowActions.length === 0) {
|
||||
// No link action to show, hide row actions div.
|
||||
this.gridHandler.hideRowActionsDiv();
|
||||
}
|
||||
$moveItemRowAction.hide();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Hooks implementation.
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.addElement =
|
||||
function($element) {
|
||||
this.addOrderingClassToRows();
|
||||
this.toggleItemsDragMode();
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.replaceElement =
|
||||
function($content) {
|
||||
this.addOrderingClassToRows();
|
||||
this.toggleItemsDragMode();
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.
|
||||
replaceElementResponseHandler = function(handledJsonData) {
|
||||
this.updateOrderLinkVisibility_();
|
||||
this.setupNonOrderableMessage_();
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Make sure that the order action visibility state is correct,
|
||||
* based on the grid rows number.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.
|
||||
updateOrderLinkVisibility_ = function() {
|
||||
var $orderLink = $('.pkp_linkaction_orderItems', this.getGridHtmlElement());
|
||||
if (this.gridHandler.getRows().length <= 1) {
|
||||
$orderLink.hide();
|
||||
} else {
|
||||
$orderLink.show();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the state of the grid link actions, based on current ordering state.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.toggleGridLinkActions_ =
|
||||
function() {
|
||||
var isOrdering = this.isOrdering,
|
||||
// We want to enable/disable all link actions, except this
|
||||
// features controls.
|
||||
$gridLinkActions = $('.pkp_controllers_linkAction',
|
||||
this.getGridHtmlElement()).not(
|
||||
this.getMoveItemRowActionSelector()).not(
|
||||
this.getOrderButton()).not(
|
||||
this.getFinishControl().find('*'));
|
||||
|
||||
this.gridHandler.changeLinkActionsState(!isOrdering, $gridLinkActions);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enable/disable the order link action.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.toggleOrderLink_ =
|
||||
function() {
|
||||
if (this.isOrdering) {
|
||||
this.$orderButton_.unbind('click');
|
||||
this.$orderButton_.attr('disabled', 'disabled');
|
||||
} else {
|
||||
var clickHandler = this.gridHandler.callbackWrapper(
|
||||
this.clickOrderHandler, this);
|
||||
this.$orderButton_.click(clickHandler);
|
||||
this.$orderButton_.removeAttr('disabled');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show/hide the ordering process finish control, based
|
||||
* on the current ordering state.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.toggleFinishControl_ =
|
||||
function() {
|
||||
if (this.isOrdering) {
|
||||
this.bindOrderFinishControlsHandlers_();
|
||||
this.getFinishControl().slideDown(300);
|
||||
} else {
|
||||
this.unbindOrderFinishControlsHandlers_();
|
||||
this.getFinishControl().slideUp(300);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind event handlers to the controls that finish the
|
||||
* ordering action (save and cancel).
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.
|
||||
bindOrderFinishControlsHandlers_ = function() {
|
||||
var $saveButton = this.getSaveOrderButton(),
|
||||
$cancelLink = this.getCancelOrderButton(),
|
||||
cancelLinkHandler = this.gridHandler.callbackWrapper(
|
||||
this.cancelOrderHandler, this),
|
||||
saveButtonHandler = this.gridHandler.callbackWrapper(
|
||||
this.saveOrderHandler, this);
|
||||
|
||||
$saveButton.click(saveButtonHandler);
|
||||
$cancelLink.click(cancelLinkHandler);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unbind event handlers from the controls that finish the
|
||||
* ordering action (save and cancel).
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.
|
||||
unbindOrderFinishControlsHandlers_ = function() {
|
||||
|
||||
this.getSaveOrderButton().unbind('click');
|
||||
this.getCancelOrderButton().unbind('click');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Toggle hover action to show message for non orderable
|
||||
* grid rows.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.OrderItemsFeature.prototype.
|
||||
setupNonOrderableMessage_ = function() {
|
||||
if (this.isOrdering) {
|
||||
this.gridHandler.getRows().hover(function() {
|
||||
$(this).find('div.order_message').toggle();
|
||||
});
|
||||
} else {
|
||||
this.gridHandler.getRows().unbind('mouseenter mouseleave');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* @file js/classes/features/OrderListbuilderItemsFeature.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 OrderListbuilderItemsFeature
|
||||
* @ingroup js_classes_features
|
||||
*
|
||||
* @brief Feature for ordering grid items.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @inheritDoc
|
||||
* @extends $.pkp.classes.features.OrderItemsFeature
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature =
|
||||
function(gridHandler, options) {
|
||||
this.parent(gridHandler, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature,
|
||||
$.pkp.classes.features.OrderItemsFeature);
|
||||
|
||||
|
||||
//
|
||||
// Extended methods from OrderItemsFeature.
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.addFeatureHtml =
|
||||
function($gridElement, options) {
|
||||
|
||||
var $itemSequenceInput, $gridRows, index, limit, $gridRow,
|
||||
$itemSequenceInputClone;
|
||||
|
||||
this.parent('addFeatureHtml', $gridElement, options);
|
||||
|
||||
$itemSequenceInput = this.getSequenceInput_();
|
||||
$gridRows = this.gridHandler.getRows();
|
||||
for (index = 0, limit = $gridRows.length; index < limit; index++) {
|
||||
$gridRow = $($gridRows[index]);
|
||||
$itemSequenceInputClone = $itemSequenceInput.clone();
|
||||
|
||||
$('td.first_column', $gridRow).append($itemSequenceInputClone);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set up the sortable plugin.
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.
|
||||
setupSortablePlugin = function() {
|
||||
this.applySortPlgOnElements(
|
||||
this.getGridHtmlElement(), 'tr.orderable', null);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Extended methods from ToggleableOrderItemsFeature.
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.init =
|
||||
function() {
|
||||
this.parent('init');
|
||||
this.toggleItemsDragMode();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.toggleState =
|
||||
function(isOrdering) {
|
||||
this.parent('toggleState', isOrdering);
|
||||
this.toggleContentHandlers_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.storeRowOrder =
|
||||
function(index, $row) {
|
||||
var seq = index + 1,
|
||||
$orderableInput = $row.find('.itemSequence'),
|
||||
$modifiedInput;
|
||||
|
||||
$orderableInput.attr('value', seq);
|
||||
$modifiedInput = $row.find('.isModified');
|
||||
$modifiedInput.attr('value', 1);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.saveOrderHandler =
|
||||
function() {
|
||||
this.parent('saveOrderHandler');
|
||||
this.toggleState(false);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.
|
||||
updateOrderCallback = function(contextElement, event, ui) {
|
||||
|
||||
var $rows;
|
||||
this.parent('updateOrderCallback');
|
||||
$rows = this.gridHandler.getRows();
|
||||
this.storeOrder($rows);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.
|
||||
clickOrderHandler = function() {
|
||||
var $selects = $('select:visible', this.getGridHtmlElement()),
|
||||
index, limit;
|
||||
if ($selects.length > 0) {
|
||||
for (index = 0, limit = $selects.length; index < limit; index++) {
|
||||
this.gridHandler.saveRow($($selects[index]).parents('.gridRow'));
|
||||
}
|
||||
}
|
||||
|
||||
return /** @type {boolean} */ (this.parent('clickOrderHandler'));
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Implemented Feature template hook methods.
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.addElement =
|
||||
function($newElement) {
|
||||
this.parent('addElement', $newElement);
|
||||
this.formatAndStoreNewRow_($newElement);
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.replaceElement =
|
||||
function($newContent) {
|
||||
this.parent('replaceElement', $newContent);
|
||||
this.formatAndStoreNewRow_($newContent);
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Get the sequence input html element.
|
||||
* @private
|
||||
* @return {jQueryObject} Sequence input.
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.
|
||||
getSequenceInput_ = function() {
|
||||
return $('<input type="hidden" name="newRowId[sequence]" ' +
|
||||
'class="itemSequence" />');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enable/disable row content handlers.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.
|
||||
toggleContentHandlers_ = function() {
|
||||
var $rows = this.gridHandler.getRows(),
|
||||
index, limit, $row;
|
||||
for (index = 0, limit = $rows.length; index < limit; index++) {
|
||||
$row = $($rows[index]);
|
||||
if (this.isOrdering) {
|
||||
$row.find('.gridCellDisplay').unbind('click');
|
||||
} else {
|
||||
this.gridHandler.attachContentHandlers_($row);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Format and store new row.
|
||||
* @private
|
||||
* @param {jQueryObject} $row The new row element.
|
||||
*/
|
||||
$.pkp.classes.features.OrderListbuilderItemsFeature.prototype.
|
||||
formatAndStoreNewRow_ = function($row) {
|
||||
|
||||
var $rows;
|
||||
|
||||
$row.children().after(this.getSequenceInput_());
|
||||
$rows = this.gridHandler.getRows();
|
||||
this.storeOrder($rows);
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* @file js/classes/features/PagingFeature.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 PagingFeature
|
||||
* @ingroup js_classes_features
|
||||
*
|
||||
* @brief Feature that implements paging on grids.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @inheritDoc
|
||||
* @extends $.pkp.classes.features.GeneralPagingFeature
|
||||
*/
|
||||
$.pkp.classes.features.PagingFeature =
|
||||
function(gridHandler, options) {
|
||||
this.parent(gridHandler, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.features.PagingFeature,
|
||||
$.pkp.classes.features.GeneralPagingFeature);
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.PagingFeature.prototype.init =
|
||||
function() {
|
||||
this.configPagingLinks_();
|
||||
this.configItemsPerPageElement_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.PagingFeature.prototype.addFeatureHtml =
|
||||
function($gridElement, options) {
|
||||
$gridElement.append(options.pagingMarkup);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Hooks implementation.
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.PagingFeature.prototype.resequenceRows =
|
||||
function(sequenceMap) {
|
||||
var $rows = this.gridHandler.getRows(),
|
||||
extraRowsNum, index,
|
||||
options = this.getOptions();
|
||||
// Clean any extra rows that might still be visible from old range data.
|
||||
extraRowsNum = $rows.length - options.currentItemsPerPage;
|
||||
if (extraRowsNum > 0) {
|
||||
for (index = 0; index < extraRowsNum; index++) {
|
||||
this.gridHandler.deleteElement($rows.first(), true);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.PagingFeature.prototype.refreshGrid =
|
||||
function(opt_elementId) {
|
||||
var options = this.getOptions(), params, $firstRow, $lastRow;
|
||||
|
||||
params = this.gridHandler.getFetchExtraParams();
|
||||
|
||||
params[options.pageParamName] = options.currentPage;
|
||||
params[options.itemsPerPageParamName] = options.currentItemsPerPage;
|
||||
|
||||
$firstRow = this.gridHandler.getRows().first();
|
||||
$lastRow = this.gridHandler.getRows().last();
|
||||
|
||||
if ($firstRow.length == 0) {
|
||||
params.topLimitRowId = 0;
|
||||
} else {
|
||||
params.topLimitRowId = this.gridHandler.getRowDataId($firstRow);
|
||||
}
|
||||
|
||||
if ($lastRow.length == 0) {
|
||||
params.bottomLimitRowId = 0;
|
||||
} else {
|
||||
params.bottomLimitRowId = this.gridHandler.getRowDataId($lastRow);
|
||||
}
|
||||
|
||||
this.setGridParams(params);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.features.PagingFeature.prototype.replaceElementResponseHandler =
|
||||
function(handledJsonData) {
|
||||
var rowMarkup, rowDataId, pagingInfo, options, $rows, castJsonData;
|
||||
options = this.getOptions();
|
||||
castJsonData = /** @type {{deletedRowReplacement: string,
|
||||
pagingInfo: string,
|
||||
loadLastPage: boolean,
|
||||
newTopRow: string}} */
|
||||
(handledJsonData);
|
||||
|
||||
if (castJsonData.deletedRowReplacement != undefined) {
|
||||
rowMarkup = handledJsonData.deletedRowReplacement;
|
||||
this.gridHandler.insertOrReplaceElement(rowMarkup);
|
||||
}
|
||||
|
||||
if (castJsonData.pagingInfo != undefined) {
|
||||
pagingInfo = handledJsonData.pagingInfo;
|
||||
this.setOptions(pagingInfo);
|
||||
|
||||
this.gridHandler.replacePartialWith(pagingInfo.pagingMarkup,
|
||||
$('div.gridPaging', this.getGridHtmlElement()));
|
||||
this.init();
|
||||
}
|
||||
|
||||
if (castJsonData.loadLastPage) {
|
||||
this.getGridHtmlElement().trigger('dataChanged');
|
||||
}
|
||||
|
||||
if (castJsonData.newTopRow != undefined) {
|
||||
// Check if we need to remove one row from the bottom
|
||||
// to keep the same range info count value.
|
||||
$rows = this.gridHandler.getRows();
|
||||
|
||||
if (options.currentItemsPerPage == $rows.length) {
|
||||
this.gridHandler.deleteElement($rows.last(), true);
|
||||
}
|
||||
|
||||
rowMarkup = handledJsonData.newTopRow;
|
||||
this.gridHandler.insertOrReplaceElement(rowMarkup, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Configure paging links.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.PagingFeature.prototype.configPagingLinks_ =
|
||||
function() {
|
||||
|
||||
var options, $pagingDiv, $links, index, limit, $link, regex, match,
|
||||
clickPagesCallback;
|
||||
|
||||
options = this.getOptions();
|
||||
$pagingDiv = $('div.gridPaging', this.getGridHtmlElement());
|
||||
|
||||
if ($pagingDiv) {
|
||||
clickPagesCallback = this.callbackWrapper(
|
||||
function(sourceElement, event) {
|
||||
regex = new RegExp('[?&]' + options.pageParamName +
|
||||
'(?:=([^&]*))?', 'i');
|
||||
match = regex.exec($(event.target).attr('href'));
|
||||
if (match != null) {
|
||||
options.currentPage = parseInt(match[1], 10);
|
||||
this.getGridHtmlElement().trigger('dataChanged');
|
||||
}
|
||||
|
||||
// Stop event handling.
|
||||
return false;
|
||||
}, this);
|
||||
|
||||
$links = $pagingDiv.find('a').
|
||||
not('.showMoreItems').not('.showLessItems');
|
||||
for (index = 0, limit = $links.length; index < limit; index++) {
|
||||
$link = $($links[index]);
|
||||
$link.click(clickPagesCallback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Configure items per page element.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.features.PagingFeature.prototype.configItemsPerPageElement_ =
|
||||
function() {
|
||||
|
||||
var options, $pagingDiv, index, limit, $select, itemsPerPageValues,
|
||||
changeItemsPerPageCallback;
|
||||
|
||||
options = this.getOptions();
|
||||
$pagingDiv = $('div.gridPaging', this.getGridHtmlElement());
|
||||
|
||||
if ($pagingDiv) {
|
||||
changeItemsPerPageCallback = this.callbackWrapper(
|
||||
function(sourceElement, event) {
|
||||
options.currentItemsPerPage = parseInt($('option',
|
||||
event.target).filter(':selected').attr('value'), 10);
|
||||
// Reset to first page.
|
||||
options.currentPage = 1;
|
||||
|
||||
this.getGridHtmlElement().trigger('dataChanged');
|
||||
|
||||
// Stop event handling.
|
||||
return false;
|
||||
}, this);
|
||||
|
||||
$select = $pagingDiv.find('select.itemsPerPage');
|
||||
itemsPerPageValues = [10, 25, 50, 75, 100];
|
||||
if ($.inArray(options.defaultItemsPerPage,
|
||||
itemsPerPageValues) < 0) {
|
||||
itemsPerPageValues.push(options.defaultItemsPerPage);
|
||||
}
|
||||
|
||||
itemsPerPageValues.sort(function(a, b) { return a - b; });
|
||||
|
||||
if (options.itemsTotal <= itemsPerPageValues[0]) {
|
||||
$('div.gridItemsPerPage', $pagingDiv).hide();
|
||||
} else {
|
||||
limit = itemsPerPageValues.length - 1;
|
||||
for (index = 0; index <= limit; index++) {
|
||||
$select.append($('<option value="' + itemsPerPageValues[index] +
|
||||
'">' + itemsPerPageValues[index] + '</option>'));
|
||||
}
|
||||
$select.val(options.currentItemsPerPage.toString());
|
||||
$select.change(changeItemsPerPageCallback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @file js/classes/linkAction/AjaxRequest.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 AjaxRequest
|
||||
* @ingroup js_classes_linkAction
|
||||
*
|
||||
* @brief AJAX link action request.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.linkAction.LinkActionRequest
|
||||
*
|
||||
* @param {jQueryObject} $linkActionElement The element the link
|
||||
* action was attached to.
|
||||
* @param {{
|
||||
* requestType: string,
|
||||
* data: PlainObject
|
||||
* }} options Configuration of the link action
|
||||
* request.
|
||||
*/
|
||||
$.pkp.classes.linkAction.AjaxRequest =
|
||||
function($linkActionElement, options) {
|
||||
|
||||
this.parent($linkActionElement, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.linkAction.AjaxRequest,
|
||||
$.pkp.classes.linkAction.LinkActionRequest);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.linkAction.AjaxRequest.prototype.activate =
|
||||
function(element, event) {
|
||||
|
||||
var returnValue = /** @type {boolean} */ (
|
||||
this.parent('activate', element, event)),
|
||||
options = this.getOptions(),
|
||||
responseHandler = $.pkp.classes.Helper.curry(
|
||||
this.handleResponse, this);
|
||||
switch (options.requestType) {
|
||||
case 'get':
|
||||
$.getJSON(options.url, options.data, responseHandler);
|
||||
break;
|
||||
|
||||
case 'post':
|
||||
$.post(options.url, options.data, responseHandler, 'json');
|
||||
break;
|
||||
}
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the AJAX response.
|
||||
* @param {Object} jsonData The data returned by the server.
|
||||
*/
|
||||
$.pkp.classes.linkAction.AjaxRequest.prototype.handleResponse =
|
||||
function(jsonData) {
|
||||
|
||||
var $linkActionHandler = this.getLinkActionElement().data('pkp.handler');
|
||||
$linkActionHandler.handleJson(jsonData);
|
||||
this.finish();
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @file js/classes/linkAction/EventAction.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 EventAction
|
||||
* @ingroup js_classes_linkAction
|
||||
*
|
||||
* @brief A simple action request that triggers a Javascript event.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.linkAction.LinkActionRequest
|
||||
*
|
||||
* @param {jQueryObject} $linkActionElement The element the link
|
||||
* action was attached to.
|
||||
* @param {Object} options Configuration of the link action
|
||||
* request.
|
||||
*/
|
||||
$.pkp.classes.linkAction.EventAction =
|
||||
function($linkActionElement, options) {
|
||||
this.parent($linkActionElement, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.linkAction.EventAction,
|
||||
$.pkp.classes.linkAction.LinkActionRequest);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.linkAction.EventAction.prototype.activate =
|
||||
function(element, event) {
|
||||
$(this.options.target).trigger(this.options.event,
|
||||
/** @type {Array} */ (this.options));
|
||||
return /** @type {boolean} */ (this.parent('activate', element, event));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether or not the link action should be debounced.
|
||||
* @return {boolean} Whether or not to debounce the link action.
|
||||
*/
|
||||
$.pkp.classes.linkAction.EventAction.prototype.shouldDebounce =
|
||||
function() {
|
||||
return false;
|
||||
};
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* @defgroup js_classes_linkAction
|
||||
*/
|
||||
/**
|
||||
* @file js/classes/linkAction/LinkActionRequest.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 LinkActionRequest
|
||||
* @ingroup js_classes_linkAction
|
||||
*
|
||||
* @brief Base class for all link action requests.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.classes.linkAction = $.pkp.classes.linkAction || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.ObjectProxy
|
||||
*
|
||||
* @param {jQueryObject} $linkActionElement The element the link
|
||||
* action was attached to.
|
||||
* @param {Object} options Configuration of the link action
|
||||
* request.
|
||||
*/
|
||||
$.pkp.classes.linkAction.LinkActionRequest =
|
||||
function($linkActionElement, options) {
|
||||
|
||||
// Save the reference to the link action element.
|
||||
this.$linkActionElement = $linkActionElement;
|
||||
|
||||
// Save the link action request options.
|
||||
this.options = options;
|
||||
|
||||
// If the link action element is an actual link
|
||||
// and we find a URL in the options then set the
|
||||
// link of the link action for better documentation
|
||||
// and easier debugging in the DOM and for other
|
||||
// JS to easily access the target if required.
|
||||
if ($linkActionElement.is('a') && options.url) {
|
||||
$linkActionElement.attr('href', options.url);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected properties
|
||||
//
|
||||
/**
|
||||
* The element the link action was attached to.
|
||||
* @protected
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.classes.linkAction.LinkActionRequest.prototype.
|
||||
$linkActionElement = null;
|
||||
|
||||
|
||||
/**
|
||||
* The link action request options.
|
||||
* @protected
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.classes.linkAction.LinkActionRequest.prototype.options = null;
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Callback that will be bound to the link action element.
|
||||
* @param {HTMLElement} element The element that triggered the link
|
||||
* action activation event.
|
||||
* @param {Event} event The event that activated the link action.
|
||||
* @return {boolean} Should return false to stop event propagation.
|
||||
*/
|
||||
$.pkp.classes.linkAction.LinkActionRequest.prototype.activate =
|
||||
function(element, event) {
|
||||
|
||||
this.getLinkActionElement().trigger('actionStart');
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be bound to the 'action finished' event of the
|
||||
* link action.
|
||||
*
|
||||
* @return {boolean} Should return false to stop event propagation.
|
||||
*/
|
||||
$.pkp.classes.linkAction.LinkActionRequest.prototype.finish =
|
||||
function() {
|
||||
|
||||
// Execute the finish callback if there is one.
|
||||
if (this.options.finishCallback) {
|
||||
this.options.finishCallback();
|
||||
}
|
||||
|
||||
this.getLinkActionElement().trigger('actionStop');
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the link action request url.
|
||||
* @return {?string} The link action request url.
|
||||
*/
|
||||
$.pkp.classes.linkAction.LinkActionRequest.prototype.getUrl =
|
||||
function() {
|
||||
if (this.options.url) {
|
||||
return this.options.url;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* Retrieve the link action request options.
|
||||
* @return {Object} The link action request options.
|
||||
*/
|
||||
$.pkp.classes.linkAction.LinkActionRequest.prototype.getOptions = function() {
|
||||
return this.options;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the element the link action was attached to.
|
||||
* @return {jQueryObject} The element the link action was attached to.
|
||||
*/
|
||||
$.pkp.classes.linkAction.LinkActionRequest.prototype.
|
||||
getLinkActionElement = function() {
|
||||
|
||||
return this.$linkActionElement;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether or not the link action should be debounced.
|
||||
* @return {boolean} Whether or not to debounce the link action.
|
||||
*/
|
||||
$.pkp.classes.linkAction.LinkActionRequest.prototype.
|
||||
shouldDebounce = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @file js/classes/linkAction/ModalRequest.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 ModalRequest
|
||||
* @ingroup js_classes_linkAction
|
||||
*
|
||||
* @brief Modal link action request.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.linkAction.LinkActionRequest
|
||||
*
|
||||
* @param {jQueryObject} $linkActionElement The element the link
|
||||
* action was attached to.
|
||||
* @param {{
|
||||
* modalHandler: Object
|
||||
* }} options Configuration of the link action
|
||||
* request.
|
||||
*/
|
||||
$.pkp.classes.linkAction.ModalRequest =
|
||||
function($linkActionElement, options) {
|
||||
|
||||
this.parent($linkActionElement, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.linkAction.ModalRequest,
|
||||
$.pkp.classes.linkAction.LinkActionRequest);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* A pointer to the modal HTML element.
|
||||
* @private
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.classes.linkAction.ModalRequest.prototype.$modal_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.linkAction.ModalRequest.prototype.activate =
|
||||
function(element, event) {
|
||||
|
||||
// If there is no title then try to retrieve a title
|
||||
// from the calling element's text.
|
||||
var modalOptions = this.getOptions(),
|
||||
$handledElement = this.getLinkActionElement(),
|
||||
title = $handledElement.text(),
|
||||
uuid,
|
||||
$linkActionElement,
|
||||
linkActionHandler,
|
||||
handlerOptions,
|
||||
modalHandler;
|
||||
|
||||
if (modalOptions.title === undefined) {
|
||||
if (title === '') {
|
||||
// Try to retrieve a title from the link action element's
|
||||
// title attribute.
|
||||
title = $handledElement.attr('title');
|
||||
}
|
||||
modalOptions.title = title;
|
||||
}
|
||||
|
||||
// Generate a unique ID.
|
||||
uuid = $.pkp.classes.Helper.uuid();
|
||||
|
||||
// Instantiate the modal.
|
||||
if (!modalOptions.modalHandler) {
|
||||
throw new Error(['The "modalHandler" setting is required ',
|
||||
'in a ModalRequest'].join(''));
|
||||
}
|
||||
|
||||
// Make sure that all events triggered on the modal will be
|
||||
// forwarded to the link action. This is necessary because the
|
||||
// modal has to be created outside the regular DOM.
|
||||
$linkActionElement = /** @type {jQueryObject} */ (
|
||||
this.getLinkActionElement());
|
||||
linkActionHandler = $.pkp.classes.Handler.getHandler($linkActionElement);
|
||||
handlerOptions = $.extend(true,
|
||||
{eventBridge: linkActionHandler.getStaticId()}, modalOptions);
|
||||
this.$modal_ = $(
|
||||
'<div id="' + uuid + '" ' +
|
||||
'class="pkp_modal pkpModalWrapper" tabindex="-1"></div>')
|
||||
.pkpHandler(modalOptions.modalHandler, handlerOptions);
|
||||
|
||||
// Subscribe to the modal handler's 'removed' event so that
|
||||
// we can clean up.
|
||||
modalHandler = $.pkp.classes.Handler.getHandler(this.$modal_);
|
||||
modalHandler.bind('pkpRemoveHandler',
|
||||
$.pkp.classes.Helper.curry(this.finish, this));
|
||||
|
||||
return /** @type {boolean} */ (this.parent('activate', element, event));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.linkAction.ModalRequest.prototype.finish =
|
||||
function() {
|
||||
|
||||
// A workaround for a bug in IE9-11 (and maybe others), whereby restoring
|
||||
// the focus to the New Review Round tab causes the modal to be opened
|
||||
// again. This hack effects accessibility and should be removed if/when we
|
||||
// move away from jQueryUI tabs.
|
||||
// See: https://github.com/pkp/pkp-lib/issues/2703
|
||||
if (this.$linkActionElement.attr('id')
|
||||
.indexOf('newRoundTabContainer') !== 0) {
|
||||
// Put the focus back on the linkAction which launched the modal
|
||||
this.$linkActionElement.focus();
|
||||
}
|
||||
|
||||
this.$modal_.remove();
|
||||
return /** @type {boolean} */ (this.parent('finish'));
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @file js/classes/linkAction/NullAction.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 NullAction
|
||||
* @ingroup js_classes_linkAction
|
||||
*
|
||||
* @brief A simple action request that doesn't.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.linkAction.LinkActionRequest
|
||||
*
|
||||
* @param {jQueryObject} $linkActionElement The element the link
|
||||
* action was attached to.
|
||||
* @param {Object} options Configuration of the link action
|
||||
* request.
|
||||
*/
|
||||
$.pkp.classes.linkAction.NullAction =
|
||||
function($linkActionElement, options) {
|
||||
|
||||
this.parent($linkActionElement, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.linkAction.NullAction,
|
||||
$.pkp.classes.linkAction.LinkActionRequest);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.linkAction.NullAction.prototype.activate =
|
||||
function(element, event) {
|
||||
|
||||
return /** @type {boolean} */ (this.parent('activate', element, event));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether or not the link action should be debounced.
|
||||
* @return {boolean} Whether or not to debounce the link action.
|
||||
*/
|
||||
$.pkp.classes.linkAction.NullAction.prototype.shouldDebounce =
|
||||
function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @file js/classes/linkAction/OpenWindowRequest.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 OpenWindowRequest
|
||||
* @ingroup js_classes_linkAction
|
||||
*
|
||||
* @brief A simple action request that will follow the given URL.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.linkAction.LinkActionRequest
|
||||
*
|
||||
* @param {jQueryObject} $linkActionElement The element the link
|
||||
* action was attached to.
|
||||
* @param {Object} options Configuration of the link action
|
||||
* request.
|
||||
*/
|
||||
$.pkp.classes.linkAction.OpenWindowRequest =
|
||||
function($linkActionElement, options) {
|
||||
|
||||
this.parent($linkActionElement, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.linkAction.OpenWindowRequest,
|
||||
$.pkp.classes.linkAction.LinkActionRequest);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.linkAction.OpenWindowRequest.prototype.activate =
|
||||
function(element, event) {
|
||||
|
||||
var options = this.getOptions();
|
||||
window.open(options.url);
|
||||
|
||||
return /** @type {boolean} */ (this.parent('activate', element, event));
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @file js/classes/linkAction/PostAndRedirectRequest.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 PostAndRedirectRequest
|
||||
* @ingroup js_classes_linkAction
|
||||
*
|
||||
* @brief An action request that will post data and then redirect, using two
|
||||
* different urls. For both requests, it will post the passed data. If none is
|
||||
* passed, then it will post nothing. You can provide a js event response for
|
||||
* the first post request and it will be handled.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.linkAction.LinkActionRequest
|
||||
*
|
||||
* @param {jQueryObject} $linkActionElement The element the link
|
||||
* action was attached to.
|
||||
* @param {Object} options Configuration of the link action
|
||||
* request.
|
||||
*/
|
||||
$.pkp.classes.linkAction.PostAndRedirectRequest =
|
||||
function($linkActionElement, options) {
|
||||
|
||||
this.parent($linkActionElement, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.linkAction.PostAndRedirectRequest,
|
||||
$.pkp.classes.linkAction.LinkActionRequest);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* Post request response data.
|
||||
* @private
|
||||
* @type {?Object}
|
||||
*/
|
||||
$.pkp.classes.linkAction.PostAndRedirectRequest.prototype.
|
||||
postJsonData_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.linkAction.PostAndRedirectRequest.prototype.activate =
|
||||
function(element, event) {
|
||||
var returner = this.parent('activate', element, event),
|
||||
options = this.getOptions(),
|
||||
// Create a response handler for the first request (post).
|
||||
responseHandler = $.pkp.classes.Helper.curry(
|
||||
this.handleResponse_, this),
|
||||
finishCallback;
|
||||
|
||||
// Post.
|
||||
$.post(/** @type {{postUrl: string}} */ (options).postUrl,
|
||||
responseHandler, 'json');
|
||||
|
||||
return /** @type {boolean} */ (returner);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Callback to be called after a timeout.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.linkAction.PostAndRedirectRequest.prototype.finishCallback_ =
|
||||
function() {
|
||||
var $linkActionElement = this.getLinkActionElement(),
|
||||
// Get the link action handler to handle the json response.
|
||||
linkActionHandler = $.pkp.classes.Handler.getHandler($linkActionElement);
|
||||
|
||||
this.finish();
|
||||
linkActionHandler.handleJson(this.postJsonData_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The post data response handler.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.classes.linkAction.PostAndRedirectRequest.prototype.handleResponse_ =
|
||||
function(jsonData) {
|
||||
var options = this.getOptions(), timer = null, finishCallback = null;
|
||||
|
||||
// Save return data to be handled at the finish callback. If
|
||||
// the redirect action loads another page, then the interface
|
||||
// will be updated anyway and any events that could be triggered
|
||||
// by the post response will be useless, so that's ok that the
|
||||
// finish callback is not called in that case, and that the post
|
||||
// json answer is never handled.
|
||||
// If a new page is not loaded, then we have to wait for the redirect
|
||||
// action to start (probably a file download) and only then handle the post
|
||||
// answer, avoiding triggering events that could replace the current link
|
||||
// action element before the redirect request starts.
|
||||
// In a download action, it avoids the activation of the download link
|
||||
// action before the download triggered by the first click starts.
|
||||
this.postJsonData_ = jsonData;
|
||||
|
||||
// Redirect, making sure there is no ajax request in progress,
|
||||
// to avoid stoping them.
|
||||
timer = setInterval(function() {
|
||||
if ($.active == 0) {
|
||||
clearInterval(timer);
|
||||
window.location = /** @type {{url: string}} */ (options).url;
|
||||
}
|
||||
},100);
|
||||
|
||||
// When it's a download action, try to avoid double execution.
|
||||
// Not ideal, see issue #247.
|
||||
finishCallback = $.pkp.classes.Helper.curry(
|
||||
this.finishCallback_, this);
|
||||
setTimeout(finishCallback, 2000);
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @file js/classes/linkAction/RedirectRequest.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 RedirectRequest
|
||||
* @ingroup js_classes_linkAction
|
||||
*
|
||||
* @brief A simple action request that will follow the given URL.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.linkAction.LinkActionRequest
|
||||
*
|
||||
* @param {jQueryObject} $linkActionElement The element the link
|
||||
* action was attached to.
|
||||
* @param {Object} options Configuration of the link action
|
||||
* request.
|
||||
*/
|
||||
$.pkp.classes.linkAction.RedirectRequest =
|
||||
function($linkActionElement, options) {
|
||||
|
||||
this.parent($linkActionElement, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.classes.linkAction.RedirectRequest,
|
||||
$.pkp.classes.linkAction.LinkActionRequest);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.classes.linkAction.RedirectRequest.prototype.activate =
|
||||
function(element, event) {
|
||||
|
||||
var options = this.getOptions();
|
||||
window.open(options.url, options.name,
|
||||
/** @type {{specs: string}} */ (options).specs);
|
||||
|
||||
return /** @type {boolean} */ (this.parent('activate', element, event));
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* @defgroup js_classes_notification
|
||||
*/
|
||||
/**
|
||||
* @file js/classes/notification/NotificationHelper.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 NotificationHelper
|
||||
* @ingroup js_classes_notification
|
||||
*
|
||||
* @brief Class that perform notification helper actions.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.classes.notification = $.pkp.classes.notification || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
$.pkp.classes.notification.NotificationHelper = function() {
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public static helper methods
|
||||
//
|
||||
/**
|
||||
* Decides which notification will be used: in place or general.
|
||||
* This method finds all notification widgets that are inside of the
|
||||
* handled element of the controller that is handling the current
|
||||
* notify user event (page or modal). We need to make sure that all
|
||||
* notifications will be shown inside the same widget where the notify
|
||||
* user was triggered. Beyond that, we also need to make sure that,
|
||||
* inside some widgets (tabs and accordions, for example) we use the
|
||||
* right notification controller.
|
||||
* To do this, the notification element must follow these rules:
|
||||
*
|
||||
* 1 - the notification element must not have a hidden parent, although
|
||||
* it can be hidden itself.
|
||||
*
|
||||
* 2 - the notification element first widget parent also needs to contain
|
||||
* the element that triggered the notify user event.
|
||||
*
|
||||
* 3 - if the notification element is inside an accordion container, it
|
||||
* will only notify user from events that have the trigger element also
|
||||
* inside the accordion container.
|
||||
*
|
||||
* At the final, if this method find and select more than one
|
||||
* notification element, we get the closest comparing to the element
|
||||
* that triggered the event. If it don't find any visible element, it
|
||||
* bubbles up the event so the site handler can show general
|
||||
* notifications.
|
||||
*
|
||||
* @param {$.pkp.classes.Handler} handler The widget handler that is
|
||||
* handling the notify user event.
|
||||
* @param {HTMLElement|Object} triggerElement The element that triggered the
|
||||
* notify user event.
|
||||
*/
|
||||
$.pkp.classes.notification.NotificationHelper.redirectNotifyUserEvent =
|
||||
function(handler, triggerElement) {
|
||||
|
||||
// Get the selector for a notification element.
|
||||
var $notificationSelector = '.pkp_notification',
|
||||
$handledElement,
|
||||
trivialAlreadyHandled,
|
||||
$pageNotificationElements,
|
||||
possibleNotificationWidgets,
|
||||
i, length,
|
||||
notificationsData,
|
||||
$accordionContainer,
|
||||
$element,
|
||||
$elementParents, parentHandler,
|
||||
j, parentsLength, $elementParentWidget;
|
||||
|
||||
|
||||
// Sometimes the notification handler will bubble up
|
||||
// the notifyUser event when in place notifications are
|
||||
// not visible because of scrolling. When this happens, the
|
||||
// trigger element will not be an element, but the notifications
|
||||
// data that were shown by the in place but no visible. In those
|
||||
// cases, just bubble up again the event until it gets the right
|
||||
// handler (the site handler).
|
||||
if (triggerElement !== undefined && triggerElement.content !== undefined) {
|
||||
notificationsData = triggerElement;
|
||||
handler.getHtmlElement().parent().trigger(
|
||||
'notifyUser', [notificationsData]);
|
||||
return; // no need to do any other event redirection.
|
||||
}
|
||||
|
||||
// Get the html element of the handler.
|
||||
$handledElement = handler.getHtmlElement();
|
||||
|
||||
// If the trigger element is inside a grid, let the site
|
||||
// handler show TRIVIAL notifications.
|
||||
trivialAlreadyHandled = false;
|
||||
if (!(handler instanceof $.pkp.controllers.SiteHandler)) {
|
||||
if (triggerElement !== undefined &&
|
||||
$(triggerElement).parents('.pkp_controllers_grid').length > 0) {
|
||||
$handledElement.parent().trigger('notifyUser');
|
||||
trivialAlreadyHandled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all notification elements inside the handled element.
|
||||
$pageNotificationElements = $($notificationSelector, $handledElement);
|
||||
|
||||
// Create a variable to store all possible notification widgets
|
||||
// that can notify this event.
|
||||
possibleNotificationWidgets = [];
|
||||
|
||||
for (i = 0, length = $pageNotificationElements.length; i < length; i++) {
|
||||
$element = $($pageNotificationElements[i]);
|
||||
|
||||
// If it is inside a hidden parent, get next element.
|
||||
if ($element.parents(':hidden').length > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find its parent widget.
|
||||
// FIXME If we use a class to identify pkp widgets, we can avoid
|
||||
// this code duplication from the get handler method in Handler class,
|
||||
// unnecessary access to the element data and unnecessary loop.
|
||||
$elementParents = $element.parents();
|
||||
for (j = 0, parentsLength = $elementParents.length;
|
||||
j < parentsLength; j++) {
|
||||
parentHandler = $($elementParents[j]).data('pkp.handler');
|
||||
if ((parentHandler instanceof $.pkp.classes.Handler)) {
|
||||
$elementParentWidget = $($elementParents[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the element that triggered the event is inside of
|
||||
// this widget or is the widget...
|
||||
if (triggerElement !== undefined &&
|
||||
($elementParentWidget.has(triggerElement[0]).length ||
|
||||
$elementParentWidget[0] === triggerElement[0])) {
|
||||
|
||||
// If it is inside an accordion container, and this accordion container
|
||||
// doesn't also contain the element that triggered the event, get other
|
||||
// element.
|
||||
if ($element.parents('.ui-accordion:first').length > 0) {
|
||||
$accordionContainer = $element.parents('.ui-accordion:first');
|
||||
|
||||
if (!$accordionContainer.has(triggerElement[0])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// This notification element is able to notify this event.
|
||||
possibleNotificationWidgets.push($element);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we found a notification element.
|
||||
if (possibleNotificationWidgets.length) {
|
||||
|
||||
// Trigger all in place notification widgets found, from the
|
||||
// closest to the element that triggered the action to the top.
|
||||
for (i = possibleNotificationWidgets.length - 1; i > -1; i--) {
|
||||
// Show in place notification to user.
|
||||
possibleNotificationWidgets[i].triggerHandler('notifyUser');
|
||||
}
|
||||
} else {
|
||||
if (!trivialAlreadyHandled) {
|
||||
// Bubble up the notify user event so the site can handle the
|
||||
// general notification.
|
||||
handler.getHtmlElement().parent().trigger('notifyUser');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
Reference in New Issue
Block a user