first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
@@ -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));
+640
View File
@@ -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));