first commit
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* @file js/controllers/AutocompleteHandler.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 AutocompleteHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief PKP autocomplete handler (extends the functionality of the
|
||||
* jqueryUI autocomplete)
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $autocompleteField the wrapped HTML input element.
|
||||
* @param {Object} options options to be passed
|
||||
* into the jqueryUI autocomplete plugin.
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler = function($autocompleteField, options) {
|
||||
var autocompleteOptions, opt;
|
||||
|
||||
this.parent($autocompleteField, options);
|
||||
|
||||
// Get the URL passed in
|
||||
this.sourceUrl_ = options.sourceUrl;
|
||||
options.sourceUrl = undefined;
|
||||
|
||||
this.disableSync_ = options.disableSync;
|
||||
|
||||
// Create autocomplete settings.
|
||||
opt = {};
|
||||
opt.source = this.callbackWrapper(this.fetchAutocomplete);
|
||||
|
||||
// Set the anchor where to append the suggestions menu
|
||||
opt.appendTo = '#' + $autocompleteField.attr('id');
|
||||
|
||||
autocompleteOptions = $.extend({ },
|
||||
this.self('DEFAULT_PROPERTIES_'), opt, options);
|
||||
|
||||
// Get the text input inside of this Div.
|
||||
this.textInput = $autocompleteField.find(':text');
|
||||
if (!this.disableSync_) {
|
||||
this.textInput.keyup(this.callbackWrapper(this.synchronizeFields_));
|
||||
}
|
||||
// Create the autocomplete field with the jqueryUI plug-in.
|
||||
this.textInput.autocomplete(autocompleteOptions);
|
||||
|
||||
// Get the text input inside of this Div.
|
||||
this.hiddenInput_ = $autocompleteField.find('input:hidden');
|
||||
|
||||
this.bind('autocompleteselect', this.itemSelected);
|
||||
this.bind('autocompletefocus', this.itemFocused);
|
||||
this.textInput.blur(this.callbackWrapper(this.textInputBlurHandler_));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.AutocompleteHandler, $.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private static properties
|
||||
//
|
||||
/**
|
||||
* Whether or not to disable syncing the text and hidden field.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.disableSync_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Default options
|
||||
* @private
|
||||
* @type {Object}
|
||||
* @const
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.DEFAULT_PROPERTIES_ = {
|
||||
// General settings
|
||||
minLength: 2
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The hidden input inside the autocomplete div that holds the value.
|
||||
* @private
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.prototype.hiddenInput_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The URL to be used for autocomplete
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.prototype.sourceUrl_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Protected properties
|
||||
//
|
||||
/**
|
||||
* The text input inside the autocomplete div that holds the label.
|
||||
* @protected
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.prototype.textInput = null;
|
||||
|
||||
|
||||
//
|
||||
// Public Methods
|
||||
//
|
||||
/**
|
||||
* Handle event triggered by selecting an autocomplete item
|
||||
*
|
||||
* @param {HTMLElement} autocompleteElement The element that triggered
|
||||
* the event.
|
||||
* @param {Event} event The triggered event.
|
||||
* @param {Object} ui The tabs ui data.
|
||||
* @return {boolean} Return value to be passed back
|
||||
* to jQuery.
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.prototype.itemSelected =
|
||||
function(autocompleteElement, event, ui) {
|
||||
var $hiddenInput, $textInput;
|
||||
|
||||
$hiddenInput = this.hiddenInput_;
|
||||
$textInput = this.textInput;
|
||||
|
||||
// only update the text field if the item has a value
|
||||
// this allows us to return a 'no items' label with
|
||||
// an empty value.
|
||||
|
||||
if (ui.item.value !== '') {
|
||||
$hiddenInput.val(ui.item.value);
|
||||
$textInput.val(ui.item.label);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle event triggered by moving through the autocomplete suggestions.
|
||||
* The default behaviour is to insert the value in the text field. This
|
||||
* handler inserts the label instead.
|
||||
*
|
||||
* @param {HTMLElement} autocompleteElement The element that triggered
|
||||
* the event.
|
||||
* @param {Event} event The triggered event.
|
||||
* @param {Object} ui The tabs ui data.
|
||||
* @return {boolean} Return value to be passed back
|
||||
* to jQuery.
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.prototype.itemFocused =
|
||||
function(autocompleteElement, event, ui) {
|
||||
var $textInput;
|
||||
|
||||
$textInput = this.textInput;
|
||||
|
||||
if (ui.item.value !== '') {
|
||||
$textInput.val(ui.item.label);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fetch autocomplete results.
|
||||
* @param {HTMLElement} callingElement The calling HTML element.
|
||||
* @param {Object} request The autocomplete search request.
|
||||
* @param {Function} response The response handler function.
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.prototype.fetchAutocomplete =
|
||||
function(callingElement, request, response) {
|
||||
var $textInput;
|
||||
|
||||
$textInput = this.textInput;
|
||||
$textInput.addClass('spinner');
|
||||
$.post(this.getAutocompleteUrl(), { term: request.term }, function(data) {
|
||||
$textInput.removeClass('spinner');
|
||||
response(data.content);
|
||||
}, 'json');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the autocomplete Url
|
||||
* @return {?string} Autocomplete URL.
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.prototype.getAutocompleteUrl =
|
||||
function() {
|
||||
return this.sourceUrl_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the autocomplete url
|
||||
* @param {string} url Autocomplete URL.
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.prototype.setAutocompleteUrl =
|
||||
function(url) {
|
||||
this.sourceUrl_ = url;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods.
|
||||
//
|
||||
/**
|
||||
* Text input element blur handler.
|
||||
* @param {HTMLElement} autocompleteElement The element that triggered
|
||||
* the event.
|
||||
* @param {Event} event The blur event.
|
||||
* @param {Object} ui UI.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.prototype.textInputBlurHandler_ =
|
||||
function(autocompleteElement, event, ui) {
|
||||
// Make sure we clean the text input if user selected no option
|
||||
// from the available ones but leaved some text behind. This
|
||||
// is needed to avoid bad form validation and to make it clear to
|
||||
// users that they need to select an option.
|
||||
if (this.hiddenInput_.val() === '') {
|
||||
this.textInput.val('');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Synchronize the input field and the hidden field.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.AutocompleteHandler.prototype.synchronizeFields_ =
|
||||
function() {
|
||||
this.hiddenInput_.val(String(this.textInput.val()));
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @file js/controllers/EditorialActionsHandler.js
|
||||
*
|
||||
* Copyright (c) 2014-2018 Simon Fraser University
|
||||
* Copyright (c) 2000-2018 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class EditorialActionsHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief A handler for the editorial actions button in the workflow
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $element The outer <div> element
|
||||
* @param {Object} options Handler options.
|
||||
*/
|
||||
$.pkp.controllers.EditorialActionsHandler = function($element, options) {
|
||||
this.parent($element, options);
|
||||
$element.find('.pkp_workflow_change_decision')
|
||||
.click(this.callbackWrapper(this.showActions_));
|
||||
$element.find('[data-decision]')
|
||||
.click(this.callbackWrapper(this.emitRevisionDecision_));
|
||||
$element.find('[data-recommendation]')
|
||||
.click(this.callbackWrapper(this.emitRevisionRecommendation_));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.EditorialActionsHandler, $.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Show the editorial actions
|
||||
* @private
|
||||
* @param {HTMLElement} sourceElement The clicked link.
|
||||
* @param {Event} event The triggered event (click).
|
||||
*/
|
||||
$.pkp.controllers.EditorialActionsHandler.prototype.showActions_ =
|
||||
function(sourceElement, event) {
|
||||
this.getHtmlElement().find('.pkp_workflow_change_decision').hide();
|
||||
this.getHtmlElement().find('.pkp_workflow_decisions_options')
|
||||
.removeClass('pkp_workflow_decisions_options_hidden');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Emit an event when a request revisions decision is initiated
|
||||
* @private
|
||||
* @param {HTMLElement} sourceElement The clicked link.
|
||||
* @param {Event} event The triggered event (click).
|
||||
*/
|
||||
$.pkp.controllers.EditorialActionsHandler.prototype.emitRevisionDecision_ =
|
||||
function(sourceElement, event) {
|
||||
var $el = $(sourceElement);
|
||||
pkp.eventBus.$emit('decision:revisions', $el.data('reviewRoundId'));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Emit an event when a request revisions recommendation is initiated
|
||||
* @private
|
||||
* @param {HTMLElement} sourceElement The clicked link.
|
||||
* @param {Event} event The triggered event (click).
|
||||
*/
|
||||
$.pkp.controllers.EditorialActionsHandler.prototype.
|
||||
emitRevisionRecommendation_ = function(sourceElement, event) {
|
||||
var $el = $(sourceElement);
|
||||
pkp.eventBus.$emit('recommendation:revisions', $el.data('reviewRoundId'));
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @file js/controllers/ExtrasOnDemandHandler.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 ExtrasOnDemandHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief A basic handler for extras on demand UI pattern.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $widgetWrapper An HTML element that contains the
|
||||
* widget.
|
||||
* @param {Object} options Handler options.
|
||||
*/
|
||||
$.pkp.controllers.ExtrasOnDemandHandler = function($widgetWrapper, options) {
|
||||
this.parent($widgetWrapper, options);
|
||||
|
||||
$('.toggleExtras', $widgetWrapper).click(
|
||||
this.callbackWrapper(this.toggleExtras));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.ExtrasOnDemandHandler, $.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Event handler that is called when toggle extras div is clicked.
|
||||
*
|
||||
* @param {HTMLElement} toggleExtras The div that is clicked to toggle extras.
|
||||
* @param {Event} event The triggering event.
|
||||
*/
|
||||
$.pkp.controllers.ExtrasOnDemandHandler.prototype.toggleExtras =
|
||||
function(toggleExtras, event) {
|
||||
var $widgetWrapper = this.getHtmlElement(),
|
||||
$scrollable;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
$widgetWrapper.toggleClass('active');
|
||||
|
||||
if ($widgetWrapper.hasClass('active')) {
|
||||
// Identify if there is a scrollable parent.
|
||||
$scrollable = $widgetWrapper.closest('.scrollable');
|
||||
if ($scrollable.length > 0) {
|
||||
// Scroll the parent so that all extra content in
|
||||
// extras container is visible.
|
||||
this.scrollToMakeVisible_($widgetWrapper, $scrollable);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Scroll a scrollable element to make the
|
||||
* given content element visible. The content element
|
||||
* must be a descendant of a scrollable
|
||||
* element (needs to have class "scrollable").
|
||||
*
|
||||
* NB: This method depends on the position() method
|
||||
* to refer to the same parent element for both the
|
||||
* content element and the scrollable element.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {jQueryObject} $widgetWrapper The element to be made visible.
|
||||
* @param {Array|jQueryObject} $scrollable The parent scrollable element.
|
||||
*/
|
||||
$.pkp.controllers.ExtrasOnDemandHandler.prototype.scrollToMakeVisible_ =
|
||||
function($widgetWrapper, $scrollable) {
|
||||
var extrasWidgetTop, scrollingWidgetTop, currentScrollingTop,
|
||||
hiddenPixels, newScrollingTop;
|
||||
|
||||
extrasWidgetTop = $widgetWrapper.position().top;
|
||||
scrollingWidgetTop = $scrollable.position().top;
|
||||
currentScrollingTop = parseInt($scrollable.scrollTop(), 10);
|
||||
|
||||
// Do we have to scroll down or scroll up?
|
||||
if (extrasWidgetTop > scrollingWidgetTop) {
|
||||
// Consider scrolling down...
|
||||
|
||||
// Calculate the number of hidden pixels of the child
|
||||
// element within the scrollable element.
|
||||
hiddenPixels = Math.ceil(extrasWidgetTop +
|
||||
$widgetWrapper.height() - $scrollable.height());
|
||||
|
||||
// Scroll down if parts or all of this widget are hidden.
|
||||
if (hiddenPixels > 0) {
|
||||
$scrollable.scrollTop(currentScrollingTop + hiddenPixels);
|
||||
}
|
||||
} else {
|
||||
// Scroll up...
|
||||
|
||||
// Calculate the new scrolling top.
|
||||
newScrollingTop = Math.max(Math.floor(
|
||||
currentScrollingTop + extrasWidgetTop - scrollingWidgetTop), 0);
|
||||
|
||||
// Set the new scrolling top.
|
||||
$scrollable.scrollTop(newScrollingTop);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,368 @@
|
||||
/**
|
||||
* @file js/controllers/HelpPanelHandler.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 HelpPanelHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief A handler for the fly-out contextual help panel.
|
||||
*
|
||||
* Listens: pkp.HelpPanel.Open
|
||||
* Listens: pkp.HelpPanel.Close
|
||||
* Emits: pkp.HelpPanel.Open
|
||||
* Emits: pkp.HelpPanel.Close
|
||||
*
|
||||
* This handler expects to be be attached to an element which matches the
|
||||
* following base markup. There should only be one help panel on any page.
|
||||
*
|
||||
* <div id="pkpHelpPanel" tabindex="-1">
|
||||
* <div>
|
||||
* <!-- This handler should only ever interact with the .content div. -->
|
||||
* <div class="content"></div>
|
||||
* <button class="pkpCloseHelpPanel"></button>
|
||||
* </div>
|
||||
* </div>
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $element The outer <div> element
|
||||
* @param {Object} options Handler options.
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler = function($element, options) {
|
||||
|
||||
this.parent($element, {});
|
||||
|
||||
// Let help link events bubble up to the body tag. This way we only
|
||||
// register one listener once per page load. We won't need to worry
|
||||
// about content getting swapped out on the page either, as any new
|
||||
// help links loaded will bubble up to the body tag.
|
||||
$('body').click(function(e) {
|
||||
var $self = $(e.target),
|
||||
options;
|
||||
if (!$self.hasClass('requestHelpPanel') &&
|
||||
!$self.parents('.requestHelpPanel').length) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
options = $.extend({}, $self.data(), {caller: $self});
|
||||
$element.trigger('pkp.HelpPanel.Open', options);
|
||||
});
|
||||
|
||||
// Register click handler on close button
|
||||
$element.find('.pkpCloseHelpPanel').click(function(e) {
|
||||
e.preventDefault();
|
||||
$element.trigger('pkp.HelpPanel.Close');
|
||||
});
|
||||
|
||||
// Register click handler on home button
|
||||
$element.find('.pkpHomeHelpPanel').click(function(e) {
|
||||
e.preventDefault();
|
||||
$element.trigger('pkp.HelpPanel.Home');
|
||||
});
|
||||
|
||||
// Handlers for "next" and "previous" buttons
|
||||
$element.find('.pkpPreviousHelpPanel')
|
||||
.click(this.callbackWrapper(function(e) {
|
||||
this.loadHelpContent_(this.previousTopic_, this.helpLocale_);
|
||||
}));
|
||||
$element.find('.pkpNextHelpPanel').click(this.callbackWrapper(function(e) {
|
||||
this.loadHelpContent_(this.nextTopic_, this.helpLocale_);
|
||||
}));
|
||||
|
||||
// Register listeners
|
||||
$element.on('pkp.HelpPanel.Open', this.callbackWrapper(this.openPanel_))
|
||||
.on('pkp.HelpPanel.Close', this.callbackWrapper(this.closePanel_))
|
||||
.on('pkp.HelpPanel.Home', this.callbackWrapper(this.homePanel_));
|
||||
|
||||
this.helpUrl_ = options.helpUrl;
|
||||
this.helpLocale_ = options.helpLocale;
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.HelpPanelHandler, $.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* Calling element. Focus will be returned here when help panel is closed
|
||||
* @private
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.caller_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Help subsystem's URL. Used to fetch help content for presentation.
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.helpUrl_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Help subsystem's locale. Default: `en`
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.helpLocale_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Current help topic
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.currentTopic_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Previous help topic
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.previousTopic_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Next help topic
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.nextTopic_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Requested help section
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.requestedSection_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Open the helper panel
|
||||
* @private
|
||||
* @param {HTMLElement} context The context in which this function was called
|
||||
* @param {Event} event The event triggered on this handler
|
||||
* @param {{
|
||||
* caller: jQueryObject,
|
||||
* topic: string,
|
||||
* section: string
|
||||
* }} options The options with which to open this handler
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.openPanel_ =
|
||||
function(context, event, options) {
|
||||
|
||||
var $element = this.getHtmlElement();
|
||||
|
||||
// Save the calling element
|
||||
if (typeof options.caller !== 'undefined') {
|
||||
this.caller_ = options.caller;
|
||||
}
|
||||
|
||||
// Show the help panel
|
||||
$element.addClass('is_visible');
|
||||
$('body').addClass('help_panel_is_visible'); // manage scrollbars
|
||||
|
||||
// Listen to close interaction events
|
||||
$element.on('click.pkp.HelpPanel keyup.pkp.HelpPanel',
|
||||
this.callbackWrapper(this.handleWrapperEvents));
|
||||
|
||||
// Listen to clicks on links
|
||||
$element.on('click.pkp.HelpPanelContentLink', '.content a',
|
||||
this.callbackWrapper(this.handleContentLinks_));
|
||||
|
||||
// Load the appropriate help content
|
||||
this.loadHelpContent_(options.topic, this.helpLocale_);
|
||||
this.requestedSection_ = options.section || '';
|
||||
|
||||
// Set focus inside the help panel (delay is required so that element is
|
||||
// visible when jQuery tries to focus on it)
|
||||
// @todo This should only happen once content is loaded in
|
||||
setTimeout(function() {
|
||||
$element.focus();
|
||||
}, 300);
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Load help content in the panel.
|
||||
* @param {string?} topic The help context.
|
||||
* @param {string?} locale The language locale to load the help topic.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.loadHelpContent_ =
|
||||
function(topic, locale) {
|
||||
locale = locale || this.helpLocale_;
|
||||
this.currentTopic_ = topic || '';
|
||||
var url = this.helpUrl_ + '/index/' + locale + '/';
|
||||
|
||||
this.getHtmlElement().addClass('is_loading');
|
||||
|
||||
// Don't escape slashes
|
||||
url += encodeURIComponent(this.currentTopic_).replace(/%2F/g, '/');
|
||||
|
||||
$.get(url, null, this.callbackWrapper(this.updateContentHandler_),
|
||||
'json');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A callback to update the tabs on the interface.
|
||||
* @private
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.
|
||||
updateContentHandler_ = function(ajaxContext, jsonData) {
|
||||
var workingJsonData = this.handleJson(jsonData),
|
||||
responseObject = workingJsonData.content,
|
||||
helpPanelHandler = this,
|
||||
$element = this.getHtmlElement(),
|
||||
hashIndex = this.currentTopic_.indexOf('#'),
|
||||
$targetHash,
|
||||
panel = $element.find('.panel');
|
||||
|
||||
this.previousTopic_ = responseObject.previous;
|
||||
this.nextTopic_ = responseObject.next;
|
||||
|
||||
// Place the new content into the DOM
|
||||
$element.find('.content').replaceWith(
|
||||
'<div class="content">' + responseObject.content + '</div>');
|
||||
|
||||
// If a section was specified, scroll to the named section.
|
||||
panel.scrollTop(0);
|
||||
if (this.requestedSection_) {
|
||||
$targetHash = $element.find(
|
||||
'a[name="' + this.requestedSection_ + '"]');
|
||||
if ($targetHash.length) {
|
||||
panel.scrollTop($targetHash.offset().top - 50);
|
||||
}
|
||||
}
|
||||
|
||||
this.getHtmlElement().removeClass('is_loading');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A callback to handle clicks on links in the help content
|
||||
*
|
||||
* This function will allow external links to open in a new window but take
|
||||
* control of relative links and try to open the appropriate help topic.
|
||||
*
|
||||
* @private
|
||||
* @param {HTMLElement} target The target element the event was triggered on
|
||||
* @param {Event} event The event triggered on this handler
|
||||
* @return {boolean} Event handling status.
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.
|
||||
handleContentLinks_ = function(target, event) {
|
||||
|
||||
var url = $(target).attr('href'),
|
||||
urlParts,
|
||||
topic,
|
||||
locale,
|
||||
topicParts;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// External links aren't yet supported in the help docs
|
||||
// See: https://github.com/pkp/pkp-lib/issues/1032#issuecomment-199342940
|
||||
if (url.substring(0, 4) == 'http') {
|
||||
window.open(url);
|
||||
return false;
|
||||
}
|
||||
|
||||
urlParts = url.split('/');
|
||||
topic = urlParts.slice(1).join('/');
|
||||
locale = urlParts[0];
|
||||
if (topic.indexOf('#') > -1) {
|
||||
topicParts = topic.split('#');
|
||||
topic = topicParts[0];
|
||||
this.requestedSection_ = topicParts[1];
|
||||
}
|
||||
this.loadHelpContent_(topic, locale);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Close the helper panel
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.closePanel_ = function() {
|
||||
|
||||
// Get a reference to this handler's element as a jQuery object
|
||||
var $element = this.getHtmlElement();
|
||||
|
||||
// Show the help panel
|
||||
$element.removeClass('is_visible');
|
||||
$('body').removeClass('help_panel_is_visible'); // manage scrollbars
|
||||
|
||||
// Clear the help content
|
||||
$element.find('.content').empty();
|
||||
|
||||
// Set focus back to the calling element
|
||||
if (this.caller_ !== null) {
|
||||
this.caller_.focus();
|
||||
}
|
||||
|
||||
// Unbind wrapper events from element and reset vars
|
||||
$element.off('click.pkp.HelpPanel keyup.pkp.HelpPanel');
|
||||
$element.off('click.pkp.HelpPanelContentLink', '.content a');
|
||||
this.caller_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Home the helper panel
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.homePanel_ = function() {
|
||||
this.loadHelpContent_(null, this.helpLocale_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Process events that reach the wrapper element.
|
||||
*
|
||||
* @param {HTMLElement} context The context in which this function was called
|
||||
* @param {Event} event The event triggered on this handler
|
||||
*/
|
||||
$.pkp.controllers.HelpPanelHandler.prototype.handleWrapperEvents =
|
||||
function(context, event) {
|
||||
|
||||
// Get a reference to this handler's element as a jQuery object
|
||||
var $element = this.getHtmlElement();
|
||||
|
||||
// Close click events directly on modal (background screen)
|
||||
if (event.type == 'click' && $element.is($(event.target))) {
|
||||
$element.trigger('pkp.HelpPanel.Close');
|
||||
return;
|
||||
}
|
||||
|
||||
// Close for ESC keypresses (27)
|
||||
if (event.type == 'keyup' && event.which == 27) {
|
||||
$element.trigger('pkp.HelpPanel.Close');
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* @file js/controllers/MenuHandler.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 MenuHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief A basic handler for a hierarchical list of navigation items.
|
||||
*
|
||||
* Attach this handler to a <ul> with nested <li> and <ul> elements.
|
||||
* <li> elements with submenu should have aria properties:
|
||||
* <li aria-haspopup="true" aria-expanded="false">
|
||||
*
|
||||
* <li> elements wiith a submenu that opens below the parent item should add a
|
||||
* submenuOpensBelow class to support scrolling in long lists when necessary.
|
||||
* <li class="submenuOpensBelow"
|
||||
* aria-haspopup="true" aria-expanded="false"></li>
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $menu The outer <ul> element
|
||||
* @param {Object} options Handler options.
|
||||
*/
|
||||
$.pkp.controllers.MenuHandler = function($menu, options) {
|
||||
|
||||
this.parent($menu, options);
|
||||
|
||||
// Fix dropdown menus that may go off-screen and recalculate whenever
|
||||
// the browser window is resized
|
||||
// 1ms delay allows dom insertion to complete
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
self.callbackWrapper( /** @type {Function} */ (
|
||||
self.setDropdownAlignment()));
|
||||
}, 1);
|
||||
$(window).resize(this.callbackWrapper(this.onResize));
|
||||
|
||||
// Show/hide dropdown menus using WCAG-compliant aria attributes
|
||||
this.getHtmlElement().on('focus mouseenter', '[aria-haspopup="true"]',
|
||||
function(e) {
|
||||
$(e.currentTarget).attr('aria-expanded', 'true');
|
||||
});
|
||||
this.getHtmlElement().on('blur mouseleave', '[aria-haspopup="true"]',
|
||||
function(e) {
|
||||
$(e.currentTarget).attr('aria-expanded', 'false');
|
||||
});
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.MenuHandler, $.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Check if submenus are straying off-screen and adjust as needed
|
||||
*/
|
||||
$.pkp.controllers.MenuHandler.prototype.setDropdownAlignment = function() {
|
||||
var $this = $(this),
|
||||
width = Math.max(
|
||||
document.documentElement.clientWidth, window.innerWidth || 0),
|
||||
height = Math.max(
|
||||
document.documentElement.clientHeight, window.innerHeight || 0);
|
||||
|
||||
this.getHtmlElement().find('[aria-haspopup="true"]').each(function() {
|
||||
var $parent = $(this),
|
||||
$submenus = $parent.children('ul'),
|
||||
right, pos_top, min_top, pos_btm, offset_top, new_top;
|
||||
|
||||
// Width
|
||||
right = $parent.offset().left + $submenus.outerWidth();
|
||||
if (right > width) {
|
||||
$parent.addClass('align_right');
|
||||
} else {
|
||||
$parent.removeClass('align_right');
|
||||
}
|
||||
|
||||
// Height
|
||||
$submenus.attr('style', ''); // reset
|
||||
pos_top = $parent.offset().top;
|
||||
min_top = 0;
|
||||
if ($parent.hasClass('submenuOpensBelow')) {
|
||||
min_top = pos_top + $parent.outerHeight();
|
||||
}
|
||||
pos_btm = pos_top + $submenus.outerHeight();
|
||||
if (pos_btm > height) {
|
||||
offset_top = pos_btm - height;
|
||||
new_top = pos_top - offset_top;
|
||||
if (new_top < min_top) {
|
||||
if (min_top > 0) {
|
||||
offset_top = min_top;
|
||||
} else {
|
||||
offset_top = -Math.abs(offset_top) - new_top;
|
||||
}
|
||||
$submenus.css('overflow-y', 'scroll');
|
||||
$submenus.css('bottom',
|
||||
-Math.abs(height - pos_top - $parent.outerHeight()) + 'px');
|
||||
}
|
||||
$submenus.css('top', offset_top + 'px');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Throttle the dropdown alignment check during resize events. During
|
||||
* browser resizing this will fire off every single frame, causing lag
|
||||
* during the resize. So this just throttles the actual alignment check
|
||||
* function by only firing when resizing has stopped.
|
||||
*/
|
||||
$.pkp.controllers.MenuHandler.prototype.onResize = function() {
|
||||
clearTimeout(this.resize_check);
|
||||
this.resize_check = setTimeout(
|
||||
this.callbackWrapper(this.setDropdownAlignment), 1000);
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,312 @@
|
||||
/**
|
||||
* @file js/controllers/NotificationHandler.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 NotificationHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief Handle in place notifications.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $notificationElement The html notification element.
|
||||
* @param {Object} options Notification options.
|
||||
*/
|
||||
$.pkp.controllers.NotificationHandler =
|
||||
function($notificationElement, options) {
|
||||
this.parent($notificationElement, options);
|
||||
|
||||
this.options_ = options;
|
||||
|
||||
this.bind('notifyUser', this.fetchNotificationHandler_);
|
||||
|
||||
// Hide the notification element.
|
||||
this.getHtmlElement().hide();
|
||||
|
||||
// Trigger the notify user event without bubbling up.
|
||||
this.getHtmlElement().triggerHandler('notifyUser');
|
||||
|
||||
if (this.options_.refreshOn) {
|
||||
this.bindGlobal(this.options_.refreshOn, this.fetchNotificationHandler_);
|
||||
}
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.NotificationHandler,
|
||||
$.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties.
|
||||
//
|
||||
/**
|
||||
* The options to fetch a notification.
|
||||
* @private
|
||||
* @type {Object?}
|
||||
*/
|
||||
$.pkp.controllers.NotificationHandler.prototype.options_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Time to hide trivial inplace notifications.
|
||||
* @private
|
||||
* @type {number?}
|
||||
*/
|
||||
$.pkp.controllers.NotificationHandler.prototype.trivialTimer_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Private methods.
|
||||
//
|
||||
/**
|
||||
* Handler to fetch the notification data.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.NotificationHandler.prototype.fetchNotificationHandler_ =
|
||||
function() {
|
||||
|
||||
var requestOptions = {};
|
||||
requestOptions.requestOptions = this.options_.requestOptions;
|
||||
|
||||
// Avoid race conditions with other notification controllers.
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: this.options_.fetchNotificationUrl,
|
||||
data: requestOptions,
|
||||
success: this.callbackWrapper(this.showNotificationResponseHandler_),
|
||||
dataType: 'json',
|
||||
async: false
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback to show the notification data in place.
|
||||
*
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.NotificationHandler.prototype.
|
||||
showNotificationResponseHandler_ = function(ajaxContext, jsonData) {
|
||||
// Delete any existing trivial notification timer.
|
||||
clearTimeout(this.trivialTimer_);
|
||||
|
||||
var $notificationElement = this.getHtmlElement(),
|
||||
workingJsonData = this.handleJson(jsonData),
|
||||
inPlaceNotificationsData, newNotificationsData,
|
||||
trivialNotificationsId, notificationId, i;
|
||||
|
||||
if (workingJsonData === false) {
|
||||
return;
|
||||
}
|
||||
if (workingJsonData.content.inPlace) {
|
||||
inPlaceNotificationsData = this.concatenateNotifications_(
|
||||
workingJsonData.content.inPlace);
|
||||
newNotificationsData = this.removeAlreadyShownNotifications_(
|
||||
/** @type {Object} */ (workingJsonData));
|
||||
|
||||
this.unbindPartial($notificationElement);
|
||||
$notificationElement.html(inPlaceNotificationsData);
|
||||
|
||||
// We need to show the notification element now so the
|
||||
// visibility test can be executed.
|
||||
$notificationElement.show();
|
||||
|
||||
trivialNotificationsId = this.getTrivialNotifications_(
|
||||
workingJsonData.content.inPlace);
|
||||
|
||||
if (!(this.visibleWithoutScrolling_()) && newNotificationsData) {
|
||||
// In place notification is not visible. Let parent widgets
|
||||
// show the notification data.
|
||||
$notificationElement.parent().
|
||||
trigger('notifyUser', [newNotificationsData]);
|
||||
|
||||
// Remove in place trivial notifications.
|
||||
for (i in trivialNotificationsId) {
|
||||
notificationId = trivialNotificationsId[i];
|
||||
$notificationElement =
|
||||
$('#pkp_notification_' + notificationId,
|
||||
this.getHtmlElement());
|
||||
this.unbindPartial($notificationElement);
|
||||
$notificationElement.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// After visibility test and possible trivial notifications
|
||||
// removal, we need to test if the in place notification widget
|
||||
// shows any notification. If not, hide it.
|
||||
if ($notificationElement.children().length === 0) {
|
||||
$notificationElement.hide();
|
||||
} else {
|
||||
// Add a timer to any trivial notifications
|
||||
// inside this widget.
|
||||
this.addTimerToNotifications(trivialNotificationsId);
|
||||
}
|
||||
|
||||
} else {
|
||||
this.unbindPartial(this.getHtmlElement());
|
||||
this.getHtmlElement().empty();
|
||||
this.getHtmlElement().hide();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if the notification is inside the window are visible.
|
||||
* @return {boolean} True iff the notification is visible.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.NotificationHandler.prototype.
|
||||
visibleWithoutScrolling_ = function() {
|
||||
var $notificationElement = this.getHtmlElement(),
|
||||
notificationTop = $notificationElement.offset().top,
|
||||
notificationMiddle = notificationTop + this.getHtmlElement().height() / 2,
|
||||
windowScrollTop = $(window).scrollTop(),
|
||||
windowBottom = windowScrollTop + $(window).height(),
|
||||
// Consider modals and its own scroll functionality.
|
||||
$parentModalContentWrapper = $notificationElement
|
||||
.parents('.ui-dialog-content'),
|
||||
modalContentTop, modalContentBottom;
|
||||
|
||||
if ($parentModalContentWrapper.length > 0) {
|
||||
modalContentTop = $parentModalContentWrapper.offset().top;
|
||||
modalContentBottom = modalContentTop +
|
||||
$parentModalContentWrapper.height();
|
||||
if (notificationMiddle < modalContentTop ||
|
||||
notificationMiddle > modalContentBottom) {
|
||||
// The element is outside of the modal content wrapper area.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the element is inside of the visible window area.
|
||||
if (notificationMiddle < windowScrollTop ||
|
||||
notificationMiddle > windowBottom) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove notification data from object that is already on page.
|
||||
* @param {Object} notificationsData The notification data to perform
|
||||
* the deletion on.
|
||||
* @return {Object|boolean} Notification data after deletion.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.NotificationHandler.prototype.
|
||||
removeAlreadyShownNotifications_ = function(notificationsData) {
|
||||
|
||||
var workingNotificationsData = /** @type {{ content: {
|
||||
inPlace: Array, general: Array} }} */
|
||||
(notificationsData),
|
||||
emptyObject = true,
|
||||
levelId, notificationId, element;
|
||||
for (levelId in workingNotificationsData.content.inPlace) {
|
||||
for (notificationId in
|
||||
workingNotificationsData.content.inPlace[levelId]) {
|
||||
element = $('#pkp_notification_' + notificationId);
|
||||
if (element.length > 0) {
|
||||
delete workingNotificationsData.content.
|
||||
inPlace[levelId][notificationId];
|
||||
delete workingNotificationsData.content.
|
||||
general[levelId][notificationId];
|
||||
} else {
|
||||
emptyObject = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (emptyObject) {
|
||||
return false;
|
||||
} else {
|
||||
return workingNotificationsData;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Concatenate notification data in a string variable.
|
||||
* @param {Object} notificationsData The notification data to assemble
|
||||
* the concatenation from.
|
||||
* @return {string} The concatenated notification data.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.NotificationHandler.prototype.
|
||||
concatenateNotifications_ = function(notificationsData) {
|
||||
var returner = '', levelId, notificationId;
|
||||
for (levelId in notificationsData) {
|
||||
for (notificationId in notificationsData[levelId]) {
|
||||
// Concatenate all notifications.
|
||||
returner += notificationsData[levelId][notificationId];
|
||||
}
|
||||
}
|
||||
|
||||
return returner;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get all trivial notifications id inside the passed notifications.
|
||||
* @param {Object} notificationsData The data returned from the fetch
|
||||
* notification request.
|
||||
* @return {Array} The trivial notifications id.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.NotificationHandler.prototype.
|
||||
getTrivialNotifications_ = function(notificationsData) {
|
||||
|
||||
var trivialNotificationsId = [], levelId, notificationId;
|
||||
for (levelId in notificationsData) {
|
||||
if (levelId == 1) { // Trivial level.
|
||||
for (notificationId in notificationsData[levelId]) {
|
||||
trivialNotificationsId.push(notificationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trivialNotificationsId;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a timer for passed notifications to hide them after a time.
|
||||
* @param {Object} notificationsId Array with the notifications id
|
||||
* that will receive the timer.
|
||||
*/
|
||||
$.pkp.controllers.NotificationHandler.prototype.
|
||||
addTimerToNotifications = function(notificationsId) {
|
||||
|
||||
var self, removeNotification;
|
||||
|
||||
self = this; // remember "this" reference for later use in callback
|
||||
|
||||
removeNotification = function() {
|
||||
var $notification = $(this);
|
||||
self.unbindPartial($notification);
|
||||
$notification.remove();
|
||||
};
|
||||
|
||||
if (notificationsId.length) {
|
||||
this.trivialTimer_ = setTimeout(function() {
|
||||
var notificationId, $notification;
|
||||
for (notificationId in notificationsId) {
|
||||
$notification = $('#pkp_notification_' +
|
||||
notificationsId[notificationId]);
|
||||
$notification.fadeOut(400, removeNotification);
|
||||
}
|
||||
}, 6000);
|
||||
}
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @file js/controllers/RevealMoreHandler.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 RevealMoreHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief A basic handler for the reveal more UI pattern
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $widgetWrapper An HTML element that contains the
|
||||
* widget.
|
||||
* @param {Object} options Handler options.
|
||||
*/
|
||||
$.pkp.controllers.RevealMoreHandler = function($widgetWrapper, options) {
|
||||
this.parent($widgetWrapper, options);
|
||||
|
||||
if ($widgetWrapper.outerHeight() > options.height) {
|
||||
$widgetWrapper.addClass('isHidden').
|
||||
css('max-height', options.height + 'px');
|
||||
$('.revealMoreButton', $widgetWrapper).click(
|
||||
this.callbackWrapper(this.revealMore));
|
||||
}
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.RevealMoreHandler, $.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Event handler that is called when the button to reveal more is clicked
|
||||
*
|
||||
* @param {HTMLElement} revealMoreButton The button that is clicked to
|
||||
* toggle extras.
|
||||
* @param {Event} event The triggering event.
|
||||
*/
|
||||
$.pkp.controllers.RevealMoreHandler.prototype.revealMore =
|
||||
function(revealMoreButton, event) {
|
||||
this.getHtmlElement().removeClass('isHidden').removeAttr('style');
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,752 @@
|
||||
/**
|
||||
* @defgroup js_controllers
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/SiteHandler.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 SiteHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief Handle the site widget.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $widgetWrapper An HTML element that this handle will
|
||||
* be attached to.
|
||||
* @param {{
|
||||
* fetchNotificationUrl: string,
|
||||
* requestOptions: Object
|
||||
* }} options Handler options.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler = function($widgetWrapper, options) {
|
||||
this.parent($widgetWrapper, options);
|
||||
|
||||
this.options_ = options;
|
||||
this.unsavedFormElements_ = [];
|
||||
|
||||
$('.go').button();
|
||||
|
||||
this.bind('redirectRequested', this.redirectToUrl);
|
||||
this.bind('notifyUser', this.fetchNotificationHandler_);
|
||||
this.bind('reloadTab', this.reloadTabHandler_);
|
||||
this.bind('callWhenClickOutside', this.callWhenClickOutsideHandler_);
|
||||
this.bind('mousedown', this.mouseDownHandler_);
|
||||
|
||||
// Bind the pageUnloadHandler_ method to the DOM so it is
|
||||
// called.
|
||||
$(window).bind('beforeunload', this.pageUnloadHandler_);
|
||||
|
||||
// Avoid IE8 caching ajax results. If it does, widgets like
|
||||
// grids will not refresh correctly.
|
||||
$.ajaxSetup({cache: false});
|
||||
|
||||
// Check if we have notifications to show.
|
||||
if (options.hasSystemNotifications) {
|
||||
this.trigger('notifyUser');
|
||||
}
|
||||
|
||||
// bind event handlers for form status change events.
|
||||
this.bind('formChanged', this.callbackWrapper(
|
||||
this.registerUnsavedFormElement_));
|
||||
this.bind('unregisterChangedForm', this.callbackWrapper(
|
||||
this.unregisterUnsavedFormElement_));
|
||||
this.bind('unregisterAllForms', this.callbackWrapper(
|
||||
this.unregisterAllFormElements_));
|
||||
|
||||
// React to a modal events
|
||||
this.bind('pkpModalOpen', this.callbackWrapper(this.openModal_));
|
||||
this.bind('pkpModalClose', this.callbackWrapper(this.closeModal_));
|
||||
|
||||
this.bind('pkpObserveScrolling', this.callbackWrapper(
|
||||
this.registerScrollingObserver_));
|
||||
this.bind('pkpRemoveScrollingObserver', this.callbackWrapper(
|
||||
this.unregisterScrollingObserver_));
|
||||
|
||||
this.outsideClickChecks_ = {};
|
||||
|
||||
this.initializeTinyMCE();
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.SiteHandler, $.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* Help context.
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.helpContext_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Site handler options.
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.options_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Object with data to be used when checking if user
|
||||
* clicked outside a site element. See callWhenClickOutsideHandler_()
|
||||
* to check the expected check options.
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.outsideClickChecks_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A state variable to store the form elements that have unsaved data.
|
||||
* @private
|
||||
* @type {Array}
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.unsavedFormElements_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Public static methods.
|
||||
//
|
||||
/**
|
||||
* Initialize the tinyMCE plugin
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.initializeTinyMCE =
|
||||
function() {
|
||||
|
||||
if (typeof tinyMCE !== 'undefined') {
|
||||
tinyMCE.PluginManager.load('pkpTags', $.pkp.app.baseUrl +
|
||||
'/plugins/generic/tinymce/plugins/pkpTags/plugin.js');
|
||||
tinyMCE.PluginManager.load('pkpwordcount', $.pkp.app.baseUrl +
|
||||
'/plugins/generic/tinymce/plugins/pkpWordcount/plugin.js');
|
||||
|
||||
|
||||
var contentCSS = $.pkp.app.tinyMceContentCSS,
|
||||
tinymceParams,
|
||||
tinymceParamDefaults;
|
||||
|
||||
tinymceParamDefaults = {
|
||||
width: '100%',
|
||||
resize: 'both',
|
||||
entity_encoding: 'raw',
|
||||
plugins: 'paste,fullscreen,link,lists,code,' +
|
||||
'image,-pkpTags,noneditable',
|
||||
convert_urls: false,
|
||||
forced_root_block: 'p',
|
||||
paste_auto_cleanup_on_paste: true,
|
||||
apply_source_formatting: false,
|
||||
toolbar: 'copy paste | bold italic underline | link unlink ' +
|
||||
'code fullscreen | image | pkpTags',
|
||||
richToolbar: 'copy paste | bold italic underline | bullist numlist | ' +
|
||||
'superscript subscript | link unlink code fullscreen | ' +
|
||||
'image | pkpTags',
|
||||
statusbar: false,
|
||||
content_css: contentCSS,
|
||||
browser_spellcheck: true
|
||||
};
|
||||
|
||||
// Support image uploads
|
||||
if (typeof $.pkp.plugins.generic.tinymceplugin !== 'undefined' &&
|
||||
typeof $.pkp.plugins.generic.tinymceplugin.uploadUrl !== 'undefined') {
|
||||
tinymceParamDefaults.paste_data_images = true;
|
||||
tinymceParamDefaults.relative_urls = false;
|
||||
tinymceParamDefaults.remove_script_host = false;
|
||||
// https://www.tiny.cloud/docs/general-configuration-guide/upload-images/
|
||||
tinymceParamDefaults.images_upload_handler = function(
|
||||
blobInfo, success, failure) {
|
||||
|
||||
/*global FormData: false */
|
||||
var data = new FormData();
|
||||
data.append('file', blobInfo.blob(), blobInfo.filename());
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: $.pkp.plugins.generic.tinymceplugin.uploadUrl,
|
||||
data: data,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
'X-Csrf-Token': pkp.currentUser.csrfToken
|
||||
},
|
||||
success: function(r) {
|
||||
success(r.url);
|
||||
},
|
||||
error: function(r) {
|
||||
failure(r.responseJSON.errorMessage);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Allow default params to be overridden
|
||||
if (typeof $.pkp.plugins.generic.tinymceplugin !== 'undefined' &&
|
||||
typeof $.pkp.plugins.generic.tinymceplugin.tinymceParams) {
|
||||
tinymceParams = $.extend({}, tinymceParamDefaults,
|
||||
$.pkp.plugins.generic.tinymceplugin.tinymceParams);
|
||||
} else {
|
||||
tinymceParams = $.extend({}, tinymceParamDefaults);
|
||||
}
|
||||
|
||||
// Don't allow the following settings to be overridden
|
||||
tinymceParams.init_instance_callback =
|
||||
$.pkp.controllers.SiteHandler.prototype.triggerTinyMCEInitialized;
|
||||
tinymceParams.setup =
|
||||
$.pkp.controllers.SiteHandler.prototype.triggerTinyMCESetup;
|
||||
|
||||
tinyMCE.init(tinymceParams);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback used by the tinyMCE plugin to trigger the tinyMCEInitialized
|
||||
* event in the DOM.
|
||||
* @param {Object} tinyMCEObject The tinyMCE object instance being
|
||||
* initialized.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.triggerTinyMCEInitialized =
|
||||
function(tinyMCEObject) {
|
||||
|
||||
var $inputElement = $('#' +
|
||||
$.pkp.classes.Helper.escapeJQuerySelector(tinyMCEObject.id));
|
||||
$inputElement.trigger('tinyMCEInitialized', [tinyMCEObject]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback used by the tinyMCE plugin upon setup.
|
||||
* @param {Object} tinyMCEObject The tinyMCE object instance being
|
||||
* set up.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.triggerTinyMCESetup =
|
||||
function(tinyMCEObject) {
|
||||
var target = $('#' +
|
||||
$.pkp.classes.Helper.escapeJQuerySelector(tinyMCEObject.id)),
|
||||
height;
|
||||
|
||||
// For read-only controls, set up TinyMCE read-only mode.
|
||||
if (target.attr('readonly')) {
|
||||
tinyMCEObject.settings.readonly = true;
|
||||
}
|
||||
|
||||
if (target.attr('wordCount') && target.attr('wordCount') > 0) {
|
||||
tinyMCEObject.settings.plugins =
|
||||
tinyMCEObject.settings.plugins + ',pkpwordcount';
|
||||
tinyMCEObject.settings.statusbar = true;
|
||||
}
|
||||
|
||||
if (target.attr('dir')) {
|
||||
tinyMCEObject.settings.directionality = target.attr('dir');
|
||||
}
|
||||
|
||||
// Set height based on textarea rows
|
||||
height = target.attr('rows') || 10; // default: 10
|
||||
height *= 20; // 20 pixels per row
|
||||
tinyMCEObject.settings.height = height.toString() + 'px';
|
||||
|
||||
// Add a fake HTML5 placeholder when the editor is intitialized
|
||||
tinyMCEObject.on('init', function(tinyMCEObject) {
|
||||
var $element = $('#' + tinyMCEObject.id),
|
||||
placeholderText,
|
||||
$placeholder,
|
||||
$placeholderParent;
|
||||
|
||||
// Don't add anything if we don't have a placeholder
|
||||
placeholderText = $('#' + tinyMCEObject.id).attr('placeholder');
|
||||
if (placeholderText === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create placeholder element
|
||||
$placeholder = $('<span></span>');
|
||||
$placeholder.html(/** @type {string} */ (placeholderText));
|
||||
$placeholder.addClass('mcePlaceholder');
|
||||
$placeholder.attr('id', 'mcePlaceholder-' + tinyMCEObject.id);
|
||||
|
||||
if (tinyMCEObject.target.getContent().length) {
|
||||
$placeholder.hide();
|
||||
}
|
||||
|
||||
// Create placeholder wrapper
|
||||
$placeholderParent = $('<div></div>');
|
||||
$placeholderParent.addClass('mcePlaceholderParent');
|
||||
$element.wrap($placeholderParent);
|
||||
$element.parent().append($placeholder);
|
||||
});
|
||||
|
||||
tinyMCEObject.on('activate', function(tinyMCEObject) {
|
||||
// Hide the placeholder when the editor is activated
|
||||
$('#mcePlaceholder-' + tinyMCEObject.id).hide();
|
||||
});
|
||||
|
||||
tinyMCEObject.on('deactivate', function(tinyMCEObject) {
|
||||
// Show the placholder when the editor is deactivated
|
||||
if (!tinyMCEObject.target.getContent().length) {
|
||||
$('#mcePlaceholder-' + tinyMCEObject.id).show();
|
||||
}
|
||||
});
|
||||
|
||||
tinyMCEObject.on('BeforeSetContent', function(e) {
|
||||
var variablesParsed = $.pkp.classes.TinyMCEHelper.prototype.getVariableMap(
|
||||
'#' + $.pkp.classes.Helper.escapeJQuerySelector(tinyMCEObject.id));
|
||||
|
||||
e.content = e.content.replace(
|
||||
/\{\$([a-zA-Z]+)\}(?![^<]*>)/g, function(match, contents, offset, s) {
|
||||
if (variablesParsed[contents] !== undefined) {
|
||||
return $.pkp.classes.TinyMCEHelper.prototype.getVariableElement(
|
||||
contents, variablesParsed[contents], '#' + tinyMCEObject.id)
|
||||
.html();
|
||||
}
|
||||
return match;
|
||||
});
|
||||
});
|
||||
|
||||
// When the field is being saved, replace any tag placeholders
|
||||
tinyMCEObject.on('GetContent', function(e) {
|
||||
var $content = $('<div>' + e.content + '</div>');
|
||||
|
||||
// Replace tag span elements with the raw tags
|
||||
$content.find('.pkpTag').replaceWith(function() {
|
||||
return '{$' + $(this).attr('data-symbolic') + '}';
|
||||
});
|
||||
e.content = $content.html();
|
||||
});
|
||||
|
||||
// In fullscreen mode, also present the toolbar.
|
||||
tinyMCEObject.on('FullscreenStateChanged init', function(e) {
|
||||
var target = e.target, $container = $(target.editorContainer);
|
||||
if (target.plugins.fullscreen) {
|
||||
if (target.plugins.fullscreen.isFullscreen()) {
|
||||
$container.find('.tox-menubar').show();
|
||||
} else {
|
||||
$container.find('.tox-menubar').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the current window dimensions.
|
||||
* @return {Object} The current window dimensions (height and width)
|
||||
* in pixels.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.getWindowDimensions =
|
||||
function() {
|
||||
var dimensions = {'height': $(window).height(),
|
||||
'width': $(window).width()};
|
||||
|
||||
return dimensions;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Callback that is triggered when the page should redirect.
|
||||
*
|
||||
* @param {HTMLElement} sourceElement The element that issued the
|
||||
* "redirectRequested" event.
|
||||
* @param {Event} event The "redirect requested" event.
|
||||
* @param {string} url The URL to redirect to.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.redirectToUrl =
|
||||
function(sourceElement, event, url) {
|
||||
|
||||
window.location = url;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler bound to 'formChanged' events propagated by forms
|
||||
* that wish to have their form data tracked.
|
||||
* @private
|
||||
* @param {HTMLElement} siteHandlerElement The html element
|
||||
* attached to this handler.
|
||||
* @param {HTMLElement} sourceElement The element wishes to
|
||||
* register.
|
||||
* @param {Event} event The formChanged event.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.registerUnsavedFormElement_ =
|
||||
function(siteHandlerElement, sourceElement, event) {
|
||||
var $formElement, formId, index;
|
||||
|
||||
$formElement = $(event.target.lastElementChild);
|
||||
formId = $formElement.attr('id');
|
||||
index = $.inArray(formId, this.unsavedFormElements_);
|
||||
if (index == -1) {
|
||||
this.unsavedFormElements_.push(formId);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler bound to 'unregisterChangedForm' events propagated by forms
|
||||
* that wish to inform that they no longer wish to be tracked as 'unsaved'.
|
||||
* @private
|
||||
* @param {HTMLElement} siteHandlerElement The html element
|
||||
* attached to this handler.
|
||||
* @param {HTMLElement} sourceElement The element that wishes to
|
||||
* unregister.
|
||||
* @param {Event} event The unregisterChangedForm event.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.unregisterUnsavedFormElement_ =
|
||||
function(siteHandlerElement, sourceElement, event) {
|
||||
var $formElement, formId, index;
|
||||
|
||||
$formElement = $(event.target.lastElementChild);
|
||||
formId = $formElement.attr('id');
|
||||
index = $.inArray(formId, this.unsavedFormElements_);
|
||||
if (index !== -1) {
|
||||
delete this.unsavedFormElements_[index];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unregister all unsaved form elements.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.unregisterAllFormElements_ =
|
||||
function() {
|
||||
this.unsavedFormElements_ = [];
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods.
|
||||
//
|
||||
/**
|
||||
* Fetch the notification data.
|
||||
* @private
|
||||
* @param {HTMLElement} sourceElement The element that issued the
|
||||
* "fetchNotification" event.
|
||||
* @param {Event} event The "fetch notification" event.
|
||||
* @param {Object} jsonData The JSON content representing the
|
||||
* notification.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.fetchNotificationHandler_ =
|
||||
function(sourceElement, event, jsonData) {
|
||||
|
||||
if (jsonData !== undefined) {
|
||||
// This is an event that came from an inplace notification
|
||||
// widget that was not visible because of the scrolling.
|
||||
this.showNotification_(jsonData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid race conditions with in place notifications.
|
||||
$.ajax({
|
||||
url: this.options_.fetchNotificationUrl,
|
||||
data: this.options_.requestOptions,
|
||||
success: this.callbackWrapper(this.showNotificationResponseHandler_),
|
||||
dataType: 'json',
|
||||
async: false
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reload a tab.
|
||||
* @private
|
||||
* @param {HTMLElement} sourceElement The element that issued the
|
||||
* "reloadTab" event.
|
||||
* @param {Event} event The "reload tab" event.
|
||||
* @param {Object} jsonData The JSON content representing the
|
||||
* reload request.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.reloadTabHandler_ =
|
||||
function(sourceElement, event, jsonData) {
|
||||
|
||||
$(jsonData.tabsSelector).tabs('load', jsonData.tabSelector);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Call when click outside event handler. Stores the event
|
||||
* parameters as checks to be used later by mouse down handler so we
|
||||
* can track if user clicked outside the passed element or not.
|
||||
* @private
|
||||
* @param {HTMLElement} sourceElement The element that issued the
|
||||
* callWhenClickOutside event.
|
||||
* @param {Event} event The "call when click outside" event.
|
||||
* @param {{
|
||||
* container: jQueryObject,
|
||||
* callback: Function
|
||||
* }} eventParams The event parameters.
|
||||
* - container: a jQuery element to be used to test if user click
|
||||
* outside of it or not.
|
||||
* - callback: a callback function in case test is true.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.callWhenClickOutsideHandler_ =
|
||||
function(sourceElement, event, eventParams) {
|
||||
// Check the required parameters.
|
||||
if (eventParams.container === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var id = eventParams.container.attr('id');
|
||||
|
||||
if (eventParams.clear) {
|
||||
delete this.outsideClickChecks_[id];
|
||||
} else if (eventParams.callback !== undefined) {
|
||||
this.outsideClickChecks_[id] = eventParams;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Mouse down event handler attached to the site element.
|
||||
* @private
|
||||
* @param {HTMLElement} sourceElement The element that issued the
|
||||
* click event.
|
||||
* @param {Event} event The "mousedown" event.
|
||||
* @return {?boolean} Event handling status.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.mouseDownHandler_ =
|
||||
function(sourceElement, event) {
|
||||
|
||||
var $container, callback, id;
|
||||
if (!$.isEmptyObject(this.outsideClickChecks_)) {
|
||||
for (id in this.outsideClickChecks_) {
|
||||
this.processOutsideClickCheck_(
|
||||
this.outsideClickChecks_[id], event);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if the passed event target is outside the element
|
||||
* inside the passed check data. If true and no other check
|
||||
* option avoids it, use the callback.
|
||||
* @private
|
||||
* @param {{
|
||||
* container: Object,
|
||||
* callback: Function
|
||||
* }} checkOptions Object with data to be used to
|
||||
* check the click.
|
||||
* @param {Event} event The click event to be checked.
|
||||
* @return {boolean} Whether the check was processed or not.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.processOutsideClickCheck_ =
|
||||
function(checkOptions, event) {
|
||||
|
||||
// Make sure we have a click event.
|
||||
if (event.type !== 'click' &&
|
||||
event.type !== 'mousedown' && event.type !== 'mouseup') {
|
||||
throw new Error('Can not check outside click with the passed event: ' +
|
||||
event.type + '.');
|
||||
}
|
||||
|
||||
// Get the container element.
|
||||
var $container = checkOptions.container;
|
||||
|
||||
// Doesn't make sense to check an outside click
|
||||
// with an invisible element, so skip test if
|
||||
// container is hidden.
|
||||
if ($container.is(':hidden')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do the click origin checking.
|
||||
if ($container.has(event.target).length === 0) {
|
||||
|
||||
// Once the check was processed, delete it.
|
||||
delete this.outsideClickChecks_[$container.attr('id')];
|
||||
|
||||
checkOptions.callback();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback called upon page unload. If it returns
|
||||
* anything other than void, a message will be displayed to
|
||||
* the user.
|
||||
* @private
|
||||
* @param {Object} object The window object.
|
||||
* @param {Event} event The before unload event.
|
||||
* @return {string|undefined} The warning message string, if needed.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.pageUnloadHandler_ =
|
||||
function(object, event) {
|
||||
var handler, unsavedElementCount, element;
|
||||
|
||||
// any registered and then unregistered forms will exist
|
||||
// as properties in the unsavedFormElements_ object. They
|
||||
// will just be undefined. See if there are any that are
|
||||
// not.
|
||||
|
||||
// we need to get the handler this way since this event is bound
|
||||
// to window, not to SiteHandler.
|
||||
handler = $.pkp.classes.Handler.getHandler($('body'));
|
||||
|
||||
unsavedElementCount = 0;
|
||||
for (element in handler.unsavedFormElements_) {
|
||||
if (element) {
|
||||
unsavedElementCount++;
|
||||
}
|
||||
}
|
||||
if (unsavedElementCount > 0) {
|
||||
return pkp.localeKeys['form.dataHasChanged'];
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Method to determine if a form is currently registered as having
|
||||
* unsaved changes.
|
||||
*
|
||||
* @param {string} id the id of the form to check.
|
||||
* @return {boolean} true if the form is unsaved.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.isFormUnsaved =
|
||||
function(id) {
|
||||
|
||||
if (this.unsavedFormElements_ !== null &&
|
||||
this.unsavedFormElements_[id] !== undefined) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Response handler to the notification fetch.
|
||||
* @private
|
||||
* @param {Object} ajaxContext The data returned from the server.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.showNotificationResponseHandler_ =
|
||||
function(ajaxContext, jsonData) {
|
||||
this.showNotification_(jsonData);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show the notification content.
|
||||
* @private
|
||||
* @param {Object} jsonData The JSON-encoded notification data.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.showNotification_ =
|
||||
function(jsonData) {
|
||||
var workingJsonData, notificationsData, levelId, notificationId,
|
||||
addclass, type;
|
||||
|
||||
workingJsonData = this.handleJson(jsonData);
|
||||
if (workingJsonData !== false) {
|
||||
if (workingJsonData.content.general) {
|
||||
notificationsData = workingJsonData.content.general;
|
||||
for (levelId in notificationsData) {
|
||||
for (notificationId in notificationsData[levelId]) {
|
||||
addclass = notificationsData[levelId][notificationId].addclass;
|
||||
type = 'notice';
|
||||
if (addclass == 'notifySuccess') {
|
||||
type = 'success';
|
||||
} else if (addclass == 'notifyWarning' || addclass == 'notifyError' ||
|
||||
addclass == 'notifyFormError' || addclass == 'notifyForbidden') {
|
||||
type = 'warning';
|
||||
}
|
||||
pkp.eventBus.$emit('notify',
|
||||
notificationsData[levelId][notificationId].text, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reacts to a modal being opened. Adds a class to the body representing
|
||||
* a modal open state.
|
||||
* @private
|
||||
* @param {HTMLElement} handledElement The modal that has been added
|
||||
* @param {HTMLElement} siteHandlerElement The html element
|
||||
* attached to this handler.
|
||||
* @param {HTMLElement} sourceElement The element wishes to
|
||||
* register.
|
||||
* @param {Event} event The formChanged event.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.openModal_ =
|
||||
function(handledElement, siteHandlerElement, sourceElement, event) {
|
||||
this.getHtmlElement().addClass('modal_is_visible');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reacts to a modal being closed. Removes a class from the body
|
||||
* representing a modal closed state, after checking if no other modals are
|
||||
* open.
|
||||
* @private
|
||||
* @param {HTMLElement} handledElement The modal that has been added
|
||||
* @param {HTMLElement} siteHandlerElement The html element
|
||||
* attached to this handler.
|
||||
* @param {HTMLElement} sourceElement The element wishes to
|
||||
* register.
|
||||
* @param {Event} event The formChanged event.
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.closeModal_ =
|
||||
function(handledElement, siteHandlerElement, sourceElement, event) {
|
||||
|
||||
var $htmlElement = this.getHtmlElement();
|
||||
if (!$htmlElement.find('.pkp_modal.is_visible').length) {
|
||||
$htmlElement.removeClass('modal_is_visible');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Register a function to observe the body scrolling event.
|
||||
* @private
|
||||
* @param {Object} siteHandler The site handler object.
|
||||
* @param {HTMLElement} siteHandlerElement The html element
|
||||
* attached to this handler.
|
||||
* @param {Object} event The pkpObserveScrolling event object.
|
||||
* @param {Function} observerFunction The observer function.
|
||||
* @return {boolean}
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.registerScrollingObserver_ =
|
||||
function(siteHandler, siteHandlerElement, event, observerFunction) {
|
||||
$(document).scroll(observerFunction);
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unregister a function that was observing the body scrolling event.
|
||||
* @private
|
||||
* @param {Object} siteHandler The site handler object.
|
||||
* @param {HTMLElement} siteHandlerElement The html element
|
||||
* attached to this handler.
|
||||
* @param {Object} event The pkpRemoveScrollingObserver event object.
|
||||
* @param {Function} observerFunction The observer function.
|
||||
* @return {boolean}
|
||||
*/
|
||||
$.pkp.controllers.SiteHandler.prototype.unregisterScrollingObserver_ =
|
||||
function(siteHandler, siteHandlerElement, event, observerFunction) {
|
||||
var castObserverFunction = /** @type {function()} */ (observerFunction);
|
||||
$(document).unbind('scroll', castObserverFunction);
|
||||
return false;
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,369 @@
|
||||
/**
|
||||
* @file js/controllers/TabHandler.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 TabHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief A basic handler for a tabbed set of pages.
|
||||
*
|
||||
* See <http://jqueryui.com/demos/tabs/> for documentation on JQuery tabs.
|
||||
* Attach this handler to a div that contains a <ul> with a <li> for each tab
|
||||
* to be created.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $tabs A wrapped HTML element that
|
||||
* represents the tabbed interface.
|
||||
* @param {Object} options Handler options.
|
||||
*/
|
||||
$.pkp.controllers.TabHandler = function($tabs, options) {
|
||||
var pageUrl, pageAnchor, pattern, pageAnchors, tabAnchors, i,
|
||||
self = this;
|
||||
|
||||
this.parent($tabs, options);
|
||||
|
||||
// Attach the tabs event handlers.
|
||||
this.bind('tabsbeforeactivate', this.tabsBeforeActivate);
|
||||
this.bind('tabsactivate', this.tabsActivate);
|
||||
this.bind('tabscreate', this.tabsCreate);
|
||||
this.bind('tabsbeforeload', this.tabsBeforeLoad);
|
||||
this.bind('tabsload', this.tabsLoad);
|
||||
this.bind('addTab', this.addTab);
|
||||
|
||||
if (options.emptyLastTab) {
|
||||
this.emptyLastTab_ = options.emptyLastTab;
|
||||
}
|
||||
|
||||
// if the page has been loaded with an #anchor
|
||||
// determine what tab that is for and set the
|
||||
// options.selected value to it so it gets used
|
||||
// when tabs() are initialized.
|
||||
pageUrl = document.location.toString();
|
||||
if (pageUrl.match('#')) {
|
||||
pageAnchor = pageUrl.split('#')[1];
|
||||
tabAnchors = $tabs.find('li a');
|
||||
for (i = 0; i < tabAnchors.length; i++) {
|
||||
if (pageAnchor == tabAnchors[i].getAttribute('name')) {
|
||||
// Matched on anchor name.
|
||||
options.selected = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render the tabs as jQueryUI tabs.
|
||||
$tabs.tabs({
|
||||
// Enable AJAX-driven tabs with JSON messages.
|
||||
beforeLoad: function(event, ui) {
|
||||
ui.ajaxSettings.dataType = 'json';
|
||||
ui.jqXHR.setRequestHeader('Accept', 'application/json');
|
||||
ui.ajaxSettings.dataFilter = self.callbackWrapper(self.dataFilter);
|
||||
},
|
||||
disabled: options.disabled,
|
||||
active: options.selected
|
||||
});
|
||||
|
||||
// Load a tab when the URL hash changes to a named tab
|
||||
// Original issue: https://github.com/pkp/pkp-lib/issues/1787
|
||||
// This technique introduced to resolve tab activation errors from #1787.
|
||||
// See: https://github.com/pkp/pkp-lib/issues/4352
|
||||
window.addEventListener('hashchange', function(e) {
|
||||
var parts = e.newURL.split('#'), hash, $tab;
|
||||
if (parts.length < 2) {
|
||||
return;
|
||||
}
|
||||
hash = parts[1];
|
||||
$tab = $tabs.find('li > a[name="' + hash + '"]');
|
||||
if ($tab.length) {
|
||||
$tab.click();
|
||||
}
|
||||
}, false);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.TabHandler, $.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The current tab.
|
||||
* @private
|
||||
* @type {jQueryObject}
|
||||
*/
|
||||
$.pkp.controllers.TabHandler.prototype.$currentTab_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The current tab index.
|
||||
* @private
|
||||
* @type {number}
|
||||
*/
|
||||
$.pkp.controllers.TabHandler.prototype.currentTabIndex_ = 0;
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Event handler that is called when a tab is selected.
|
||||
*
|
||||
* @param {HTMLElement} tabsElement The tab element that triggered
|
||||
* the event.
|
||||
* @param {Event} event The triggered event.
|
||||
* @param {jQueryObject} ui The tabs ui data.
|
||||
* @return {boolean} Should return true to continue tab loading.
|
||||
*/
|
||||
$.pkp.controllers.TabHandler.prototype.tabsBeforeActivate =
|
||||
function(tabsElement, event, ui) {
|
||||
|
||||
var unsavedForm = false;
|
||||
this.$currentTab_.find('form').each(function(index) {
|
||||
|
||||
if ($.pkp.classes.Handler.hasHandler($('#' + $(this).attr('id')))) {
|
||||
var handler = $.pkp.classes.Handler.getHandler(
|
||||
$('#' + $(this).attr('id')));
|
||||
if (handler.formChangesTracked) {
|
||||
unsavedForm = true;
|
||||
return false; // found an unsaved form, no need to continue with each().
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.$currentTab_.find('.hasDatepicker').datepicker('hide');
|
||||
|
||||
if (unsavedForm) {
|
||||
if (!confirm(pkp.localeKeys['form.dataHasChanged'])) {
|
||||
return false;
|
||||
} else {
|
||||
this.trigger('unregisterAllForms');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.emptyLastTab_) {
|
||||
// bind a single (i.e. one()) error event handler to prevent
|
||||
// propagation if the tab being unloaded no longer exists.
|
||||
// We cannot simply getHandler() since that in of itself throws
|
||||
// an Error.
|
||||
$(window).one('error', function(msg, url, line) { return false; });
|
||||
if (this.$currentTab_) {
|
||||
// Unbind global events for handlers embedded in this tab's
|
||||
// content.
|
||||
this.unbindPartial(this.$currentTab_);
|
||||
this.$currentTab_.empty();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Event handler that is called when a tab is created.
|
||||
*
|
||||
* @param {HTMLElement} tabsElement The tab element that triggered
|
||||
* the event.
|
||||
* @param {Event} event The triggered event.
|
||||
* @param {jQueryObject} ui The tabs ui data.
|
||||
* @return {boolean} Should return true to continue tab loading.
|
||||
*/
|
||||
$.pkp.controllers.TabHandler.prototype.tabsCreate =
|
||||
function(tabsElement, event, ui) {
|
||||
|
||||
// Save the tab index.
|
||||
this.currentTabIndex_ = ui.tab.index();
|
||||
|
||||
// Save a reference to the current panel.
|
||||
this.$currentTab_ = ui.panel.jquery ? ui.panel : $(ui.panel);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Event handler that is called when a tab is activated
|
||||
*
|
||||
* @param {HTMLElement} tabsElement The tab element that triggered
|
||||
* the event.
|
||||
* @param {Event} event The triggered event.
|
||||
* @param {jQueryObject} ui The tabs ui data.
|
||||
* @return {boolean} Should return true to continue tab loading.
|
||||
*/
|
||||
$.pkp.controllers.TabHandler.prototype.tabsActivate =
|
||||
function(tabsElement, event, ui) {
|
||||
|
||||
// Save the tab index.
|
||||
this.currentTabIndex_ = ui.newTab.index();
|
||||
|
||||
// Save a reference to the current panel.
|
||||
this.$currentTab_ = ui.newPanel.jquery ? ui.newPanel : $(ui.newPanel);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Event handler that is called after a remote tab was loaded.
|
||||
*
|
||||
* @param {HTMLElement} tabsElement The tab element that triggered
|
||||
* the event.
|
||||
* @param {Event} event The triggered event.
|
||||
* @param {jQueryObject} ui The tabs ui data.
|
||||
* @return {boolean} Should return true to continue tab loading.
|
||||
*/
|
||||
$.pkp.controllers.TabHandler.prototype.tabsLoad =
|
||||
function(tabsElement, event, ui) {
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that that is triggered before the tab is loaded.
|
||||
*
|
||||
* @param {HTMLElement} tabsElement The tab element that triggered
|
||||
* the event.
|
||||
* @param {Event} event The triggered event.
|
||||
* @param {jQueryObject} ui The tabs ui data.
|
||||
*/
|
||||
$.pkp.controllers.TabHandler.prototype.tabsBeforeLoad =
|
||||
function(tabsElement, event, ui) {
|
||||
|
||||
// We must unbind global events before the new tab content is loaded.
|
||||
// This reaches out to the tab content element and unbinds any events
|
||||
// attached to that element or any embedded handlers before it gets
|
||||
// destroyed.
|
||||
this.unbindPartial($('#' + ui.tab.attr('aria-controls')));
|
||||
|
||||
// Initialize AJAX settings for loading tab content remotely
|
||||
ui.ajaxSettings.cache = false;
|
||||
ui.ajaxSettings.dataFilter = this.callbackWrapper(this.dataFilter);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that processes AJAX data returned by the server before
|
||||
* it is displayed in a tab.
|
||||
*
|
||||
* @param {Object} ajaxOptions The options object from which the
|
||||
* callback originated.
|
||||
* @param {string} jsonString Unparsed JSON data returned from the server.
|
||||
* @return {string} The tab mark-up.
|
||||
*/
|
||||
$.pkp.controllers.TabHandler.prototype.dataFilter =
|
||||
function(ajaxOptions, jsonString) {
|
||||
|
||||
var jsonData = this.handleJson($.parseJSON(jsonString));
|
||||
if (jsonData === false) {
|
||||
return '';
|
||||
}
|
||||
return JSON.stringify(jsonData.content);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that processes data returned by the server when
|
||||
* an 'addTab' event is received.
|
||||
*
|
||||
* This is useful e.g. when the results of a form handler
|
||||
* should be sent to a different tab in the containing tabset.
|
||||
*
|
||||
* @param {HTMLElement} divElement The parent DIV element
|
||||
* which contains the tabs.
|
||||
* @param {Event} event The triggered event (addTab).
|
||||
* @param {{url: string, title: string}} jsonContent The tabs ui data.
|
||||
*/
|
||||
$.pkp.controllers.TabHandler.prototype.addTab =
|
||||
function(divElement, event, jsonContent) {
|
||||
|
||||
var $element = this.getHtmlElement(),
|
||||
numTabs = $element.children('ul').children('li').length + 1,
|
||||
$anchorElement = $('<a/>')
|
||||
.text(jsonContent.title)
|
||||
.attr('href', jsonContent.url),
|
||||
$closeSpanElement = $('<a/>')
|
||||
.addClass('close')
|
||||
.text(pkp.localeKeys['common.close'])
|
||||
.attr('href', '#'),
|
||||
$liElement = $('<li/>')
|
||||
.append($anchorElement)
|
||||
.append($closeSpanElement);
|
||||
|
||||
// Get the "close" button working
|
||||
$closeSpanElement.click(function() {
|
||||
var $liElement = $(this).closest('li'),
|
||||
$divElement = $('#' + $liElement.attr('aria-controls')),
|
||||
thisTabIndex, unsavedForm;
|
||||
|
||||
// Check to see if any unsaved changes need to be confirmed
|
||||
unsavedForm = false;
|
||||
$divElement.find('form').each(function() {
|
||||
var handler = $.pkp.classes.Handler.getHandler($(this));
|
||||
if (handler.formChangesTracked) {
|
||||
// Confirm before proceeding
|
||||
if (!confirm(pkp.localeKeys['form.dataHasChanged'])) {
|
||||
unsavedForm = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!unsavedForm) {
|
||||
$divElement.find('form').each(function() {
|
||||
var handler = $.pkp.classes.Handler.getHandler($(this));
|
||||
if (handler) {
|
||||
handler.unregisterForm();
|
||||
}
|
||||
});
|
||||
|
||||
// If the panel being closed is currently selected, move off first.
|
||||
thisTabIndex = $liElement.eq(0).index();
|
||||
if ($element.tabs('option', 'active') == thisTabIndex) {
|
||||
$element.tabs('option', 'active', thisTabIndex - 1);
|
||||
}
|
||||
|
||||
$liElement.remove();
|
||||
$divElement.remove();
|
||||
|
||||
$element.tabs('refresh');
|
||||
}
|
||||
});
|
||||
|
||||
// Add the new tab element and refresh the tab set.
|
||||
$element.children('ul').append($liElement);
|
||||
$element.tabs('refresh');
|
||||
$element.tabs('option', 'active', numTabs - 1);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* Get the current tab.
|
||||
* @protected
|
||||
* @return {jQueryObject} The current tab.
|
||||
*/
|
||||
$.pkp.controllers.TabHandler.prototype.getCurrentTab = function() {
|
||||
return this.$currentTab_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the current tab index.
|
||||
* @protected
|
||||
* @return {number} The current tab index.
|
||||
*/
|
||||
$.pkp.controllers.TabHandler.prototype.getCurrentTabIndex = function() {
|
||||
return this.currentTabIndex_;
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* @file js/controllers/UploaderHandler.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 UploaderHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief PKP file uploader widget handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $uploader the wrapped HTML uploader element.
|
||||
* @param {{
|
||||
* uploadUrl: string,
|
||||
* baseUrl: string
|
||||
* }} options options to be passed
|
||||
* into the validator plug-in.
|
||||
*/
|
||||
$.pkp.controllers.UploaderHandler = function($uploader, options) {
|
||||
this.parent($uploader, options);
|
||||
|
||||
// Check whether we really got a div to attach
|
||||
// our uploader to.
|
||||
if (!$uploader.is('div')) {
|
||||
throw new Error(['An uploader widget controller can only be attached',
|
||||
' to a div!'].join(''));
|
||||
}
|
||||
|
||||
var uploaderOptions,
|
||||
pluploaderId,
|
||||
$browseButton,
|
||||
self;
|
||||
|
||||
// Set up options to pass to plupload
|
||||
uploaderOptions = {
|
||||
url: options.uploadUrl,
|
||||
// Flash settings
|
||||
flash_swf_url: options.baseUrl +
|
||||
'/lib/pkp/lib/vendor/moxiecode/plupload/js/Moxie.swf',
|
||||
// Silverlight settings
|
||||
silverlight_xap_url: options.baseUrl +
|
||||
'/lib/pkp/lib/vendor/moxiecode/plupload/js/Moxie.xap'
|
||||
};
|
||||
if (typeof options.filters) {
|
||||
uploaderOptions.filters = options.filters;
|
||||
}
|
||||
if (typeof options.resize) {
|
||||
uploaderOptions.resize = options.resize;
|
||||
}
|
||||
if (typeof options.browse_button) {
|
||||
uploaderOptions.browse_button = options.browse_button;
|
||||
}
|
||||
if (typeof options.multipart_params) {
|
||||
uploaderOptions.multipart_params = options.multipart_params;
|
||||
}
|
||||
|
||||
uploaderOptions.drop_element = $uploader.first().find('#' +
|
||||
$uploader.first().attr('id') +
|
||||
'-pkpUploaderDropZone').attr('id');
|
||||
|
||||
uploaderOptions = $.extend(
|
||||
{},
|
||||
this.self('DEFAULT_PROPERTIES_'),
|
||||
uploaderOptions
|
||||
);
|
||||
|
||||
// Create the uploader with the puploader plug-in.
|
||||
// Setup the upload widget.
|
||||
this.pluploader = new plupload.Uploader(uploaderOptions);
|
||||
this.pluploader.init();
|
||||
this.updateStatus('waiting');
|
||||
|
||||
// Cache re-used DOM references
|
||||
this.$progress = $uploader.find('.pkpUploaderProgress .percentage');
|
||||
this.$progressBar = $uploader.find('.pkpUploaderProgressBar');
|
||||
this.$fileName = $uploader.find('.pkpUploaderFilename');
|
||||
|
||||
// Bind to the pluploader for some configuration
|
||||
this.pluploader.bind('FilesAdded',
|
||||
this.callbackWrapper(this.startUpload));
|
||||
this.pluploader.bind('UploadProgress',
|
||||
this.callbackWrapper(this.updateProgress));
|
||||
this.pluploader.bind('Error',
|
||||
this.callbackWrapper(this.handleError));
|
||||
this.pluploader.bind('FileUploaded',
|
||||
this.callbackWrapper(this.uploadComplete));
|
||||
this.pluploader.bind('QueueChanged',
|
||||
this.callbackWrapper(this.refreshUploader));
|
||||
|
||||
// Ensure clicks on the visual button don't attempt to submit the form
|
||||
$browseButton = $('#' + uploaderOptions.browse_button, this.getHtmlElement());
|
||||
$browseButton.click(function(e) {
|
||||
return false;
|
||||
});
|
||||
|
||||
this.pluploader.refresh();
|
||||
|
||||
// Fake a focus effect on the visual button when plupload's hidden
|
||||
// button is focused
|
||||
self = this;
|
||||
setTimeout(function() {
|
||||
self.getHtmlElement().find('.moxie-shim input')
|
||||
.focus(function(e) {
|
||||
$browseButton.addClass('in_focus');
|
||||
})
|
||||
.blur(function(e) {
|
||||
$browseButton.removeClass('in_focus');
|
||||
});
|
||||
}, 100);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.UploaderHandler, $.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Initiate upload of a file
|
||||
* @param {Object} caller The original context in which the callback was called.
|
||||
* @param {Object} pluploader The pluploader object.
|
||||
* @param {Object} file The data of the uploaded file.
|
||||
*
|
||||
*/
|
||||
$.pkp.controllers.UploaderHandler.prototype.
|
||||
startUpload = function(caller, pluploader, file) {
|
||||
|
||||
// Prevent > 1 files from being added.
|
||||
if (pluploader.files.length > 1) {
|
||||
pluploader.removeFile(pluploader.files[0]);
|
||||
}
|
||||
|
||||
// Initiate the upload process
|
||||
this.updateStatus('uploading');
|
||||
pluploader.start();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update the progress indicator for a file
|
||||
* @param {Object} caller The original context in which the callback was called.
|
||||
* @param {Object} pluploader The pluploader object.
|
||||
* @param {Object} file The data of the uploaded file.
|
||||
*
|
||||
*/
|
||||
$.pkp.controllers.UploaderHandler.prototype.
|
||||
updateProgress = function(caller, pluploader, file) {
|
||||
|
||||
this.$progress.html(file.percent);
|
||||
this.$progressBar.css('width', file.percent + '%');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Indicate the file upload has completed
|
||||
* @param {Object} caller The original context in which the callback was called.
|
||||
* @param {Object} pluploader The pluploader object.
|
||||
* @param {Object} file The data of the uploaded file.
|
||||
* @param {{response: string}} response
|
||||
*/
|
||||
$.pkp.controllers.UploaderHandler.prototype.
|
||||
uploadComplete = function(caller, pluploader, file, response) {
|
||||
var jsonData = $.parseJSON(response.response), filename = file.name;
|
||||
|
||||
if (!jsonData.status) {
|
||||
this.showError(jsonData.content);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof jsonData.uploadedFile !== 'undefined') {
|
||||
filename = jsonData.uploadedFile.name || jsonData.uploadedFile.fileLabel;
|
||||
|
||||
// Store uploaded file data so that it can be referenced during
|
||||
// other API events. This is used by the submission file wizard
|
||||
// to delete files that are uploaded then replaced before submission
|
||||
// is complete.
|
||||
//
|
||||
// See: $.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
// prototype.handleRemovedFiles
|
||||
file.storedData = jsonData.uploadedFile;
|
||||
}
|
||||
|
||||
this.$fileName.html(filename);
|
||||
this.updateStatus('complete');
|
||||
this.$progress.html('0');
|
||||
this.$progressBar.css('width', 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle error revents from plupload
|
||||
* @param {Object} caller The original context in which the callback was called.
|
||||
* @param {Object} pluploader The pluploader object.
|
||||
* @param {{message: string}} err An object describing an error condition
|
||||
*
|
||||
*/
|
||||
$.pkp.controllers.UploaderHandler.prototype.
|
||||
handleError = function(caller, pluploader, err) {
|
||||
this.showError(err.message);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Display an error if encountered during upload
|
||||
* @param {string} msg The error message
|
||||
*
|
||||
*/
|
||||
$.pkp.controllers.UploaderHandler.prototype.
|
||||
showError = function(msg) {
|
||||
|
||||
this.$progress.html('0');
|
||||
this.$progressBar.css('width', 0);
|
||||
this.updateStatus('error');
|
||||
this.getHtmlElement().find('.pkpUploaderError').html(msg);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Refresh the uploader interface so buttons work correctly.
|
||||
* @param {Object} caller The original context in which the callback was called.
|
||||
* @param {Object} pluploader The pluploader object.
|
||||
* @param {Object} file The data of the uploaded file.
|
||||
*
|
||||
*/
|
||||
$.pkp.controllers.UploaderHandler.prototype.
|
||||
refreshUploader = function(caller, pluploader, file) {
|
||||
pluploader.refresh();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update the status of the element in the DOM
|
||||
* @param {string} status The new status
|
||||
*
|
||||
*/
|
||||
$.pkp.controllers.UploaderHandler.prototype.
|
||||
updateStatus = function(status) {
|
||||
this.getHtmlElement().removeClass('loading waiting uploading error complete')
|
||||
.addClass(status);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private static properties
|
||||
//
|
||||
/**
|
||||
* Default options
|
||||
* @private
|
||||
* @type {Object}
|
||||
* @const
|
||||
*/
|
||||
$.pkp.controllers.UploaderHandler.DEFAULT_PROPERTIES_ = {
|
||||
runtimes: 'html5,flash,silverlight,html4',
|
||||
max_file_size: $.pkp.cons.UPLOAD_MAX_FILESIZE,
|
||||
multi_selection: false,
|
||||
file_data_name: 'uploadedFile',
|
||||
multipart: true,
|
||||
headers: {'browser_user_agent': navigator.userAgent},
|
||||
browse_button: 'pkpUploaderButton'
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* @file js/controllers/UrlInDivHandler.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 UrlInDivHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief "URL in div" handler
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $divElement the wrapped div element.
|
||||
* @param {Object} options options to be passed.
|
||||
*/
|
||||
$.pkp.controllers.UrlInDivHandler = function($divElement, options) {
|
||||
this.parent($divElement, options);
|
||||
|
||||
// Store the URL (e.g. for reloads)
|
||||
this.sourceUrl_ = options.sourceUrl;
|
||||
|
||||
// Load the contents.
|
||||
this.reload();
|
||||
|
||||
if (options.refreshOn) {
|
||||
this.bindGlobal(options.refreshOn, this.reload);
|
||||
}
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.UrlInDivHandler, $.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The URL to be used for data loaded into this div
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.UrlInDivHandler.sourceUrl_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Public Methods
|
||||
//
|
||||
/**
|
||||
* Reload the div contents.
|
||||
*/
|
||||
$.pkp.controllers.UrlInDivHandler.prototype.reload = function() {
|
||||
$.get(this.sourceUrl_,
|
||||
this.callbackWrapper(this.handleLoadedContent_), 'json');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fetches the progress bar URL.
|
||||
* @return {?string} the source URL.
|
||||
*/
|
||||
$.pkp.controllers.UrlInDivHandler.prototype.getSourceUrl = function() {
|
||||
return this.sourceUrl_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the progress bar URL.
|
||||
* @param {string} sourceUrl the new source URL.
|
||||
*/
|
||||
$.pkp.controllers.UrlInDivHandler.prototype.setSourceUrl = function(sourceUrl) {
|
||||
this.sourceUrl_ = sourceUrl;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private Methods
|
||||
//
|
||||
/**
|
||||
* Handle a callback after a load operation returns.
|
||||
*
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
* @return {boolean} Message handling result.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.UrlInDivHandler.prototype.handleLoadedContent_ =
|
||||
function(ajaxContext, jsonData) {
|
||||
|
||||
var handledJsonData = this.handleJson(jsonData), urlInDivHandler = this;
|
||||
if (handledJsonData.status === true) {
|
||||
if (handledJsonData.content === undefined) {
|
||||
// Request successful, but no data returned.
|
||||
// Hide this div element.
|
||||
this.getHtmlElement().hide();
|
||||
} else {
|
||||
// See bug #8237.
|
||||
if (! /msie/.test(navigator.userAgent.toLowerCase())) {
|
||||
this.getHtmlElement().hide();
|
||||
this.html(handledJsonData.content);
|
||||
this.getHtmlElement().fadeIn(400);
|
||||
} else {
|
||||
this.html(handledJsonData.content);
|
||||
}
|
||||
|
||||
$(function() {
|
||||
urlInDivHandler.trigger('urlInDivLoaded',
|
||||
[urlInDivHandler.getHtmlElement().attr('id')]);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Alert that loading failed.
|
||||
alert(handledJsonData.content);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @defgroup js_controllers_dashboard_form
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/dashboard/form/DashboardTaskFormHandler.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 DashboardTaskFormHandler
|
||||
* @ingroup js_controllers_dashboard_form
|
||||
*
|
||||
* @brief Handle the styling and actions on the 'start new submission' form
|
||||
* on the Task tab in the dashboard.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.dashboard =
|
||||
$.pkp.controllers.dashboard || {form: { } };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.FormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
* @param {Object} options form options.
|
||||
*/
|
||||
$.pkp.controllers.dashboard.form.DashboardTaskFormHandler =
|
||||
function($form, options) {
|
||||
|
||||
this.parent($form, options);
|
||||
this.singleContextSubmissionUrl_ = options.singleContextSubmissionUrl;
|
||||
|
||||
$('#singleContext', $form).click(
|
||||
this.callbackWrapper(this.startSingleContextSubmission_));
|
||||
|
||||
$('#multipleContext', $form).change(
|
||||
this.callbackWrapper(this.startMultipleContextSubmission_));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.dashboard.form.DashboardTaskFormHandler,
|
||||
$.pkp.controllers.form.FormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The URL to be called to fetch a spotlight item via autocomplete.
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.dashboard.form.DashboardTaskFormHandler.
|
||||
prototype.singleContextSubmissionUrl_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Private Methods
|
||||
//
|
||||
/**
|
||||
* Redirect to the wizard for single context submissions
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.dashboard.form.DashboardTaskFormHandler.
|
||||
prototype.startSingleContextSubmission_ = function() {
|
||||
|
||||
window.location.href =
|
||||
/** @type {string} */ (this.singleContextSubmissionUrl_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Redirect to the wizard for multiple context submissions
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.dashboard.form.DashboardTaskFormHandler.
|
||||
prototype.startMultipleContextSubmission_ = function() {
|
||||
|
||||
var $form = this.getHtmlElement(),
|
||||
url = $form.find('#multipleContext').val();
|
||||
|
||||
if (url != 0) { // not the default
|
||||
window.location.href = /** @type {string} */ (url);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* @file js/controllers/form/AjaxFormHandler.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 AjaxFormHandler
|
||||
* @ingroup js_controllers_form
|
||||
*
|
||||
* @brief Form handler that submits the form to the server via AJAX and
|
||||
* either replaces the form if it is re-rendered by the server or
|
||||
* triggers the "formSubmitted" event after the server confirmed
|
||||
* form submission.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.FormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
* @param {Object} options options to configure the AJAX form handler.
|
||||
*/
|
||||
$.pkp.controllers.form.AjaxFormHandler = function($form, options) {
|
||||
options.submitHandler = this.submitForm;
|
||||
this.parent($form, options);
|
||||
|
||||
if (typeof options.confirmText !== 'undefined') {
|
||||
this.confirmText = options.confirmText;
|
||||
}
|
||||
|
||||
this.bind('refreshForm', this.refreshFormHandler_);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.form.AjaxFormHandler,
|
||||
$.pkp.controllers.form.FormHandler);
|
||||
|
||||
|
||||
/**
|
||||
* Overridden default from FormHandler -- disable form controls
|
||||
* on AJAX forms by default.
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
$.pkp.controllers.form.AjaxFormHandler.prototype.
|
||||
disableControlsOnSubmit = true;
|
||||
|
||||
|
||||
/**
|
||||
* A confirmation message to display before submitting the form
|
||||
* @protected
|
||||
* @type {string}
|
||||
*/
|
||||
$.pkp.controllers.form.AjaxFormHandler.prototype.
|
||||
confirmText = '';
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Internal callback called after form validation to handle form
|
||||
* submission.
|
||||
*
|
||||
* @param {Object} validator The validator plug-in.
|
||||
* @param {HTMLElement} formElement The wrapped HTML form.
|
||||
*/
|
||||
$.pkp.controllers.form.AjaxFormHandler.prototype.submitForm =
|
||||
function(validator, formElement) {
|
||||
|
||||
// This form implementation will post the form,
|
||||
// and act depending on the returned JSON message.
|
||||
var $form = this.getHtmlElement();
|
||||
|
||||
this.disableFormControls();
|
||||
|
||||
if (!this.confirmText.length || confirm(this.confirmText)) {
|
||||
$.post($form.attr('action'), $form.serialize(),
|
||||
this.callbackWrapper(this.handleResponse), 'json');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback to replace the element's content.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {jQueryObject} sourceElement The containing element.
|
||||
* @param {Event} event The calling event.
|
||||
* @param {string} content The content to replace with.
|
||||
*/
|
||||
$.pkp.controllers.form.AjaxFormHandler.prototype.refreshFormHandler_ =
|
||||
function(sourceElement, event, content) {
|
||||
|
||||
if (content) {
|
||||
this.replaceWith(content);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback called after form validation to handle the
|
||||
* response to a form submission.
|
||||
*
|
||||
* You can override this handler if you want to do custom handling
|
||||
* of a form response.
|
||||
*
|
||||
* @param {HTMLElement} formElement The wrapped HTML form.
|
||||
* @param {Object} jsonData The data returned from the server.
|
||||
* @return {boolean} The response status.
|
||||
*/
|
||||
$.pkp.controllers.form.AjaxFormHandler.prototype.handleResponse =
|
||||
function(formElement, jsonData) {
|
||||
|
||||
var $form, formSubmittedEvent, processedJsonData;
|
||||
|
||||
processedJsonData = this.handleJson(jsonData);
|
||||
if (processedJsonData !== false) {
|
||||
if (processedJsonData.content === '') {
|
||||
// Notify any nested formWidgets of form submitted event.
|
||||
formSubmittedEvent = new $.Event('formSubmitted');
|
||||
$(this.getHtmlElement()).find('.formWidget').trigger(formSubmittedEvent);
|
||||
|
||||
// Trigger the "form submitted" event.
|
||||
this.trigger('formSubmitted');
|
||||
|
||||
// Fire off any other optional events.
|
||||
this.publishChangeEvents();
|
||||
// re-enable the form control if it was disabled previously.
|
||||
if (this.disableControlsOnSubmit) {
|
||||
this.enableFormControls();
|
||||
}
|
||||
} else {
|
||||
// Redisplay the form.
|
||||
this.replaceWith(processedJsonData.content);
|
||||
}
|
||||
} else {
|
||||
// data was false -- assume errors, re-enable form controls.
|
||||
this.enableFormControls();
|
||||
}
|
||||
|
||||
// Trigger the notify user event, passing this
|
||||
// html element as data.
|
||||
this.trigger('notifyUser');
|
||||
|
||||
// Hide the form spinner.
|
||||
this.hideSpinner();
|
||||
|
||||
return processedJsonData.status;
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* @file js/controllers/form/CancelActionAjaxFormHandler.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 CancelActionAjaxFormHandler
|
||||
* @ingroup js_controllers_form
|
||||
*
|
||||
* @brief A Handler for controlling the Query form
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.AjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $handledElement The clickable element
|
||||
* the modal will be attached to.
|
||||
* @param {Object} options non-default Dialog options
|
||||
* to be passed into the dialog widget.
|
||||
*
|
||||
* Options are:
|
||||
* - all options documented for the AjaxModalHandler.
|
||||
* - cancelUrl: The URL to POST to in case of cancel.
|
||||
*/
|
||||
$.pkp.controllers.form.CancelActionAjaxFormHandler =
|
||||
function($handledElement, options) {
|
||||
|
||||
var formHandler = this;
|
||||
|
||||
this.parent($handledElement, options);
|
||||
|
||||
// Store the options.
|
||||
this.cancelUrl_ = options.cancelUrl;
|
||||
|
||||
this.cancelActionHandler = function() {
|
||||
formHandler.handleCancelAction();
|
||||
};
|
||||
$(window).on('unload', this.cancelActionHandler);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.form.
|
||||
CancelActionAjaxFormHandler, $.pkp.controllers.form.AjaxFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Public properties
|
||||
//
|
||||
/**
|
||||
* Function to handle deregistration of the modal as needed
|
||||
* @public
|
||||
* @type {function()?}
|
||||
*/
|
||||
$.pkp.controllers.form.CancelActionAjaxFormHandler.
|
||||
prototype.cancelActionHandler = null;
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The URL to be called when a cancel event occurs.
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.form.CancelActionAjaxFormHandler.
|
||||
prototype.cancelUrl_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* True iff the form is complete (i.e. a normal "Save" action is in progress).
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
$.pkp.controllers.form.CancelActionAjaxFormHandler.
|
||||
prototype.isComplete_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.form.CancelActionAjaxFormHandler.prototype.
|
||||
containerCloseHandler = function(input, event, informationObject) {
|
||||
|
||||
var result = /** @type {boolean} */ (
|
||||
this.parent('containerCloseHandler', input, event, informationObject));
|
||||
if (result) {
|
||||
this.handleCancelAction();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.form.CancelActionAjaxFormHandler.prototype.
|
||||
submitForm = function(validator, formElement) {
|
||||
|
||||
// Flag the form as complete.
|
||||
this.isComplete_ = true;
|
||||
this.parent('submitForm', validator, formElement);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cancel the form if necessary.
|
||||
*/
|
||||
$.pkp.controllers.form.CancelActionAjaxFormHandler.prototype.
|
||||
handleCancelAction = function() {
|
||||
|
||||
// Unregister the window unload listener
|
||||
if (this.cancelActionHandler !== null) {
|
||||
$(window).off('unload', this.cancelActionHandler);
|
||||
this.cancelActionHandler = null;
|
||||
}
|
||||
|
||||
// If the form wasn't completed, post a cancel.
|
||||
if (!this.isComplete_ && this.cancelUrl_ !== null) {
|
||||
$.post(this.cancelUrl_);
|
||||
}
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @file js/controllers/form/ClientFormHandler.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 ClientFormHandler
|
||||
* @ingroup js_controllers_form
|
||||
*
|
||||
* @brief Form handler that serializes the form on submission and
|
||||
* triggers a "formSubmitted" event with the form data. This enables
|
||||
* other widgets to use forms to request data from users although the
|
||||
* data is not meant to be sent to the server through the form.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.FormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
* @param {Object} options options to be passed
|
||||
* into the validator plug-in.
|
||||
*/
|
||||
$.pkp.controllers.form.ClientFormHandler = function($form, options) {
|
||||
options.submitHandler = this.submitForm;
|
||||
this.parent($form, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.form.ClientFormHandler,
|
||||
$.pkp.controllers.form.FormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Internal callback called after form validation to handle form
|
||||
* submission.
|
||||
*
|
||||
* @param {Object} validator The validator plug-in.
|
||||
* @param {HTMLElement} formElement The wrapped HTML form.
|
||||
*/
|
||||
$.pkp.controllers.form.ClientFormHandler.prototype.submitForm =
|
||||
function(validator, formElement) {
|
||||
var $form, formData;
|
||||
|
||||
// This form implementation will trigger an event
|
||||
// with the form data.
|
||||
$form = this.getHtmlElement();
|
||||
|
||||
// Retrieve form data.
|
||||
formData = $form.serializeArray();
|
||||
|
||||
// Inform the server that the form has been submitted.
|
||||
formData.push({name: 'clientSubmit', value: true});
|
||||
|
||||
// Trigger a "form submitted" event with the form
|
||||
// data as argument.
|
||||
this.trigger('formSubmitted', [$.param(formData)]);
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* @file js/controllers/form/FileUploadFormHandler.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 FileUploadFormHandler
|
||||
* @ingroup js_controllers_form
|
||||
*
|
||||
* @brief File upload form handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.AjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form The wrapped HTML form element.
|
||||
* @param {{
|
||||
* readOnly: boolean,
|
||||
* resetUploader: boolean,
|
||||
* $uploader: jQueryObject,
|
||||
* $preview: jQueryObject,
|
||||
* uploaderOptions: Object
|
||||
* }} options Form validation options.
|
||||
*/
|
||||
$.pkp.controllers.form.FileUploadFormHandler =
|
||||
function($form, options) {
|
||||
|
||||
this.parent($form, options);
|
||||
|
||||
if (options.readOnly === undefined || options.readOnly === null) {
|
||||
if (options.resetUploader !== undefined) {
|
||||
this.resetUploader_ = options.resetUploader;
|
||||
}
|
||||
|
||||
// An optional preview container for the file. If this option is passed
|
||||
// the preview container will be hidden when a new file is uploaded and
|
||||
// when the `fileDeleted` event is fired.
|
||||
if (options.$preview !== undefined && options.$preview.length) {
|
||||
this.$preview = options.$preview;
|
||||
this.bind('fileDeleted', this.callbackWrapper(this.fileDeleted));
|
||||
}
|
||||
|
||||
// Attach the uploader handler to the uploader HTML element.
|
||||
this.attachUploader_(options.$uploader, options.uploaderOptions);
|
||||
|
||||
this.uploaderSetup(options.$uploader);
|
||||
}
|
||||
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.form.FileUploadFormHandler,
|
||||
$.pkp.controllers.form.AjaxFormHandler);
|
||||
|
||||
|
||||
/**
|
||||
* Reset the uploader widget flag.
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
$.pkp.controllers.form.FileUploadFormHandler.prototype.
|
||||
resetUploader_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The file preview DOM element. A jQuery object when available
|
||||
* @protected
|
||||
* @type {boolean|jQueryObject}
|
||||
*/
|
||||
$.pkp.controllers.form.FileUploadFormHandler.prototype.
|
||||
$preview = false;
|
||||
|
||||
|
||||
//
|
||||
// Extended methods from AjaxFormHandler.
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.form.FileUploadFormHandler.prototype.handleResponse =
|
||||
function(formElement, jsonData) {
|
||||
|
||||
var fileUploader;
|
||||
|
||||
if (this.resetUploader_) {
|
||||
fileUploader = $('#plupload', this.getHtmlElement())
|
||||
.plupload('getUploader');
|
||||
fileUploader.splice();
|
||||
fileUploader.refresh();
|
||||
|
||||
// Reset the temporary file id value.
|
||||
$('#temporaryFileId', this.getHtmlElement()).val('');
|
||||
}
|
||||
|
||||
return /** @type {boolean} */ (
|
||||
this.parent('handleResponse', formElement, jsonData));
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* The setup callback of the uploader.
|
||||
* @param {jQueryObject} $uploader Element that contains the plupload object.
|
||||
*/
|
||||
$.pkp.controllers.form.FileUploadFormHandler.prototype.
|
||||
uploaderSetup = function($uploader) {
|
||||
|
||||
var uploadHandler = $.pkp.classes.Handler.getHandler($uploader);
|
||||
// Subscribe to uploader events.
|
||||
uploadHandler.pluploader.bind('FileUploaded',
|
||||
this.callbackWrapper(this.handleUploadResponse));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the response of a "file upload" request.
|
||||
* @param {Object} caller The original context in which the callback was called.
|
||||
* @param {Object} pluploader The pluploader object.
|
||||
* @param {Object} file The data of the uploaded file.
|
||||
* @param {{response: string}} ret The serialized JSON response.
|
||||
*/
|
||||
$.pkp.controllers.form.FileUploadFormHandler.prototype.
|
||||
handleUploadResponse = function(caller, pluploader, file, ret) {
|
||||
|
||||
// Handle the server's JSON response.
|
||||
var jsonData = /** @type {boolean|{uploadedFile: Object,
|
||||
temporaryFileId: string, content: string}} */
|
||||
(this.handleJson($.parseJSON(ret.response))),
|
||||
$uploadForm, $temporaryFileId;
|
||||
if (jsonData !== false) {
|
||||
// Trigger the file uploaded event.
|
||||
this.trigger('fileUploaded', [jsonData.uploadedFile]);
|
||||
|
||||
// Hide preview if one exists
|
||||
if (this.$preview) {
|
||||
this.$preview.hide();
|
||||
}
|
||||
|
||||
if (jsonData.content === '') {
|
||||
// Successful upload to temporary file; save to main form.
|
||||
$uploadForm = this.getHtmlElement();
|
||||
$temporaryFileId = $uploadForm.find('#temporaryFileId');
|
||||
$temporaryFileId.val(jsonData.temporaryFileId);
|
||||
} else {
|
||||
// Display the revision confirmation form.
|
||||
this.replaceWith(jsonData.content);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires when the file has been removed
|
||||
*/
|
||||
$.pkp.controllers.form.FileUploadFormHandler.prototype.
|
||||
fileDeleted = function() {
|
||||
|
||||
if (this.$preview) {
|
||||
this.$preview.hide();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Attach the uploader handler.
|
||||
* @private
|
||||
* @param {jQueryObject} $uploader The wrapped HTML uploader element.
|
||||
* @param {Object} options Uploader options.
|
||||
*/
|
||||
$.pkp.controllers.form.FileUploadFormHandler.prototype.
|
||||
attachUploader_ = function($uploader, options) {
|
||||
|
||||
// Attach the uploader handler to the uploader div.
|
||||
$uploader.pkpHandler('$.pkp.controllers.UploaderHandler', options);
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,640 @@
|
||||
/**
|
||||
* @defgroup js_controllers_form
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/form/FormHandler.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 FormHandler
|
||||
* @ingroup js_controllers_form
|
||||
*
|
||||
* @brief Abstract form handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.form = $.pkp.controllers.form || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
* @param {{
|
||||
* transformButtons: boolean,
|
||||
* submitHandler: Function,
|
||||
* cancelRedirectUrl: string,
|
||||
* disableControlsOnSubmit: boolean,
|
||||
* trackFormChanges: boolean,
|
||||
* enableDisablePairs: Object
|
||||
* }} options options to configure the form handler.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler = function($form, options) {
|
||||
var key, validator;
|
||||
|
||||
this.parent($form, options);
|
||||
|
||||
// Check whether we really got a form.
|
||||
if (!$form.is('form')) {
|
||||
throw new Error(['A form handler controller can only be bound',
|
||||
' to an HTML form element!'].join(''));
|
||||
}
|
||||
|
||||
// Activate and configure the validation plug-in.
|
||||
if (options.submitHandler) {
|
||||
this.callerSubmitHandler_ = options.submitHandler;
|
||||
}
|
||||
|
||||
// Handle datepicker
|
||||
// If we find a .datepicker in tpl the code adds (form/textInput.tpl) a new
|
||||
// hidden field with the id = <original-element-id>-altField attr:
|
||||
// data-date-format = date format from config.inc.php translated into JQuery
|
||||
// Datepicker date format. The <original-element-name> if changed to
|
||||
// <original-element-name>-removed in order for the hidden field value to
|
||||
// send to post request altField and altFormat are used in order for the user
|
||||
// to have its config.inc.php dateFormatShort parameter displayed but
|
||||
// 'yy-mm-dd' to be send to post request.
|
||||
// [http://api.jqueryui.com/datepicker/#option-altField]
|
||||
$form.find('.datepicker').each(function() {
|
||||
var $this = $(this);
|
||||
$this.datepicker({
|
||||
altField: '#' + $this.prop('id') + '-altField',
|
||||
altFormat: 'yy-mm-dd',
|
||||
dateFormat: $('#' + $this.prop('id') + '-altField')
|
||||
.attr('data-date-format')
|
||||
});
|
||||
|
||||
$this.prop('name', $this.prop('name') + '-removed');
|
||||
});
|
||||
|
||||
|
||||
// Set the redirect-to URL for the cancel button (if there is one).
|
||||
if (options.cancelRedirectUrl) {
|
||||
this.cancelRedirectUrl_ = options.cancelRedirectUrl;
|
||||
}
|
||||
|
||||
// specific forms may override the form's default behavior
|
||||
// to warn about unsaved changes.
|
||||
if (typeof options.trackFormChanges !== 'undefined') {
|
||||
this.trackFormChanges = options.trackFormChanges;
|
||||
}
|
||||
|
||||
// disable submission controls on certain forms.
|
||||
if (options.disableControlsOnSubmit) {
|
||||
this.disableControlsOnSubmit = options.disableControlsOnSubmit;
|
||||
}
|
||||
|
||||
// Items that should enable or disable each other.
|
||||
if (options.enableDisablePairs) {
|
||||
this.enableDisablePairs_ = options.enableDisablePairs;
|
||||
this.setupEnableDisablePairs();
|
||||
}
|
||||
// Update enable disable pairs state.
|
||||
for (key in this.enableDisablePairs_) {
|
||||
$form.find("[id^='" + key + "']").trigger('updatePair');
|
||||
}
|
||||
|
||||
validator = $form.validate({
|
||||
onfocusout: this.callbackWrapper(this.onFocusOutValidation_),
|
||||
errorClass: 'error',
|
||||
highlight: function(element, errorClass) {
|
||||
$(element).parent().parent().addClass(errorClass);
|
||||
},
|
||||
unhighlight: function(element, errorClass) {
|
||||
$(element).parent().parent().removeClass(errorClass);
|
||||
},
|
||||
submitHandler: this.callbackWrapper(this.submitHandler_),
|
||||
showErrors: this.callbackWrapper(this.showErrors),
|
||||
errorPlacement: function(error, element) {
|
||||
if (element.is(':checkbox')) {
|
||||
// place error after checkbox text
|
||||
element.parent().closest(':not(label)').append(error);
|
||||
} else {
|
||||
// default jquery validate placement
|
||||
error.insertAfter(element);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Activate the cancel button (if present).
|
||||
$('[id^=\'cancelFormButton-\']', $form)
|
||||
.click(this.callbackWrapper(this.cancelForm));
|
||||
|
||||
$form.find('.showMore, .showLess').bind('click', this.switchViz);
|
||||
|
||||
// Initial form validation.
|
||||
if (validator.checkForm()) {
|
||||
this.trigger('formValid');
|
||||
} else {
|
||||
this.trigger('formInvalid');
|
||||
}
|
||||
|
||||
this.initializeTinyMCE();
|
||||
|
||||
// bind a handler to make sure tinyMCE fields are populated.
|
||||
$('[id^=\'submitFormButton\']', $form).click(this.callbackWrapper(
|
||||
this.pushTinyMCEChanges_));
|
||||
|
||||
// bind a handler to handle change events on input fields.
|
||||
// 1. For normal inputs...
|
||||
$(':input', $form).change(this.callbackWrapper(this.formChange));
|
||||
// 2. For other kinds of controls like listbuilders
|
||||
this.bind('formChange', this.callbackWrapper(this.formChange));
|
||||
|
||||
// ensure that date picker modals are hidden when clicked away from.
|
||||
$form.click(this.callbackWrapper(this.hideDatepicker_));
|
||||
|
||||
this.publishEvent('tinyMCEInitialized');
|
||||
this.bind('tinyMCEInitialized', this.tinyMCEInitHandler_);
|
||||
this.bind('containerClose', this.containerCloseHandler);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.form.FormHandler,
|
||||
$.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Protected properties
|
||||
//
|
||||
/**
|
||||
* If true, the FormHandler will disable the submit button if the form
|
||||
* successfully validates and is submitted.
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.disableControlsOnSubmit = false;
|
||||
|
||||
|
||||
/**
|
||||
* By default, all FormHandler instances and subclasses track changes to
|
||||
* form data.
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.trackFormChanges = true;
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* If provided, the caller's submit handler, which will be
|
||||
* triggered to save the form.
|
||||
* @private
|
||||
* @type {Function?}
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.callerSubmitHandler_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* If provided, the URL to redirect to when the cancel button is clicked
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.cancelRedirectUrl_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Only submit a track event for this form once.
|
||||
* @type {boolean}
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.formChangesTracked = false;
|
||||
|
||||
|
||||
/**
|
||||
* An object containing items that should enable or disable each other.
|
||||
* @private
|
||||
* @type {Object?}
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.enableDisablePairs_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Internal callback called whenever the validator has to show form errors.
|
||||
*
|
||||
* @param {Object} validator The validator plug-in.
|
||||
* @param {Object} errorMap An associative list that attributes
|
||||
* element names to error messages.
|
||||
* @param {Array} errorList An array with objects that contains
|
||||
* error messages and the corresponding HTMLElements.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.showErrors =
|
||||
function(validator, errorMap, errorList) {
|
||||
// ensure that rich content elements have their
|
||||
// values stored before validation.
|
||||
if (typeof tinyMCE !== 'undefined') {
|
||||
tinyMCE.EditorManager.triggerSave();
|
||||
}
|
||||
|
||||
// Clone the form validator before checking the entire form.
|
||||
// We need to clone otherwise the internal validator error
|
||||
// list will be changed when the show errors timeout is
|
||||
// executed, showing/hiding the wrong errors.
|
||||
var validatorClone = $.extend(true, {}, validator);
|
||||
|
||||
// Show errors generated by the form change.
|
||||
// Use a timer so we make sure that concurrent triggered events
|
||||
// are handled before the error messages appear in the UI.
|
||||
//
|
||||
// The main issue is a click event in cancel buttons while a non
|
||||
// valid field is focused. Without the timer, the UI is changed
|
||||
// before the click action is complete (the mouse up will not occur in
|
||||
// the cancel link, because it will be moved by the error messages).
|
||||
setTimeout(this.callbackWrapper(function() {
|
||||
validatorClone.defaultShowErrors();
|
||||
validatorClone = null;
|
||||
}), 250);
|
||||
|
||||
// Emit validation events.
|
||||
if (validator.checkForm()) {
|
||||
// Trigger a "form valid" event.
|
||||
this.trigger('formValid');
|
||||
} else {
|
||||
// Trigger a "form invalid" event.
|
||||
this.trigger('formInvalid');
|
||||
this.enableFormControls();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback called when a form element changes.
|
||||
*
|
||||
* @param {HTMLElement} formElement The form element that generated the event.
|
||||
* @param {Event} event The formChange event.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.formChange =
|
||||
function(formElement, event) {
|
||||
if (this.trackFormChanges && !this.formChangesTracked) {
|
||||
this.formChangesTracked = true;
|
||||
this.trigger('formChanged');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* Protected method to disable a form's submit control if it is
|
||||
* desired.
|
||||
*
|
||||
* @return {boolean} true.
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.disableFormControls =
|
||||
function() {
|
||||
|
||||
// We have made it to submission, disable the form control if
|
||||
// necessary, submit the form.
|
||||
if (this.disableControlsOnSubmit) {
|
||||
this.getHtmlElement().find(':submit').attr('disabled', 'disabled').
|
||||
addClass('ui-state-disabled');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Protected method to reenable a form's submit control if it is
|
||||
* desired.
|
||||
*
|
||||
* @return {boolean} true.
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.enableFormControls =
|
||||
function() {
|
||||
|
||||
this.getHtmlElement().find(':submit').removeAttr('disabled').
|
||||
removeClass('ui-state-disabled');
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback called to cancel the form.
|
||||
*
|
||||
* @param {HTMLElement} cancelButton The cancel button.
|
||||
* @param {Event} event The event that triggered the
|
||||
* cancel button.
|
||||
* @return {boolean} false.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.cancelForm =
|
||||
function(cancelButton, event) {
|
||||
|
||||
this.unregisterForm();
|
||||
this.trigger('formCanceled');
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unregister form for tracking changed data.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.unregisterForm =
|
||||
function() {
|
||||
// Trigger the "form canceled" event and unregister the form.
|
||||
this.formChangesTracked = false;
|
||||
this.trigger('unregisterChangedForm');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Configures the enable/disable pair bindings between a checkbox
|
||||
* and some other form element.
|
||||
*
|
||||
* @return {boolean} true.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.setupEnableDisablePairs =
|
||||
function() {
|
||||
|
||||
var formElement = this.getHtmlElement(), key;
|
||||
for (key in this.enableDisablePairs_) {
|
||||
$(formElement).find("[id^='" + key + "']").bind(
|
||||
'click updatePair', this.callbackWrapper(this.toggleDependentElement_));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback called to submit the form
|
||||
* without further validation.
|
||||
*
|
||||
* @param {Object} validator The validator plug-in.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.submitFormWithoutValidation =
|
||||
function(validator) {
|
||||
|
||||
// NB: When setting a submitHandler in jQuery's validator
|
||||
// plugin then the submit event will always be canceled and our
|
||||
// return value will be ignored (see the handle() method in the
|
||||
// validator plugin). The only way around this seems to be unsetting
|
||||
// the submit handler before calling the submit method again.
|
||||
validator.settings.submitHandler = null;
|
||||
this.disableFormControls();
|
||||
this.getHtmlElement().submit();
|
||||
this.formChangesTracked = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a class to the spinner to indicate it should be hidden
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.hideSpinner =
|
||||
function() {
|
||||
this.getHtmlElement()
|
||||
.find('.formButtons .pkp_spinner').removeClass('is_visible');
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private Methods
|
||||
//
|
||||
/**
|
||||
* Internal callback called after form validation to handle form
|
||||
* submission.
|
||||
*
|
||||
* NB: Returning from this method without explicitly submitting
|
||||
* the form will cancel form submission.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} validator The validator plug-in.
|
||||
* @param {HTMLElement} formElement The wrapped HTML form.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.submitHandler_ =
|
||||
function(validator, formElement) {
|
||||
|
||||
// Notify any nested formWidgets of the submit action.
|
||||
var defaultPrevented = false;
|
||||
$(formElement).find('.formWidget').each(function() {
|
||||
var formSubmitEvent = new $.Event('formSubmitRequested');
|
||||
if (!defaultPrevented) {
|
||||
$(this).trigger(formSubmitEvent);
|
||||
defaultPrevented = formSubmitEvent.isDefaultPrevented();
|
||||
}
|
||||
});
|
||||
|
||||
// If the default behavior was prevented for any reason, stop.
|
||||
if (defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For datepicker controls, ensure that empty values are respected.
|
||||
$(formElement).find('.datepicker').each(function() {
|
||||
if ($(this).prop('value') === '') {
|
||||
$('#' + $(this).prop('id') + '-altField').prop('value', '');
|
||||
}
|
||||
});
|
||||
|
||||
this.showSpinner_();
|
||||
|
||||
this.trigger('unregisterChangedForm');
|
||||
|
||||
if (this.callerSubmitHandler_ !== null) {
|
||||
this.formChangesTracked = false;
|
||||
// A form submission handler (e.g. Ajax) was provided. Use it.
|
||||
this.callbackWrapper(this.callerSubmitHandler_).
|
||||
call(validator, formElement);
|
||||
} else {
|
||||
// No form submission handler was provided. Use the usual method.
|
||||
this.submitFormWithoutValidation(validator);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback called to push TinyMCE changes back to fields
|
||||
* so they can be validated.
|
||||
*
|
||||
* @param {HTMLElement} submitButton The submit button.
|
||||
* @param {Event} event The event that triggered the
|
||||
* submit button.
|
||||
* @return {boolean} true.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.pushTinyMCEChanges_ =
|
||||
function(submitButton, event) {
|
||||
|
||||
// ensure that rich content elements have their
|
||||
// values stored before validation.
|
||||
if (typeof tinyMCE !== 'undefined') {
|
||||
tinyMCE.EditorManager.triggerSave();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enables or disables the item which depends on the state of source of the
|
||||
* Event.
|
||||
* @param {HTMLElement} sourceElement The element which generated the event.
|
||||
* @param {Event} event The event.
|
||||
* @return {boolean} true.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.toggleDependentElement_ =
|
||||
function(sourceElement, event) {
|
||||
var formElement, elementId, targetElement;
|
||||
|
||||
formElement = this.getHtmlElement();
|
||||
elementId = $(sourceElement).attr('id');
|
||||
targetElement = $(formElement).find(
|
||||
"[id^='" + this.enableDisablePairs_[elementId] + "']");
|
||||
|
||||
if ($(sourceElement).is(':checked')) {
|
||||
$(targetElement).prop('disabled', false);
|
||||
} else {
|
||||
$(targetElement).prop('disabled', true);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind a blur handler on tinyMCE instances inside this form
|
||||
* to call form validation on form elements that stores the correspondent
|
||||
* tinyMCE editors.
|
||||
* @private
|
||||
* @param {HTMLElement} input The input element that triggered the
|
||||
* event.
|
||||
* @param {Event} event The tinyMCE initialized event.
|
||||
* @param {Object} tinyMCEObject An array containing the tinyMCE object inside
|
||||
* this multilingual element handler that was initialized.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.tinyMCEInitHandler_ =
|
||||
function(input, event, tinyMCEObject) {
|
||||
|
||||
var editorId = tinyMCEObject.id;
|
||||
|
||||
tinyMCEObject.on('blur', this.callbackWrapper(function(tinyMCEObject) {
|
||||
// Save the current tinyMCE value to the form element.
|
||||
tinyMCEObject.save();
|
||||
|
||||
// Get the form element that stores the tinyMCE data.
|
||||
var $form = this.getHtmlElement(),
|
||||
formElement = $('#' +
|
||||
$.pkp.classes.Helper.escapeJQuerySelector(editorId), $form),
|
||||
// Validate only this element.
|
||||
validator = $form.validate();
|
||||
|
||||
validator.element(formElement);
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind a handler for container (e.g. modal) close events to permit forms
|
||||
* to clean up.blur handler on tinyMCE instances inside this form
|
||||
* @param {HTMLElement} input The input element that triggered the
|
||||
* event.
|
||||
* @param {Event} event The initialized event.
|
||||
* @param {{closePermitted: boolean}} informationObject
|
||||
* @return {boolean} Event handling success.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.containerCloseHandler =
|
||||
function(input, event, informationObject) {
|
||||
|
||||
var $form = $(this.getHtmlElement());
|
||||
// prevent orphaned date pickers that may be still open.
|
||||
$form.find('.hasDatepicker').datepicker('hide');
|
||||
if (this.formChangesTracked) {
|
||||
if (!confirm(pkp.localeKeys['form.dataHasChanged'])) {
|
||||
if (informationObject) {
|
||||
informationObject.closePermitted = false;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
this.trigger('unregisterAllForms');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof informationObject !== 'undefined') {
|
||||
informationObject.closePermitted = true;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Blur event handler, attached to all input fields on this form
|
||||
* by the form validator. It's passed as an option when initializing
|
||||
* the validator.
|
||||
*
|
||||
* It will make sure that fields are always validated on blur. Without this
|
||||
* users can delete data from a required field and move to another one
|
||||
* without receiving any validation alert.
|
||||
* @private
|
||||
* @param {Object} validator Validator.
|
||||
* @param {Object} element Element.
|
||||
* @return {boolean} True so the blur event can still be handled.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.onFocusOutValidation_ =
|
||||
function(validator, element) {
|
||||
|
||||
var $form = this.getHtmlElement();
|
||||
// Make sure the element is still present in form.
|
||||
if ($(element).parents('#' + $form.attr('id')).length) {
|
||||
validator.element(element);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hide a date picker if a user clicks outside of the element.
|
||||
* @private
|
||||
* @param {Object} formElement Element.
|
||||
* @param {Event} event The event.
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.hideDatepicker_ =
|
||||
function(formElement, event) {
|
||||
|
||||
var originalEvent, ele, form;
|
||||
|
||||
originalEvent = event.originalEvent;
|
||||
if (typeof originalEvent == 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
ele = originalEvent.target;
|
||||
|
||||
form = this.getHtmlElement();
|
||||
if (!$(ele).hasClass('hasDatepicker') &&
|
||||
!$(ele).hasClass('ui-datepicker') &&
|
||||
!$(ele).hasClass('ui-icon') &&
|
||||
!$(ele).hasClass('ui-datepicker-next') &&
|
||||
!$(ele).hasClass('ui-datepicker-prev') &&
|
||||
!$(ele).parent().parents('.ui-datepicker').length) {
|
||||
$(form).find('.hasDatepicker').datepicker('hide');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a class to the spinner to indicate it should be visible
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.FormHandler.prototype.showSpinner_ =
|
||||
function() {
|
||||
this.getHtmlElement()
|
||||
.find('.formButtons .pkp_spinner').addClass('is_visible');
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* @file js/controllers/form/MultilingualInputHandler.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 MultilingualInputHandler
|
||||
* @ingroup js_controllers_form
|
||||
*
|
||||
* @brief Handler for toggling the pop-over on multi lingual inputs (text
|
||||
* inputs and text areas, mostly).
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $popover the wrapped HTML element.
|
||||
* @param {Object} options options to be passed
|
||||
* into the validator plug-in.
|
||||
*/
|
||||
$.pkp.controllers.form.MultilingualInputHandler = function($popover, options) {
|
||||
this.parent($popover, options);
|
||||
|
||||
// Bind to the focus of the primary language (the first input)
|
||||
// open the pop-over
|
||||
var $popoverNode = null;
|
||||
|
||||
if ($popover.hasClass('pkpTagit')) {
|
||||
$popoverNode = $popover.find(':input').filter(':visible');
|
||||
} else {
|
||||
$popoverNode = $popover.find(':input').first();
|
||||
}
|
||||
|
||||
$popoverNode
|
||||
.focus(this.callbackWrapper(this.focusHandler_));
|
||||
// Bind to the blur of any of the inputs to check if we should close.
|
||||
$popover.find(':input').
|
||||
blur(this.callbackWrapper(this.blurHandler_));
|
||||
|
||||
this.publishEvent('tinyMCEInitialized');
|
||||
|
||||
this.tinyMCEInitHandler_();
|
||||
|
||||
setTimeout(this.callbackWrapper(this.isIncomplete_), 500);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.form.MultilingualInputHandler,
|
||||
$.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Focus event handler. This is attached to all primary inputs.
|
||||
*
|
||||
* @param {HTMLElement} multilingualInput The primary multilingual
|
||||
* element.
|
||||
* @param {Event} event The focus event.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.MultilingualInputHandler.prototype.focusHandler_ =
|
||||
function(multilingualInput, event) {
|
||||
|
||||
this.showPopover_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Blur event handler. This is attached to all inputs inside this
|
||||
* popover element.
|
||||
*
|
||||
* @param {HTMLElement} multilingualInput The element in the
|
||||
* multilingual set to hide.
|
||||
* @param {Event} event The event that triggered the action.
|
||||
* @return {boolean} Return true to continue the event handling.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.MultilingualInputHandler.prototype.blurHandler_ =
|
||||
function(multilingualInput, event) {
|
||||
|
||||
// Use a timeout to give the other element a chance to acquire the focus.
|
||||
setTimeout(this.callbackWrapper(function() {
|
||||
if (!this.hasElementInFocus_()) {
|
||||
this.hidePopover_();
|
||||
}
|
||||
}), 100);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hide this popover.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.MultilingualInputHandler.prototype.hidePopover_ =
|
||||
function() {
|
||||
var $popover = this.getHtmlElement();
|
||||
$popover.removeClass('localization_popover_container_focus');
|
||||
$popover.find('.localization_popover').hide();
|
||||
this.isIncomplete_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show this popover.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.MultilingualInputHandler.prototype.showPopover_ =
|
||||
function() {
|
||||
var $popover = this.getHtmlElement();
|
||||
$popover.addClass('localization_popover_container_focus');
|
||||
|
||||
// Hack alert: setting width in JS because they do not line up otherwise.
|
||||
$popover.find('.localization_popover').width(
|
||||
/** @type {number} */ ($popover.width()));
|
||||
|
||||
// Show the pop over.
|
||||
$popover.find('.localization_popover').show();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Test if any of the elements inside this popover has focus.
|
||||
* @return {boolean} True iff an element is in focus.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.MultilingualInputHandler.prototype.hasElementInFocus_ =
|
||||
function() {
|
||||
|
||||
var $popover = this.getHtmlElement();
|
||||
|
||||
// Do the test.
|
||||
if ($popover.has(document.activeElement).length) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if the field is missing a localization
|
||||
*
|
||||
* Fields are not considered to be missing a localization unless at least
|
||||
* one of the localizations has an entry.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.MultilingualInputHandler.prototype.isIncomplete_ =
|
||||
function() {
|
||||
|
||||
var $popover = this.getHtmlElement(),
|
||||
$inputs = [],
|
||||
valuesCount = 0;
|
||||
|
||||
if (typeof tinyMCE === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track current values in the tinyMCE control
|
||||
if (this.getHtmlElement().find('.richContent').length) {
|
||||
$popover.find('textarea').each(function() {
|
||||
var id = $(this).attr('id'),
|
||||
tinymce;
|
||||
|
||||
$inputs.push($(this));
|
||||
tinymce = tinyMCE.EditorManager.get(/** @type {string} */(
|
||||
$(this).attr('id')));
|
||||
if (tinymce.getContent()) {
|
||||
valuesCount++;
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
$inputs = $popover.find(':input');
|
||||
$inputs.each(function() {
|
||||
if ($(this).val()) {
|
||||
valuesCount++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (valuesCount > 0 && valuesCount < $inputs.length) {
|
||||
$popover.removeClass('localizationComplete')
|
||||
.addClass('localizationIncomplete');
|
||||
} else if (valuesCount === $inputs.length) {
|
||||
$popover.removeClass('localizationIncomplete')
|
||||
.addClass('localizationComplete');
|
||||
} else {
|
||||
$popover.removeClass('localizationIncomplete localizationComplete');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attach focus and blur event handlers to the tinyMCE window element, which
|
||||
* show and hide the localization popover
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.MultilingualInputHandler.prototype.tinyMCEInitHandler_ =
|
||||
function() {
|
||||
|
||||
if (!this.getHtmlElement().find('.richContent').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof tinyMCE === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var htmlElement = this.getHtmlElement(),
|
||||
tinyMCEObject = tinyMCE.EditorManager.get(/** @type {string} */(
|
||||
htmlElement.find('textarea').first().attr('id')));
|
||||
|
||||
tinyMCEObject.on('focus', this.callbackWrapper(function() {
|
||||
// We need also to close the multilingual popover when user clicks
|
||||
// outside the popover element. The blur event is not enough because
|
||||
// sometimes (with text selected in editor) Chrome will consider the
|
||||
// tinyMCE editor as still active and that will avoid the popover to
|
||||
// close (see the first check of the blur handler, just above).
|
||||
//
|
||||
// Firefox will also not completely focus on tinyMCE editors after
|
||||
// coming back from fullscreen mode (the callback to focus the
|
||||
// editor when set content will only trigger the focus handler that
|
||||
// we attach here, but will not move the cursor inside the tinyMCE
|
||||
// editor). Then, if user clicks outside the popover, it will not
|
||||
// close because no blur event will be triggered.
|
||||
this.trigger('callWhenClickOutside', {
|
||||
container: this.getHtmlElement(),
|
||||
callback: this.callbackWrapper(this.hidePopover_)
|
||||
});
|
||||
|
||||
this.showPopover_();
|
||||
}));
|
||||
|
||||
tinyMCEObject.on('blur', this.callbackWrapper(function() {
|
||||
// Check if the active document element is still the tinyMCE
|
||||
// editor. If true, return false. This will avoid closing the
|
||||
// popover if user is just inserting an image or editing the
|
||||
// html source, for example (both actions open a new window).
|
||||
if ($(tinyMCEObject.getContainer()).find('iframe').attr('id') ==
|
||||
$(document.activeElement).attr('id')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use a timeout to give the other element a chance to acquire the
|
||||
// focus.
|
||||
setTimeout(this.callbackWrapper(function() {
|
||||
if (!this.hasElementInFocus_()) {
|
||||
this.hidePopover_();
|
||||
}
|
||||
}), 0);
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @file js/controllers/form/ToggleFormHandler.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 ToggleFormHandler
|
||||
* @ingroup js_controllers_form
|
||||
*
|
||||
* @brief Extension to ClientFormHandler that accepts a checkbox click as a
|
||||
* submit action.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.ClientFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
*/
|
||||
$.pkp.controllers.form.ToggleFormHandler =
|
||||
function($form) {
|
||||
this.parent($form, {trackFormChanges: false});
|
||||
$form.change(
|
||||
this.callbackWrapper(this.toggleHandler_));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.form.ToggleFormHandler,
|
||||
$.pkp.controllers.form.ClientFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Click handler for the checkbox.
|
||||
* @private
|
||||
* @return {boolean} Always returns true.
|
||||
*/
|
||||
$.pkp.controllers.form.ToggleFormHandler.
|
||||
prototype.toggleHandler_ = function() {
|
||||
this.getHtmlElement().submit();
|
||||
return true;
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* @file js/controllers/form/UserFormHandler.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 UserFormHandler
|
||||
* @ingroup js_controllers_form
|
||||
*
|
||||
* @brief Add tools to the AjaxFormHandler facilitating user creation/editing.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.AjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
* @param {{
|
||||
* fetchUsernameSuggestionUrl: string,
|
||||
* usernameSuggestionTextAlert: string,
|
||||
* hideNonReviewerInterests: boolean
|
||||
* }} options options to configure the form handler.
|
||||
*/
|
||||
$.pkp.controllers.form.UserFormHandler = function($form, options) {
|
||||
this.parent($form, options);
|
||||
|
||||
// Set data for suggesting usernames. Both keys should be present.
|
||||
if (options.fetchUsernameSuggestionUrl &&
|
||||
options.usernameSuggestionTextAlert) {
|
||||
this.fetchUsernameSuggestionUrl_ = options.fetchUsernameSuggestionUrl;
|
||||
this.usernameSuggestionTextAlert_ = options.usernameSuggestionTextAlert;
|
||||
}
|
||||
|
||||
// Attach handler to suggest username button (if present)
|
||||
$('[id^="suggestUsernameButton"]', $form).click(
|
||||
this.callbackWrapper(this.generateUsername));
|
||||
|
||||
if (options.hideNonReviewerInterests) {
|
||||
$('[id^="reviewerGroup-"]', $form).click(
|
||||
this.callbackWrapper(this.setInterestsVisibility_));
|
||||
this.setInterestsVisibility_();
|
||||
}
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.form.UserFormHandler,
|
||||
$.pkp.controllers.form.AjaxFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The URL to be called to fetch a username suggestion.
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
$.pkp.controllers.form.UserFormHandler.
|
||||
prototype.fetchUsernameSuggestionUrl_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* The message that will be displayed if users click on suggest
|
||||
* username button with no data in family name.
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
$.pkp.controllers.form.UserFormHandler.
|
||||
prototype.usernameSuggestionTextAlert_ = '';
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* Event handler that is called when the suggest username button is clicked.
|
||||
*
|
||||
* @param {HTMLElement} el clicked by this event
|
||||
* @param {Event} e triggered
|
||||
*/
|
||||
$.pkp.controllers.form.UserFormHandler.prototype.
|
||||
generateUsername = function(el, e) {
|
||||
|
||||
// Don't submit the form!
|
||||
e.preventDefault();
|
||||
|
||||
var $form = this.getHtmlElement(),
|
||||
givenName, familyName, fetchUrl, sitePrimaryLocale;
|
||||
|
||||
// Fetch entered names
|
||||
sitePrimaryLocale =
|
||||
/** @type {string} */ ($('[name="sitePrimaryLocale"]', $form).val());
|
||||
givenName = /** @type {string} */ ($('[name="givenName[' +
|
||||
sitePrimaryLocale + ']"]', $form).val());
|
||||
familyName = /** @type {string} */ ($('[name="familyName[' +
|
||||
sitePrimaryLocale + ']"]', $form).val());
|
||||
|
||||
// Replace dummy values in the URL with entered values
|
||||
fetchUrl = this.fetchUsernameSuggestionUrl_.
|
||||
replace('GIVEN_NAME_PLACEHOLDER', givenName).
|
||||
replace('FAMILY_NAME_PLACEHOLDER', familyName);
|
||||
|
||||
$.get(fetchUrl, this.callbackWrapper(this.setUsername), 'json');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check JSON message and set it to username, back on form.
|
||||
* @param {HTMLElement} formElement The Form HTML element.
|
||||
* @param {JSONType} jsonData The jsonData response.
|
||||
*/
|
||||
$.pkp.controllers.form.UserFormHandler.prototype.
|
||||
setUsername = function(formElement, jsonData) {
|
||||
|
||||
var processedJsonData = this.handleJson(jsonData),
|
||||
$form = this.getHtmlElement();
|
||||
|
||||
if (processedJsonData === false) {
|
||||
throw new Error('JSON response must be set to true!');
|
||||
}
|
||||
|
||||
// Re-validate the field
|
||||
$('[id^="username"]', $form).val(processedJsonData.content)
|
||||
.trigger('blur');
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Event handler that is called when a reviewer role checkbox is clicked.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.UserFormHandler.prototype.
|
||||
setInterestsVisibility_ = function() {
|
||||
var $form = this.getHtmlElement(), $interestsElement = $('#interests', $form);
|
||||
if ($('[id^="reviewerGroup-"]:checked', $form).size()) {
|
||||
// At least one checked reviewer role was found; show interests
|
||||
$interestsElement.show(300);
|
||||
} else {
|
||||
// No checked reviewer roles found; hide interests
|
||||
$interestsElement.hide(300);
|
||||
}
|
||||
};
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* @defgroup js_controllers_form_reviewer
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/form/reviewer/ReviewerReviewStep3FormHandler.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 ReviewerReviewStep3FormHandler
|
||||
* @ingroup js_controllers_form_reviewer
|
||||
*
|
||||
* @brief Reviewer step 3 form handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.form.reviewer =
|
||||
$.pkp.controllers.form.reviewer || { };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.AjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $formElement A wrapped HTML element that
|
||||
* represents the approved proof form interface element.
|
||||
* @param {Object} options Tabbed modal options.
|
||||
*/
|
||||
$.pkp.controllers.form.reviewer.ReviewerReviewStep3FormHandler =
|
||||
function($formElement, options) {
|
||||
this.parent($formElement, options);
|
||||
|
||||
// bind a handler to make sure we update the required state
|
||||
// of the comments field.
|
||||
$formElement.find('[id^=\'submitFormButton-\']').click(this.callbackWrapper(
|
||||
this.updateCommentsRequired_));
|
||||
$formElement.find('[type^=\'submit\']').click(this.callbackWrapper(
|
||||
this.updateRecommendationRequired_));
|
||||
$formElement.find('[type^=\'submit\']').click(this.callbackWrapper(
|
||||
this.updateSaveOrSubmit_));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.form.reviewer.ReviewerReviewStep3FormHandler,
|
||||
$.pkp.controllers.form.AjaxFormHandler
|
||||
);
|
||||
|
||||
|
||||
//
|
||||
// Private methods.
|
||||
//
|
||||
/**
|
||||
* Internal callback called before form validation to ensure the
|
||||
* proper "required" state of the Recommendation field
|
||||
*
|
||||
* @param {HTMLElement} submitButton The submit button.
|
||||
* @param {Event} event The event that triggered the
|
||||
* submit button.
|
||||
* @return {boolean} true.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.reviewer.ReviewerReviewStep3FormHandler.
|
||||
prototype.updateRecommendationRequired_ = function(submitButton, event) {
|
||||
|
||||
var $formElement = this.getHtmlElement(),
|
||||
$recommendationElement = $formElement.find('[id^="recommendation"]');
|
||||
if ($recommendationElement.length) {
|
||||
if (submitButton.id.includes('submitFormButton-')) {
|
||||
$recommendationElement.attr('required', '1');
|
||||
} else {
|
||||
$recommendationElement.removeAttr('required');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback called before form validation to ensure the
|
||||
* proper "required" state of the comments field, depending on grid
|
||||
* contents.
|
||||
*
|
||||
* @param {HTMLElement} submitButton The submit button.
|
||||
* @param {Event} event The event that triggered the
|
||||
* submit button.
|
||||
* @return {boolean} true.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.reviewer.ReviewerReviewStep3FormHandler.
|
||||
prototype.updateCommentsRequired_ = function(submitButton, event) {
|
||||
|
||||
var $formElement = this.getHtmlElement(),
|
||||
$commentsElement = $formElement.find('[id^="comments"]');
|
||||
|
||||
if ($('#reviewAttachmentsGridContainer').
|
||||
find('tbody.empty:visible').length == 1) {
|
||||
// There's nothing in the files grid; make sure the
|
||||
// comments field is required.
|
||||
$commentsElement.attr('required', '1');
|
||||
} else {
|
||||
// There's something in the files grid; the comments
|
||||
// field is optional.
|
||||
$commentsElement.removeAttr('required');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback called before form validation to ensure the
|
||||
* proper handling of the save vs. submit button, using a hidden
|
||||
* field.
|
||||
*
|
||||
* @param {HTMLElement} submitButton The submit button.
|
||||
* @param {Event} event The event that triggered the
|
||||
* submit button.
|
||||
* @return {boolean} true.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.form.reviewer.ReviewerReviewStep3FormHandler.
|
||||
prototype.updateSaveOrSubmit_ = function(submitButton, event) {
|
||||
|
||||
var $formElement = this.getHtmlElement();
|
||||
switch ($(submitButton).attr('name')) {
|
||||
case 'submitFormButton':
|
||||
$formElement.find('input[name="isSave"]').val('0');
|
||||
break;
|
||||
case 'saveFormButton':
|
||||
$formElement.find('input[name="isSave"]').val('1');
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,411 @@
|
||||
/**
|
||||
* @file js/controllers/grid/CategoryGridHandler.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 CategoryGridHandler
|
||||
* @ingroup js_controllers_grid
|
||||
*
|
||||
* @brief Category grid handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.grid.GridHandler
|
||||
*
|
||||
* @param {jQueryObject} $grid The grid this handler is
|
||||
* attached to.
|
||||
* @param {Object} options Grid handler configuration.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler = function($grid, options) {
|
||||
this.parent($grid, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.grid.CategoryGridHandler,
|
||||
$.pkp.controllers.grid.GridHandler);
|
||||
|
||||
|
||||
//
|
||||
// Public methods.
|
||||
//
|
||||
/**
|
||||
* Get category id prefix.
|
||||
* @return {string} Category id prefix.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategoryIdPrefix =
|
||||
function() {
|
||||
return this.getGridIdPrefix() + '-category-';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get categories tbody element.
|
||||
* @return {jQueryObject} Categories tbody elements.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategories =
|
||||
function() {
|
||||
return $('.category_grid_body:not(.empty)',
|
||||
this.getHtmlElement());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a category tbody element by category data id.
|
||||
* @param {string} categoryDataId The category data id.
|
||||
* @return {jQueryObject} Category tbody element.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategoryByDataId =
|
||||
function(categoryDataId) {
|
||||
return $('#' + this.getCategoryIdPrefix() + categoryDataId);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the category row inside a tbody category element. If none element
|
||||
* is passed, get all grid category rows.
|
||||
* @param {jQueryObject} $opt_category Category tbody element.
|
||||
* @return {jQueryObject} Category rows.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategoryRow =
|
||||
function($opt_category) {
|
||||
var $context = this.getHtmlElement();
|
||||
if ($opt_category !== undefined) {
|
||||
$context = $opt_category;
|
||||
}
|
||||
|
||||
return $('tr.category', $context);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get rows inside a tbody category element, excluding the category row.
|
||||
* @param {jQueryObject} $category Category tbody element.
|
||||
* @return {jQueryObject} Category rows.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getRowsInCategory =
|
||||
function($category) {
|
||||
return $('tr.gridRow', $category).not('.category');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the category empty placeholder.
|
||||
* @param {jQueryObject} $category A grid category element.
|
||||
* @return {jQueryObject} The category empty placeholder.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.
|
||||
getCategoryEmptyPlaceholder = function($category) {
|
||||
var selector = '#' + $category.attr('id') + '-emptyPlaceholder';
|
||||
return $(selector, this.getHtmlElement());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the category data id by the passed category element.
|
||||
* @param {jQueryObject} $category Category element.
|
||||
* @return {string} Category data id.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategoryDataId =
|
||||
function($category) {
|
||||
var categoryId = $category.attr('id'),
|
||||
startExtractPosition = this.getCategoryIdPrefix().length;
|
||||
|
||||
return /** @type {string} */ (categoryId.slice(startExtractPosition));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the category data id by the passed row element id.
|
||||
* @param {string} gridRowId Category row element id.
|
||||
* @return {string} Category data id.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getCategoryDataIdByRowId =
|
||||
function(gridRowId) {
|
||||
// Remove the category id prefix to avoid getting wrong data.
|
||||
gridRowId = gridRowId.replace(this.getCategoryIdPrefix(), ' ');
|
||||
|
||||
// Get the category data id.
|
||||
var categoryDataId = gridRowId.match('(.*)-row');
|
||||
return $.trim(categoryDataId[1]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the id prefix of the grid row inside a category.
|
||||
* @return {string}
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getRowIdPrefix =
|
||||
function() {
|
||||
return this.getGridIdPrefix() + '-category-';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the grid row by the passed data element id.
|
||||
* @param {number} rowDataId
|
||||
* @return {jQueryObject}
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getRowByDataId =
|
||||
function(rowDataId) {
|
||||
this.parent('getRowByDataId', rowDataId);
|
||||
return $('#' + this.getRowIdPrefix() + this.currentCategoryId_ +
|
||||
'-row-' + rowDataId, this.getHtmlElement());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the data element id of the passed grid row.
|
||||
* @param {jQueryObject} $gridRow The grid row JQuery object.
|
||||
* @return {string} The data element id of the passed grid row.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getRowDataId =
|
||||
function($gridRow) {
|
||||
var rowDataId;
|
||||
rowDataId = $gridRow.attr('id').
|
||||
slice(this.getRowIdPrefix().length);
|
||||
rowDataId = rowDataId.match('-row-(.*)');
|
||||
return /** @type {string} */ ($.trim(rowDataId[1]));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Append a category to the end of the list.
|
||||
* @param {jQueryObject} $category Category to append.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.appendCategory =
|
||||
function($category) {
|
||||
var $gridBody = this.getHtmlElement().find(this.bodySelector);
|
||||
$gridBody.append($category);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Re-sequence all category elements based on the passed sequence map.
|
||||
* @param {Array} sequenceMap A sequence array with the category
|
||||
* element id as value.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.resequenceCategories =
|
||||
function(sequenceMap) {
|
||||
var categoryId, index, $category;
|
||||
for (index in sequenceMap) {
|
||||
categoryId = sequenceMap[index];
|
||||
$category = $('#' + categoryId);
|
||||
this.appendCategory($category);
|
||||
}
|
||||
|
||||
this.updateEmptyPlaceholderPosition();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Move all empty category placeholders to their correct position,
|
||||
* below of each correspondent category element.
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.
|
||||
updateEmptyPlaceholderPosition = function() {
|
||||
var $categories = this.getCategories(),
|
||||
index, limit,
|
||||
$category, $emptyPlaceholder;
|
||||
for (index = 0, limit = $categories.length; index < limit; index++) {
|
||||
$category = $($categories[index]);
|
||||
$emptyPlaceholder = this.getCategoryEmptyPlaceholder($category);
|
||||
if ($emptyPlaceholder.length > 0) {
|
||||
$emptyPlaceholder.insertAfter($category);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Extended methods from GridHandler
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.initialize =
|
||||
function(options) {
|
||||
// Save the URL to fetch a whole category.
|
||||
this.fetchCategoryUrl_ = options.fetchCategoryUrl;
|
||||
|
||||
this.parent('initialize', options);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getElementsByType =
|
||||
function($element) {
|
||||
if ($element.hasClass('category_grid_body')) {
|
||||
return this.getCategories();
|
||||
} else {
|
||||
return /** @type {jQueryObject} */ (
|
||||
this.parent('getElementsByType', $element));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.getEmptyElement =
|
||||
function($element) {
|
||||
if ($element.hasClass('category_grid_body')) {
|
||||
// Return the grid empty element placeholder.
|
||||
return this.getHtmlElement().find('.empty').not('.category_placeholder');
|
||||
} else {
|
||||
return /** @type {jQueryObject} */ (
|
||||
this.parent('getEmptyElement', $element));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.refreshGridHandler =
|
||||
function(sourceElement, event, opt_elementId) {
|
||||
|
||||
var fetchedAlready = false, elementIds,
|
||||
// Hack to avoid closure compiler warnings on type difference
|
||||
castElementId = /** @type {{parentElementId: number}} */ (opt_elementId);
|
||||
|
||||
if (opt_elementId !== undefined) {
|
||||
// Check if we want to refresh a row inside a category.
|
||||
if (castElementId.parentElementId !== undefined) {
|
||||
elementIds = {rowId: opt_elementId[0],
|
||||
rowCategoryId: castElementId.parentElementId};
|
||||
|
||||
// Store the category id.
|
||||
this.currentCategoryId_ = castElementId.parentElementId;
|
||||
|
||||
// Retrieve a single row from the server.
|
||||
$.get(this.fetchRowUrl, elementIds,
|
||||
this.callbackWrapper(
|
||||
this.replaceElementResponseHandler), 'json');
|
||||
} else {
|
||||
// Retrieve the entire category from the server.
|
||||
$.get(this.fetchCategoryUrl_, {rowId: opt_elementId},
|
||||
this.callbackWrapper(
|
||||
this.replaceElementResponseHandler), 'json');
|
||||
}
|
||||
fetchedAlready = true;
|
||||
}
|
||||
|
||||
this.parent('refreshGridHandler', sourceElement,
|
||||
event, opt_elementId, fetchedAlready);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.deleteElement =
|
||||
function($element) {
|
||||
|
||||
var $gridBody, index, limit, $parent, $emptyPlaceholder;
|
||||
|
||||
if ($element.length > 1) {
|
||||
// Category and category row have the same element data id,
|
||||
// handle this case.
|
||||
if ($element.length == 2 &&
|
||||
$element.hasClass('category_grid_body') &&
|
||||
$element.hasClass('category')) {
|
||||
// Always delete the entire category.
|
||||
$element = $element.filter('.category_grid_body');
|
||||
}
|
||||
|
||||
// Sometimes grid rows inside different categories may have
|
||||
// the same id. Try to find the correct one to delete.
|
||||
if (this.currentCategoryId_) {
|
||||
$gridBody = this.getCategoryByDataId(this.currentCategoryId_);
|
||||
for (index = 0, limit = $element.length; index < limit; index++) {
|
||||
$parent = $($element[index]).
|
||||
parents('#' + $gridBody.attr('id'));
|
||||
if ($parent.length === 1) {
|
||||
$element = $($element[index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($element.hasClass('category_grid_body')) {
|
||||
// Need to delete the category empty placeholder.
|
||||
$emptyPlaceholder = this.getCategoryEmptyPlaceholder($element);
|
||||
this.unbindPartial($emptyPlaceholder);
|
||||
$emptyPlaceholder.remove();
|
||||
}
|
||||
|
||||
this.parent('deleteElement', $element);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.addElement =
|
||||
function($element) {
|
||||
var $gridBody = null, categoryDataId, $emptyPlaceholder;
|
||||
|
||||
if ($element.hasClass('gridRow')) {
|
||||
// New row must be inside a category.
|
||||
categoryDataId = /** @type {string} */ (
|
||||
this.getCategoryDataIdByRowId(
|
||||
/** @type {string} */ ($element.attr('id'))));
|
||||
$gridBody = /** @type {jQueryObject} */ (
|
||||
this.getCategoryByDataId(categoryDataId));
|
||||
}
|
||||
|
||||
// Add the element.
|
||||
this.parent('addElement', $element, $gridBody);
|
||||
|
||||
// Make sure the placeholder is the last grid element.
|
||||
if ($element.hasClass('category_grid_body')) {
|
||||
$emptyPlaceholder = this.getEmptyElement($element);
|
||||
this.getHtmlElement().find(this.bodySelector).append($emptyPlaceholder);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.replaceElement =
|
||||
function($existingElement, $newElement) {
|
||||
|
||||
if ($newElement.hasClass('category_grid_body')) {
|
||||
// Need to delete the category empty placeholder.
|
||||
var $emptyPlaceholder = this.getCategoryEmptyPlaceholder($existingElement);
|
||||
this.unbindPartial($emptyPlaceholder);
|
||||
$emptyPlaceholder.remove();
|
||||
}
|
||||
|
||||
this.parent('replaceElement', $existingElement, $newElement);
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.grid.CategoryGridHandler.prototype.hasSameNumOfColumns =
|
||||
function($row) {
|
||||
var $element = $row,
|
||||
checkColSpan = false;
|
||||
|
||||
if ($row.hasClass('category_grid_body')) {
|
||||
$element = $row.find('tr');
|
||||
checkColSpan = true;
|
||||
}
|
||||
|
||||
return /** @type {boolean} */ (
|
||||
this.parent('hasSameNumOfColumns', $element, checkColSpan));
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,983 @@
|
||||
/**
|
||||
* @defgroup js_controllers_grid
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/grid/GridHandler.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 GridHandler
|
||||
* @ingroup js_controllers_grid
|
||||
*
|
||||
* @brief Grid row handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
// Define the namespace.
|
||||
$.pkp.controllers.grid = $.pkp.controllers.grid || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $grid The grid this handler is
|
||||
* attached to.
|
||||
* @param {{features}} options Grid handler configuration.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler = function($grid, options) {
|
||||
this.parent($grid, options);
|
||||
|
||||
// We give a chance for this handler to initialize
|
||||
// before we initialize its features.
|
||||
this.initialize(options);
|
||||
|
||||
this.initFeatures_(options.features);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.grid.GridHandler,
|
||||
$.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Constants.
|
||||
//
|
||||
/**
|
||||
* Flag to be used to fetch all curent page grid rows.
|
||||
* @public
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.FETCH_ALL_ROWS_ID = {};
|
||||
|
||||
|
||||
//
|
||||
// Protected properties
|
||||
//
|
||||
/**
|
||||
* The selector for the grid body.
|
||||
* @protected
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.bodySelector = null;
|
||||
|
||||
|
||||
/**
|
||||
* The URL to fetch a grid row.
|
||||
* @protected
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.fetchRowUrl = null;
|
||||
|
||||
|
||||
/**
|
||||
* The URL to fetch all loaded grid rows.
|
||||
* @protected
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.fetchRowsUrl = null;
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The id of the grid.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.gridId_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The URL to fetch the entire grid.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.fetchGridUrl_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* This grid features.
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.features_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Fetch elements extra request parameters.
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.fetchExtraParams_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Get fetch element extra request parameters.
|
||||
* @return {Object} Extra request parameters.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getFetchExtraParams =
|
||||
function() {
|
||||
return this.fetchExtraParams_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set fetch element extra request parameters.
|
||||
* @param {Object} extraParams Extra request parameters.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.setFetchExtraParams =
|
||||
function(extraParams) {
|
||||
this.fetchExtraParams_ = extraParams;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the fetch row URL.
|
||||
* @return {?string} URL to the "fetch row" operation handler.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getFetchRowUrl =
|
||||
function() {
|
||||
|
||||
return this.fetchRowUrl;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the fetch rows URL.
|
||||
* @return {?string} URL to the "fetch rows" operation handler.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getFetchRowsUrl =
|
||||
function() {
|
||||
|
||||
return this.fetchRowsUrl;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get all grid rows.
|
||||
*
|
||||
* @return {jQueryObject} The rows as a JQuery object.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getRows =
|
||||
function() {
|
||||
return $('.gridRow', this.getHtmlElement()).not('.gridRowDeleted');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the id prefix of this grid.
|
||||
* @return {string} The ID prefix of this grid.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getGridIdPrefix =
|
||||
function() {
|
||||
return 'component-' + this.gridId_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the id prefix of this grid row.
|
||||
* @return {string} The id prefix of this grid row.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getRowIdPrefix =
|
||||
function() {
|
||||
return this.getGridIdPrefix() + '-row-';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the grid row by the passed data element id.
|
||||
* @param {number} rowDataId
|
||||
* @param {number=} opt_parentElementId
|
||||
* @return {jQueryObject}
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getRowByDataId =
|
||||
function(rowDataId, opt_parentElementId) {
|
||||
return $('#' +
|
||||
this.getRowIdPrefix() +
|
||||
$.pkp.classes.Helper.escapeJQuerySelector(String(rowDataId)),
|
||||
this.getHtmlElement());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the data element id of the passed grid row.
|
||||
* @param {jQueryObject} $gridRow The grid row JQuery object.
|
||||
* @return {string} The data element id of the passed grid row.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getRowDataId =
|
||||
function($gridRow) {
|
||||
var rowDataId;
|
||||
rowDataId = /** @type {string} */ ($gridRow.attr('id').
|
||||
slice(this.getRowIdPrefix().length));
|
||||
return rowDataId;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the parent grid row of the passed element, if any.
|
||||
* @param {jQueryObject} $element The element that is inside the row.
|
||||
* @return {jQueryObject} The element parent grid row.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getParentRow =
|
||||
function($element) {
|
||||
return $element.parents('.gridRow:first');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the same type elements of the passed element.
|
||||
* @param {jQueryObject} $element The element to get the type from.
|
||||
* @return {jQueryObject} The grid elements with the same type
|
||||
* of the passed element.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getElementsByType =
|
||||
function($element) {
|
||||
if ($element.hasClass('gridRow')) {
|
||||
var $container = $element.parents('tbody:first');
|
||||
return $('.gridRow', $container);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the empty element based on the type of the passed element.
|
||||
* @param {jQueryObject} $element The element to get the type from.
|
||||
* @return {jQueryObject} The empty element.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getEmptyElement =
|
||||
function($element) {
|
||||
if ($element.hasClass('gridRow')) {
|
||||
// Return the rows empty element placeholder.
|
||||
var $container = $element.parents('tbody:first');
|
||||
return $container.next('.empty');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show/hide row actions.
|
||||
*
|
||||
* @param {HTMLElement} sourceElement The element that
|
||||
* issued the event.
|
||||
* @param {Event} event The triggering event.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.toggleRowActions =
|
||||
function(sourceElement, event) {
|
||||
|
||||
// Don't follow the link
|
||||
event.preventDefault();
|
||||
|
||||
// Toggle the extras link class.
|
||||
$(sourceElement).toggleClass('show_extras');
|
||||
$(sourceElement).toggleClass('hide_extras');
|
||||
|
||||
// Toggle the row actions.
|
||||
var $controlRow = $(sourceElement).parents('tr').next('.row_controls');
|
||||
this.applyToggleRowActionEffect_($controlRow);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hide all visible row controls.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.hideAllVisibleRowActions =
|
||||
function() {
|
||||
this.getHtmlElement().find('a.hide_extras').click();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hide row actions div container.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.hideRowActionsDiv =
|
||||
function() {
|
||||
var $rowActionDivs, index, limit, $div;
|
||||
|
||||
$rowActionDivs = $('.gridRow div.row_actions', this.getHtmlElement());
|
||||
$rowActionDivs.hide();
|
||||
|
||||
// FIXME: Hack to correctly align the first column cell content after
|
||||
// hiding the row actions div.
|
||||
for (index = 0, limit = $rowActionDivs.length; index < limit; index++) {
|
||||
$div = $($rowActionDivs[index]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show row actions div container.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.showRowActionsDiv =
|
||||
function() {
|
||||
var $rowActionDivs, index, limit, $div;
|
||||
|
||||
$rowActionDivs = $('.gridRow div.row_actions', this.getHtmlElement());
|
||||
$rowActionDivs.show();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enable/disable all link actions inside this grid.
|
||||
* @param {boolean} enable Enable/disable flag.
|
||||
* @param {jQueryObject} $linkElements Link elements JQuery object.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.changeLinkActionsState =
|
||||
function(enable, $linkElements) {
|
||||
if ($linkElements === undefined) {
|
||||
$linkElements = $('.pkp_controllers_linkAction', this.getHtmlElement());
|
||||
}
|
||||
$linkElements.each(function() {
|
||||
/** {$.pkp.controllers.LinkActionHandler} */
|
||||
var linkHandler;
|
||||
linkHandler = $.pkp.classes.Handler.getHandler($(this));
|
||||
if (enable) {
|
||||
linkHandler.enableLink();
|
||||
} else {
|
||||
linkHandler.disableLink();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Re-sequence all grid rows based on the passed sequence map.
|
||||
* @param {Array} sequenceMap A sequence array with the row id or
|
||||
* row data id as value.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.resequenceRows =
|
||||
function(sequenceMap) {
|
||||
var id, index, $row;
|
||||
if (!sequenceMap) {
|
||||
return;
|
||||
}
|
||||
for (index in sequenceMap) {
|
||||
id = sequenceMap[index];
|
||||
$row = $('#' + $.pkp.classes.Helper.escapeJQuerySelector(String(id)));
|
||||
if ($row.length == 0) {
|
||||
$row = this.getRowByDataId(id);
|
||||
}
|
||||
if ($row.length == 0) {
|
||||
throw new Error('Row with id ' + id + ' not found!');
|
||||
}
|
||||
this.addElement($row);
|
||||
}
|
||||
this.updateControlRowsPosition();
|
||||
|
||||
this.callFeaturesHook('resequenceRows', sequenceMap);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Move all grid control rows to their correct position,
|
||||
* below of each correspondent data grid row.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.updateControlRowsPosition =
|
||||
function() {
|
||||
var $rows, index, limit, $row, $controlRow;
|
||||
|
||||
$rows = this.getRows();
|
||||
for (index = 0, limit = $rows.length; index < limit; index++) {
|
||||
$row = $($rows[index]);
|
||||
$controlRow = this.getControlRowByGridRow($row);
|
||||
if ($controlRow.length > 0) {
|
||||
$controlRow.insertAfter($row);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Inserts or replaces a grid element.
|
||||
* @param {string|jQueryObject} elementContent The new mark-up of the element.
|
||||
* @param {boolean=} opt_prepend Prepend the new row instead of append it?
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.insertOrReplaceElement =
|
||||
function(elementContent, opt_prepend) {
|
||||
var $newElement, newElementId, $grid, $existingElement;
|
||||
// Parse the HTML returned from the server.
|
||||
$newElement = $(elementContent);
|
||||
newElementId = $newElement.attr('id');
|
||||
|
||||
// Does the element exist already?
|
||||
$grid = this.getHtmlElement();
|
||||
$existingElement = newElementId ?
|
||||
$grid.find('#' +
|
||||
$.pkp.classes.Helper.escapeJQuerySelector(
|
||||
/** @type {string} */ (newElementId))
|
||||
) :
|
||||
null;
|
||||
|
||||
if ($existingElement !== null && $existingElement.length > 1) {
|
||||
throw new Error('There were ' + $existingElement.length +
|
||||
' rather than 0 or 1 elements to be replaced!');
|
||||
}
|
||||
|
||||
if (!this.hasSameNumOfColumns($newElement)) {
|
||||
// Redraw the whole grid so new columns
|
||||
// get added/removed to match element.
|
||||
$.get(this.fetchGridUrl_, null,
|
||||
this.callbackWrapper(this.replaceGridResponseHandler_), 'json');
|
||||
} else {
|
||||
if ($existingElement !== null && $existingElement.length === 1) {
|
||||
// Update element.
|
||||
this.replaceElement($existingElement, $newElement);
|
||||
} else {
|
||||
// Insert row.
|
||||
this.addElement($newElement, null, opt_prepend);
|
||||
}
|
||||
|
||||
// Refresh row action event binding.
|
||||
this.activateRowActions_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delete a grid element.
|
||||
* @param {jQueryObject} $element The element to be deleted.
|
||||
* @param {boolean=} opt_noFadeOut Whether the item deletion
|
||||
* will use the fade out effect or not.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.deleteElement =
|
||||
function($element, opt_noFadeOut) {
|
||||
var lastElement, $emptyElement, deleteFunction, self;
|
||||
|
||||
// Check whether we really only match one element.
|
||||
if ($element.length !== 1) {
|
||||
throw new Error('There were ' + $element.length +
|
||||
' rather than 1 element to delete!');
|
||||
}
|
||||
|
||||
// Flag this element as deleted, so getRows()
|
||||
// will return only existing rows from now on.
|
||||
$element.addClass('gridRowDeleted');
|
||||
|
||||
// Check whether this is the last row.
|
||||
lastElement = false;
|
||||
if (this.getElementsByType($element).length == 1) {
|
||||
lastElement = true;
|
||||
}
|
||||
|
||||
// Remove the controls row, if any.
|
||||
if ($element.hasClass('gridRow')) {
|
||||
this.deleteControlsRow_($element);
|
||||
}
|
||||
|
||||
$emptyElement = this.getEmptyElement($element);
|
||||
self = this;
|
||||
deleteFunction = function() {
|
||||
self.unbindPartial($element);
|
||||
$element.remove();
|
||||
if (lastElement) {
|
||||
$emptyElement.fadeIn(100);
|
||||
}
|
||||
};
|
||||
|
||||
if (opt_noFadeOut != undefined && opt_noFadeOut) {
|
||||
deleteFunction();
|
||||
} else {
|
||||
$element.fadeOut(500, deleteFunction);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* Set data and execute operations to initialize.
|
||||
*
|
||||
* @protected
|
||||
*
|
||||
* @param {Object} options Grid options.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.initialize =
|
||||
function(options) {
|
||||
var $searchLink;
|
||||
|
||||
// Bind the handler for the "elements changed" event.
|
||||
this.bind('dataChanged', this.refreshGridHandler);
|
||||
|
||||
// Bind the handler for the "add new row" event.
|
||||
this.bind('addRow', this.addRowHandler_);
|
||||
|
||||
// Handle grid filter events.
|
||||
this.bind('formSubmitted', this.refreshGridWithFilterHandler_);
|
||||
|
||||
// Save the ID of this grid.
|
||||
this.gridId_ = options.gridId;
|
||||
|
||||
// Save the URL to fetch a row.
|
||||
this.fetchRowUrl = options.fetchRowUrl;
|
||||
|
||||
// Save the URL to fetch all rows.
|
||||
this.fetchRowsUrl = options.fetchRowsUrl;
|
||||
|
||||
// Save the URL to fetch the entire grid
|
||||
this.fetchGridUrl_ = options.fetchGridUrl;
|
||||
|
||||
// Save the selector for the grid body.
|
||||
if ($('div.scrollable', this.getHtmlElement()).length > 0) {
|
||||
this.bodySelector = 'div.scrollable table';
|
||||
} else {
|
||||
this.bodySelector = options.bodySelector;
|
||||
}
|
||||
|
||||
// Show/hide row action feature.
|
||||
this.activateRowActions_();
|
||||
|
||||
this.setFetchExtraParams({});
|
||||
|
||||
// Search control.
|
||||
this.getHtmlElement().find('.pkp_form').hide();
|
||||
$searchLink = this.getHtmlElement().
|
||||
find('.pkp_linkaction_search');
|
||||
if ($searchLink.length !== 0) {
|
||||
$searchLink.click(
|
||||
this.callbackWrapper(function() {
|
||||
this.getHtmlElement().find('.pkp_form').toggle();
|
||||
$searchLink.toggleClass('is_open');
|
||||
}));
|
||||
} else {
|
||||
// This grid doesn't have an expand/collapse control. If there is
|
||||
// a form, expand it.
|
||||
this.getHtmlElement().find('.pkp_form').toggle();
|
||||
}
|
||||
|
||||
this.trigger('gridInitialized');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Call features hooks.
|
||||
*
|
||||
* @protected
|
||||
*
|
||||
* @param {string} hookName The name of the hook.
|
||||
* @param {Array|jQueryObject|Object|number|boolean=} opt_args
|
||||
* The arguments array.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.callFeaturesHook =
|
||||
function(hookName, opt_args) {
|
||||
var featureName;
|
||||
if (!$.isArray(opt_args)) {
|
||||
opt_args = [opt_args];
|
||||
}
|
||||
for (featureName in this.features_) {
|
||||
this.features_[featureName][hookName].
|
||||
apply(this.features_[featureName], opt_args);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Refresh either a single row of the grid or the whole grid.
|
||||
*
|
||||
* @protected
|
||||
*
|
||||
* @param {HTMLElement} sourceElement The element that
|
||||
* issued the event.
|
||||
* @param {Event} event The triggering event.
|
||||
* @param {number|Object=} opt_elementId The id of a data element that was
|
||||
* updated, added or deleted. If not given then the whole grid
|
||||
* will be refreshed.
|
||||
* @param {Boolean=} opt_fetchedAlready Flag that subclasses can send
|
||||
* telling that a fetch operation was already handled there.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.refreshGridHandler =
|
||||
function(sourceElement, event, opt_elementId, opt_fetchedAlready) {
|
||||
var params;
|
||||
|
||||
this.callFeaturesHook('refreshGrid', opt_elementId);
|
||||
|
||||
params = this.getFetchExtraParams();
|
||||
|
||||
|
||||
// Check if subclasses already handled the fetch of new elements.
|
||||
if (!opt_fetchedAlready) {
|
||||
if (opt_elementId) {
|
||||
if (opt_elementId ==
|
||||
$.pkp.controllers.grid.GridHandler.FETCH_ALL_ROWS_ID) {
|
||||
$.get(this.fetchRowsUrl, params,
|
||||
this.callbackWrapper(this.replaceElementResponseHandler), 'json');
|
||||
} else {
|
||||
params.rowId = opt_elementId;
|
||||
// Retrieve a single row from the server.
|
||||
$.get(this.fetchRowUrl, params,
|
||||
this.callbackWrapper(this.replaceElementResponseHandler), 'json');
|
||||
}
|
||||
} else {
|
||||
// Retrieve the whole grid from the server.
|
||||
$.get(this.fetchGridUrl_, params,
|
||||
this.callbackWrapper(this.replaceGridResponseHandler_), 'json');
|
||||
}
|
||||
}
|
||||
|
||||
// Let the calling context (page?) know that the grids are being redrawn.
|
||||
this.trigger('gridRefreshRequested');
|
||||
this.publishChangeEvents();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a new row to the grid.
|
||||
*
|
||||
* @protected
|
||||
*
|
||||
* @param {jQueryObject} $newRow The new row to append.
|
||||
* @param {jQueryObject=} opt_$gridBody The tbody container element.
|
||||
* @param {boolean=} opt_prepend Prepend the element instead of append it?
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.addElement =
|
||||
function($newRow, opt_$gridBody, opt_prepend) {
|
||||
|
||||
if (opt_$gridBody === undefined || opt_$gridBody === null) {
|
||||
opt_$gridBody = this.getHtmlElement().find(this.bodySelector);
|
||||
}
|
||||
|
||||
// Add the new element.
|
||||
if (opt_prepend != undefined && opt_prepend) {
|
||||
opt_$gridBody.prepend($newRow);
|
||||
} else {
|
||||
opt_$gridBody.append($newRow);
|
||||
}
|
||||
|
||||
|
||||
// Hide the empty placeholder.
|
||||
var $emptyElement = this.getEmptyElement($newRow);
|
||||
if ($emptyElement) {
|
||||
$emptyElement.hide();
|
||||
}
|
||||
|
||||
this.callFeaturesHook('addElement', $newRow);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update an existing element using the passed new element content.
|
||||
*
|
||||
* @protected
|
||||
*
|
||||
* @param {jQueryObject} $existingElement The element that is already
|
||||
* in grid.
|
||||
* @param {jQueryObject} $newElement The element with new content.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.replaceElement =
|
||||
function($existingElement, $newElement) {
|
||||
|
||||
if ($newElement.hasClass('gridRow')) {
|
||||
this.deleteControlsRow_($existingElement);
|
||||
}
|
||||
|
||||
this.replacePartialWith($newElement, $existingElement);
|
||||
this.callFeaturesHook('replaceElement', $newElement);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Does the passed row have a different number of columns than the
|
||||
* existing grid?
|
||||
*
|
||||
* @protected
|
||||
*
|
||||
* @param {jQueryObject} $row The row to be checked against grid columns.
|
||||
* @param {Boolean=} opt_checkColSpan Will get the number of row columns
|
||||
* by column span.
|
||||
* @return {boolean} Whether it has the same number of grid columns
|
||||
* or not.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.hasSameNumOfColumns =
|
||||
function($row, opt_checkColSpan) {
|
||||
var $grid, numColumns, $tdElements, numCellsInNewRow;
|
||||
|
||||
$grid = this.getHtmlElement();
|
||||
numColumns = $grid.find('th').length;
|
||||
$tdElements = $row.first().find('td');
|
||||
if (opt_checkColSpan) {
|
||||
numCellsInNewRow = $tdElements.attr('colspan');
|
||||
} else {
|
||||
numCellsInNewRow = $tdElements.length;
|
||||
}
|
||||
|
||||
return (numColumns == numCellsInNewRow);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback to insert, remove or replace a row after an
|
||||
* element has been inserted, update or deleted.
|
||||
*
|
||||
* @protected
|
||||
*
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
* @return {boolean|undefined} Return false when no replace action is taken.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.replaceElementResponseHandler =
|
||||
function(ajaxContext, jsonData) {
|
||||
var elementId, $element, handledJsonData, castJsonData, $responseElement,
|
||||
$responseRow, $responseControlRow, $responseRows, $responseRowsControls,
|
||||
index, limit;
|
||||
|
||||
handledJsonData = this.handleJson(jsonData);
|
||||
if (handledJsonData !== false) {
|
||||
if (handledJsonData.elementNotFound) {
|
||||
// The server reported that this element no
|
||||
// longer exists in the database so let's
|
||||
// delete it.
|
||||
elementId = handledJsonData.elementNotFound;
|
||||
$element = this.getRowByDataId(elementId);
|
||||
|
||||
// Sometimes we get a delete event before the
|
||||
// element has actually been inserted (e.g. when deleting
|
||||
// elements due to a cancel action or similar).
|
||||
if ($element.length > 0) {
|
||||
this.deleteElement($element);
|
||||
}
|
||||
} else {
|
||||
// The server returned mark-up to replace
|
||||
// or insert the row.
|
||||
$responseElement = $(handledJsonData.content);
|
||||
if ($responseElement.filter("tr:not('.row_controls')").length > 1) {
|
||||
$responseRows = $responseElement.filter('tr.gridRow');
|
||||
$responseRowsControls = $responseElement.filter('tr.row_controls');
|
||||
for (index = 0, limit = $responseRows.length; index < limit; index++) {
|
||||
$responseRow = $($responseRows[index]);
|
||||
$responseControlRow = this.getControlRowByGridRow($responseRow,
|
||||
$responseRowsControls);
|
||||
this.insertOrReplaceElement($responseRow.add($responseControlRow));
|
||||
}
|
||||
} else {
|
||||
this.insertOrReplaceElement(handledJsonData.content);
|
||||
}
|
||||
|
||||
castJsonData = /** @type {{sequenceMap: Array}} */ (handledJsonData);
|
||||
this.resequenceRows(castJsonData.sequenceMap);
|
||||
}
|
||||
}
|
||||
|
||||
this.callFeaturesHook('replaceElementResponseHandler', handledJsonData);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Refresh the grid after its filter has changed.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {$.pkp.controllers.form.ClientFormHandler} filterForm
|
||||
* The filter form.
|
||||
* @param {Event} event A "formSubmitted" event.
|
||||
* @param {string} filterData Serialized filter data.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.refreshGridWithFilterHandler_ =
|
||||
function(filterForm, event, filterData) {
|
||||
|
||||
// Retrieve the grid from the server and add the
|
||||
// filter data as form data.
|
||||
$.post(this.fetchGridUrl_, filterData,
|
||||
this.callbackWrapper(this.replaceGridResponseHandler_), 'json');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a new row to the grid.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {HTMLElement} sourceElement The element that
|
||||
* issued the event.
|
||||
* @param {Event} event The triggering event.
|
||||
* @param {Object} params The request parameters to use to generate
|
||||
* the new row.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.addRowHandler_ =
|
||||
function(sourceElement, event, params) {
|
||||
|
||||
// Retrieve a single new row from the server.
|
||||
$.get(this.fetchRowUrl, params,
|
||||
this.callbackWrapper(this.replaceElementResponseHandler), 'json');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback to replace a grid's content.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.replaceGridResponseHandler_ =
|
||||
function(ajaxContext, jsonData) {
|
||||
var handledJsonData, $grid, $gridParent, $newGrid,
|
||||
isFilterVisible;
|
||||
|
||||
handledJsonData = this.handleJson(jsonData);
|
||||
if (handledJsonData !== false) {
|
||||
// Get the grid that we're updating
|
||||
$grid = this.getHtmlElement();
|
||||
$gridParent = $grid.parent();
|
||||
|
||||
isFilterVisible = $grid.find('.filter').is(':visible');
|
||||
|
||||
// Replace the grid content
|
||||
this.replaceWith(handledJsonData.content);
|
||||
|
||||
// Update the html element of this handler.
|
||||
$newGrid = $('div[id^="' + this.getGridIdPrefix() + '"]', $gridParent);
|
||||
this.setHtmlElement($newGrid);
|
||||
|
||||
// Refresh row action event binding.
|
||||
this.activateRowActions_();
|
||||
|
||||
if (isFilterVisible) {
|
||||
// Open search control again.
|
||||
$newGrid.find('.pkp_linkaction_search').click();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper that deletes the row of controls (if present).
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {jQueryObject} $row The row whose matching control row should be
|
||||
* deleted.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.deleteControlsRow_ =
|
||||
function($row) {
|
||||
var $controlRow = $('#' + $.pkp.classes.Helper.escapeJQuerySelector(
|
||||
/** @type {string} */ ($row.attr('id'))) + '-control-row',
|
||||
this.getHtmlElement());
|
||||
|
||||
if ($controlRow.is('tr') && $controlRow.hasClass('row_controls')) {
|
||||
this.unbindPartial($controlRow);
|
||||
$controlRow.remove();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the control row for the passed the grid row.
|
||||
*
|
||||
* @param {jQueryObject} $gridRow The grid row JQuery object.
|
||||
* @param {jQueryObject=} opt_$context Optional context to get
|
||||
* the control row from.
|
||||
* @return {jQueryObject} The control row JQuery object.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.getControlRowByGridRow =
|
||||
function($gridRow, opt_$context) {
|
||||
var rowId, controlRowId, $context;
|
||||
|
||||
if (opt_$context === undefined || opt_$context === null) {
|
||||
$context = this.getHtmlElement().find('tr');
|
||||
} else {
|
||||
$context = opt_$context;
|
||||
}
|
||||
|
||||
rowId = $gridRow.attr('id');
|
||||
controlRowId = rowId + '-control-row';
|
||||
return $context.filter('#' +
|
||||
$.pkp.classes.Helper.escapeJQuerySelector(controlRowId));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper that attaches any action handlers related to rows.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.activateRowActions_ =
|
||||
function() {
|
||||
|
||||
var $grid = this.getHtmlElement(),
|
||||
$gridRows = this.getHtmlElement().find('tr.gridRow').not('.category');
|
||||
|
||||
$grid.find('a.show_extras').unbind('click').bind('click',
|
||||
this.callbackWrapper(this.toggleRowActions));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Apply the effect for hide/show row actions.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {jQueryObject} $controlRow The control row JQuery object.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.applyToggleRowActionEffect_ =
|
||||
function($controlRow) {
|
||||
var $row;
|
||||
|
||||
$row = $controlRow.prev().find('td:not(.indent_row)');
|
||||
$row = $row.add($controlRow.prev());
|
||||
$controlRow.toggle();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a grid feature.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {string} id Feature id.
|
||||
* @param {$.pkp.classes.features.Feature} $feature The grid
|
||||
* feature to be added.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.addFeature_ =
|
||||
function(id, $feature) {
|
||||
if (!this.features_) {
|
||||
this.features_ = [];
|
||||
}
|
||||
this.features_[id] = $feature;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add grid features.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Array.<{JSClass, options}>} features The features options array.
|
||||
*/
|
||||
$.pkp.controllers.grid.GridHandler.prototype.initFeatures_ =
|
||||
function(features) {
|
||||
|
||||
var id, $feature, jsClass;
|
||||
|
||||
for (id in features) {
|
||||
// Only initiate features that have a js handler.
|
||||
jsClass = features[id].JSClass;
|
||||
if (jsClass === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$feature =
|
||||
/** @type {$.pkp.classes.features.Feature} */
|
||||
($.pkp.classes.Helper.objectFactory(
|
||||
jsClass, [this, features[id].options]));
|
||||
|
||||
this.addFeature_(id, $feature);
|
||||
this.features_[id].init();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @file js/controllers/grid/files/review/AuthorReviewRevisionsGridHandler.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 AuthorReviewRevisionsGridHandler
|
||||
* @ingroup js_controllers_grid
|
||||
*
|
||||
* @brief Author revisions grid handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/**
|
||||
* Define the namespace
|
||||
*/
|
||||
$.pkp.controllers.grid.files = $.pkp.controllers.grid.files ||
|
||||
{ review: {} };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.grid.GridHandler
|
||||
*
|
||||
* @param {jQueryObject} $grid The grid this handler is
|
||||
* attached to.
|
||||
* @param {Object} options Grid handler configuration.
|
||||
*/
|
||||
$.pkp.controllers.grid.files.review.AuthorReviewRevisionsGridHandler = function(
|
||||
$grid, options) {
|
||||
this.parent($grid, options);
|
||||
this.bindGlobal('refreshRevisionsGrid', function() {
|
||||
this.refreshGridHandler();
|
||||
});
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.grid.files.review.
|
||||
AuthorReviewRevisionsGridHandler, $.pkp.controllers.grid.GridHandler);
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,269 @@
|
||||
/**
|
||||
* @file js/controllers/grid/navigationMenus/form/NavigationMenuFormHandler.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 NavigationMenuFormHandler
|
||||
* @ingroup js_controllers_grid_navigationMenus_form
|
||||
*
|
||||
* @brief NavigationMenuItems form handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/**
|
||||
* Define the namespace
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus =
|
||||
$.pkp.controllers.grid.navigationMenus ||
|
||||
{ form: {} };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.AjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $formElement The form element
|
||||
* @param {Object} options Modal options.
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuFormHandler =
|
||||
function($formElement, options) {
|
||||
|
||||
this.okButton_ = options.okButton;
|
||||
this.warningModalTitle_ = options.warningModalTitle;
|
||||
this.submenuWarning_ = options.submenuWarning;
|
||||
this.itemTypeConditionalWarnings_ = options.itemTypeConditionalWarnings;
|
||||
|
||||
$formElement.on('click', '.btnConditionalDisplay',
|
||||
this.callbackWrapper(this.showConditionalDisplayWarning));
|
||||
$formElement.on('click', '.btnSubmenuWarning',
|
||||
this.callbackWrapper(this.showSubmenuWarning));
|
||||
|
||||
this.parent($formElement, options);
|
||||
this.initSorting();
|
||||
};
|
||||
|
||||
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuFormHandler,
|
||||
$.pkp.controllers.form.AjaxFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
|
||||
|
||||
/**
|
||||
* The label for the ok button on the modals displaying submenuWarning and
|
||||
* conditionalWarnings.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuFormHandler
|
||||
.prototype.okButton_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The title of the modals displaying submenuWarning and conditionalWarnings.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuFormHandler
|
||||
.prototype.warningModalTitle_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The warning message to display about submenus
|
||||
* @private
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuFormHandler
|
||||
.prototype.submenuWarning_ = undefined;
|
||||
|
||||
|
||||
/**
|
||||
* Warnings about the conditions of display for each item type.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuFormHandler
|
||||
.prototype.itemTypeConditionalWarnings_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the .sortable() lists, limit nesting to one level deep and
|
||||
* ensure lists are formatted properly the CSS styles
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuFormHandler.
|
||||
prototype.initSorting = function() {
|
||||
var self = this;
|
||||
|
||||
// Remove any submenu warning buttons
|
||||
$('.btnSubmenuWarning', this.getHtmlElement()).remove();
|
||||
|
||||
// Limit nesting to one level deep and ensure nested lists are formatted
|
||||
// properly for appropriate styles
|
||||
$('#pkpNavAssigned > li').each(
|
||||
function() {
|
||||
var $childList = $(this).children('ul'),
|
||||
$children = $childList.children(),
|
||||
$grandchildren = $children.find('li');
|
||||
|
||||
if (!$childList.length) {
|
||||
$(this).append('<ul></ul>');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$children.length) {
|
||||
// Ensure it's just an empty ul (with no spaces) so the CSS
|
||||
// :empty psuedo class can be used
|
||||
$childList.replaceWith('<ul></ul>');
|
||||
} else {
|
||||
// Prevent nesting two levels deep by moving any items
|
||||
// nested at that level up one level.
|
||||
if ($grandchildren.length) {
|
||||
$grandchildren.each(function() {
|
||||
$(this).appendTo($childList);
|
||||
});
|
||||
}
|
||||
|
||||
// Add a submenu warning button
|
||||
if (!$(this).find(
|
||||
'> .item > .item_buttons .btnSubmenuWarning').length) {
|
||||
$(this).find('> .item > .item_buttons').prepend(
|
||||
$('<button></button>')
|
||||
.addClass('btnSubmenuWarning')
|
||||
.append(
|
||||
$('<span></span>')
|
||||
.addClass('fa fa-exclamation-triangle')
|
||||
)
|
||||
.append(
|
||||
$('<span></span>')
|
||||
.addClass('-screenReader')
|
||||
.text(self.submenuWarning_)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Reset any nesting that's been carried over from the assigned list
|
||||
$('#pkpNavUnassigned > li').each(
|
||||
function() {
|
||||
var $childList = $(this).children('ul');
|
||||
if ($childList.length) {
|
||||
$childList.find('li').each(function() {
|
||||
$(this).appendTo($('#pkpNavUnassigned'));
|
||||
});
|
||||
}
|
||||
$childList.remove();
|
||||
}
|
||||
);
|
||||
|
||||
// Initialize the sortable controls
|
||||
$('#pkpNavManagement ul').sortable({
|
||||
placeholder: 'pkp_nav_item_placeholder',
|
||||
delay: 250,
|
||||
connectWith: '#pkpNavManagement ul',
|
||||
update: this.callbackWrapper(this.updateSorting),
|
||||
start: function() {
|
||||
$('#pkpNavAssigned').addClass('pkp_is_sorting');
|
||||
},
|
||||
stop: function() {
|
||||
$('#pkpNavAssigned').removeClass('pkp_is_sorting');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Re-initialize the .sortable() components and generate the form fields
|
||||
* whenever the list is re-sorted.
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuFormHandler.
|
||||
prototype.updateSorting = function() {
|
||||
var $navManagement = $('#pkpNavManagement'),
|
||||
seq = 0,
|
||||
parent = null,
|
||||
currentName = '';
|
||||
|
||||
// Re-intialize the sortable component after adjusting sort order
|
||||
this.initSorting();
|
||||
|
||||
// Remove existing hidden fields
|
||||
$('input', $navManagement).remove();
|
||||
|
||||
// Generate new hidden form fields
|
||||
$('#pkpNavAssigned > li').each(function() {
|
||||
currentName = 'menuTree[' + $(this).data('id') + ']';
|
||||
$navManagement.append('<input type="hidden" name="' +
|
||||
currentName + '[seq]" value="' +
|
||||
seq + '">');
|
||||
seq++;
|
||||
|
||||
var parentId = $(this).data('id');
|
||||
$(this).find('li').each(function() {
|
||||
currentName = 'menuTree[' + $(this).data('id') + ']';
|
||||
$navManagement.append('<input type="hidden" name="' +
|
||||
currentName + '[seq]" value="' +
|
||||
seq + '">');
|
||||
$navManagement.append('<input type="hidden" name="' +
|
||||
currentName + '[parentId]" value="' +
|
||||
parentId + '">');
|
||||
seq++;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show the conditional display warning message
|
||||
* @param {jQueryObject} htmlElement The html element
|
||||
* @return {boolean}
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuFormHandler.
|
||||
prototype.showConditionalDisplayWarning = function(htmlElement) {
|
||||
var itemType = $(htmlElement).closest('li').data('type'),
|
||||
opts = {
|
||||
title: this.warningModalTitle_,
|
||||
okButton: this.okButton_,
|
||||
cancelButton: false,
|
||||
dialogText: this.itemTypeConditionalWarnings_[itemType]
|
||||
};
|
||||
|
||||
if (this.itemTypeConditionalWarnings_[itemType] !== null) {
|
||||
$('<div id="' + $.pkp.classes.Helper.uuid() + '" ' +
|
||||
'class="pkp_modal pkpModalWrapper" tabindex="-1"></div>')
|
||||
.pkpHandler('$.pkp.controllers.modal.ConfirmationModalHandler', opts);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show the submenu link warning message
|
||||
* @return {boolean}
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuFormHandler.
|
||||
prototype.showSubmenuWarning = function() {
|
||||
|
||||
var opts = {
|
||||
title: this.warningModalTitle_,
|
||||
okButton: this.okButton_,
|
||||
cancelButton: false,
|
||||
dialogText: this.submenuWarning_
|
||||
};
|
||||
|
||||
$('<div id="' + $.pkp.classes.Helper.uuid() + '" ' +
|
||||
'class="pkp_modal pkpModalWrapper" tabindex="-1"></div>')
|
||||
.pkpHandler('$.pkp.controllers.modal.ConfirmationModalHandler', opts);
|
||||
|
||||
return false;
|
||||
};
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* @file js/controllers/grid/navigationMenus/form/NavigationMenuItemsFormHandler.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 NavigationMenuItemsFormHandler
|
||||
* @ingroup js_controllers_grid_navigationMenus_form
|
||||
*
|
||||
* @brief NavigationMenuItems form handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.AjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $formElement A wrapped HTML element that
|
||||
* represents the form interface element.
|
||||
* @param {Object} options javascript form options.
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuItemsFormHandler =
|
||||
function($formElement, options) {
|
||||
|
||||
this.parent($formElement, options);
|
||||
|
||||
this.previewUrl_ = options.previewUrl;
|
||||
this.itemTypeDescriptions_ = options.itemTypeDescriptions;
|
||||
this.itemTypeConditionalWarnings_ = options.itemTypeConditionalWarnings;
|
||||
|
||||
$('#previewButton', $formElement).click(this.callbackWrapper(
|
||||
this.showPreview_));
|
||||
|
||||
$('#menuItemType', $formElement).change(this.callbackWrapper(this.setType));
|
||||
$('#menuItemType', $formElement).trigger('change');
|
||||
};
|
||||
|
||||
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuItemsFormHandler,
|
||||
$.pkp.controllers.form.AjaxFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
|
||||
|
||||
/**
|
||||
* The preview url.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.
|
||||
NavigationMenuItemsFormHandler.prototype.previewUrl_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Descriptions for each item type.
|
||||
* @private
|
||||
* @type {?Object}
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.
|
||||
NavigationMenuItemsFormHandler.prototype.itemTypeDescriptions_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Warnings about the conditions of display for each item type.
|
||||
* @private
|
||||
* @type {?Object}
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.
|
||||
NavigationMenuItemsFormHandler.prototype
|
||||
.itemTypeConditionalWarnings_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Callback triggered on clicking the "preview"
|
||||
* button to open a preview window.
|
||||
*
|
||||
* @param {HTMLElement} submitButton The submit button.
|
||||
* @param {Event} event The event that triggered the
|
||||
* submit button.
|
||||
* @return {boolean} true.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuItemsFormHandler.
|
||||
prototype.showPreview_ = function(submitButton, event) {
|
||||
|
||||
var $formElement = this.getHtmlElement();
|
||||
$.post(this.previewUrl_,
|
||||
$formElement.serialize(),
|
||||
function(data) {
|
||||
var win = window.open('about:blank');
|
||||
win.document.open();
|
||||
win.document.write(data);
|
||||
win.document.close();
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback triggered when the type is set
|
||||
*/
|
||||
$.pkp.controllers.grid.navigationMenus.form.NavigationMenuItemsFormHandler.
|
||||
prototype.setType = function() {
|
||||
var itemType = $('#menuItemType', this.getHtmlElement()).val(),
|
||||
$descriptionEl = $('#menuItemTypeSection [for="menuItemType"]');
|
||||
|
||||
$('.NMI_TYPE_CUSTOM_EDIT', this.getHtmlElement()).hide();
|
||||
$('#' + itemType).fadeIn();
|
||||
|
||||
if (typeof this.itemTypeDescriptions_[itemType] !== 'undefined') {
|
||||
$descriptionEl.text(this.itemTypeDescriptions_[itemType]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* @file js/controllers/grid/notifications/NotificationsGridHandler.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 NotificationsGridHandler
|
||||
* @ingroup js_controllers_grid
|
||||
*
|
||||
* @brief Category grid handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
// Define the namespace.
|
||||
$.pkp.controllers.grid.notifications =
|
||||
$.pkp.controllers.grid.notifications || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.grid.GridHandler
|
||||
*
|
||||
* @param {jQueryObject} $grid The grid this handler is
|
||||
* attached to.
|
||||
* @param {Object} options Grid handler configuration.
|
||||
*/
|
||||
$.pkp.controllers.grid.notifications.NotificationsGridHandler =
|
||||
function($grid, options) {
|
||||
|
||||
$grid.find('a[id*="markNew"]').click(
|
||||
this.callbackWrapper(this.markNewHandler_));
|
||||
|
||||
$grid.find('a[id*="markRead"]').click(
|
||||
this.callbackWrapper(this.markReadHandler_));
|
||||
|
||||
$grid.find('a[id*="deleteNotification"]').click(
|
||||
this.callbackWrapper(this.deleteHandler_));
|
||||
|
||||
this.parent($grid, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.grid.notifications
|
||||
.NotificationsGridHandler, $.pkp.controllers.grid.GridHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The CSRF token for POST requests.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.notifications.NotificationsGridHandler
|
||||
.prototype.csrfToken_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The "mark notifications as new" URL
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.notifications.NotificationsGridHandler
|
||||
.prototype.markNewUrl_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The "mark notifications as read" URL
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.notifications.NotificationsGridHandler
|
||||
.prototype.markReadUrl_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The "delete notifications" URL
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.notifications.NotificationsGridHandler
|
||||
.prototype.deleteUrl_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Extended methods from GridHandler
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.grid.notifications.NotificationsGridHandler.
|
||||
prototype.initialize = function(options) {
|
||||
|
||||
// Save the URLs to interact with selected sets of notifications
|
||||
this.markNewUrl_ = options.markNewUrl;
|
||||
this.markReadUrl_ = options.markReadUrl;
|
||||
this.deleteUrl_ = options.deleteUrl;
|
||||
|
||||
this.csrfToken_ = options.csrfToken;
|
||||
|
||||
this.parent('initialize', options);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods.
|
||||
//
|
||||
/**
|
||||
* Get the array of selected notifications
|
||||
* @private
|
||||
* @return {Array} List of selected notification IDs.
|
||||
*/
|
||||
$.pkp.controllers.grid.notifications.NotificationsGridHandler.prototype.
|
||||
getSelectedNotifications_ = function() {
|
||||
var selectedElements = [];
|
||||
this.getHtmlElement().find('input:checkbox:checked').each(function() {
|
||||
selectedElements.push($(this).val());
|
||||
});
|
||||
return selectedElements;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be activated when the "mark new" icon is clicked
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} callingContext The calling element or object.
|
||||
* @param {Event=} opt_event The triggering event (e.g. a click on
|
||||
* a button.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.grid.notifications.NotificationsGridHandler.prototype.
|
||||
markNewHandler_ = function(callingContext, opt_event) {
|
||||
$.post(this.markNewUrl_, {selectedElements: this.getSelectedNotifications_(),
|
||||
csrfToken: this.csrfToken_},
|
||||
this.callbackWrapper(this.responseHandler_, null), 'json');
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be activated when the "mark read" icon is clicked
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} callingContext The calling element or object.
|
||||
* @param {Event=} opt_event The triggering event (e.g. a click on
|
||||
* a button.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.grid.notifications.NotificationsGridHandler.prototype.
|
||||
markReadHandler_ = function(callingContext, opt_event) {
|
||||
$.post(this.markReadUrl_,
|
||||
{selectedElements: this.getSelectedNotifications_(),
|
||||
csrfToken: this.csrfToken_},
|
||||
this.callbackWrapper(this.responseHandler_, null), 'json');
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be activated when the "delete" icon is clicked
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} callingContext The calling element or object.
|
||||
* @param {Event=} opt_event The triggering event (e.g. a click on
|
||||
* a button.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.grid.notifications.NotificationsGridHandler.prototype.
|
||||
deleteHandler_ = function(callingContext, opt_event) {
|
||||
$.post(this.deleteUrl_, {selectedElements: this.getSelectedNotifications_(),
|
||||
csrfToken: this.csrfToken_},
|
||||
this.callbackWrapper(this.responseHandler_, null), 'json');
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback after a response returns from the server.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
*/
|
||||
$.pkp.controllers.grid.notifications.NotificationsGridHandler.prototype.
|
||||
responseHandler_ = function(ajaxContext, jsonData) {
|
||||
|
||||
// Bounce the selected notification IDs back to the server
|
||||
// so that selections can be maintained
|
||||
var params = this.getFetchExtraParams();
|
||||
params.selectedNotificationIds = jsonData.content;
|
||||
this.setFetchExtraParams(params);
|
||||
|
||||
// Pass through the JSON handler to cause the grid to be
|
||||
// refreshed.
|
||||
this.handleJson(jsonData);
|
||||
};
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* @defgroup js_controllers_grid_queries
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/grid/queries/QueryFormHandler.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 ReadQueryHandler
|
||||
* @ingroup js_controllers_grid_queries
|
||||
*
|
||||
* @brief Handler for a query form modal
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.grid.queries =
|
||||
$.pkp.controllers.grid.queries || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.CancelActionAjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form The query form element
|
||||
* @param {Object} options non-default Dialog options
|
||||
* to be passed into the dialog widget.
|
||||
*
|
||||
* Options are:
|
||||
* - all options documented for the CancelActionAjaxFormHandler.
|
||||
* - templateUrl: The URL to retrieve templates from.
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.QueryFormHandler =
|
||||
function($form, options) {
|
||||
this.parent($form, options);
|
||||
|
||||
// Set the URL to retrieve templates from.
|
||||
if (options.templateUrl) {
|
||||
this.templateUrl_ = options.templateUrl;
|
||||
}
|
||||
|
||||
// Attach form elements events.
|
||||
$form.find('#template').change(
|
||||
this.callbackWrapper(this.selectTemplateHandler_));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.grid.queries.
|
||||
QueryFormHandler, $.pkp.controllers.form.CancelActionAjaxFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The URL to use to retrieve template bodies
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.
|
||||
QueryFormHandler.prototype.templateUrl_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Respond to an "item selected" call by triggering a published event.
|
||||
*
|
||||
* @param {HTMLElement} sourceElement The element that
|
||||
* issued the event.
|
||||
* @param {Event} event The triggering event.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.
|
||||
QueryFormHandler.prototype.selectTemplateHandler_ =
|
||||
function(sourceElement, event) {
|
||||
var $form = this.getHtmlElement(),
|
||||
template = $form.find('[name="template"]');
|
||||
$.post(this.templateUrl_, template.serialize(),
|
||||
this.callbackWrapper(this.updateTemplate), 'json');
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Internal callback to replace the textarea with the contents of the
|
||||
* template body.
|
||||
*
|
||||
* @param {HTMLElement} formElement The wrapped HTML form.
|
||||
* @param {Object} jsonData The data returned from the server.
|
||||
* @return {boolean} The response status.
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.
|
||||
QueryFormHandler.prototype.updateTemplate =
|
||||
function(formElement, jsonData) {
|
||||
|
||||
var $form = this.getHtmlElement(),
|
||||
processedJsonData = this.handleJson(jsonData),
|
||||
jsonDataContent =
|
||||
/** @type {{variables: Object, body: string}} */ (jsonData.content),
|
||||
$textarea = $form.find('textarea[name="comment"]'),
|
||||
editor =
|
||||
tinyMCE.EditorManager.get(/** @type {string} */ ($textarea.attr('id')));
|
||||
|
||||
if (jsonDataContent.variables) {
|
||||
$textarea.attr('data-variables', JSON.stringify(jsonDataContent.variables));
|
||||
}
|
||||
editor.setContent(jsonDataContent.body);
|
||||
$form.find('[name="subject"]').val(jsonDataContent.subject);
|
||||
|
||||
return processedJsonData.status;
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @defgroup js_controllers_grid_queries
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/grid/queries/ReadQueryHandler.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 ReadQueryHandler
|
||||
* @ingroup js_controllers_grid_queries
|
||||
*
|
||||
* @brief Handler for a "read query" modal
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.grid.queries =
|
||||
$.pkp.controllers.grid.queries || { };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $containerElement The HTML element encapsulating
|
||||
* the carousel container.
|
||||
* @param {Object} options Handler options.
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.ReadQueryHandler =
|
||||
function($containerElement, options) {
|
||||
|
||||
this.fetchNoteFormUrl_ = options.fetchNoteFormUrl;
|
||||
this.fetchParticipantsListUrl_ = options.fetchParticipantsListUrl;
|
||||
|
||||
$containerElement.find('.openNoteForm a').click(
|
||||
this.callbackWrapper(this.showNoteFormHandler_));
|
||||
|
||||
$containerElement.bind('dataChanged',
|
||||
this.callbackWrapper(this.reloadParticipantsList_));
|
||||
|
||||
$containerElement.bind('user-left-discussion', function() {
|
||||
$containerElement.parent().trigger('modalFinished');
|
||||
});
|
||||
|
||||
this.loadParticipantsList();
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.queries.ReadQueryHandler,
|
||||
$.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The URL to be called to fetch a new note form for a query.
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.ReadQueryHandler.
|
||||
prototype.fetchNoteFormUrl_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The URL to be called to fetch a list of participants.
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.ReadQueryHandler.
|
||||
prototype.fetchParticipantsListUrl_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Load the participants list.
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.ReadQueryHandler.prototype.
|
||||
loadParticipantsList = function() {
|
||||
$.get(this.fetchParticipantsListUrl_,
|
||||
this.callbackWrapper(this.showFetchedParticipantsList_), 'json');
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Event handler that is called when the "new note" button is clicked.
|
||||
* @param {HTMLElement} element The checkbox input element.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.ReadQueryHandler.prototype.
|
||||
showNoteFormHandler_ = function(element) {
|
||||
$(element).parents('.queryEditButtons').addClass('is_loading');
|
||||
$.get(this.fetchNoteFormUrl_,
|
||||
this.callbackWrapper(this.showFetchedNoteForm_), 'json');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Event handler that is called when the new note form is ready.
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.ReadQueryHandler.prototype.
|
||||
showFetchedNoteForm_ = function(ajaxContext, jsonData) {
|
||||
|
||||
var processedJsonData = this.handleJson(jsonData),
|
||||
$noteFormContainer = $('#newNotePlaceholder', this.getHtmlElement()),
|
||||
$queryEditButtons = $('.queryEditButtons.is_loading',
|
||||
this.getHtmlElement());
|
||||
|
||||
this.unbindPartial($queryEditButtons);
|
||||
$queryEditButtons.remove();
|
||||
this.unbindPartial($noteFormContainer);
|
||||
$noteFormContainer.html(processedJsonData.content);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Event handler that is called when the participants list fetch is complete.
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.ReadQueryHandler.prototype.
|
||||
showFetchedParticipantsList_ = function(ajaxContext, jsonData) {
|
||||
|
||||
var processedJsonData = this.handleJson(jsonData),
|
||||
$participantsListContainer = $('#participantsListPlaceholder',
|
||||
this.getHtmlElement()),
|
||||
$leaveQueryButton = $('.leaveQueryForm', this.getHtmlElement());
|
||||
|
||||
if (processedJsonData.showLeaveQueryButton) {
|
||||
$leaveQueryButton.show();
|
||||
} else {
|
||||
$leaveQueryButton.hide();
|
||||
}
|
||||
|
||||
this.unbindPartial($participantsListContainer);
|
||||
$participantsListContainer.children().remove();
|
||||
$participantsListContainer.append(processedJsonData.content);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler to update the participants list on change.
|
||||
* @param {HTMLElement} sourceElement The element that issued the
|
||||
* "dataChanged" event.
|
||||
* @param {Event} event The "dataChanged" event.
|
||||
* @param {HTMLElement} triggerElement The element that triggered
|
||||
* the "dataChanged" event.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.grid.queries.ReadQueryHandler.prototype.
|
||||
reloadParticipantsList_ = function(sourceElement, event, triggerElement) {
|
||||
this.loadParticipantsList();
|
||||
};
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* @defgroup js_controllers_grid_representations_form
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/grid/representations/form/RepresentationFormHandler.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 RepresentationFormHandler
|
||||
* @ingroup js_controllers_grid_representations_form
|
||||
*
|
||||
* @brief Handle the representations forms.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.grid.representations =
|
||||
$.pkp.controllers.grid.representations ||
|
||||
{ form: { } };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.AjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped page element.
|
||||
* @param {Object} options handler options.
|
||||
*/
|
||||
$.pkp.controllers.grid.representations.form.RepresentationFormHandler =
|
||||
function($form, options) {
|
||||
this.parent($form, options);
|
||||
|
||||
this.remoteRepresentation_ = options.remoteRepresentation;
|
||||
if (this.remoteRepresentation_) {
|
||||
$('#remotelyHostedContent').prop('checked', true);
|
||||
$('#remote').show(20);
|
||||
$('#urlPathSection').hide();
|
||||
} else {
|
||||
$('#remotelyHostedContent').prop('checked', false);
|
||||
$('#remote').hide();
|
||||
$('#urlPathSection').show(20);
|
||||
}
|
||||
|
||||
$('#remotelyHostedContent').change(this.callbackWrapper(this.toggleRemote_));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.representations.form.RepresentationFormHandler,
|
||||
$.pkp.controllers.form.AjaxFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private methods.
|
||||
//
|
||||
/**
|
||||
* Internal callback called on checkbox change to show or hide
|
||||
* remote URL input field.
|
||||
* @private
|
||||
* @param {HTMLElement} element The remotely hosted content checkbox.
|
||||
* @param {Event} event The event that triggered the checkbox.
|
||||
* @return {boolean} true.
|
||||
*/
|
||||
$.pkp.controllers.grid.representations.form.RepresentationFormHandler.
|
||||
prototype.toggleRemote_ = function(element, event) {
|
||||
|
||||
if ($('#remotelyHostedContent').prop('checked')) {
|
||||
// show the remote URL input field
|
||||
$('#remote').show(20);
|
||||
$('#urlPathSection').hide();
|
||||
$('input[id^="urlPath"]').val('');
|
||||
} else {
|
||||
// hide and clear the remote URL input field
|
||||
$('#remote').hide(20);
|
||||
$('input[id^="urlRemote"]').val('');
|
||||
$('#urlPathSection').show(20);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* @defgroup js_controllers_grid_roles_form Role form javascript
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/grid/settings/roles/form/UserGroupFormHandler.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 UserGroupFormHandler
|
||||
* @ingroup js_controllers_grid_settings_roles_form
|
||||
*
|
||||
* @brief Handle the role create/edit form.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.grid.settings.roles =
|
||||
$.pkp.controllers.grid.settings.roles || { form: { }};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.AjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
* @param {{
|
||||
* selfRegistrationRoleIds: Array,
|
||||
* roleForbiddenStagesJSON: Object.<string, *>,
|
||||
* stagesSelector: string
|
||||
* }} options form options.
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.roles.form.UserGroupFormHandler =
|
||||
function($form, options) {
|
||||
|
||||
var $roleId = $('[id^="roleId"]', $form);
|
||||
|
||||
this.parent($form, options);
|
||||
|
||||
// Set the role IDs for which the self-registration checkbox
|
||||
// is relevant.
|
||||
if (options.selfRegistrationRoleIds) {
|
||||
this.selfRegistrationRoleIds_ = options.selfRegistrationRoleIds;
|
||||
}
|
||||
|
||||
// Set the role IDs for which the recommendOnly checkbox
|
||||
// is relevant.
|
||||
if (options.recommendOnlyRoleIds) {
|
||||
this.recommendOnlyRoleIds_ = options.recommendOnlyRoleIds;
|
||||
}
|
||||
|
||||
// Set the roles that are not able to change
|
||||
// submission metadata edit perissions
|
||||
if (options.notChangeMetadataEditPermissionRoles) {
|
||||
this.notChangeMetadataEditPermissionRoles_ =
|
||||
options.notChangeMetadataEditPermissionRoles;
|
||||
}
|
||||
|
||||
this.roleForbiddenStages_ = options.roleForbiddenStagesJSON.content;
|
||||
this.stagesSelector_ = options.stagesSelector;
|
||||
|
||||
// Initialize the "permit self register" checkbox disabled
|
||||
// state based on the form's current selection
|
||||
this.updatePermitSelfRegistration(
|
||||
/** @type {string} */ ($roleId.val()));
|
||||
|
||||
// Initialize the "permit metadata edit" checkbox disabled
|
||||
// state based on the form's current selection
|
||||
this.updatePermitMetadataEdit(
|
||||
/** @type {string} */ ($roleId.val()), false);
|
||||
|
||||
// ...also initialize the stage options, disabling the ones
|
||||
// that are forbidden for the current role.
|
||||
this.updateStageOptions(
|
||||
/** @type {string} */ ($roleId.val()));
|
||||
|
||||
// ...also initialize the recommendOnly option, disabling it
|
||||
// if it is forbidden for the current role.
|
||||
this.updateRecommendOnly(
|
||||
/** @type {string} */ ($roleId.val()));
|
||||
|
||||
// ...and make sure both it's updated when changing roles.
|
||||
$roleId.change(this.callbackWrapper(this.changeRoleId));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.settings.roles.form.UserGroupFormHandler,
|
||||
$.pkp.controllers.form.AjaxFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The list of self-registration role IDs
|
||||
* @private
|
||||
* @type {Object?}
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.roles.form.
|
||||
UserGroupFormHandler.prototype.selfRegistrationRoleIds_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A list of role forbidden stages.
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.roles.form.
|
||||
UserGroupFormHandler.prototype.roleForbiddenStages_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The stage options selector.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.roles.form.
|
||||
UserGroupFormHandler.prototype.stagesSelector_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The list of not allowed to change submission metadata edit permissions roles
|
||||
* @private
|
||||
* @type {Object?}
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.roles.form.
|
||||
UserGroupFormHandler.prototype.notChangeMetadataEditPermissionRoles_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Private methods.
|
||||
//
|
||||
/**
|
||||
* Event handler that is called when the role ID dropdown is changed.
|
||||
* @param {string} dropdown The dropdown input element.
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.roles.form.UserGroupFormHandler.prototype.
|
||||
changeRoleId = function(dropdown) {
|
||||
|
||||
var dropDownValue = /** @type {string} */ ($(dropdown).val());
|
||||
|
||||
this.updatePermitSelfRegistration((dropDownValue));
|
||||
this.updatePermitMetadataEdit(/** @type {string} */ (dropDownValue), true);
|
||||
|
||||
// Also update the stages options.
|
||||
this.updateStageOptions(/** @type {string} */ (dropDownValue));
|
||||
|
||||
this.updateRecommendOnly(/** @type {string} */ (dropDownValue));
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update the enabled/disabled state of the permitSelfRegistration
|
||||
* checkbox.
|
||||
* @param {number|string} roleId The role ID to select.
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.roles.form.UserGroupFormHandler.prototype.
|
||||
updatePermitSelfRegistration = function(roleId) {
|
||||
|
||||
// JQuerify the element
|
||||
var $checkbox = $('[id^="permitSelfRegistration"]'),
|
||||
$form = this.getHtmlElement(),
|
||||
i,
|
||||
found = false;
|
||||
|
||||
for (i = 0; i < this.selfRegistrationRoleIds_.length; i++) {
|
||||
if (this.selfRegistrationRoleIds_[i] == roleId) {
|
||||
found = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
$checkbox.removeAttr('disabled');
|
||||
} else {
|
||||
$checkbox.attr('disabled', 'disabled');
|
||||
$checkbox.removeAttr('checked');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update the enabled/disabled state of the PermitMetadataEdit
|
||||
* checkbox.
|
||||
* @param {number|string} roleId The role ID to select.
|
||||
* @param {boolean} roleIdChanged True iff the role ID changed.
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.roles.form.UserGroupFormHandler.prototype.
|
||||
updatePermitMetadataEdit = function(roleId, roleIdChanged) {
|
||||
var i, $checkbox = $('[id^="permitMetadataEdit"]'),
|
||||
found = false;
|
||||
|
||||
for (i = 0; i < this.notChangeMetadataEditPermissionRoles_.length; i++) {
|
||||
if (this.notChangeMetadataEditPermissionRoles_[i] == roleId) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If found then the check box should be disabled and checked
|
||||
if (found) {
|
||||
$checkbox.attr('disabled', 'disabled');
|
||||
$checkbox.attr('checked', 'checked');
|
||||
$checkbox.prop('checked', 'checked');
|
||||
} else {
|
||||
$checkbox.removeAttr('disabled');
|
||||
if (roleIdChanged) {
|
||||
$checkbox.removeAttr('checked');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update the stage options.
|
||||
* @param {number|string} roleId The role ID to select.
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.roles.form.UserGroupFormHandler.prototype.
|
||||
updateStageOptions = function(roleId) {
|
||||
|
||||
// JQuerify the element
|
||||
var $htmlElement = this.getHtmlElement(),
|
||||
$stageContainer = $htmlElement.find('#userGroupStageContainer'),
|
||||
$stageOptions = $(this.stagesSelector_, $htmlElement).filter('input'),
|
||||
i,
|
||||
stageId = null;
|
||||
|
||||
$stageOptions.removeAttr('disabled');
|
||||
|
||||
if (this.roleForbiddenStages_[roleId] != undefined) {
|
||||
for (i = 0; i < this.roleForbiddenStages_[roleId].length; i++) {
|
||||
stageId = this.roleForbiddenStages_[roleId][i];
|
||||
$stageOptions.filter('input[value="' + stageId + '"]').
|
||||
attr('disabled', 'disabled');
|
||||
}
|
||||
}
|
||||
|
||||
if ($htmlElement.find(
|
||||
'input[id^=\'assignedStages-\']:enabled').length == 0) {
|
||||
$stageContainer.hide('slow');
|
||||
$('#showTitle').attr('disabled', 'disabled');
|
||||
} else {
|
||||
$stageContainer.show('slow');
|
||||
$('#showTitle').removeAttr('disabled');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update the enabled/disabled state of the recommendOnly checkbox.
|
||||
* @param {number|string} roleId The role ID to select.
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.roles.form.UserGroupFormHandler.prototype.
|
||||
updateRecommendOnly = function(roleId) {
|
||||
|
||||
// JQuerify the element
|
||||
var $checkbox = $('[id^=\'recommendOnly\']', this.getHtmlElement()),
|
||||
i,
|
||||
found = false;
|
||||
|
||||
for (i = 0; i < this.recommendOnlyRoleIds_.length; i++) {
|
||||
if (this.recommendOnlyRoleIds_[i] == roleId) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
$checkbox.removeAttr('disabled');
|
||||
} else {
|
||||
$checkbox.attr('disabled', 'disabled');
|
||||
$checkbox.removeAttr('checked');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @defgroup js_controllers_grid_users_user_form User form javascript
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/grid/settings/user/form/UserDetailsFormHandler.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 UserDetailsFormHandler
|
||||
* @ingroup js_controllers_grid_settings_user_form
|
||||
*
|
||||
* @brief Handle the user settings form.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.grid.settings =
|
||||
$.pkp.controllers.grid.settings || { user: { form: { } }};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.UserFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
* @param {Object} options form options.
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.user.form.UserDetailsFormHandler =
|
||||
function($form, options) {
|
||||
|
||||
this.parent($form, options);
|
||||
|
||||
// Attach form elements events.
|
||||
$('[id^="generatePassword"]', $form).click(
|
||||
this.callbackWrapper(this.setGenerateRandom));
|
||||
|
||||
// Check the generate password check box.
|
||||
if ($('[id^="generatePassword"]', $form).attr('checked')) {
|
||||
this.setGenerateRandom('[id^="generatePassword"]');
|
||||
}
|
||||
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.settings.user.form.UserDetailsFormHandler,
|
||||
$.pkp.controllers.form.UserFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Public methods.
|
||||
//
|
||||
/**
|
||||
* @see AjaxFormHandler::submitForm
|
||||
* @param {Object} validator The validator plug-in.
|
||||
* @param {HTMLElement} formElement The wrapped HTML form.
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.user.form.UserDetailsFormHandler.prototype.
|
||||
submitForm = function(validator, formElement) {
|
||||
|
||||
var $form = this.getHtmlElement();
|
||||
$(':password', $form).removeAttr('disabled');
|
||||
this.parent('submitForm', validator, formElement);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Event handler that is called when generate password checkbox is
|
||||
* clicked.
|
||||
* @param {string} checkbox The checkbox input element.
|
||||
*/
|
||||
$.pkp.controllers.grid.settings.user.form.UserDetailsFormHandler.prototype.
|
||||
setGenerateRandom = function(checkbox) {
|
||||
|
||||
// JQuerify the element
|
||||
var $checkbox = $(checkbox),
|
||||
$form = this.getHtmlElement(),
|
||||
passwordValue = '',
|
||||
activeAndCheck = 0;
|
||||
|
||||
if ($checkbox.prop('checked')) {
|
||||
passwordValue = '********';
|
||||
activeAndCheck = 'disabled';
|
||||
} else {
|
||||
passwordValue = '';
|
||||
activeAndCheck = '';
|
||||
}
|
||||
$(':password', $form).
|
||||
prop('disabled', activeAndCheck).val(passwordValue);
|
||||
$('[id^="sendNotify"]', $form).attr('disabled', activeAndCheck).
|
||||
prop('checked', activeAndCheck);
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file js/controllers/grid/users/UserGridHandler.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 UserGridHandler
|
||||
* @ingroup js_controllers_grid
|
||||
*
|
||||
* @brief User grid handler. Used to keep user grids in sync, such as when
|
||||
* merging users.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
// Define the namespace.
|
||||
$.pkp.controllers.grid.users = $.pkp.controllers.grid.users || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.grid.GridHandler
|
||||
*
|
||||
* @param {jQueryObject} $grid The grid this handler is
|
||||
* attached to.
|
||||
* @param {Object} options Grid handler configuration.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.UserGridHandler =
|
||||
function($grid, options) {
|
||||
this.parent($grid, options);
|
||||
|
||||
this.bindGlobal('userMerged', function() {
|
||||
|
||||
// Signal to any parent modals that they can close now
|
||||
this.trigger('modalFinished');
|
||||
|
||||
this.refreshGridHandler();
|
||||
});
|
||||
|
||||
// Refresh the grid when a user group has been added/edited
|
||||
this.bindGlobal('userGroupUpdated', function() {
|
||||
this.refreshGridHandler();
|
||||
});
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.grid.users.UserGridHandler,
|
||||
$.pkp.controllers.grid.GridHandler);
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @defgroup js_controllers_grid_users_stageParticipant_form
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/AdvancedReviewerSearchHandler.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 AdvancedReviewerSearchHandler
|
||||
* @ingroup js_controllers
|
||||
*
|
||||
* @brief Handle the advanced reviewer search tab in the add reviewer modal.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.grid.users.reviewer =
|
||||
$.pkp.controllers.grid.users.reviewer || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $container the wrapped page element.
|
||||
* @param {Object} options handler options.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.AdvancedReviewerSearchHandler =
|
||||
function($container, options) {
|
||||
this.parent($container, options);
|
||||
|
||||
$container.find('.button').button();
|
||||
|
||||
pkp.eventBus.$on('selected:reviewer', function(reviewer) {
|
||||
$('#reviewerId').val(reviewer.id);
|
||||
$('[id^="selectedReviewerName"]').text(reviewer.fullName);
|
||||
$('#searchGridAndButton').hide();
|
||||
$('#regularReviewerForm').show();
|
||||
|
||||
// Set the email message for reviewers depending
|
||||
// on previous completed assignments
|
||||
var $textarea = $('#reviewerFormFooter [name="personalMessage"]'),
|
||||
$templateInput,
|
||||
$templateOption,
|
||||
editor,
|
||||
templateKey;
|
||||
if ($textarea.val()) {
|
||||
return; // The message is already set; shouldn't happen
|
||||
}
|
||||
// Only 1 template available
|
||||
$templateInput = $('#reviewerFormFooter input[name="template"]');
|
||||
// Multiple available templates
|
||||
$templateOption = $('#reviewerFormFooter select[name="template"]');
|
||||
editor = tinyMCE.EditorManager.get($textarea.attr('id'));
|
||||
templateKey = '';
|
||||
if (options.lastRoundReviewerIds.includes(reviewer.id)) {
|
||||
templateKey = 'REVIEW_REQUEST_SUBSEQUENT';
|
||||
editor.setContent(options.reviewerMessages[templateKey]);
|
||||
$templateInput.val(templateKey);
|
||||
$templateOption.find('[value="REVIEW_REQUEST"]').remove();
|
||||
} else {
|
||||
templateKey = 'REVIEW_REQUEST';
|
||||
editor.setContent(options.reviewerMessages[templateKey]);
|
||||
$templateInput.val(templateKey);
|
||||
$templateOption.find('[value="REVIEW_REQUEST_SUBSEQUENT"]').remove();
|
||||
}
|
||||
// Select the right template option to correspond
|
||||
// the one, which is set in TinyMCE
|
||||
$templateOption.find('[value="' + templateKey + '"]')
|
||||
.prop('selected', true);
|
||||
});
|
||||
|
||||
$('#regularReviewerForm').hide();
|
||||
|
||||
this.bind('refreshForm', this.handleRefresh_);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.users.reviewer.AdvancedReviewerSearchHandler,
|
||||
$.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Handle the form refresh event.
|
||||
* @private
|
||||
* @param {HTMLElement} sourceElement The element that issued the event.
|
||||
* @param {Event} event The triggering event.
|
||||
* @param {string} content HTML contents to replace element contents.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.AdvancedReviewerSearchHandler.prototype.
|
||||
handleRefresh_ = function(sourceElement, event, content) {
|
||||
|
||||
if (content) {
|
||||
this.replaceWith(content);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @defgroup js_controllers_grid_users_reviewer
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/grid/users/reviewer/ReadReviewHandler.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 ReadReviewHandler
|
||||
* @ingroup js_controllers_grid_users_reviewer
|
||||
*
|
||||
* @brief Handle the advanced reviewer search tab in the add reviewer modal.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.AjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped page element.
|
||||
* @param {Object} options handler options.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.ReadReviewHandler =
|
||||
function($form, options) {
|
||||
this.parent($form, options);
|
||||
|
||||
this.reviewCompleted_ = options.reviewCompleted;
|
||||
// bind a handler to make sure that a review file has been uploaded.
|
||||
$form.find('[id^=\'submitFormButton-\']').click(this.callbackWrapper(
|
||||
this.reviewFilesRequired_));
|
||||
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.users.reviewer.ReadReviewHandler,
|
||||
$.pkp.controllers.form.AjaxFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private methods.
|
||||
//
|
||||
/**
|
||||
* Is the review completed.
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.ReadReviewHandler.
|
||||
prototype.reviewCompleted_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback called on form submit to ensure there are
|
||||
* some review files uploaded.
|
||||
* @private
|
||||
* @param {HTMLElement} submitButton The submit button.
|
||||
* @param {Event} event The event that triggered the
|
||||
* submit button.
|
||||
* @return {boolean} true.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.ReadReviewHandler.
|
||||
prototype.reviewFilesRequired_ = function(submitButton, event) {
|
||||
|
||||
if (!this.reviewCompleted_ && $('#readReviewAttachmentsGridContainer').
|
||||
find('tbody.empty:visible').length == 1) {
|
||||
// There's nothing in the files grid; don't submit the form
|
||||
this.showWarning_();
|
||||
return false;
|
||||
} else {
|
||||
// There's something in the files grid;
|
||||
this.hideWarning_();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hide the "no files" warning.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.ReadReviewHandler.
|
||||
prototype.hideWarning_ = function() {
|
||||
this.getHtmlElement().find('#noFilesWarning').hide(250);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show the "no files" warning.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.ReadReviewHandler.
|
||||
prototype.showWarning_ = function() {
|
||||
this.getHtmlElement().find('#noFilesWarning').show(250);
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @file js/controllers/grid/users/reviewer/form/AddReviewerFormHandler.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 AddReviewerFormHandler
|
||||
* @ingroup js_controllers_grid_users_reviewer_form
|
||||
*
|
||||
* @brief Handle the Add Reviewer form (and template for message body).
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.grid.users.reviewer.form.EditReviewFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
* @param {Object} options form options.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.form.
|
||||
AddReviewerFormHandler = function($form, options) {
|
||||
|
||||
this.parent($form, options);
|
||||
|
||||
// Set the URL to retrieve templates from.
|
||||
if (options.templateUrl) {
|
||||
this.templateUrl_ = options.templateUrl;
|
||||
}
|
||||
|
||||
// Attach form elements events.
|
||||
$form.find('#template').change(
|
||||
this.callbackWrapper(this.selectTemplateHandler_));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.users.reviewer.form.
|
||||
AddReviewerFormHandler,
|
||||
$.pkp.controllers.grid.users.reviewer.form.
|
||||
EditReviewFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The URL to use to retrieve template bodies
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.form.
|
||||
AddReviewerFormHandler.prototype.templateUrl_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* Show the "no files" warning.
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.form.AddReviewerFormHandler.
|
||||
prototype.showWarning = function() {
|
||||
// Call the parent showWarning to show the warning
|
||||
this.parent('showWarning');
|
||||
|
||||
// Ask the reviewer form footer handler to expand the file
|
||||
// list extras-on-demand if it isn't already expanded.
|
||||
this.getHtmlElement().find('#reviewerFormFooter')
|
||||
.trigger('expandFileList');
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Respond to an "item selected" call by triggering a published event.
|
||||
*
|
||||
* @param {HTMLElement} sourceElement The element that
|
||||
* issued the event.
|
||||
* @param {Event} event The triggering event.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.form.
|
||||
AddReviewerFormHandler.prototype.selectTemplateHandler_ =
|
||||
function(sourceElement, event) {
|
||||
|
||||
var $form = this.getHtmlElement();
|
||||
$.post(this.templateUrl_, $form.find('#template').serialize(),
|
||||
this.callbackWrapper(this.updateTemplate), 'json');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback to replace the textarea with the contents of the
|
||||
* template body.
|
||||
*
|
||||
* @param {HTMLElement} formElement The wrapped HTML form.
|
||||
* @param {Object} jsonData The data returned from the server.
|
||||
* @return {boolean} The response status.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.form.
|
||||
AddReviewerFormHandler.prototype.updateTemplate =
|
||||
function(formElement, jsonData) {
|
||||
|
||||
var $form = this.getHtmlElement(),
|
||||
processedJsonData = this.handleJson(jsonData),
|
||||
$textarea = $form.find('textarea[name="personalMessage"]'),
|
||||
editor =
|
||||
tinyMCE.EditorManager.get(/** @type {string} */ ($textarea.attr('id')));
|
||||
|
||||
if (processedJsonData !== false) {
|
||||
if (processedJsonData.content !== '') {
|
||||
editor.setContent(processedJsonData.content);
|
||||
}
|
||||
}
|
||||
return processedJsonData.status;
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* @defgroup js_controllers_grid_users_reviewer_form
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/grid/users/reviewer/form/EditReviewFormHandler.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 EditReviewFormHandler
|
||||
* @ingroup js_controllers_grid_users_reviewer_form
|
||||
*
|
||||
* @brief Handle the limit reviewer files form. Also used as a base class
|
||||
* for the add reviewer form handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.grid.users.reviewer.form =
|
||||
$.pkp.controllers.grid.users.reviewer.form || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.UserFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
* @param {Object} options form options.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.form.
|
||||
EditReviewFormHandler = function($form, options) {
|
||||
|
||||
this.parent($form, options);
|
||||
|
||||
// When the form changes, check to see if a warning is necessary
|
||||
// (if all reviewer files are unchecked)
|
||||
$form.change(this.callbackWrapper(this.handleFormChange));
|
||||
|
||||
// When the reviewer files list loads, trigger the above check
|
||||
this.bind('urlInDivLoaded', this.handleFileListLoad_);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.users.reviewer.form.
|
||||
EditReviewFormHandler,
|
||||
$.pkp.controllers.form.UserFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Protected methods.
|
||||
//
|
||||
/**
|
||||
* Handle a form change event.
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.form.EditReviewFormHandler.
|
||||
prototype.handleFormChange = function() {
|
||||
if (this.getHtmlElement()
|
||||
.find('input[name="selectedFiles[]"]:checked').length) {
|
||||
this.hideWarning();
|
||||
} else {
|
||||
this.showWarning();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hide the "no files" warning.
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.form.EditReviewFormHandler.
|
||||
prototype.hideWarning = function() {
|
||||
this.getHtmlElement().find('#noFilesWarning').hide(250);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show the "no files" warning.
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.form.EditReviewFormHandler.
|
||||
prototype.showWarning = function() {
|
||||
this.getHtmlElement().find('#noFilesWarning').show(250);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods.
|
||||
//
|
||||
/**
|
||||
* Handle the loading of the reviewer files list.
|
||||
* @private
|
||||
* @param {HTMLElement} sourceElement The element that
|
||||
* issued the event.
|
||||
* @param {Event} event The triggering event.
|
||||
* @param {?string} data additional event data.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.reviewer.form.EditReviewFormHandler.
|
||||
prototype.handleFileListLoad_ =
|
||||
function(sourceElement, event, data) {
|
||||
|
||||
// Trigger a form change event to display the "no files
|
||||
// selected" warning, if necessary.
|
||||
this.getHtmlElement().change();
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file js/controllers/grid/users/stageParticipant/StageParticipantGridHandler.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 StageParticipantGridHandler
|
||||
* @ingroup js_controllers_grid
|
||||
*
|
||||
* @brief Stage participant grid handler.
|
||||
*/
|
||||
/*global pkp */
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.grid.users.stageParticipant =
|
||||
$.pkp.controllers.grid.users.stageParticipant || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.grid.CategoryGridHandler
|
||||
*
|
||||
* @param {jQueryObject} $grid The grid this handler is
|
||||
* attached to.
|
||||
* @param {Object} options Grid handler configuration.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.StageParticipantGridHandler =
|
||||
function($grid, options) {
|
||||
this.parent($grid, options);
|
||||
|
||||
// Reload any editorial actions on the page.
|
||||
this.bind('dataChanged', function() {
|
||||
this.refreshGridHandler();
|
||||
$(['#submissionEditorDecisionsDiv',
|
||||
'#copyeditingEditorDecisionsDiv',
|
||||
'[id^=reviewDecisionsDiv]'].join(','))
|
||||
.each(function() {
|
||||
$.pkp.classes.Handler.getHandler($(this)).reload();
|
||||
});
|
||||
});
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.users.stageParticipant.StageParticipantGridHandler,
|
||||
$.pkp.controllers.grid.CategoryGridHandler);
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @defgroup js_controllers_grid_users_stageParticipant_form
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/grid/users/stageParticipant/form/AddParticipantFormHandler.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 AddParticipantFormHandler
|
||||
* @ingroup js_controllers_grid_users_stageParticipant_form
|
||||
*
|
||||
* @brief Handle the search user filter and
|
||||
* add the value to the hidden userGroupId field.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.ClientFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
* @param {Object} options form options.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.AddParticipantFormHandler =
|
||||
function($form, options) {
|
||||
|
||||
this.parent($form, options);
|
||||
|
||||
$('select[name^=\'filterUserGroupId\']', $form).change(
|
||||
this.callbackWrapper(this.addUserGroupId));
|
||||
|
||||
$form.parent().click(function(e) {
|
||||
var $target = $(e.target), filterUserIdVal;
|
||||
if ($target.is('input[name="userId"]')) {
|
||||
filterUserIdVal = $('input[name=\'userId\']:checked').val();
|
||||
$('input[name=\'userIdSelected\']').val(
|
||||
filterUserIdVal).trigger('change');
|
||||
}
|
||||
});
|
||||
|
||||
// initially populate the input field.
|
||||
this.addUserGroupId();
|
||||
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
AddParticipantFormHandler,
|
||||
$.pkp.controllers.form.ClientFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Method to add the value to the hidden userGroupId field
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.AddParticipantFormHandler.
|
||||
prototype.addUserGroupId = function() {
|
||||
|
||||
var $form = this.getHtmlElement(),
|
||||
$filterUserGroupId = $form.find('select[name^=\'filterUserGroupId\']'),
|
||||
filterUserGroupIdVal = /** @type {string} */ ($filterUserGroupId.val());
|
||||
|
||||
$('input[name=\'userGroupId\']').val(filterUserGroupIdVal).trigger('change');
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
+374
@@ -0,0 +1,374 @@
|
||||
/**
|
||||
* @defgroup js_controllers_grid_users_stageParticipant_form
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/grid/users/stageParticipant/form/StageParticipantNotifyHandler.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 StageParticipantNotifyHandler
|
||||
* @ingroup js_controllers_grid_users_stageParticipant_form
|
||||
*
|
||||
* @brief Handle Stage participant notification forms.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.grid.users.stageParticipant.form =
|
||||
$.pkp.controllers.grid.users.stageParticipant.form || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.AjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form the wrapped HTML form element.
|
||||
* @param {Object} options form options.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler = function($form, options) {
|
||||
|
||||
this.parent($form, options);
|
||||
|
||||
// Set the URL to retrieve templates from.
|
||||
if (options.templateUrl) {
|
||||
this.templateUrl_ = options.templateUrl;
|
||||
}
|
||||
|
||||
// Set the user group IDs with the recommendOnly possibility
|
||||
if (options.possibleRecommendOnlyUserGroupIds) {
|
||||
this.possibleRecommendOnlyUserGroupIds_ =
|
||||
options.possibleRecommendOnlyUserGroupIds;
|
||||
}
|
||||
|
||||
// Set the user group IDs with the recommendOnly option set
|
||||
if (options.recommendOnlyUserGroupIds) {
|
||||
this.recommendOnlyUserGroupIds_ = options.recommendOnlyUserGroupIds;
|
||||
}
|
||||
|
||||
// Set the user group IDs that are not allowed to change the default
|
||||
// value of permitMetadataEdit
|
||||
if (options.notChangeMetadataEditPermissionRoles) {
|
||||
this.notChangeMetadataEditPermissionRoles_ =
|
||||
options.notChangeMetadataEditPermissionRoles;
|
||||
}
|
||||
|
||||
// Set the user group IDs that have the permitMetadataEdit flag set to true
|
||||
if (options.permitMetadataEditUserGroupIds) {
|
||||
this.permitMetadataEditUserGroupIds_ =
|
||||
options.permitMetadataEditUserGroupIds;
|
||||
}
|
||||
|
||||
if (options.anonymousReviewerIds) {
|
||||
this.anonymousReviewerIds_ = options.anonymousReviewerIds;
|
||||
}
|
||||
|
||||
if (options.anonymousReviewerWarning) {
|
||||
this.anonymousReviewerWarning_ = options.anonymousReviewerWarning;
|
||||
}
|
||||
|
||||
if (options.anonymousReviewerWarningOk) {
|
||||
this.anonymousReviewerWarningOk_ = options.anonymousReviewerWarningOk;
|
||||
}
|
||||
|
||||
// Update the recommendOnly option display when user group changes
|
||||
// or user is selected
|
||||
$('input[name=\'userGroupId\'], input[name=\'userIdSelected\']', $form)
|
||||
.change(this.callbackWrapper(this.updateRecommendOnly));
|
||||
|
||||
// Update the recommendOnly option display when user group changes
|
||||
// or user is selected
|
||||
$('input[name=\'userGroupId\'], input[name=\'userIdSelected\']', $form)
|
||||
.change(this.callbackWrapper(
|
||||
this.updateSubmissionMetadataEditPermitOption));
|
||||
|
||||
// Trigger a warning message if an anonymous reviewer is selected
|
||||
$('input[name=\'userIdSelected\']', $form)
|
||||
.change(this.callbackWrapper(this.maybeTriggerReviewerWarning));
|
||||
|
||||
// Attach form elements events.
|
||||
$form.find('#template').change(
|
||||
this.callbackWrapper(this.selectTemplateHandler_));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler,
|
||||
$.pkp.controllers.form.AjaxFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The URL to use to retrieve template bodies
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.templateUrl_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A list of user IDs which are already assigned anonymous reviews for this
|
||||
* submission.
|
||||
* @private
|
||||
* @type {Array}
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.anonymousReviewerIds_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A warning message to display when an anonymous reviewer is selected to be
|
||||
* added as a recipient
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.anonymousReviewerWarning_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The OK button language for the anonymous reviewer warning message
|
||||
* @private
|
||||
* @type {string?}
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.
|
||||
anonymousReviewerWarningOk_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The list of not allowed to change submission metadata edit permissions roles
|
||||
* @private
|
||||
* @type {Object?}
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.
|
||||
notChangeMetadataEditPermissionRoles_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The list of group ids that are allowed to edit metadata
|
||||
* @private
|
||||
* @type {Object?}
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.
|
||||
permitMetadataEditUserGroupIds_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Respond to an "item selected" call by triggering a published event.
|
||||
*
|
||||
* @param {HTMLElement} sourceElement The element that
|
||||
* issued the event.
|
||||
* @param {Event} event The triggering event.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.selectTemplateHandler_ =
|
||||
function(sourceElement, event) {
|
||||
|
||||
var $form = this.getHtmlElement();
|
||||
$.post(this.templateUrl_, $form.find('#template').serialize(),
|
||||
this.callbackWrapper(this.updateTemplate), 'json');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback to replace the textarea with the contents of the
|
||||
* template body.
|
||||
*
|
||||
* @param {HTMLElement} formElement The wrapped HTML form.
|
||||
* @param {Object} jsonData The data returned from the server.
|
||||
* @return {boolean} The response status.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.updateTemplate =
|
||||
function(formElement, jsonData) {
|
||||
|
||||
var $form = this.getHtmlElement(),
|
||||
processedJsonData = this.handleJson(jsonData),
|
||||
jsonDataContent =
|
||||
/** @type {{variables: Object, body: string}} */ (jsonData.content),
|
||||
$textarea = $form.find('textarea[name="message"]'),
|
||||
editor =
|
||||
tinyMCE.EditorManager.get(/** @type {string} */ ($textarea.attr('id')));
|
||||
|
||||
if (jsonDataContent.variables) {
|
||||
$textarea.attr('data-variables', JSON.stringify(jsonDataContent.variables));
|
||||
}
|
||||
editor.setContent(jsonDataContent.body);
|
||||
|
||||
return processedJsonData.status;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update the enabled/disabled and checked state of the recommendOnly checkbox.
|
||||
* @param {HTMLElement} sourceElement The element that
|
||||
* issued the event.
|
||||
* @param {Event} event The triggering event.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.updateRecommendOnly =
|
||||
function(sourceElement, event) {
|
||||
|
||||
var $form = this.getHtmlElement(),
|
||||
$filterUserGroupId = $form.find('input[name=\'userGroupId\']'),
|
||||
$checkbox = $form.find('input[id^=\'recommendOnly\']'),
|
||||
$checkboxDiv = $form.find('.recommendOnlyWrapper'),
|
||||
i,
|
||||
found = false,
|
||||
filterUserGroupIdVal = /** @type {string} */ ($filterUserGroupId.val());
|
||||
|
||||
// If user group changes, hide the recommendOnly option
|
||||
if ($(sourceElement).prop('name') == 'userGroupId') {
|
||||
$checkbox.attr('disabled', 'disabled');
|
||||
$checkbox.removeAttr('checked');
|
||||
$checkboxDiv.hide();
|
||||
} else if ($(sourceElement).prop('name') == 'userIdSelected' &&
|
||||
!$checkboxDiv.is(':visible')) {
|
||||
// Display recommendOnly option if
|
||||
// an user group with a possible recommendOnly option is selected
|
||||
for (i = 0; i < this.possibleRecommendOnlyUserGroupIds_.length; i++) {
|
||||
if (this.possibleRecommendOnlyUserGroupIds_[i] == filterUserGroupIdVal) {
|
||||
$checkbox.removeAttr('disabled');
|
||||
$checkboxDiv.show();
|
||||
// Select the recommendOnly option if
|
||||
// an user group with a recommendOnly option set is selected
|
||||
for (i = 0; i < this.recommendOnlyUserGroupIds_.length; i++) {
|
||||
if (this.recommendOnlyUserGroupIds_[i] == filterUserGroupIdVal) {
|
||||
$checkbox.prop('checked', true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
$checkbox.attr('disabled', 'disabled');
|
||||
$checkboxDiv.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update the enabled/disabled and checked state of the recommendOnly checkbox.
|
||||
* @param {HTMLElement} sourceElement The element that
|
||||
* issued the event.
|
||||
* @param {Event} event The triggering event.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.maybeTriggerReviewerWarning =
|
||||
function(sourceElement, event) {
|
||||
|
||||
var userId = $(sourceElement).val(),
|
||||
opts;
|
||||
|
||||
if (!userId || this.anonymousReviewerIds_.indexOf(userId) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
opts = {
|
||||
title: '',
|
||||
okButton: this.anonymousReviewerWarningOk_,
|
||||
cancelButton: false,
|
||||
dialogText: this.anonymousReviewerWarning_
|
||||
};
|
||||
|
||||
$('<div id="' + $.pkp.classes.Helper.uuid() + '" ' +
|
||||
'class="pkp_modal pkpModalWrapper" tabindex="-1"></div>')
|
||||
.pkpHandler('$.pkp.controllers.modal.ConfirmationModalHandler', opts);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update the enabled/disabled and checked state of the recommendOnly checkbox.
|
||||
* @param {HTMLElement} sourceElement The element that
|
||||
* issued the event.
|
||||
* @param {Event} event The triggering event.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.
|
||||
updateSubmissionMetadataEditPermitOption =
|
||||
function(sourceElement, event) {
|
||||
|
||||
var $form = this.getHtmlElement(),
|
||||
$filterUserGroupId = $form.find('input[name=\'userGroupId\']'),
|
||||
$checkbox = $form.find('input[id^=\'canChangeMetadata\']'),
|
||||
$checkboxDiv = $form.find('.submissionEditMetadataPermit'),
|
||||
i,
|
||||
found = false,
|
||||
filterUserGroupIdVal = /** @type {string} */ ($filterUserGroupId.val());
|
||||
|
||||
// If user group changes, hide the canChangeMetadata option
|
||||
if ($(sourceElement).prop('name') == 'userGroupId') {
|
||||
$checkbox.attr('disabled', 'disabled');
|
||||
$checkbox.removeAttr('checked');
|
||||
$checkboxDiv.hide();
|
||||
} else if ($(sourceElement).prop('name') == 'userIdSelected' &&
|
||||
!$checkboxDiv.is(':visible')) {
|
||||
// Display canChangeMetadata option if
|
||||
// an user group with a possible canChangeMetadata option is selected
|
||||
for (i = 0; i < this.notChangeMetadataEditPermissionRoles_.length; i++) {
|
||||
if (this.notChangeMetadataEditPermissionRoles_[i] ==
|
||||
filterUserGroupIdVal) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
$checkbox.removeAttr('disabled');
|
||||
$checkboxDiv.show();
|
||||
// Select the recommendOnly option if
|
||||
// an user group with a recommendOnly option set is selected
|
||||
for (i = 0; i < this.permitMetadataEditUserGroupIds_.length; i++) {
|
||||
if (this.permitMetadataEditUserGroupIds_[i] == filterUserGroupIdVal) {
|
||||
$checkbox.prop('checked', true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$checkbox.attr('disabled', 'disabled');
|
||||
$checkboxDiv.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback called after form validation to handle the
|
||||
* response to a form submission.
|
||||
*
|
||||
* You can override this handler if you want to do custom handling
|
||||
* of a form response.
|
||||
*
|
||||
* @param {HTMLElement} formElement The wrapped HTML form.
|
||||
* @param {Object} jsonData The data returned from the server.
|
||||
* @return {boolean} The response status.
|
||||
*/
|
||||
$.pkp.controllers.grid.users.stageParticipant.form.
|
||||
StageParticipantNotifyHandler.prototype.handleResponse =
|
||||
function(formElement, jsonData) {
|
||||
|
||||
// Reload the query grid to show the newly created query.
|
||||
var $queries = $('#queriesGrid .pkp_controllers_grid');
|
||||
if ($.pkp.classes.Handler.hasHandler($queries)) {
|
||||
$.pkp.classes.Handler.getHandler($queries).trigger('dataChanged');
|
||||
}
|
||||
|
||||
return /** @type {boolean} */ (this.parent(
|
||||
'handleResponse', formElement, jsonData));
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @defgroup js_controllers_informationCenter
|
||||
*/
|
||||
// Create the modal namespace.
|
||||
jQuery.pkp.controllers.informationCenter =
|
||||
jQuery.pkp.controllers.informationCenter || { };
|
||||
|
||||
|
||||
/**
|
||||
* @file js/controllers/informationCenter/NotesHandler.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 NotesHandler
|
||||
* @ingroup js_controllers_informationCenter
|
||||
*
|
||||
* @brief Information center "notes" tab handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $notesDiv A wrapped HTML element that
|
||||
* represents the "notes" interface element.
|
||||
* @param {Object} options Tabbed modal options.
|
||||
*/
|
||||
$.pkp.controllers.informationCenter.NotesHandler =
|
||||
function($notesDiv, options) {
|
||||
this.parent($notesDiv, options);
|
||||
|
||||
// Refresh the widget when a note is added or deleted
|
||||
this.bind('noteAdded', this.handleRefreshNoteList);
|
||||
this.bind('noteDeleted', this.handleRefreshNoteList);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.informationCenter.NotesHandler,
|
||||
$.pkp.classes.Handler
|
||||
);
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Handle the "note added" event triggered by the
|
||||
* note form whenever a new note is added.
|
||||
*
|
||||
* @param {$.pkp.controllers.form.AjaxFormHandler} callingForm The form
|
||||
* that triggered the event.
|
||||
* @param {Event} event The upload event.
|
||||
* @param {string} html Rendered HTML to refresh the notes handler
|
||||
*/
|
||||
$.pkp.controllers.informationCenter.NotesHandler.
|
||||
prototype.handleRefreshNoteList = function(callingForm, event, html) {
|
||||
|
||||
// Scroll back to the top of the notes list, where the note will appear
|
||||
$('.pkp_modal').first().scrollTop(0);
|
||||
|
||||
this.replaceWith(html);
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* @defgroup js_controllers_linkAction
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/linkAction/LinkActionHandler.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 LinkActionHandler
|
||||
* @ingroup js_controllers_linkAction
|
||||
*
|
||||
* @brief Link action handler that executes the action's handler when activated
|
||||
* and delegates the action handler's response to the action's response
|
||||
* handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
jQuery.pkp.controllers.linkAction = jQuery.pkp.controllers.linkAction || { };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $handledElement The clickable element
|
||||
* the link action will be attached to.
|
||||
* @param {{actionRequest, actionRequestOptions}} options
|
||||
* Configuration of the link action handler. The object must contain the
|
||||
* following elements:
|
||||
* - actionRequest: The action to be executed when the link
|
||||
* action is being activated.
|
||||
* - actionRequestOptions: Configuration of the action request.
|
||||
*/
|
||||
$.pkp.controllers.linkAction.LinkActionHandler =
|
||||
function($handledElement, options) {
|
||||
this.parent($handledElement, options);
|
||||
|
||||
// We need to know the static part of the element id
|
||||
// (id attribute will change after refreshing,
|
||||
// because it uses the uniqId function) for accessing
|
||||
// the link action element in the DOM.
|
||||
if (options.staticId) {
|
||||
this.staticId_ = options.staticId;
|
||||
} else {
|
||||
// If none, the link action element id is
|
||||
// not using the unique function, so we
|
||||
// can consider it static.
|
||||
this.staticId_ = /** @type {string} */ ($handledElement.attr('id'));
|
||||
}
|
||||
|
||||
// Instantiate the link action request.
|
||||
if (!options.actionRequest || !options.actionRequestOptions) {
|
||||
throw new Error(['The "actionRequest" and "actionRequestOptions"',
|
||||
'settings are required in a LinkActionHandler'].join(''));
|
||||
}
|
||||
|
||||
// Configure the callback called when the link
|
||||
// action request finishes.
|
||||
options.actionRequestOptions.finishCallback =
|
||||
this.callbackWrapper(this.enableLink);
|
||||
|
||||
this.linkActionRequest_ =
|
||||
/** @type {$.pkp.classes.linkAction.LinkActionRequest} */
|
||||
($.pkp.classes.Helper.objectFactory(
|
||||
options.actionRequest,
|
||||
[$handledElement, options.actionRequestOptions]));
|
||||
|
||||
// Bind the link action request to the handled element.
|
||||
this.bindActionRequest();
|
||||
|
||||
// Publish this event so we can handle it and grids still
|
||||
// can listen to it to refresh themselves.
|
||||
//
|
||||
// This needs to happen before the dataChangedHandler_ bound,
|
||||
// otherwise when the publish event handler try to bubble up the
|
||||
// dataChanged event, this html element could be already removed
|
||||
// by the notifyUser event handlers triggered by dataChangedHandler_
|
||||
this.publishEvent('dataChanged');
|
||||
|
||||
// Bind the data changed event, so we know when trigger
|
||||
// the notify user event.
|
||||
this.bind('dataChanged', this.dataChangedHandler_);
|
||||
|
||||
// Re-enable submit buttons when a modal is closed
|
||||
this.bind('pkpModalClose', this.removeDisabledAttribute_);
|
||||
|
||||
if (options.selfActivate) {
|
||||
this.trigger('click');
|
||||
}
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.linkAction.LinkActionHandler,
|
||||
$.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The link action request object.
|
||||
* @private
|
||||
* @type {$.pkp.classes.linkAction.LinkActionRequest}
|
||||
*/
|
||||
$.pkp.controllers.linkAction.LinkActionHandler.prototype.
|
||||
linkActionRequest_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The part of this HTML element id that's static, not
|
||||
* changing after a refresh.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.linkAction.LinkActionHandler.prototype.
|
||||
staticId_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Getter
|
||||
//
|
||||
/**
|
||||
* Get the static id part of the HTML element id.
|
||||
* @return {?string} Non-unique part of HTML element id.
|
||||
*/
|
||||
$.pkp.controllers.linkAction.LinkActionHandler.prototype.
|
||||
getStaticId = function() {
|
||||
return this.staticId_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the link url.
|
||||
* @return {?string} Link url.
|
||||
*/
|
||||
$.pkp.controllers.linkAction.LinkActionHandler.prototype.
|
||||
getUrl = function() {
|
||||
return this.linkActionRequest_.getUrl();
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Activate the link action request.
|
||||
*
|
||||
* @param {HTMLElement} callingElement 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.controllers.linkAction.LinkActionHandler.prototype.
|
||||
activateAction = function(callingElement, event) {
|
||||
|
||||
// Unbind our click handler to avoid double-execution
|
||||
// while the link action is executing. We will not disable
|
||||
// if this link action have a null action request. In that
|
||||
// case, the action request is handled by some parent widget.
|
||||
if (this.linkActionRequest_.shouldDebounce()) {
|
||||
this.disableLink();
|
||||
}
|
||||
|
||||
// Call the link request.
|
||||
return this.linkActionRequest_.activate.call(this.linkActionRequest_,
|
||||
callingElement, event);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind the link action request.
|
||||
*/
|
||||
$.pkp.controllers.linkAction.LinkActionHandler.prototype.
|
||||
bindActionRequest = function() {
|
||||
|
||||
// (Re-)bind our click handler so that the action
|
||||
// can be executed.
|
||||
this.bind('click', this.activateAction);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enable link action.
|
||||
*/
|
||||
$.pkp.controllers.linkAction.LinkActionHandler.prototype.
|
||||
enableLink = function() {
|
||||
var $linkActionElement, actionRequestUrl;
|
||||
|
||||
$linkActionElement = $(this.getHtmlElement());
|
||||
|
||||
// only remove the disabled state if it is not a submit button.
|
||||
// we let FormHandler remove that after a form is submitted.
|
||||
if (!this.getHtmlElement().is(':submit')) {
|
||||
this.removeDisabledAttribute_();
|
||||
}
|
||||
|
||||
actionRequestUrl = this.getUrl();
|
||||
if (this.getHtmlElement().is('a') && actionRequestUrl) {
|
||||
$linkActionElement.attr('href', actionRequestUrl);
|
||||
}
|
||||
this.unbind('click', this.noAction_);
|
||||
this.bindActionRequest();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disable link action.
|
||||
*/
|
||||
$.pkp.controllers.linkAction.LinkActionHandler.prototype.
|
||||
disableLink = function() {
|
||||
var $linkActionElement = $(this.getHtmlElement());
|
||||
$linkActionElement.attr('disabled', 'disabled');
|
||||
if (this.getHtmlElement().is('a')) {
|
||||
$linkActionElement.attr('href', '#');
|
||||
}
|
||||
this.unbind('click', this.activateAction);
|
||||
this.bind('click', this.noAction_);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods.
|
||||
//
|
||||
/**
|
||||
* Remove the 'disabled' CSS class for the linkActionElement.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.linkAction.LinkActionHandler.prototype.
|
||||
removeDisabledAttribute_ = function() {
|
||||
|
||||
var $linkActionElement = $(this.getHtmlElement());
|
||||
$linkActionElement.removeAttr('disabled');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the changed data event.
|
||||
* @private
|
||||
*
|
||||
* @param {jQueryObject} callingElement The calling html element.
|
||||
* @param {Event} event The event object (dataChanged).
|
||||
* @param {Object} eventData Event data.
|
||||
*/
|
||||
$.pkp.controllers.linkAction.LinkActionHandler.prototype.
|
||||
dataChangedHandler_ = function(callingElement, event, eventData) {
|
||||
|
||||
if (this.getHtmlElement().parents('.pkp_controllers_grid').length === 0) {
|
||||
// We might want to redirect this data changed event to a grid.
|
||||
// Trigger another event so parent widgets can handle this
|
||||
// redirection.
|
||||
this.trigger('redirectDataChangedToGrid', [eventData]);
|
||||
}
|
||||
this.trigger('notifyUser');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Click event handler used to avoid any action request.
|
||||
* @return {boolean} Always returns false.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.linkAction.LinkActionHandler.prototype.
|
||||
noAction_ = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,856 @@
|
||||
/**
|
||||
* @defgroup js_controllers_listbuilder
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/listbuilder/ListbuilderHandler.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 ListbuilderHandler
|
||||
* @ingroup js_controllers_listbuilder
|
||||
*
|
||||
* @brief Listbuilder row handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.listbuilder = $.pkp.controllers.listbuilder || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.grid.GridHandler
|
||||
*
|
||||
* @param {jQueryObject} $listbuilder The listbuilder this handler is
|
||||
* attached to.
|
||||
* @param {Object} options Listbuilder handler configuration.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler =
|
||||
function($listbuilder, options) {
|
||||
this.parent($listbuilder, options);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.listbuilder.ListbuilderHandler,
|
||||
$.pkp.controllers.grid.GridHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The source type (LISTBUILDER_SOURCE_TYPE_...) of the listbuilder.
|
||||
* @private
|
||||
* @type {?number}
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
sourceType_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The "save" URL of the listbuilder (for
|
||||
* LISTBUILDER_SAVE_TYPE_INTERNAL).
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
saveUrl_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The "save" field name of the listbuilder (for
|
||||
* LISTBUILDER_SAVE_TYPE_EXTERNAL).
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
saveFieldName_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The "fetch options" URL of the listbuilder (for "select" source type).
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
fetchOptionsUrl_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Stores the calling context of the edit item click event.
|
||||
* @private
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
||||
prototype.editItemCallingContext_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Flag whether there's still available options to be selected or not.
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
||||
prototype.availableOptions_ = false;
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.initialize =
|
||||
function(options) {
|
||||
this.parent('initialize', options);
|
||||
|
||||
// Save listbuilder options
|
||||
this.sourceType_ = options.sourceType;
|
||||
this.saveUrl_ = options.saveUrl;
|
||||
this.saveFieldName_ = options.saveFieldName;
|
||||
this.fetchOptionsUrl_ = options.fetchOptionsUrl;
|
||||
this.availableOptions_ = options.availableOptions;
|
||||
|
||||
// Attach the button handlers
|
||||
var $listbuilder = this.getHtmlElement();
|
||||
// Use mousedown to avoid two events being triggered at the same time
|
||||
// (click event was being triggered together with blur event from inputs.
|
||||
// That and a syncronous ajax call triggered by those events
|
||||
// handlers, was leading to an error in IE8 and it was freezing
|
||||
// Firefox 13.0).
|
||||
$listbuilder.find('.actions .pkp_linkaction_addItem').mousedown(
|
||||
this.callbackWrapper(this.addItemHandler_));
|
||||
|
||||
// Attach the content manipulation handlers
|
||||
this.attachContentHandlers_($listbuilder);
|
||||
|
||||
// Sign up for notification of form submission.
|
||||
this.bind('formSubmitRequested', this.formSubmitHandler_);
|
||||
|
||||
// Sign up for notification of form submitted.
|
||||
this.bind('formSubmitted', this.formSubmittedHandler_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the "save" URL for LISTBUILDER_SAVE_TYPE_INTERNAL.
|
||||
* @private
|
||||
* @return {?string} URL to the "save listbuilder" handler operation.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.getSaveUrl_ =
|
||||
function() {
|
||||
|
||||
return this.saveUrl_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the "save" field name for LISTBUILDER_SAVE_TYPE_EXTERNAL.
|
||||
* @private
|
||||
* @return {string} Name of the field to transmit LB contents in.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.getSaveFieldName_ =
|
||||
function() {
|
||||
|
||||
return /** @type {string} */ (this.saveFieldName_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* "Save" and close any editing rows in the listbuilder.
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.closeEdits =
|
||||
function() {
|
||||
|
||||
var $editedRow = this.getHtmlElement().find('.gridRowEdit:visible');
|
||||
if ($editedRow.length !== 0) {
|
||||
this.saveRow($editedRow);
|
||||
$editedRow.removeClass('gridRowEdit');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Save the listbuilder.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.save =
|
||||
function() {
|
||||
|
||||
// Get deletions
|
||||
var deletions = this.getHtmlElement().find('input.deletions').val(),
|
||||
// Get insertions and modifications
|
||||
changes = [],
|
||||
numberOfRows, stringifiedData, saveUrl,
|
||||
saveFieldName, $e,
|
||||
handler = this;
|
||||
|
||||
this.getHtmlElement().find('.gridRow input.isModified[value="1"]')
|
||||
.each(function(index, v) {
|
||||
var $row = $(v).parents('.gridRow'),
|
||||
params = handler.buildParamsFromInputs_($row.find(':input'));
|
||||
changes.push(params);
|
||||
});
|
||||
|
||||
// The listbuilder form validator needs to know if this listbuilder contains
|
||||
// rows or not, so we pass the items number.
|
||||
numberOfRows = this.getRows().length;
|
||||
|
||||
// Assemble and send to the server
|
||||
stringifiedData = JSON.stringify(
|
||||
{deletions: deletions, changes: changes, numberOfRows: numberOfRows});
|
||||
saveUrl = this.getSaveUrl_();
|
||||
if (saveUrl) {
|
||||
// Post the changes to the server using the internal
|
||||
// save handler.
|
||||
$.post(saveUrl, {data: stringifiedData},
|
||||
this.callbackWrapper(this.saveResponseHandler_, null), 'json');
|
||||
} else {
|
||||
// Supply the data to an external save handler (e.g.
|
||||
// a form handler) using a hidden field.
|
||||
saveFieldName = this.getSaveFieldName_();
|
||||
|
||||
// Try to find and reuse an existing element (if
|
||||
// e.g. a previous attempt was aborted)
|
||||
$e = this.getHtmlElement()
|
||||
.find(':input[type=hidden]')
|
||||
.filter(
|
||||
function() {return $(this).attr('name') == saveFieldName;})
|
||||
.first();
|
||||
|
||||
// If we couldn't find one, create one.
|
||||
if ($e.length === 0) {
|
||||
$e = $('<input type="hidden" />');
|
||||
$e.attr('name', saveFieldName);
|
||||
this.getHtmlElement().append($e);
|
||||
}
|
||||
|
||||
// Set the value of the hidden element.
|
||||
$e.attr('value', stringifiedData);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Function that will be called to save an edited row.
|
||||
* @param {Object} $row The DOM element representing the row to save.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
saveRow = function($row) {
|
||||
|
||||
// Retrieve a single new row from the server.
|
||||
// (Avoid IE closure leak using this flag rather than passing
|
||||
// around a DOM element in a closure.)
|
||||
$row.addClass('saveRowResponsePlaceholder');
|
||||
var params = this.buildParamsFromInputs_($row.find(':input'));
|
||||
params.modify = true; // Flag the row for modification
|
||||
// Use a blocking request to avoid race conditions sometimes
|
||||
// duplicating items, i.e. when editing an existing item after
|
||||
// adding a new one.
|
||||
this.disableControls();
|
||||
$.ajax({
|
||||
url: this.getFetchRowUrl(),
|
||||
data: params,
|
||||
success: this.callbackWrapper(this.saveRowResponseHandler_, null),
|
||||
dataType: 'json',
|
||||
async: false
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Extended methods from GridHandler.
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.getEmptyElement =
|
||||
function($element) {
|
||||
// Listbuilders have only one empty element placeholder.
|
||||
return this.getHtmlElement().find('.empty');
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private Methods
|
||||
//
|
||||
/**
|
||||
* Callback that will be activated when the "add item" icon is clicked
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} callingContext The calling element or object.
|
||||
* @param {Event=} opt_event The triggering event (e.g. a click on
|
||||
* a button.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.addItemHandler_ =
|
||||
function(callingContext, opt_event) {
|
||||
|
||||
if (this.availableOptions_) {
|
||||
// Make sure this event will be handled after any other next triggered one,
|
||||
// like blur event that comes from inputs.
|
||||
setTimeout(this.callbackWrapper(function() {
|
||||
// Close any existing edits if necessary
|
||||
this.closeEdits();
|
||||
|
||||
this.disableControls();
|
||||
$.get(this.getFetchRowUrl(), {modify: true},
|
||||
this.callbackWrapper(this.appendRowResponseHandler_, null), 'json');
|
||||
}), 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be activated when a delete icon is clicked
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} callingContext The calling element or object.
|
||||
* @param {Event=} opt_event The triggering event (e.g. a click on
|
||||
* a button.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.deleteItemHandler_ =
|
||||
function(callingContext, opt_event) {
|
||||
|
||||
// Close any existing edits if necessary
|
||||
this.closeEdits();
|
||||
|
||||
var $callingContext = $(callingContext),
|
||||
$targetRow = $callingContext.closest('.gridRow'),
|
||||
$deletions = $callingContext.closest('.pkp_controllers_listbuilder')
|
||||
.find('.deletions'),
|
||||
rowId = $targetRow.find('input[name="rowId"]').val();
|
||||
|
||||
// Append the row ID to the deletions list.
|
||||
if (rowId !== undefined) {
|
||||
$deletions.val($deletions.val() + ' ' + rowId);
|
||||
|
||||
// Notify containing form (if any) about a change
|
||||
this.getHtmlElement().trigger('formChange');
|
||||
}
|
||||
|
||||
this.deleteElement(/** @type {jQueryObject} */ ($targetRow));
|
||||
|
||||
this.availableOptions_ = true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be activated when a request for row appending
|
||||
* returns.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
appendRowResponseHandler_ = function(ajaxContext, jsonData) {
|
||||
|
||||
var processedJsonData = this.handleJson(jsonData), $newRow;
|
||||
if (processedJsonData !== false) {
|
||||
// Show the new input row; hide the "empty" row
|
||||
$newRow = $(processedJsonData.content);
|
||||
this.getHtmlElement().find('.empty').hide().before($newRow);
|
||||
|
||||
// Attach content handlers and focus
|
||||
this.attachContentHandlers_($newRow);
|
||||
$newRow.addClass('gridRowEdit');
|
||||
$newRow.find(':input').not('[type="hidden"]').first().focus();
|
||||
|
||||
// If this is a select menu listbuilder, load the options
|
||||
if (this.sourceType_ == $.pkp.cons.LISTBUILDER_SOURCE_TYPE_SELECT) {
|
||||
this.disableControls();
|
||||
$.get(this.fetchOptionsUrl_, {},
|
||||
this.callbackWrapper(this.fetchOptionsResponseHandler_, null),
|
||||
'json');
|
||||
} else {
|
||||
this.enableControls();
|
||||
}
|
||||
|
||||
this.callFeaturesHook('addElement', $newRow);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be activated when a set of options is returned
|
||||
* from the server for a new select control.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
fetchOptionsResponseHandler_ = function(ajaxContext, jsonData) {
|
||||
|
||||
// Find the currently editable select menu and fill
|
||||
var pjd = this.handleJson(jsonData),
|
||||
$listbuilder = this.getHtmlElement(),
|
||||
selectedValues = [],
|
||||
$selectInput,
|
||||
i, limit,
|
||||
$pulldown, $container, optionsCount, j,
|
||||
$option,
|
||||
label, $optgroup,
|
||||
k, optionsInsideGroup, $lastElement;
|
||||
|
||||
if (pjd !== false) {
|
||||
// Get the list of already-selected options, to ensure
|
||||
// that we don't offer duplicates.
|
||||
$listbuilder.find('.gridCellDisplay :input').each(function(i, selected) {
|
||||
selectedValues[i] = $(selected).val();
|
||||
});
|
||||
|
||||
// Get the currently available input row's elements
|
||||
$selectInput = $listbuilder.find(
|
||||
'.gridRowEdit:visible .selectMenu:input'
|
||||
);
|
||||
|
||||
// For each pulldown (generally 1), add options.
|
||||
for (i = 0, limit = $selectInput.length; i < limit; i++) {
|
||||
// Fetch some useful properties
|
||||
$pulldown = $($selectInput[i]);
|
||||
$container = $pulldown.parents('.gridCellContainer');
|
||||
|
||||
// Add the options, noting the currently selected index
|
||||
optionsCount = 0;
|
||||
$pulldown.children().empty();
|
||||
j = null;
|
||||
for (j in pjd.content[i]) {
|
||||
// Ignore optgroup labels.
|
||||
if (j == $.pkp.cons.LISTBUILDER_OPTGROUP_LABEL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof(pjd.content[i][j]) == 'object') {
|
||||
// Options must go inside an optgroup.
|
||||
// Check if we have optgroup label data.
|
||||
if (
|
||||
pjd.
|
||||
content[i][$.pkp.cons.LISTBUILDER_OPTGROUP_LABEL] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof(
|
||||
pjd.content[i][$.pkp.cons.LISTBUILDER_OPTGROUP_LABEL]
|
||||
) != 'object') {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
label =
|
||||
pjd.content[i][$.pkp.cons.LISTBUILDER_OPTGROUP_LABEL][j];
|
||||
if (!label) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$optgroup = $('<optgroup></optgroup>');
|
||||
$optgroup.attr('label', label);
|
||||
$pulldown.append($optgroup);
|
||||
|
||||
k = null;
|
||||
optionsInsideGroup = 0;
|
||||
for (k in pjd.content[i][j]) {
|
||||
// Populate the optgroup.
|
||||
$option = this.populatePulldown_($optgroup,
|
||||
selectedValues, pjd.content[i][j][k], k);
|
||||
if ($option) {
|
||||
optionsCount++;
|
||||
optionsInsideGroup++;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid inserting optgroups that have no option.
|
||||
if (optionsInsideGroup === 0) {
|
||||
$optgroup.remove();
|
||||
}
|
||||
} else {
|
||||
// Just insert the current option.
|
||||
$option = this.populatePulldown_($pulldown,
|
||||
selectedValues, pjd.content[i][j], j);
|
||||
if ($option) {
|
||||
optionsCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$lastElement = $option;
|
||||
|
||||
// If only one element is available, select it.
|
||||
if (optionsCount === 1 && $lastElement) {
|
||||
$lastElement.attr('selected', 'selected');
|
||||
this.availableOptions_ = false;
|
||||
}
|
||||
|
||||
// If no options are available for this select menu,
|
||||
// hide the input to prevent empty dropdown.
|
||||
if (optionsCount === 0) {
|
||||
$container.find('.gridCellDisplay').show();
|
||||
$container.find('.gridCellEdit').hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.enableControls();
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Populate the pulldown with options.
|
||||
* @private
|
||||
* @param {jQueryObject} $element The element to be populated.
|
||||
* Can be a pulldown or an optgroup inside the pulldonw.
|
||||
* @param {Object} selectedValues Current listbuilder
|
||||
* selected values.
|
||||
* @param {string} optionText The text to populate the pulldown with.
|
||||
* @param {string} optionValue The key to populate the pulldown with.
|
||||
* @return {Object|boolean} Return the inserted option or false.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
populatePulldown_ = function(
|
||||
$element, selectedValues, optionText, optionValue) {
|
||||
|
||||
var $container = $element.parents('.gridCellContainer'),
|
||||
currentIndex = $container.find('.gridCellDisplay :input').val(),
|
||||
isDuplicate = false, k,
|
||||
$option;
|
||||
|
||||
// Check to see if this option is already in the LB.
|
||||
if (optionValue != currentIndex) {
|
||||
// If it's the current row, don't consider it a duplicate
|
||||
for (k = 0; k < selectedValues.length; k++) {
|
||||
if (selectedValues[k] == optionValue) {
|
||||
isDuplicate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDuplicate) {
|
||||
// Create and populate the option node
|
||||
$option = $('<option/>');
|
||||
$option.attr('value', optionValue);
|
||||
$option.text(optionText);
|
||||
|
||||
if (optionValue == currentIndex) {
|
||||
$option.attr('selected', 'selected');
|
||||
}
|
||||
|
||||
$element.append($option);
|
||||
return $option;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be activated when a row is clicked for editing
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {HTMLElement} callingContext The calling element or object.
|
||||
* @param {Event=} opt_event The triggering event (e.g. a click on
|
||||
* a button.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.editItemHandler_ =
|
||||
function(callingContext, opt_event) {
|
||||
|
||||
// Close any existing edits if necessary
|
||||
this.closeEdits();
|
||||
this.editItemCallingContext_ = callingContext;
|
||||
|
||||
// Show inputs; hide display. IE8 is slow, and it will execute
|
||||
// this before the timeout setted in inputBlurHandler_. Insert this
|
||||
// code inside a timeout too to avoid closing inputs that are not
|
||||
// meant to.
|
||||
setTimeout(this.callbackWrapper(function() {
|
||||
var $targetRow = $(this.editItemCallingContext_).closest('.gridRow');
|
||||
$targetRow.addClass('gridRowEdit');
|
||||
$targetRow.find(':input').not('[type="hidden"]').first().focus();
|
||||
|
||||
// If this is a select menu listbuilder, load the options
|
||||
if (this.sourceType_ == $.pkp.cons.LISTBUILDER_SOURCE_TYPE_SELECT) {
|
||||
this.disableControls();
|
||||
$.get(this.fetchOptionsUrl_, {},
|
||||
this.callbackWrapper(this.fetchOptionsResponseHandler_, null),
|
||||
'json');
|
||||
}
|
||||
}), 0);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to turn a row into an array of parameters used
|
||||
* to generate the DOM representation of that row when bounced
|
||||
* off the server.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} $inputs The grid inputs to mine for parameters.
|
||||
* @return {Object} A name: value association of relevant parameters.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
buildParamsFromInputs_ = function($inputs) {
|
||||
|
||||
var params = {};
|
||||
$.each($inputs.serializeArray(), function(k, v) {
|
||||
var name = v.name,
|
||||
value = v.value;
|
||||
|
||||
params[name] = params[name] === undefined ? value :
|
||||
$.isArray(params[name]) ? params[name].concat(value) :
|
||||
[params[name], value];
|
||||
});
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be activated upon keystroke in a new input field
|
||||
* to check for a <cr> (acts as tab-to-next, or if no next, submit).
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {HTMLElement} callingContext The calling element or object.
|
||||
* @param {Event=} opt_event The triggering event.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
inputKeystrokeHandler_ = function(callingContext, opt_event) {
|
||||
|
||||
var CR_KEY = 13, TAB_KEY = 9,
|
||||
$target, $row, $inputs, i;
|
||||
|
||||
if (opt_event.which == CR_KEY) {
|
||||
$target = $(callingContext);
|
||||
$row = $target.parents('.gridRow');
|
||||
$inputs = $row.find(':input:visible');
|
||||
i = $inputs.index($target);
|
||||
if ($inputs.length == i + 1) {
|
||||
this.saveRow($row);
|
||||
return false; // Prevent default
|
||||
} else {
|
||||
// Not the last field. Tab to the next.
|
||||
$inputs[i + 1].focus();
|
||||
return false; // Prevent default
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be activated when a new/modifying row's input
|
||||
* field is blurred to check whether or not to save the row.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {HTMLElement} callingContext The calling element or object.
|
||||
* @param {Event=} opt_event The triggering event.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
inputBlurHandler_ = function(callingContext, opt_event) {
|
||||
|
||||
// Flag currently selected input using a CSS class. (Don't
|
||||
// want to pass it into the closure because of the IE memory
|
||||
// leak bug.)
|
||||
$(callingContext).closest('.gridRow').addClass('editingRowPlaceholder');
|
||||
|
||||
// Check to see whether the row has lost focus after this event has
|
||||
// been processed.
|
||||
setTimeout(this.callbackWrapper(function() {
|
||||
var $editingRow = $('.editingRowPlaceholder'),
|
||||
found = false;
|
||||
$editingRow.find(':input').each(function(index, elem) {
|
||||
if (elem === document.activeElement) {
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up extra placeholder class.
|
||||
$editingRow.removeClass('editingRowPlaceholder');
|
||||
|
||||
// If the focused element isn't within the current row, save.
|
||||
if (!found) {
|
||||
this.closeEdits();
|
||||
}
|
||||
}), 0);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback to replace a grid row's content.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
saveRowResponseHandler_ = function(ajaxContext, jsonData) {
|
||||
|
||||
var processedJsonData = this.handleJson(jsonData),
|
||||
$newContent, rowId;
|
||||
|
||||
if (processedJsonData !== false) {
|
||||
// Unfortunately we can't use a closure to get this from
|
||||
// the calling context. Use a class flag "saveRowResponsePlaceholder".
|
||||
// (Risks IE closure/DOM element memory leak.)
|
||||
$newContent = $(processedJsonData.content);
|
||||
|
||||
// Store current row id.
|
||||
rowId = /** @type {string} */ (this.getHtmlElement()
|
||||
.find('.saveRowResponsePlaceholder').attr('id'));
|
||||
|
||||
// Add to the DOM
|
||||
this.getHtmlElement().find('.saveRowResponsePlaceholder').
|
||||
replaceWith($newContent);
|
||||
|
||||
// Make sure row id won't change.
|
||||
$newContent.attr('id', rowId);
|
||||
|
||||
// Attach handlers for content manipulation
|
||||
this.attachContentHandlers_($newContent);
|
||||
|
||||
this.callFeaturesHook('replaceElement', $newContent);
|
||||
}
|
||||
|
||||
// Ensure that containing forms are notified of the changed data
|
||||
this.getHtmlElement().trigger('formChange');
|
||||
|
||||
this.enableControls();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback after a save response returns from the server.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} ajaxContext The AJAX request context.
|
||||
* @param {Object} jsonData A parsed JSON response object.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
saveResponseHandler_ = function(ajaxContext, jsonData) {
|
||||
|
||||
// Noop
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attach content handlers to all "click-to-edit" content within
|
||||
* the provided context.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Object} $context The JQuery object to search for attachables.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
||||
attachContentHandlers_ = function($context) {
|
||||
|
||||
// Attach click handler for text fields and select menus
|
||||
$context.find('.gridCellDisplay').click(
|
||||
this.callbackWrapper(this.editItemHandler_));
|
||||
|
||||
// Attach keypress handler for text fields
|
||||
$context.find(':input')
|
||||
.keypress(this.callbackWrapper(this.inputKeystrokeHandler_))
|
||||
.blur(this.callbackWrapper(this.inputBlurHandler_));
|
||||
|
||||
// Attach deletion handler
|
||||
$context.find('.pkp_linkaction_delete').click(
|
||||
this.callbackWrapper(this.deleteItemHandler_));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Save the Listbuilder's contents upon a "form submitted" event.
|
||||
* @private
|
||||
*
|
||||
* @param {$.pkp.controllers.form.AjaxFormHandler} callingForm The form
|
||||
* that triggered the event.
|
||||
* @param {Event} event The event.
|
||||
* @return {boolean} False if the form submission should abort.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
||||
prototype.formSubmitHandler_ = function(callingForm, event) {
|
||||
|
||||
// Save the contents
|
||||
this.save();
|
||||
|
||||
// Prevent the submission of LB elements to the parent form
|
||||
// (except potentially for :input[name='getSaveFieldName()'])
|
||||
this.getHtmlElement().find('.gridRow :input').attr('disabled', 'disabled');
|
||||
|
||||
// Continue the default (form submit) behavior
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enable deactivated inputs.
|
||||
* @private
|
||||
*
|
||||
* @param {$.pkp.controllers.form.AjaxFormHandler} callingForm The form
|
||||
* that triggered the event.
|
||||
* @param {Event} event The event.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
||||
prototype.formSubmittedHandler_ = function(callingForm, event) {
|
||||
|
||||
this.getHtmlElement().find('.gridRow :input').removeAttr('disabled');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disable the add_* links and show the spinner before making AJAX calls.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
||||
prototype.disableControls = function() {
|
||||
|
||||
this.getHtmlElement().
|
||||
find('span[class="options"] > a[id*="addItem"]').unbind('mousedown');
|
||||
|
||||
this.getHtmlElement().find('span[class="options"] > a[id*="addItem"]')
|
||||
.mousedown(function() {return false;});
|
||||
this.getHtmlElement().find('.h3').addClass('spinner');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Re-enable the add_* links and hide the spinner.
|
||||
*/
|
||||
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
||||
prototype.enableControls = function() {
|
||||
// rebind our 'click' handler so we can add another item
|
||||
// if needed
|
||||
this.getHtmlElement().find('span[class="options"] > a[id*="addItem"]').
|
||||
mousedown(this.callbackWrapper(this.addItemHandler_));
|
||||
this.getHtmlElement().find('.h3').removeClass('spinner');
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @file js/controllers/modal/AjaxModalHandler.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 AjaxModalHandler
|
||||
* @ingroup js_controllers_modal
|
||||
*
|
||||
* @brief A modal that retrieves content from a remote AJAX endpoint.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.modal.ModalHandler
|
||||
*
|
||||
* @param {jQueryObject} $handledElement The clickable element
|
||||
* the modal will be attached to.
|
||||
* @param {Object} options non-default Dialog options
|
||||
* to be passed into the dialog widget.
|
||||
*
|
||||
* Options are:
|
||||
* - url string the remote AJAX endpoint that will be used
|
||||
* to retrieve the content of the modal.
|
||||
* - all options documented for the jQueryUI dialog widget,
|
||||
* except for the buttons parameter which is not supported.
|
||||
*/
|
||||
$.pkp.controllers.modal.AjaxModalHandler = function($handledElement, options) {
|
||||
this.parent($handledElement, options);
|
||||
|
||||
// We assume that AJAX modals usually contain forms and
|
||||
// therefore bind to form events by default.
|
||||
this.bind('formSubmitted', this.formSubmitted);
|
||||
this.bind('formCanceled', this.modalClose);
|
||||
this.bind('ajaxHtmlError', this.modalClose);
|
||||
this.bind('modalFinished', this.modalClose);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.modal.AjaxModalHandler,
|
||||
$.pkp.controllers.modal.ModalHandler);
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/** @inheritDoc */
|
||||
$.pkp.controllers.modal.AjaxModalHandler.prototype.checkOptions =
|
||||
function(options) {
|
||||
// Check the mandatory options of the ModalHandler handler.
|
||||
if (!this.parent('checkOptions', options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for our own mandatory options.
|
||||
return typeof options.url === 'string';
|
||||
};
|
||||
|
||||
|
||||
/** @inheritDoc */
|
||||
$.pkp.controllers.modal.AjaxModalHandler.prototype.mergeOptions =
|
||||
function(options) {
|
||||
|
||||
// Call parent.
|
||||
return /** @type {Object} */ (this.parent('mergeOptions', options));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Open the modal and fetch content via ajax
|
||||
* @param {jQueryObject} $handledElement The clickable element
|
||||
* the modal will be attached to.
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.modal.AjaxModalHandler.prototype.modalOpen =
|
||||
function($handledElement) {
|
||||
this.parent('modalOpen', $handledElement);
|
||||
|
||||
// Retrieve remote modal content.
|
||||
$handledElement.find('.content')
|
||||
.pkpAjaxHtml(/** @type {{ url: string }} */ (this.options).url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Close the modal when a form submission is complete
|
||||
* @param {Object} callingContext The calling element or object.
|
||||
* @param {Event} event The triggering event (e.g. a click on
|
||||
* a button.
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.modal.AjaxModalHandler.prototype.formSubmitted =
|
||||
function(callingContext, event) {
|
||||
|
||||
this.getHtmlElement().parent().trigger('notifyUser');
|
||||
this.modalClose();
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @file js/controllers/modal/ButtonConfirmationModalHandler.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 ButtonConfirmationModalHandler
|
||||
* @ingroup js_controllers_modal
|
||||
*
|
||||
* @brief A confirmation modal that displays a confirmation message before
|
||||
* actually triggering a click event on the calling element (usually a
|
||||
* button).
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.modal.ConfirmationModalHandler
|
||||
*
|
||||
* @param {jQueryObject} $handledElement The modal.
|
||||
* @param {Object} options Non-default options to configure
|
||||
* the modal.
|
||||
*
|
||||
* Options are:
|
||||
* - button jQuery The button to be clicked on success.
|
||||
* - All options from the ConfirmationModalHandler and ModalHandler
|
||||
* widgets.
|
||||
* - All options documented for the jQueryUI dialog widget,
|
||||
* except for the buttons parameter which is not supported.
|
||||
*/
|
||||
$.pkp.controllers.modal.ButtonConfirmationModalHandler =
|
||||
function($handledElement, options) {
|
||||
|
||||
this.parent($handledElement, options);
|
||||
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.modal.ButtonConfirmationModalHandler,
|
||||
$.pkp.controllers.modal.ConfirmationModalHandler);
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.modal.ButtonConfirmationModalHandler.prototype.checkOptions =
|
||||
function(options) {
|
||||
// Check inherited options
|
||||
if (!this.parent('checkOptions', options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return typeof options.$button == 'object' && options.$button.length == 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be activated when the modal is confirmed
|
||||
*
|
||||
* @param {HTMLElement} dialogElement The element the
|
||||
* dialog was created on.
|
||||
* @param {Event} event The click event.
|
||||
*/
|
||||
$.pkp.controllers.modal.ButtonConfirmationModalHandler.prototype.modalConfirm =
|
||||
function(dialogElement, event) {
|
||||
|
||||
var $button = (/** @type {{ $button: jQueryObject }} */ (this.options))
|
||||
.$button;
|
||||
|
||||
// Close the modal first so that the linkaction is no longer disabled
|
||||
this.modalClose(dialogElement);
|
||||
|
||||
// Trigger the link/button action
|
||||
if ($button.attr('type') == 'submit') {
|
||||
$button.trigger('submit');
|
||||
} else {
|
||||
$button.click();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* @file js/controllers/modal/ConfirmationModalHandler.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 ConfirmationModalHandler
|
||||
* @ingroup js_controllers_modal
|
||||
*
|
||||
* @brief A modal that displays a static explanatory text and has cancel and
|
||||
* confirmation buttons.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.modal.ModalHandler
|
||||
*
|
||||
* @param {jQueryObject} $handledElement The clickable element
|
||||
* the modal will be attached to.
|
||||
* @param {{
|
||||
* callback: Function,
|
||||
* callbackArgs: Object
|
||||
* }} options Non-default options to configure the modal.
|
||||
*
|
||||
* Options are:
|
||||
* - okButton string the name for the confirmation button.
|
||||
* - cancelButton string the name for the cancel button
|
||||
* (or false for no button).
|
||||
* - dialogText string the text to be displayed in the modal.
|
||||
* - All options from the ModalHandler widget.
|
||||
* - callback function A callback function to close when confirmed
|
||||
* - callbackArgs object Arguments to pass to the callback function
|
||||
*/
|
||||
$.pkp.controllers.modal.ConfirmationModalHandler =
|
||||
function($handledElement, options) {
|
||||
|
||||
this.parent($handledElement, options);
|
||||
|
||||
this.callback_ = options.callback || null;
|
||||
this.callbackArgs_ = options.callbackArgs || null;
|
||||
|
||||
// Bind to the confirmation button
|
||||
$handledElement.find('.pkpModalConfirmButton')
|
||||
.on('click', this.callbackWrapper(this.modalConfirm));
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.modal.ConfirmationModalHandler,
|
||||
$.pkp.controllers.modal.ModalHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* A callback to fire when confirmed
|
||||
* @private
|
||||
* @type {?Function}
|
||||
*/
|
||||
$.pkp.controllers.modal.ConfirmationModalHandler.prototype.
|
||||
callback_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Arguments to pass to the callback function
|
||||
* @private
|
||||
* @type {?Object}
|
||||
*/
|
||||
$.pkp.controllers.modal.ConfirmationModalHandler.prototype.
|
||||
callbackArgs_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.modal.ConfirmationModalHandler.prototype.checkOptions =
|
||||
function(options) {
|
||||
// Check the mandatory options of the ModalHandler handler.
|
||||
if (!this.parent('checkOptions', options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hack to prevent closure compiler type mismatches
|
||||
var castOptions = /** @type {{okButton: string,
|
||||
cancelButton: string, dialogText: string}} */ (options);
|
||||
|
||||
// Check for our own mandatory options.
|
||||
return typeof castOptions.okButton === 'string' &&
|
||||
(/** @type {boolean} */ (castOptions.cancelButton) === false ||
|
||||
typeof castOptions.cancelButton === 'string') &&
|
||||
typeof castOptions.dialogText === 'string';
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Add content to modal
|
||||
*
|
||||
* @return {jQueryObject} jQuery object representing modal content
|
||||
*/
|
||||
$.pkp.controllers.modal.ConfirmationModalHandler.prototype.modalBuild =
|
||||
function() {
|
||||
|
||||
var $modal = this.parent('modalBuild'),
|
||||
buttons = '<button class="ok pkpModalConfirmButton">' +
|
||||
(/** @type {{ okButton: string }} */ (this.options)).okButton +
|
||||
'</button>';
|
||||
|
||||
$modal.addClass('pkp_modal_confirmation').find('.content')
|
||||
.append('<div class="message">' +
|
||||
(/** @type {{ dialogText: string }} */ (this.options)).dialogText +
|
||||
'</div>');
|
||||
|
||||
if (this.options.cancelButton) {
|
||||
buttons += '<button class="cancel pkpModalCloseButton">' +
|
||||
this.options.cancelButton + '</button>';
|
||||
}
|
||||
|
||||
$modal.append('<div class="footer">' + buttons + '</div>');
|
||||
|
||||
// Add aria role and label
|
||||
$modal.attr('role', 'dialog')
|
||||
.attr('aria-label', this.options.title);
|
||||
|
||||
return /** @type {jQueryObject} */ ($modal);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback that will be activated when the modal's
|
||||
* confirm button is clicked.
|
||||
*
|
||||
* @param {HTMLElement} dialogElement The element the
|
||||
* dialog was created on.
|
||||
* @param {Event} event The click event.
|
||||
*/
|
||||
$.pkp.controllers.modal.ConfirmationModalHandler.prototype.modalConfirm =
|
||||
function(dialogElement, event) {
|
||||
|
||||
// The default implementation will simply close the modal.
|
||||
this.modalClose(dialogElement);
|
||||
|
||||
if (this.callback_) {
|
||||
this.callback_.call(null, this.callbackArgs_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* @file js/controllers/modal/JsEventConfirmationModalHandler.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 JsEventConfirmationModalHandler
|
||||
* @ingroup js_controllers_modal
|
||||
*
|
||||
* @brief A confirmation modal that generates a JS event.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.modal.ConfirmationModalHandler
|
||||
*
|
||||
* @param {jQueryObject} $handledElement The clickable element
|
||||
* the modal will be attached to.
|
||||
* @param {Object} options Non-default options to configure
|
||||
* the modal.
|
||||
*
|
||||
* Options are:
|
||||
* - remoteUrl string A URL to be redirected to when the confirmation
|
||||
* button has been clicked.
|
||||
* - All options from the ConfirmationModalHandler and ModalHandler
|
||||
* widgets.
|
||||
* - All options documented for the jQueryUI dialog widget,
|
||||
* except for the buttons parameter which is not supported.
|
||||
*/
|
||||
$.pkp.controllers.modal.JsEventConfirmationModalHandler =
|
||||
function($handledElement, options) {
|
||||
|
||||
this.parent($handledElement, options);
|
||||
|
||||
// Configure the event to be generated when
|
||||
// the modal closes.
|
||||
this.jsEvent_ = options.jsEvent;
|
||||
|
||||
this.extraArguments_ = options.extraArguments;
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.modal.JsEventConfirmationModalHandler,
|
||||
$.pkp.controllers.modal.ConfirmationModalHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* An event to be generated when the confirmation button
|
||||
* has been clicked.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.modal.JsEventConfirmationModalHandler.prototype.
|
||||
jsEvent_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* An array of extra information to be passed along with the event.
|
||||
* @private
|
||||
* @type {?Array}
|
||||
*/
|
||||
$.pkp.controllers.modal.JsEventConfirmationModalHandler.prototype.
|
||||
extraArguments_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/** @inheritDoc */
|
||||
$.pkp.controllers.modal.JsEventConfirmationModalHandler.prototype.
|
||||
checkOptions = function(options) {
|
||||
|
||||
// Check the mandatory options of the ModalHandler handler.
|
||||
if (!this.parent('checkOptions', options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for our own mandatory options.
|
||||
// The cancel button and event are mandatory.
|
||||
return typeof options.cancelButton === 'string' &&
|
||||
typeof options.jsEvent === 'string';
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Callback that will be activated when the modal's
|
||||
* confirm button is clicked.
|
||||
*
|
||||
* @param {HTMLElement} dialogElement The element the
|
||||
* dialog was created on.
|
||||
* @param {Event} event The click event.
|
||||
*/
|
||||
$.pkp.controllers.modal.JsEventConfirmationModalHandler.prototype.
|
||||
modalConfirm = function(dialogElement, event) {
|
||||
|
||||
this.trigger(/** @type {string} */ (this.jsEvent_),
|
||||
/** @type {Array} */ (this.extraArguments_));
|
||||
this.modalClose(dialogElement);
|
||||
};
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,360 @@
|
||||
/*global pkp */
|
||||
/**
|
||||
* @defgroup js_controllers_modal
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/modal/ModalHandler.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 ModalHandler
|
||||
* @ingroup js_controllers_modal
|
||||
*
|
||||
* @brief Basic modal implementation.
|
||||
*
|
||||
* A modal that has only one button and expects a simple message string.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.modal = $.pkp.controllers.modal || { };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.classes.Handler
|
||||
*
|
||||
* @param {jQueryObject} $handledElement The modal.
|
||||
* @param {Object.<string, *>} options The modal options.
|
||||
*/
|
||||
$.pkp.controllers.modal.ModalHandler = function($handledElement, options) {
|
||||
this.parent($handledElement, options);
|
||||
|
||||
// Check the options.
|
||||
if (!this.checkOptions(options)) {
|
||||
throw new Error('Missing or invalid modal options!');
|
||||
}
|
||||
|
||||
// Clone the options object before we manipulate them.
|
||||
var internalOptions = $.extend(true, {}, options),
|
||||
canClose;
|
||||
|
||||
// Merge user and default options.
|
||||
this.options = /** @type {{ canClose: boolean, textTitle: string,
|
||||
title: string, titleIcon: string,
|
||||
closeCleanVueInstances: Array }} */
|
||||
(this.mergeOptions(internalOptions));
|
||||
|
||||
// Attach content to the modal
|
||||
$handledElement.html(this.modalBuild()[0].outerHTML);
|
||||
|
||||
// Open the modal
|
||||
this.modalOpen($handledElement);
|
||||
|
||||
// Set up close controls
|
||||
$handledElement.find(
|
||||
'.pkpModalCloseButton').click(this.callbackWrapper(this.modalClose));
|
||||
$handledElement.on(
|
||||
'click keyup', this.callbackWrapper(this.handleWrapperEvents));
|
||||
|
||||
// Publish some otherwise private events triggered
|
||||
// by nested widgets so that they can be handled by
|
||||
// the element that opened the modal.
|
||||
this.publishEvent('redirectRequested');
|
||||
this.publishEvent('dataChanged');
|
||||
this.publishEvent('updateHeader');
|
||||
this.publishEvent('gridRefreshRequested');
|
||||
|
||||
this.bind('notifyUser', this.redirectNotifyUserEventHandler_);
|
||||
this.bindGlobal('form-success', this.onFormSuccess_);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.modal.ModalHandler,
|
||||
$.pkp.classes.Handler);
|
||||
|
||||
|
||||
//
|
||||
// Private static properties
|
||||
//
|
||||
/**
|
||||
* Default options
|
||||
* @private
|
||||
* @type {Object}
|
||||
* @const
|
||||
*/
|
||||
$.pkp.controllers.modal.ModalHandler.DEFAULT_OPTIONS_ = {
|
||||
autoOpen: true,
|
||||
width: 710,
|
||||
modal: true,
|
||||
draggable: false,
|
||||
resizable: false,
|
||||
position: {my: 'center', at: 'center center-10%', of: window},
|
||||
canClose: true,
|
||||
closeCallback: false,
|
||||
// Vue components to destroy when when modal is closed
|
||||
closeCleanVueInstances: []
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public properties
|
||||
//
|
||||
/**
|
||||
* Current options
|
||||
*
|
||||
* After passed options are merged with defaults.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.controllers.modal.ModalHandler.options = null;
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* Check whether the correct options have been
|
||||
* given for this modal.
|
||||
* @protected
|
||||
* @param {Object.<string, *>} options Modal options.
|
||||
* @return {boolean} True if options are ok.
|
||||
*/
|
||||
$.pkp.controllers.modal.ModalHandler.prototype.checkOptions =
|
||||
function(options) {
|
||||
|
||||
// Check for basic configuration requirements.
|
||||
return typeof options === 'object' &&
|
||||
(/** @type {{ buttons: Object }} */ (options)).buttons === undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine the options based on
|
||||
* default options.
|
||||
* @protected
|
||||
* @param {Object.<string, *>} options Non-default modal options.
|
||||
* @return {Object.<string, *>} The default options merged
|
||||
* with the non-default options.
|
||||
*/
|
||||
$.pkp.controllers.modal.ModalHandler.prototype.mergeOptions =
|
||||
function(options) {
|
||||
|
||||
// Merge the user options into the default options.
|
||||
var mergedOptions = $.extend(true, { },
|
||||
this.self('DEFAULT_OPTIONS_'), options);
|
||||
return mergedOptions;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Build the markup for a modal container, including the header, close
|
||||
* button and a container for the content to be placed in.
|
||||
* TODO: This kind of markup probably shouldn't be embedded within the JS...
|
||||
*
|
||||
* @protected
|
||||
* @return {Object} jQuery object representing modal content
|
||||
*/
|
||||
$.pkp.controllers.modal.ModalHandler.prototype.modalBuild =
|
||||
function() {
|
||||
|
||||
var $titleDiv, $modal = $('<div class="pkp_modal_panel"></div>');
|
||||
|
||||
// Title bar
|
||||
if (typeof(this.options.textTitle) !== 'undefined') {
|
||||
$titleDiv = $('<div class="header"/>').text(this.options.textTitle);
|
||||
$modal.append($titleDiv);
|
||||
} else if (typeof(this.options.title) !== 'undefined') {
|
||||
$modal.append('<div class="header">' + this.options.title + '</div>');
|
||||
} else {
|
||||
$modal.append('<div class="header">' + '</div>');
|
||||
}
|
||||
|
||||
// Close button
|
||||
if (this.options.canClose) {
|
||||
$modal.append(
|
||||
'<a href="#" class="close pkpModalCloseButton">' +
|
||||
'<span :aria-hidden="true">×</span>' +
|
||||
'<span class="pkp_screen_reader">' +
|
||||
(/** @type {{ closeButtonText: string }} */ (this.options))
|
||||
.closeButtonText + '</span></a>');
|
||||
}
|
||||
|
||||
// Content
|
||||
$modal.append('<div class="content"></div>');
|
||||
|
||||
// Add aria role and label
|
||||
$modal.attr('role', 'dialog')
|
||||
.attr('aria-label', this.options.title);
|
||||
|
||||
return $modal;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attach a modal to the dom and make it visible
|
||||
* @param {jQueryObject} $handledElement The modal.
|
||||
*/
|
||||
$.pkp.controllers.modal.ModalHandler.prototype.modalOpen =
|
||||
function($handledElement) {
|
||||
|
||||
// The $handledElement must be attached to the DOM before events will
|
||||
// bubble up to SiteHandler
|
||||
var $body = $('body');
|
||||
$body.append($handledElement);
|
||||
|
||||
// Trigger visibility state change on the next tick, so that CSS
|
||||
// transform animations will run
|
||||
setTimeout(function() {
|
||||
$handledElement.addClass('is_visible');
|
||||
},10);
|
||||
|
||||
// Set focus to the modal. Leave a sizeable delay here so that the
|
||||
// element can be added to the dom first
|
||||
setTimeout(function() {
|
||||
$handledElement.focus();
|
||||
}, 300);
|
||||
|
||||
// Trigger events
|
||||
$handledElement.trigger('pkpModalOpen', [$handledElement]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Close the modal. Typically invoked via an event of some kind, such as
|
||||
* a `click` or `keyup`
|
||||
*
|
||||
* @param {Object=} opt_callingContext The calling element or object.
|
||||
* @param {Event=} opt_event The triggering event (e.g. a click on
|
||||
* a close button. Not set if called via callback.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.modal.ModalHandler.prototype.modalClose =
|
||||
function(opt_callingContext, opt_event) {
|
||||
|
||||
var modalHandler = this,
|
||||
$modalElement = this.getHtmlElement(),
|
||||
$form = $modalElement.find('form').first(),
|
||||
handler, informationObject;
|
||||
|
||||
// Unregister a form if attached to this modalElement
|
||||
// modalClose is called on both 'cancel' and 'close' events. With
|
||||
// callbacks both callingContext and event are undefined. So,
|
||||
// unregister this form with SiteHandler.
|
||||
if ($form.length == 1) {
|
||||
informationObject = {closePermitted: true};
|
||||
$form.trigger('containerClose', [informationObject]);
|
||||
if (!informationObject.closePermitted) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the modal, clean up any mounted vue instances, remove it from the
|
||||
// DOM and remove the handler once the CSS animation is complete
|
||||
$modalElement.removeClass('is_visible');
|
||||
this.trigger('pkpModalClose');
|
||||
setTimeout(function() {
|
||||
var vueInstances = modalHandler.options.closeCleanVueInstances,
|
||||
instance,
|
||||
i,
|
||||
id;
|
||||
if (vueInstances.length) {
|
||||
for (i = 0; i < vueInstances.length; i++) {
|
||||
id = vueInstances[i];
|
||||
if (typeof pkp.registry._instances[id] !== 'undefined') {
|
||||
instance = /** @type {{ $destroy: Function }} */
|
||||
(pkp.registry._instances[id]);
|
||||
instance.$destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
modalHandler.unbindPartial($modalElement);
|
||||
$modalElement.empty();
|
||||
modalHandler.remove();
|
||||
// Fire a callback function if one has been passed with options
|
||||
if (typeof modalHandler.options.closeCallback === 'function') {
|
||||
modalHandler.options.closeCallback.call();
|
||||
}
|
||||
}, 300);
|
||||
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Process events that reach the wrapper element.
|
||||
* Should NOT block other events from bubbling up. Doing so
|
||||
* can disable submit buttons in nested forms.
|
||||
*
|
||||
* @param {Object=} opt_callingContext The calling element or object.
|
||||
* @param {Event=} opt_event The triggering event (e.g. a click on
|
||||
* a close button. Not set if called via callback.
|
||||
*/
|
||||
$.pkp.controllers.modal.ModalHandler.prototype.handleWrapperEvents =
|
||||
function(opt_callingContext, opt_event) {
|
||||
|
||||
// Close click events directly on modal (background screen)
|
||||
if (opt_event.type == 'click' && opt_callingContext == opt_event.target) {
|
||||
$.pkp.classes.Handler.getHandler($(opt_callingContext))
|
||||
.modalClose();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close for ESC keypresses (27) that have bubbled up
|
||||
if (opt_event.type == 'keyup' && opt_event.which == 27) {
|
||||
$.pkp.classes.Handler.getHandler($(opt_callingContext))
|
||||
.modalClose();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Handler to redirect to the correct notification widget the
|
||||
* notify user event.
|
||||
* @param {HTMLElement} sourceElement The element that issued the
|
||||
* "notifyUser" event.
|
||||
* @param {Event} event The "notify user" event.
|
||||
* @param {HTMLElement} triggerElement The element that triggered
|
||||
* the "notifyUser" event.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.modal.ModalHandler.prototype.redirectNotifyUserEventHandler_ =
|
||||
function(sourceElement, event, triggerElement) {
|
||||
|
||||
// Use the notification helper to redirect the notify user event.
|
||||
$.pkp.classes.notification.NotificationHelper.
|
||||
redirectNotifyUserEvent(this, triggerElement);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler to listen to global form success events, and close when an event
|
||||
* from a child form has been fired, and this form matches the config id
|
||||
*
|
||||
* @param {Object} source The Vue.js component which fired the event
|
||||
* @param {Object} formId The form component's id prop
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.modal.ModalHandler.prototype.onFormSuccess_ =
|
||||
function(source, formId) {
|
||||
if (this.options.closeOnFormSuccessId &&
|
||||
this.options.closeOnFormSuccessId === formId) {
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
self.modalClose();
|
||||
}, 1500);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @file js/controllers/modal/RedirectConfirmationModalHandler.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 RedirectConfirmationModalHandler
|
||||
* @ingroup js_controllers_modal
|
||||
*
|
||||
* @brief A confirmation modal that redirects to a URL upon confirmation.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.modal.ConfirmationModalHandler
|
||||
*
|
||||
* @param {jQueryObject} $handledElement The clickable element
|
||||
* the modal will be attached to.
|
||||
* @param {Object} options Non-default options to configure
|
||||
* the modal.
|
||||
*
|
||||
* Options are:
|
||||
* - remoteUrl string A URL to be redirected to when the confirmation
|
||||
* button has been clicked.
|
||||
* - All options from the ConfirmationModalHandler and ModalHandler
|
||||
* widgets.
|
||||
* - All options documented for the jQueryUI dialog widget,
|
||||
* except for the buttons parameter which is not supported.
|
||||
*/
|
||||
$.pkp.controllers.modal.RedirectConfirmationModalHandler =
|
||||
function($handledElement, options) {
|
||||
|
||||
this.parent($handledElement, options);
|
||||
|
||||
// Configure the redirect URL to be called when
|
||||
// the modal closes.
|
||||
this.remoteUrl_ = options.remoteUrl;
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.modal.RedirectConfirmationModalHandler,
|
||||
$.pkp.controllers.modal.ConfirmationModalHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* A URL to be redirected to when the confirmation button
|
||||
* has been clicked.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.modal.RedirectConfirmationModalHandler.prototype.
|
||||
remoteUrl_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/** @inheritDoc */
|
||||
$.pkp.controllers.modal.RedirectConfirmationModalHandler.prototype.
|
||||
checkOptions = function(options) {
|
||||
|
||||
// Check the mandatory options of the ModalHandler handler.
|
||||
if (!this.parent('checkOptions', options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for our own mandatory options.
|
||||
// The cancel button is mandatory for redirect confirmation modals.
|
||||
return typeof options.cancelButton === 'string' &&
|
||||
typeof options.remoteUrl === 'string';
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Callback that will be activated when the modal's
|
||||
* confirm button is clicked.
|
||||
*
|
||||
* @param {HTMLElement} dialogElement The element the
|
||||
* dialog was created on.
|
||||
* @param {Event} event The click event.
|
||||
*/
|
||||
$.pkp.controllers.modal.RedirectConfirmationModalHandler.prototype.
|
||||
modalConfirm = function(dialogElement, event) {
|
||||
|
||||
document.location = this.remoteUrl_;
|
||||
};
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* @file js/controllers/modal/RemoteActionConfirmationModalHandler.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 RemoteActionConfirmationModalHandler
|
||||
* @ingroup js_controllers_modal
|
||||
*
|
||||
* @brief A confirmation modal that executes a remote action on confirmation.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.modal.ConfirmationModalHandler
|
||||
*
|
||||
* @param {jQueryObject} $handledElement The clickable element
|
||||
* the modal will be attached to.
|
||||
* @param {{
|
||||
* remoteAction: string,
|
||||
* postData: Object,
|
||||
* csrfToken: string
|
||||
* }} options Non-default options to configure the modal.
|
||||
*
|
||||
* Options are:
|
||||
* - remoteAction string An action to be executed when the confirmation
|
||||
* button has been clicked.
|
||||
* - All options from the ConfirmationModalHandler and ModalHandler
|
||||
* widgets.
|
||||
* - All options documented for the jQueryUI dialog widget,
|
||||
* except for the buttons parameter which is not supported.
|
||||
*/
|
||||
$.pkp.controllers.modal.RemoteActionConfirmationModalHandler =
|
||||
function($handledElement, options) {
|
||||
|
||||
this.parent($handledElement, options);
|
||||
|
||||
// Configure the remote action (URL) to be called when
|
||||
// the modal closes.
|
||||
this.remoteAction_ = options.remoteAction;
|
||||
|
||||
// Store the data to send with the post request
|
||||
this.postData_ = options.postData || {};
|
||||
|
||||
// Add the CSRF token to the post data
|
||||
this.postData_.csrfToken = options.csrfToken;
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.modal.RemoteActionConfirmationModalHandler,
|
||||
$.pkp.controllers.modal.ConfirmationModalHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* A remote action to be executed when the confirmation button
|
||||
* has been clicked.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.modal.RemoteActionConfirmationModalHandler.prototype.
|
||||
remoteAction_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Data params to send with the post request
|
||||
* @private
|
||||
* @type {?Object}
|
||||
*/
|
||||
$.pkp.controllers.modal.RemoteActionConfirmationModalHandler.prototype.
|
||||
postData_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.modal.RemoteActionConfirmationModalHandler.prototype.
|
||||
checkOptions = function(options) {
|
||||
|
||||
// Check the mandatory options of the ModalHandler handler.
|
||||
if (!this.parent('checkOptions', options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for our own mandatory options.
|
||||
// The cancel button is mandatory for remote action confirmation modals.
|
||||
return typeof options.cancelButton === 'string' &&
|
||||
typeof options.remoteAction === 'string';
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Callback that will be activated when the modal's
|
||||
* confirm button is clicked.
|
||||
*
|
||||
* @param {HTMLElement} dialogElement The element the
|
||||
* dialog was created on.
|
||||
* @param {Event} event The click event.
|
||||
*/
|
||||
$.pkp.controllers.modal.RemoteActionConfirmationModalHandler.prototype.
|
||||
modalConfirm = function(dialogElement, event) {
|
||||
event.preventDefault();
|
||||
|
||||
$.post(this.remoteAction_,
|
||||
this.postData_,
|
||||
this.callbackWrapper(this.remoteResponse), 'json');
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.modal.RemoteActionConfirmationModalHandler.prototype.
|
||||
remoteResponse = function(ajaxOptions, jsonData) {
|
||||
|
||||
var processedJsonData = this.parent('remoteResponse', ajaxOptions, jsonData);
|
||||
if (processedJsonData !== false) {
|
||||
this.modalClose(ajaxOptions);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* @file js/controllers/modal/WizardModalHandler.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 WizardModalHandler
|
||||
* @ingroup js_controllers_modal
|
||||
*
|
||||
* @brief A modal that contains a wizard and handles its events.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.modal.AjaxModalHandler
|
||||
*
|
||||
* @param {jQueryObject} $handledElement The clickable element
|
||||
* the modal will be attached to.
|
||||
* @param {Object} options non-default Dialog options
|
||||
* to be passed into the dialog widget.
|
||||
*
|
||||
* Options are:
|
||||
* - all options documented for the AjaxModalHandler.
|
||||
*/
|
||||
$.pkp.controllers.modal.WizardModalHandler =
|
||||
function($handledElement, options) {
|
||||
|
||||
this.parent($handledElement, options);
|
||||
|
||||
// Subscribe the modal to wizard events.
|
||||
this.bind('wizardClose', this.wizardClose);
|
||||
this.bind('wizardCancel', this.wizardClose);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits($.pkp.controllers.modal.WizardModalHandler,
|
||||
$.pkp.controllers.modal.AjaxModalHandler);
|
||||
|
||||
|
||||
/**
|
||||
* Overridden version of the modal close button handler acting
|
||||
* as a wizard cancel button.
|
||||
*
|
||||
* @protected
|
||||
* @param {Object=} opt_callingElement The close button.
|
||||
* @param {Event=} opt_event The close button click event.
|
||||
* @param {boolean=} opt_closeWithoutCancel Set to true to immediately
|
||||
* close the modal.
|
||||
* @return {boolean} Should return false to stop event processing.
|
||||
*/
|
||||
$.pkp.controllers.modal.WizardModalHandler.prototype.modalClose =
|
||||
function(opt_callingElement, opt_event, opt_closeWithoutCancel) {
|
||||
|
||||
if (opt_closeWithoutCancel) {
|
||||
this.parent('modalClose', opt_callingElement, opt_event);
|
||||
} else {
|
||||
// Trigger a cancel event on the wizard.
|
||||
var wizardCancelRequestedEvent = new $.Event('wizardCancelRequested'),
|
||||
$wizard;
|
||||
|
||||
wizardCancelRequestedEvent.stopPropagation();
|
||||
$wizard = this.getHtmlElement().children().first();
|
||||
$wizard.trigger(wizardCancelRequestedEvent);
|
||||
|
||||
// Only close the modal if the wizard didn't prevent this.
|
||||
if (!wizardCancelRequestedEvent.isDefaultPrevented()) {
|
||||
this.parent('modalClose', opt_callingElement, opt_event);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the wizard close event.
|
||||
*
|
||||
* @param {HTMLElement} wizardElement The calling
|
||||
* wizard.
|
||||
* @param {Event} event The triggered event.
|
||||
*/
|
||||
$.pkp.controllers.modal.WizardModalHandler.prototype.wizardClose =
|
||||
function(wizardElement, event) {
|
||||
|
||||
this.modalClose(wizardElement, event, true);
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @defgroup js_controllers_tab_workflow
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/tab/workflow/WorkflowTabHandler.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 WorkflowTabHandler
|
||||
* @ingroup js_controllers_tab_workflow
|
||||
*
|
||||
* @brief A subclass of TabHandler for handling requests to load stages
|
||||
* of the workflow.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.tab =
|
||||
$.pkp.controllers.tab || {};
|
||||
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.tab.workflow =
|
||||
$.pkp.controllers.tab.workflow || {};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.TabHandler
|
||||
*
|
||||
* @param {jQueryObject} $tabs A wrapped HTML element that
|
||||
* represents the tabbed interface.
|
||||
* @param {Object} options Handler options.
|
||||
*/
|
||||
$.pkp.controllers.tab.workflow.WorkflowTabHandler =
|
||||
function($tabs, options) {
|
||||
|
||||
var pageUrl, stage, pattern, i, tabAnchors, matches;
|
||||
this.parent($tabs, options);
|
||||
|
||||
pageUrl = document.location.toString();
|
||||
matches = pageUrl.match('workflow/([^/]+)/');
|
||||
if (matches) {
|
||||
stage = matches[1];
|
||||
tabAnchors = $tabs.find('li a');
|
||||
for (i = 0; i < tabAnchors.length; i++) {
|
||||
pattern = new RegExp(stage);
|
||||
if (tabAnchors[i].getAttribute('class').match(pattern)) {
|
||||
options.selected = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.tab.workflow.WorkflowTabHandler,
|
||||
$.pkp.controllers.TabHandler);
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,632 @@
|
||||
/**
|
||||
* @defgroup js_controllers_wizard
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/wizard/WizardHandler.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 WizardHandler
|
||||
* @ingroup js_controllers_wizard
|
||||
*
|
||||
* @brief Basic wizard handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.wizard = $.pkp.controllers.wizard || { };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.TabHandler
|
||||
*
|
||||
* @param {jQueryObject} $wizard A wrapped HTML element that
|
||||
* represents the wizard.
|
||||
* @param {{
|
||||
* enforceLinear: boolean,
|
||||
* cancelButtonText: string,
|
||||
* continueButtonTest: string,
|
||||
* finishButtonText: string
|
||||
* }} options options to configure the form handler.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler = function($wizard, options) {
|
||||
this.parent($wizard, options);
|
||||
|
||||
// Add the wizard buttons
|
||||
this.addWizardButtons_($wizard, options);
|
||||
|
||||
this.enforceLinear_ = options.hasOwnProperty('enforceLinear') ?
|
||||
options.enforceLinear : true;
|
||||
|
||||
// Start the wizard.
|
||||
this.startWizard();
|
||||
|
||||
// Bind the wizard events to handlers.
|
||||
this.bindWizardEvents();
|
||||
|
||||
// Assume that we usually have forms in the wizard
|
||||
// tabs and bind to form events.
|
||||
this.bind('formValid', this.formValid);
|
||||
this.bind('formInvalid', this.formInvalid);
|
||||
this.bind('formSubmitted', this.formSubmitted);
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.wizard.WizardHandler, $.pkp.controllers.TabHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The continue button.
|
||||
* @private
|
||||
* @type {jQueryObject?}
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.$continueButton_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The progress indicator.
|
||||
* @private
|
||||
* @type {jQueryObject?}
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.$progressIndicator_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The continue button label.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.continueButtonText_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The finish button label.
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.finishButtonText_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Whether or not to enforce linear progress through the wizard.
|
||||
* @private
|
||||
* @type {?boolean}
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.enforceLinear_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Show the loading spinner
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.showProgressIndicator_ =
|
||||
function() {
|
||||
this.getProgressIndicator().css('opacity', 1);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hide the loading spinner
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.hideProgressIndicator_ =
|
||||
function() {
|
||||
this.getProgressIndicator().css('opacity', 0);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* Handle the user's request to advance the wizard.
|
||||
*
|
||||
* NB: Please do not override this method. This is an internal event
|
||||
* handler. Override the wizardAdvanceRequested() and wizardAdvance()
|
||||
* event handlers instead if you want to provide custom behavior.
|
||||
*
|
||||
* @param {HTMLElement} buttonElement The button that triggered the event.
|
||||
* @param {Event} event The triggered event.
|
||||
* @return {boolean} Should return false to stop event propagation.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.continueRequest =
|
||||
function(buttonElement, event) {
|
||||
|
||||
// Trigger the "advance requested" event on the current
|
||||
// tab's children to give it a chance to veto the advance
|
||||
// request.
|
||||
var advanceRequestedEvent = new $.Event('wizardAdvanceRequested');
|
||||
this.getCurrentTab().children().first().trigger(advanceRequestedEvent);
|
||||
|
||||
// Advance the wizard if the advanceRequestEvent handler didn't
|
||||
// prevent it.
|
||||
if (!advanceRequestedEvent.isDefaultPrevented()) {
|
||||
this.advanceOrClose_();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle "form valid" events that may be triggered by forms in the
|
||||
* wizard tab.
|
||||
*
|
||||
* @param {HTMLElement} formElement The form that triggered the event.
|
||||
* @param {Event} event The triggered event.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.formValid =
|
||||
function(formElement, event) {
|
||||
|
||||
// The default implementation enables the continue button
|
||||
// as soon as the form validates.
|
||||
this.enableContinueButton();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle "form invalid" events that may be triggered by forms in the
|
||||
* wizard tab.
|
||||
*
|
||||
* @param {HTMLElement} formElement The form that triggered the event.
|
||||
* @param {Event} event The triggered event.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.formInvalid =
|
||||
function(formElement, event) {
|
||||
|
||||
// The default implementation disables the continue button
|
||||
// as if the form no longer validates.
|
||||
this.disableContinueButton();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle "form submitted" events that may be triggered by forms in the
|
||||
* wizard tab.
|
||||
*
|
||||
* @param {HTMLElement} formElement The form that triggered the event.
|
||||
* @param {Event} event The triggered event.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.formSubmitted =
|
||||
function(formElement, event) {
|
||||
|
||||
// The default implementation advances the wizard.
|
||||
this.advanceOrClose_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the user's request to cancel the wizard.
|
||||
*
|
||||
* NB: Please do not override this method. This is an internal event
|
||||
* handler. Override the wizardCancel() event handler instead if you
|
||||
* want to provide custom behavior.
|
||||
*
|
||||
* @param {HTMLElement} buttonElement The button that triggered the event.
|
||||
* @param {Event} event The triggered event.
|
||||
* @return {boolean} Should return false to stop event propagation.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.cancelRequest =
|
||||
function(buttonElement, event) {
|
||||
|
||||
// This is a 'cancel' click, so unregister forms without prompting.
|
||||
this.checkForm_(false);
|
||||
|
||||
// Trigger the "cancel requested" event on the current
|
||||
// tab's children to give it a chance to veto the cancel
|
||||
// request.
|
||||
var cancelRequestedEvent = new $.Event('wizardCancelRequested');
|
||||
this.getCurrentTab().children().first().trigger(cancelRequestedEvent);
|
||||
|
||||
// Trigger the wizardCancel event if the
|
||||
// cancelRequestEvent handler didn't prevent it.
|
||||
if (!cancelRequestedEvent.isDefaultPrevented()) {
|
||||
this.trigger('wizardCancel');
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the wizard "cancel requested" event.
|
||||
*
|
||||
* Please override this method to clean up before the wizard is
|
||||
* being canceled. You can execute event.preventDefault() if you
|
||||
* don't want the wizard to cancel.
|
||||
*
|
||||
* NB: This is a fallback handler that will be called if no other
|
||||
* event handler calls the event.stopPropagation() method.
|
||||
*
|
||||
* @param {HTMLElement} wizardElement The wizard's HTMLElement on
|
||||
* which the event was triggered.
|
||||
* @param {Event} event The triggered event.
|
||||
* @return {boolean} Return false if not overridden and if check form
|
||||
* returns true.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.wizardCancelRequested =
|
||||
function(wizardElement, event) {
|
||||
|
||||
if (this.checkForm_(true)) {
|
||||
// User doesn't wants to leave. Return false to stop cancel request.
|
||||
return false;
|
||||
}
|
||||
|
||||
// User wants to leave or there is no form inside this wizard.
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the wizard "advance requested" event.
|
||||
*
|
||||
* Please override this method to make custom validation checks or
|
||||
* place server requests before you let the wizard advance to the next
|
||||
* step. You can execute event.preventDefault() if you don't want
|
||||
* the wizard to advance because you encountered errors during
|
||||
* validation.
|
||||
*
|
||||
* NB: This is a fallback handler that will be called if no other
|
||||
* event handler calls the event.stopPropagation() method.
|
||||
*
|
||||
* @param {HTMLElement} wizardElement The wizard's HTMLElement on
|
||||
* which the event was triggered.
|
||||
* @param {Event} event The triggered event.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.wizardAdvanceRequested =
|
||||
function(wizardElement, event) {
|
||||
|
||||
// If we find a form then submit it.
|
||||
var $form = this.getForm_();
|
||||
if ($form) {
|
||||
// Try to submit the form.
|
||||
if ($form.submit()) {
|
||||
this.disableContinueButton();
|
||||
this.showProgressIndicator_();
|
||||
}
|
||||
|
||||
// Prevent default event handling so that the form
|
||||
// can do its validation checks first.
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the "wizard advance" event. The default implementation
|
||||
* advances the wizard to the next step and disables the previous step.
|
||||
*
|
||||
* In most cases you probably don't want to override this method unless
|
||||
* you want to provide a different navigation experience. Form validation
|
||||
* and submission or similar tasks should be done in the
|
||||
* wizardAdvanceRequested() event handler.
|
||||
*
|
||||
* @param {HTMLElement} wizardElement The wizard's HTMLElement on
|
||||
* which the event was triggered.
|
||||
* @param {Event} event The triggered event.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.wizardAdvance =
|
||||
function(wizardElement, event) {
|
||||
|
||||
// The wizard can only be advanced one step at a time.
|
||||
// The step cannot be greater than the number of wizard
|
||||
// tabs and not less than 1.
|
||||
var currentStep = this.getCurrentStep(),
|
||||
lastStep = this.getNumberOfSteps() - 1,
|
||||
targetStep = currentStep + 1,
|
||||
$wizard, $continueButton;
|
||||
|
||||
// Do not advance beyond the last step.
|
||||
if (targetStep > lastStep) {
|
||||
throw new Error('Trying to set an invalid wizard step!');
|
||||
}
|
||||
|
||||
// Enable the target step.
|
||||
$wizard = this.getHtmlElement();
|
||||
$wizard.tabs('enable', targetStep);
|
||||
|
||||
// Advance to the target step.
|
||||
$wizard.tabs('option', 'active', targetStep);
|
||||
|
||||
if (this.enforceLinear_) {
|
||||
// Disable the previous step.
|
||||
$wizard.tabs('disable', currentStep);
|
||||
}
|
||||
|
||||
// If this is the last step then change the text on the
|
||||
// continue button to finish.
|
||||
$continueButton = this.getContinueButton();
|
||||
if (targetStep === lastStep) {
|
||||
$continueButton.text(
|
||||
/** @type {string} */ (this.getFinishButtonText()));
|
||||
}
|
||||
|
||||
this.hideProgressIndicator_();
|
||||
this.enableContinueButton();
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* (Re-)Start the wizard.
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.startWizard = function() {
|
||||
|
||||
// Retrieve the wizard element.
|
||||
var $wizard = this.getHtmlElement(),
|
||||
$continueButton, disabledSteps, i;
|
||||
|
||||
// Do we re-start the wizard?
|
||||
if (this.getCurrentStep() !== 0) {
|
||||
// Make sure that the first step is enabled, otherwise
|
||||
// we cannot select it.
|
||||
$wizard.tabs('enable', 0);
|
||||
|
||||
// Go to the first step.
|
||||
$wizard.tabs('option', 'active', 0);
|
||||
|
||||
// Reset the continue button label.
|
||||
$continueButton = this.getContinueButton();
|
||||
$continueButton.text(
|
||||
/** @type {string} */ (this.getContinueButtonText()));
|
||||
}
|
||||
|
||||
if (this.enforceLinear_) {
|
||||
// Disable all but the first step.
|
||||
disabledSteps = [];
|
||||
for (i = 1; i < this.getNumberOfSteps(); i++) {
|
||||
disabledSteps.push(i);
|
||||
}
|
||||
$wizard.tabs('option', 'disabled', disabledSteps);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind wizard events to default event handlers.
|
||||
* @protected
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.bindWizardEvents = function() {
|
||||
this.bind('wizardCancelRequested', this.wizardCancelRequested);
|
||||
this.bind('wizardAdvanceRequested', this.wizardAdvanceRequested);
|
||||
this.bind('wizardAdvance', this.wizardAdvance);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the current wizard step.
|
||||
* @protected
|
||||
* @return {number} The current wizard step.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.
|
||||
getCurrentStep = function() {
|
||||
|
||||
return this.getCurrentTabIndex();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the continue button.
|
||||
* @protected
|
||||
* @return {jQueryObject} The continue button.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.
|
||||
getContinueButton = function() {
|
||||
|
||||
return this.$continueButton_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the progress indicator.
|
||||
* @protected
|
||||
* @return {jQueryObject} The progress indicator.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.
|
||||
getProgressIndicator = function() {
|
||||
|
||||
return this.$progressIndicator_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the continue button label.
|
||||
* @protected
|
||||
* @return {?string} The text to display on the continue button.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.
|
||||
getContinueButtonText = function() {
|
||||
|
||||
return this.continueButtonText_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the finish button label.
|
||||
* @protected
|
||||
* @return {?string} The text to display on the continue button
|
||||
* in the last wizard step.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.
|
||||
getFinishButtonText = function() {
|
||||
|
||||
return this.finishButtonText_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Count the wizard steps.
|
||||
* @return {number} The current number of wizard steps.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.
|
||||
getNumberOfSteps = function() {
|
||||
|
||||
var $wizard = this.getHtmlElement();
|
||||
return $wizard.find('ul').first().children().length;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Return the current form (if any).
|
||||
*
|
||||
* @private
|
||||
* @return {jQueryObject?} The form (if any).
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.getForm_ = function() {
|
||||
var i, $element, $tabContent;
|
||||
|
||||
// If we find a form in the current tab then return it.
|
||||
$tabContent = this.getCurrentTab().children();
|
||||
for (i = 0; i < $tabContent.length; i++) {
|
||||
$element = $($tabContent[i]);
|
||||
if ($element.is('form')) {
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Continue to the next step or, if this is the last step,
|
||||
* then close the wizard.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.advanceOrClose_ =
|
||||
function() {
|
||||
var currentStep = this.getCurrentStep(),
|
||||
lastStep = this.getNumberOfSteps() - 1;
|
||||
|
||||
if (currentStep < lastStep) {
|
||||
this.trigger('wizardAdvance');
|
||||
} else {
|
||||
this.trigger('wizardClose');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper method to look for changed forms.
|
||||
*
|
||||
* @param {boolean} prompt Whether or not to prompt.
|
||||
* @return {boolean} Whether or not they wish to cancel. If no form is
|
||||
* available in wizard, also return false.
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.checkForm_ =
|
||||
function(prompt) {
|
||||
var $form = this.getForm_(),
|
||||
handler;
|
||||
if ($form !== null) {
|
||||
handler = $.pkp.classes.Handler.getHandler($('#' + $form.attr('id')));
|
||||
if (prompt) {
|
||||
if (handler.formChangesTracked) {
|
||||
if (!confirm(pkp.localeKeys['form.dataHasChanged'])) {
|
||||
return true; // the user has clicked cancel, they wish to stay.
|
||||
} else {
|
||||
handler.unregisterForm();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handler.unregisterForm();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add wizard buttons to the wizard.
|
||||
*
|
||||
* @private
|
||||
* @param {jQueryObject} $wizard The wizard element.
|
||||
* @param {Object} options The wizard options.
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.addWizardButtons_ =
|
||||
function($wizard, options) {
|
||||
|
||||
// Add space before wizard buttons.
|
||||
var $wizardButtons =
|
||||
$('<div id="wizardButtons" class="modal_buttons"></div>'),
|
||||
$cancelButton, $continueButton, $progressIndicator;
|
||||
|
||||
if (options.continueButtonText) {
|
||||
// Add continue/finish button.
|
||||
$continueButton = $(
|
||||
'<button id="continueButton" class="pkp_button"></button>')
|
||||
.text(options.continueButtonText);
|
||||
$wizardButtons.append($continueButton);
|
||||
|
||||
$progressIndicator = $(
|
||||
'<span class="pkp_spinner"></span>');
|
||||
$wizardButtons.append($progressIndicator);
|
||||
|
||||
$continueButton.
|
||||
// Attach the continue request handler.
|
||||
bind('click',
|
||||
this.callbackWrapper(this.continueRequest));
|
||||
this.$continueButton_ = /** @type {jQueryObject} */ ($continueButton);
|
||||
this.$progressIndicator_ = $progressIndicator;
|
||||
|
||||
// Remember the button labels.
|
||||
this.continueButtonText_ = options.continueButtonText;
|
||||
if (options.finishButtonText) {
|
||||
this.finishButtonText_ = options.finishButtonText;
|
||||
} else {
|
||||
this.finishButtonText_ = options.continueButtonText;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.cancelButtonText) {
|
||||
// Add cancel button.
|
||||
$cancelButton = $('<a id="cancelButton" class="cancel" href="#"></a>')
|
||||
.text(options.cancelButtonText);
|
||||
$wizardButtons.append($cancelButton);
|
||||
|
||||
// Attach the cancel request handler.
|
||||
$cancelButton.bind('click',
|
||||
this.callbackWrapper(this.cancelRequest));
|
||||
}
|
||||
|
||||
// Insert wizard buttons.
|
||||
$wizard.after($wizardButtons);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disable the continue button
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.disableContinueButton =
|
||||
function() {
|
||||
this.getContinueButton().attr('disabled', 'disabled');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enable the continue button
|
||||
*/
|
||||
$.pkp.controllers.wizard.WizardHandler.prototype.enableContinueButton =
|
||||
function() {
|
||||
this.getContinueButton().removeAttr('disabled');
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,384 @@
|
||||
/**
|
||||
* @defgroup controllers_wizard_fileUpload
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/wizard/fileUpload/FileUploadWizardHandler.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 FileUploadWizardHandler
|
||||
* @ingroup controllers_wizard_fileUpload
|
||||
*
|
||||
* @brief File uploader wizard handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.wizard.fileUpload =
|
||||
$.pkp.controllers.wizard.fileUpload || { };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.wizard.WizardHandler
|
||||
*
|
||||
* @param {jQueryObject} $wizard The wrapped HTML form element.
|
||||
* @param {Object} options Wizard options.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler =
|
||||
function($wizard, options) {
|
||||
|
||||
this.parent($wizard, options);
|
||||
|
||||
// Save action urls.
|
||||
this.csrfToken_ = options.csrfToken;
|
||||
this.deleteUrl_ = options.deleteUrl;
|
||||
this.metadataUrl_ = options.metadataUrl;
|
||||
this.finishUrl_ = options.finishUrl;
|
||||
this.cancelUrl_ = options.cancelUrl;
|
||||
|
||||
// Bind events of the nested upload forms.
|
||||
this.bind('fileUploaded', this.handleFileUploaded);
|
||||
this.bind('filesRemoved', this.handleRemovedFiles);
|
||||
|
||||
// Initially disable the continue button.
|
||||
this.disableContinueButton();
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler,
|
||||
$.pkp.controllers.wizard.WizardHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* The CSRF token to use with a cancel event.
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.csrfToken_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* The URL to be called when a delete event occurs.
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.deleteUrl_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* The URL from which to load the meta-data form.
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.metadataUrl_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* The URL from which to load the finish form.
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.finishUrl_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* The URL to be called when a cancel event occurs.
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.cancelUrl_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* Information about the uploaded file (once there is one).
|
||||
* @private
|
||||
* @type {{fileId: number}?}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.uploadedFile_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Information about the file being revised.
|
||||
* @private
|
||||
* @type {{fileId: number, name: string, uploaderUserId: number}}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.originalFile_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.tabsBeforeActivate = function(tabsElement, event, ui) {
|
||||
|
||||
// The last two tabs require a file to be uploaded.
|
||||
if (ui.newTab.index() > 0) {
|
||||
if (!this.uploadedFile_) {
|
||||
throw new Error('Uploaded file missing!');
|
||||
}
|
||||
|
||||
// Set the correct URLs.
|
||||
var $wizard = this.getHtmlElement(), newUrl = '';
|
||||
switch (ui.newTab.index()) {
|
||||
case 1:
|
||||
newUrl = this.metadataUrl_;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
newUrl = this.finishUrl_;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error('Unsupported tab index.');
|
||||
}
|
||||
|
||||
newUrl = newUrl + '&submissionFileId=' + this.uploadedFile_.id;
|
||||
ui.newTab.find('.ui-tabs-anchor').attr('href', newUrl);
|
||||
}
|
||||
|
||||
return /** @type {boolean} */ (
|
||||
this.parent('tabsBeforeActivate', tabsElement, event, ui));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Overridden version of WizardHandler's wizardAdvance handler.
|
||||
* This version allows a user to return to all tabs but the very
|
||||
* first one (the actual file upload).
|
||||
*
|
||||
* @param {HTMLElement} wizardElement The wizard's HTMLElement on
|
||||
* which the event was triggered.
|
||||
* @param {Event} event The triggered event.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.wizardAdvance = function(wizardElement, event) {
|
||||
|
||||
// The wizard can only be advanced one step at a time.
|
||||
// The step cannot be greater than the number of wizard
|
||||
// tabs and not less than 1.
|
||||
var currentStep = this.getCurrentStep(),
|
||||
lastStep = this.getNumberOfSteps() - 1,
|
||||
targetStep = currentStep + 1,
|
||||
$wizard = this.getHtmlElement(),
|
||||
$continueButton;
|
||||
|
||||
// Do not advance beyond the last step.
|
||||
if (targetStep > lastStep) {
|
||||
throw new Error('Trying to set an invalid wizard step!');
|
||||
}
|
||||
|
||||
// Enable the target step.
|
||||
$wizard.tabs('enable', targetStep);
|
||||
|
||||
// Advance to the target step.
|
||||
$wizard.tabs('option', 'active', targetStep);
|
||||
|
||||
// Disable the previous step if it is the first one.
|
||||
if (currentStep === 0) {
|
||||
$wizard.tabs('disable', currentStep);
|
||||
}
|
||||
|
||||
// If this is the last step then change the text on the
|
||||
// continue button to finish.
|
||||
if (targetStep === lastStep) {
|
||||
$continueButton = this.getContinueButton();
|
||||
$continueButton.text(
|
||||
/** @type {string} */ (this.getFinishButtonText()));
|
||||
this.enableContinueButton();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.tabsLoad = function(tabsElement, event, ui) {
|
||||
|
||||
var $wizard = this.getHtmlElement(),
|
||||
$newFileButton,
|
||||
$progressIndicator = this.getProgressIndicator();
|
||||
|
||||
// In the last step: Bind click a event to the button that re-starts
|
||||
// the upload process.
|
||||
if (ui.tab.index() === 2) {
|
||||
$newFileButton = $('#newFile', $wizard);
|
||||
// In some cases only a single file can be uploaded and no new
|
||||
// file button appears
|
||||
if ($newFileButton.length) {
|
||||
$newFileButton.bind('click', this.callbackWrapper(this.startWizard));
|
||||
}
|
||||
}
|
||||
|
||||
$progressIndicator.hide();
|
||||
|
||||
return /** @type {boolean} */ (
|
||||
this.parent('tabsLoad', tabsElement, event, ui));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.formValid = function(formElement, event) {
|
||||
|
||||
// Ignore form validation events for the upload form.
|
||||
if (this.getCurrentStep() === 0 &&
|
||||
this.getHtmlElement().find('#uploadConfirmationForm').length === 0 &&
|
||||
!this.uploadedFile_) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.parent('formValid', formElement, event);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.wizardCancelRequested = function(wizardElement, event) {
|
||||
if (this.parent('wizardCancelRequested', wizardElement, event)) {
|
||||
// If the user presses cancel after uploading a file then delete the file.
|
||||
if (this.uploadedFile_) {
|
||||
this.uploadedFile_.csrfToken = this.csrfToken_;
|
||||
// Authorization policy expects to find the submissionFileId para
|
||||
this.uploadedFile_.submissionFileId = this.uploadedFile_.id;
|
||||
this.uploadedFile_.originalFile = this.originalFile_;
|
||||
$.post(this.cancelUrl_, this.uploadedFile_,
|
||||
$.pkp.classes.Helper.curry(this.wizardCancelSuccess, this,
|
||||
wizardElement, event), 'json');
|
||||
|
||||
// The uploaded file is being dealt with; reset.
|
||||
this.uploadedFile_ = null;
|
||||
|
||||
// Do not cancel immediately.
|
||||
event.preventDefault();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// Stop the cancel request.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Callback triggered when the deletion of a file after clicking
|
||||
* the cancel button was successful.
|
||||
*
|
||||
* @param {HTMLElement} wizardElement The wizard's HTMLElement on
|
||||
* which the event was triggered.
|
||||
* @param {Event} event The original event.
|
||||
* @param {Object} jsonData The JSON data returned by the server on
|
||||
* file deletion.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.wizardCancelSuccess = function(wizardElement, event, jsonData) {
|
||||
|
||||
var processedJsonData = this.handleJson(jsonData);
|
||||
if (processedJsonData !== false) {
|
||||
// Cancel the wizard.
|
||||
this.trigger('wizardCancel');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the "file uploaded" event triggered by the
|
||||
* file upload/revision confirmation forms whenever the
|
||||
* uploaded file changed.
|
||||
*
|
||||
* @param {$.pkp.controllers.form.AjaxFormHandler} callingForm The form
|
||||
* that triggered the event.
|
||||
* @param {Event} event The upload event.
|
||||
* @param {{fileId: number}} uploadedFile Information about the uploaded
|
||||
* file.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.handleFileUploaded = function(callingForm, event, uploadedFile) {
|
||||
|
||||
// Keep the original file data to restore if the wizard is canceled
|
||||
if (this.originalFile_ === null) {
|
||||
this.originalFile_ = uploadedFile.originalFile;
|
||||
}
|
||||
delete uploadedFile.originalFile;
|
||||
|
||||
// Save the uploaded file information
|
||||
this.uploadedFile_ = uploadedFile;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the filesRemoved event triggered by the associated form. The
|
||||
* original event is triggered by plupload and passed via
|
||||
* FileUploadFormHandler.
|
||||
*
|
||||
* See the TODO note under FileUPloadFormHandler::handleFilesRemoved
|
||||
*
|
||||
* @param {$.pkp.controllers.form.AjaxFormHandler} callingForm The form
|
||||
* that triggered the event.
|
||||
* @param {Event} event The upload event.
|
||||
* @param {Object} pluploader plupload component that fired the original
|
||||
* event.
|
||||
* @param {Array} file Array of files removed
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.handleRemovedFiles =
|
||||
function(callingForm, event, pluploader, file) {
|
||||
|
||||
var i;
|
||||
|
||||
if (typeof file === 'undefined' || !file.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There's no error handling done for the response because we don't
|
||||
// really have an elegant way to handle or display a failed deletion
|
||||
for (i in file) {
|
||||
if (typeof file[i].storedData === 'undefined') {
|
||||
return;
|
||||
}
|
||||
file[i].storedData.csrfToken = this.csrfToken_;
|
||||
$.post(this.deleteUrl_, file[i].storedData);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Protected methods
|
||||
//
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.FileUploadWizardHandler.
|
||||
prototype.startWizard = function() {
|
||||
|
||||
// Reset the uploaded and original file.
|
||||
this.uploadedFile_ = this.originalFile_ = null;
|
||||
|
||||
this.parent('startWizard');
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
@@ -0,0 +1,354 @@
|
||||
/**
|
||||
* @defgroup js_controllers_wizard_fileUpload_form
|
||||
*/
|
||||
/**
|
||||
* @file js/controllers/wizard/fileUpload/form/FileUploadFormHandler.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 FileUploadFormHandler
|
||||
* @ingroup js_controllers_wizard_fileUpload_form
|
||||
*
|
||||
* @brief File upload tab handler.
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
/** @type {Object} */
|
||||
$.pkp.controllers.wizard.fileUpload.form =
|
||||
$.pkp.controllers.wizard.fileUpload.form || { };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
* @extends $.pkp.controllers.form.AjaxFormHandler
|
||||
*
|
||||
* @param {jQueryObject} $form The wrapped HTML form element.
|
||||
* @param {Object} options Form validation options.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler =
|
||||
function($form, options) {
|
||||
|
||||
this.parent($form, options);
|
||||
|
||||
// Set internal state properties.
|
||||
this.hasFileSelector_ = options.hasFileSelector;
|
||||
this.hasGenreSelector_ = options.hasGenreSelector;
|
||||
|
||||
if (options.presetRevisedFileId) {
|
||||
this.presetRevisedFileId_ = options.presetRevisedFileId;
|
||||
}
|
||||
this.fileGenres_ = options.fileGenres;
|
||||
|
||||
this.$uploader_ = options.$uploader;
|
||||
|
||||
// Attach the uploader handler to the uploader HTML element.
|
||||
this.attachUploader_(this.$uploader_, options.uploaderOptions);
|
||||
|
||||
this.uploaderSetup(options.$uploader);
|
||||
|
||||
// Enable/disable the uploader and genre selection based on selection
|
||||
this.$revisedFileSelector_ = $form.find('#revisedFileId')
|
||||
.change(this.callbackWrapper(this.revisedFileChange));
|
||||
if (this.hasGenreSelector_) {
|
||||
this.$genreSelector = $form.find('#genreId')
|
||||
.change(this.callbackWrapper(this.genreChange));
|
||||
}
|
||||
|
||||
this.setUploaderVisibility_();
|
||||
};
|
||||
$.pkp.classes.Helper.inherits(
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler,
|
||||
$.pkp.controllers.form.AjaxFormHandler);
|
||||
|
||||
|
||||
//
|
||||
// Private properties
|
||||
//
|
||||
/**
|
||||
* Whether the file upload form has a file selector.
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler
|
||||
.hasFileSelector_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The file upload form's file selector if available.
|
||||
* @private
|
||||
* @type {boolean?}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler
|
||||
.$revisedFileSelector_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the file upload form has a genre selector.
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler
|
||||
.hasGenreSelector_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The file upload form's genre selector if available.
|
||||
* @private
|
||||
* @type {boolean?}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler
|
||||
.$genreSelector_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A preset revised file id (if any).
|
||||
* @private
|
||||
* @type {?string}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler
|
||||
.presetRevisedFileId_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* All currently available file genres.
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler
|
||||
.fileGenres_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A jQuery object referencing the DOM element plupload is attached to
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler
|
||||
.$uploader_ = null;
|
||||
|
||||
|
||||
//
|
||||
// Public methods
|
||||
//
|
||||
/**
|
||||
* The setup callback of the uploader.
|
||||
* @param {jQueryObject} $uploader Element that contains the plupload object.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler.prototype.
|
||||
uploaderSetup = function($uploader) {
|
||||
|
||||
var uploadHandler = $.pkp.classes.Handler.getHandler($uploader);
|
||||
|
||||
// Subscribe to uploader events.
|
||||
uploadHandler.pluploader.bind('BeforeUpload',
|
||||
this.callbackWrapper(this.prepareFileUploadRequest));
|
||||
uploadHandler.pluploader.bind('FileUploaded',
|
||||
this.callbackWrapper(this.handleUploadResponse));
|
||||
uploadHandler.pluploader.bind('FilesRemoved',
|
||||
this.callbackWrapper(this.handleRemovedFiles));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Prepare the request parameters for the file upload request.
|
||||
* @param {Object} caller The original context in which the callback was called.
|
||||
* @param {Object} pluploader The pluploader object.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler.prototype.
|
||||
prepareFileUploadRequest = function(caller, pluploader) {
|
||||
|
||||
var $uploadForm = this.getHtmlElement(),
|
||||
multipartParams = {};
|
||||
|
||||
// Add the revised file to the upload message.
|
||||
if (this.hasFileSelector_) {
|
||||
this.$revisedFileSelector_.attr('disabled', 'disabled');
|
||||
multipartParams.revisedFileId = this.$revisedFileSelector_.val();
|
||||
} else {
|
||||
if (this.presetRevisedFileId_ !== null) {
|
||||
multipartParams.revisedFileId = this.presetRevisedFileId_;
|
||||
} else {
|
||||
multipartParams.revisedFileId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the file genre to the upload message.
|
||||
if (this.hasGenreSelector_) {
|
||||
this.$genreSelector.attr('disabled', 'disabled');
|
||||
multipartParams.genreId = this.$genreSelector.val();
|
||||
} else {
|
||||
multipartParams.genreId = '';
|
||||
}
|
||||
|
||||
// Add the upload message parameters to the uploader.
|
||||
pluploader.settings.multipart_params = multipartParams;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the response of a "file upload" request.
|
||||
* @param {Object} caller The original context in which the callback was called.
|
||||
* @param {Object} pluploader The pluploader object.
|
||||
* @param {Object} file The data of the uploaded file.
|
||||
* @param {{response: string}} ret The serialized JSON response.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler.prototype.
|
||||
handleUploadResponse = function(caller, pluploader, file, ret) {
|
||||
|
||||
// Handle the server's JSON response.
|
||||
var jsonData = this.handleJson($.parseJSON(ret.response)),
|
||||
$uploadForm = this.getHtmlElement();
|
||||
|
||||
if (jsonData !== false) {
|
||||
// Trigger the file uploaded event.
|
||||
this.trigger('fileUploaded', jsonData.uploadedFile);
|
||||
|
||||
// Display the revision confirmation form.
|
||||
if (jsonData.content !== '') {
|
||||
this.replaceWith(jsonData.content);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger validation on the form. This doesn't happen automatically
|
||||
// until `blur` is triggered on the file input field, requiring the
|
||||
// user to click before any disabled form functions become available.
|
||||
this.getHtmlElement().valid();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Pass the `FilesRemoved` event from plupload on to FileUploadWizardHandler
|
||||
* so it can delete the file.
|
||||
*
|
||||
* TODO this is necessary because only the FileUploadWizardHandler knows
|
||||
* the delete URL. But other file upload utilities could benefit from this
|
||||
* feature, so it would be best to internalize this functionality in the
|
||||
* UploadHandler by passing in a deleteURL option. This is a task that
|
||||
* should be handled when the file upload process is rewritten to support
|
||||
* a multi-file upload workflow.
|
||||
* @param {Object} caller The original context in which the callback was called.
|
||||
* @param {Object} pluploader The pluploader object.
|
||||
* @param {Object} file The data of the uploaded file.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler.prototype.
|
||||
handleRemovedFiles = function(caller, pluploader, file) {
|
||||
this.trigger('filesRemoved', [pluploader, file]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal callback to handle form submission.
|
||||
*
|
||||
* @param {Object} validator The validator plug-in.
|
||||
* @param {HTMLElement} formElement The wrapped HTML form.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler.prototype.
|
||||
submitForm = function(validator, formElement) {
|
||||
|
||||
// There is no form to submit (file already uploaded).
|
||||
// Trigger event to signal that user requests the form to be submitted.
|
||||
this.trigger('formSubmitted');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the "change" event of the revised file selector.
|
||||
* @param {HTMLElement} revisedFileElement The original context in
|
||||
* which the event was triggered.
|
||||
* @param {Event} event The change event.
|
||||
* @return {boolean} Event handling status.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler.prototype.
|
||||
revisedFileChange = function(revisedFileElement, event) {
|
||||
|
||||
// Enable/disable the genre field when a revision is selected
|
||||
if (!this.$revisedFileSelector_.val()) {
|
||||
this.$genreSelector.removeAttr('disabled');
|
||||
} else {
|
||||
this.$genreSelector.val(this.fileGenres_[this.$revisedFileSelector_.val()]);
|
||||
this.$genreSelector.attr('disabled', 'disabled');
|
||||
}
|
||||
|
||||
this.setUploaderVisibility_();
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the "change" event of the genre selector, if it exists.
|
||||
* @param {HTMLElement} genreElement The original context in
|
||||
* which the event was triggered.
|
||||
* @param {Event} event The change event.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler.prototype.
|
||||
genreChange = function(genreElement, event) {
|
||||
|
||||
this.setUploaderVisibility_();
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
/**
|
||||
* Attach the uploader handler.
|
||||
* @private
|
||||
* @param {jQueryObject} $uploader The wrapped HTML uploader element.
|
||||
* @param {Object} options Uploader options.
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler.prototype.
|
||||
attachUploader_ = function($uploader, options) {
|
||||
|
||||
// Attach the uploader handler to the uploader div.
|
||||
$uploader.pkpHandler('$.pkp.controllers.UploaderHandler', options);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adjust the display of the plupload component depending on required
|
||||
* settings
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler.prototype.
|
||||
setUploaderVisibility_ = function() {
|
||||
|
||||
if ((this.hasGenreSelector_ && this.$genreSelector.val()) ||
|
||||
this.$revisedFileSelector_.val()) {
|
||||
this.showUploader_();
|
||||
} else if (!this.hasGenreSelector_ && !this.hasFileSelector_) {
|
||||
this.showUploader_();
|
||||
} else {
|
||||
this.hideUploader_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hide the plupload component
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler.prototype.
|
||||
hideUploader_ = function() {
|
||||
this.$uploader_.addClass('pkp_screen_reader');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show the the plupload component
|
||||
* @private
|
||||
*/
|
||||
$.pkp.controllers.wizard.fileUpload.form.FileUploadFormHandler.prototype.
|
||||
showUploader_ = function() {
|
||||
this.$uploader_.removeClass('pkp_screen_reader');
|
||||
// Reset the button position
|
||||
$.pkp.classes.Handler.getHandler(this.$uploader_)
|
||||
.pluploader.refresh();
|
||||
};
|
||||
|
||||
|
||||
}(jQuery));
|
||||
Reference in New Issue
Block a user