first commit
This commit is contained in:
@@ -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));
|
||||
Reference in New Issue
Block a user