1/** 2 * @defgroup js_controllers_form 3 */ 4/** 5 * @file js/controllers/form/FormHandler.js 6 * 7 * Copyright (c) 2013-2019 Simon Fraser University 8 * Copyright (c) 2000-2019 John Willinsky 9 * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING. 10 * 11 * @class FormHandler 12 * @ingroup js_controllers_form 13 * 14 * @brief Abstract form handler. 15 */ 16(function($) { 17 18 19 /** 20 * @constructor 21 * 22 * @extends $.pkp.classes.Handler 23 * 24 * @param {jQueryObject} $form the wrapped HTML form element. 25 * @param {Object} options options to configure the form handler. 26 */ 27 $.pkp.controllers.form.FormHandler = function($form, options) { 28 this.parent($form, options); 29 30 // Check whether we really got a form. 31 if (!$form.is('form')) { 32 throw new Error(['A form handler controller can only be bound', 33 ' to an HTML form element!'].join('')); 34 } 35 36 // Transform all form buttons with jQueryUI. 37 if (options.transformButtons !== false) { 38 $('.button', $form).button(); 39 } 40 41 // Activate and configure the validation plug-in. 42 if (options.submitHandler) { 43 this.callerSubmitHandler_ = options.submitHandler; 44 } 45 46 // Set the redirect-to URL for the cancel button (if there is one). 47 if (options.cancelRedirectUrl) { 48 this.cancelRedirectUrl_ = options.cancelRedirectUrl; 49 } 50 51 // specific forms may override the form's default behavior 52 // to warn about unsaved changes. 53 if (typeof options.trackFormChanges !== 'undefined') { 54 this.trackFormChanges_ = options.trackFormChanges; 55 } 56 57 // disable submission controls on certain forms. 58 if (options.disableControlsOnSubmit) { 59 this.disableControlsOnSubmit = options.disableControlsOnSubmit; 60 } 61 62 if (options.enableDisablePairs) { 63 this.enableDisablePairs_ = options.enableDisablePairs; 64 this.setupEnableDisablePairs(); 65 } 66 67 var validator = $form.validate({ 68 onfocusout: false, 69 errorClass: 'error', 70 highlight: function(element, errorClass) { 71 $(element).parent().parent().addClass(errorClass); 72 }, 73 unhighlight: function(element, errorClass) { 74 $(element).parent().parent().removeClass(errorClass); 75 }, 76 submitHandler: this.callbackWrapper(this.submitHandler_), 77 showErrors: this.callbackWrapper(this.showErrors) 78 }); 79 80 // Activate the cancel button (if present). 81 $('#cancelFormButton', $form).click(this.callbackWrapper(this.cancelForm)); 82 83 // Activate the reset button (if present). 84 $('#resetFormButton', $form).click(this.callbackWrapper(this.resetForm)); 85 $form.find('.showMore, .showLess').bind('click', this.switchViz); 86 87 88 // Initial form validation. 89 if (validator.checkForm()) { 90 this.trigger('formValid'); 91 } else { 92 this.trigger('formInvalid'); 93 } 94 95 this.initializeTinyMCE_(); 96 97 // bind a handler to make sure tinyMCE fields are populated. 98 $('#submitFormButton', $form).click(this.callbackWrapper( 99 this.pushTinyMCEChanges_)); 100 101 // bind a handler to handle change events on input fields. 102 $(':input', $form).change(this.callbackWrapper(this.formChange)); 103 }; 104 $.pkp.classes.Helper.inherits( 105 $.pkp.controllers.form.FormHandler, 106 $.pkp.classes.Handler); 107 108 109 // 110 // Private properties 111 // 112 /** 113 * If provided, the caller's submit handler, which will be 114 * triggered to save the form. 115 * @private 116 * @type {Function} 117 */ 118 $.pkp.controllers.form.FormHandler.prototype.callerSubmitHandler_ = null; 119 120 121 /** 122 * If provided, the URL to redirect to when the cancel button is clicked 123 * @private 124 * @type {String} 125 */ 126 $.pkp.controllers.form.FormHandler.prototype.cancelRedirectUrl_ = null; 127 128 129 /** 130 * By default, all FormHandler instances and subclasses track changes to 131 * form data. 132 * @private 133 * @type {boolean} 134 */ 135 $.pkp.controllers.form.FormHandler.prototype.trackFormChanges_ = true; 136 137 138 /** 139 * Only submit a track event for this form once. 140 * @type {boolean} 141 */ 142 $.pkp.controllers.form.FormHandler.prototype.formChangesTracked = false; 143 144 145 /** 146 * If true, the FormHandler will disable the submit button if the form 147 * successfully validates and is submitted. 148 * @protected 149 * @type {boolean} 150 */ 151 $.pkp.controllers.form.FormHandler.prototype.disableControlsOnSubmit = false;
152 153 154 /** 155 * An object containing items that should enable or disable each other. 156 * @private 157 * @type {Object} 158 */ 159 $.pkp.controllers.form.FormHandler.prototype.enableDisablePairs_ = null; 160 161 162 // 163 // Public methods 164 // 165 /** 166 * Internal callback called whenever the validator has to show form errors. 167 * 168 * @param {Object} validator The validator plug-in. 169 * @param {Object} errorMap An associative list that attributes 170 * element names to error messages. 171 * @param {Array} errorList An array with objects that contains 172 * error messages and the corresponding HTMLElements. 173 */ 174 /*jslint unparam: true*/ 175 $.pkp.controllers.form.FormHandler.prototype.showErrors = 176 function(validator, errorMap, errorList) { 177 178 // ensure that rich content elements have their 179 // values stored before validation. 180 if (typeof tinyMCE !== 'undefined') { 181 tinyMCE.triggerSave(); 182 } 183 184 // Show errors generated by the form change. 185 validator.defaultShowErrors(); 186 187 // Emit validation events. 188 if (validator.checkForm()) { 189 // Trigger a "form valid" event. 190 this.trigger('formValid'); 191 } else { 192 // Trigger a "form invalid" event. 193 this.trigger('formInvalid'); 194 this.enableFormControls(); 195 } 196 }; 197 /*jslint unparam: false*/ 198 199 200 /** 201 * Internal callback called when a form element changes. 202 * 203 * @param {HTMLElement} formElement The form element that generated the event. 204 * @param {Event} event The formChange event. 205 */ 206 /*jslint unparam: true*/ 207 $.pkp.controllers.form.FormHandler.prototype.formChange = 208 function(formElement, event) { 209 210 if (this.trackFormChanges_ && !this.formChangesTracked) { 211 this.trigger('formChanged'); 212 this.formChangesTracked = true; 213 } 214 }; 215 /*jslint unparam: false*/ 216 217 218 // 219 // Protected methods 220 // 221 /** 222 * Protected method to disable a form's submit control if it is 223 * desired. 224 * 225 * @return {boolean} true. 226 * @protected 227 */ 228 $.pkp.controllers.form.FormHandler.prototype.disableFormControls = 229 function() { 230 231 // We have made it to submission, disable the form control if 232 // necessary, submit the form. 233 if (this.disableControlsOnSubmit) { 234 this.getHtmlElement().find(':submit').attr('disabled', 'disabled'). 235 addClass('ui-state-disabled'); 236 } 237 return true; 238 }; 239 240 241 /** 242 * Protected method to reenable a form's submit control if it is 243 * desired. 244 * 245 * @return {boolean} true. 246 * @protected 247 */ 248 $.pkp.controllers.form.FormHandler.prototype.enableFormControls = 249 function() { 250 251 this.getHtmlElement().find(':submit').removeAttr('disabled'). 252 removeClass('ui-state-disabled'); 253 return true; 254 }; 255 256 257 /** 258 * Internal callback called to cancel the form. 259 * 260 * @param {HTMLElement} cancelButton The cancel button. 261 * @param {Event} event The event that triggered the 262 * cancel button. 263 * @return {boolean} false. 264 */ 265 /*jslint unparam: true*/ 266 $.pkp.controllers.form.FormHandler.prototype.cancelForm = 267 function(cancelButton, event) { 268 269 // Trigger the "form canceled" event and unregister the form. 270 this.formChangesTracked = false; 271 this.trigger('unregisterChangedForm'); 272 this.trigger('formCanceled'); 273 return false; 274 }; 275 /*jslint unparam: false*/ 276 277 278 /** 279 * Internal callback called to reset the form. 280 * 281 * @param {HTMLElement} resetButton The reset button. 282 * @param {Event} event The event that triggered the 283 * reset button. 284 * @return {boolean} false. 285 */ 286 /*jslint unparam: true*/ 287 $.pkp.controllers.form.FormHandler.prototype.resetForm = 288 function(resetButton, event) { 289 290 //unregister the form. 291 this.formChangesTracked = false; 292 this.trigger('unregisterChangedForm'); 293 294 var $form = this.getHtmlElement(); 295 $form.each(function() { 296 this.reset(); 297 }); 298 299 return false; 300 }; 301 /*jslint unparam: false*/ 302 303 304 /** 305 * Internal callback called to submit the form 306 * without further validation. 307 * 308 * @param {Object} validator The validator plug-in. 309 */ 310 $.pkp.controllers.form.FormHandler.prototype.submitFormWithoutValidation = 311 function(validator) { 312 313 // NB: When setting a submitHandler in jQuery's validator 314 // plugin then the submit event will always be canceled and our 315 // return value will be ignored (see the handle() method in the
316 // validator plugin). The only way around this seems to be unsetting 317 // the submit handler before calling the submit method again. 318 validator.settings.submitHandler = null; 319 this.disableFormControls(); 320 this.getHtmlElement().submit(); 321 this.formChangesTracked = false; 322 }; 323 324 325 // 326 // Private Methods 327 // 328 /** 329 * Initialize TinyMCE instances. 330 * 331 * There are instances where TinyMCE is not initialized with the call to 332 * init(). These occur when content is loaded after the fact (via AJAX). 333 * 334 * In these cases, search for richContent fields and initialize them. 335 * 336 * @private 337 */ 338 $.pkp.controllers.form.FormHandler.prototype.initializeTinyMCE_ = 339 function() { 340 341 if (typeof tinyMCE !== 'undefined') { 342 var $element, elementId; 343 $element = this.getHtmlElement(); 344 elementId = $element.attr('id'); 345 setTimeout(function() { 346 // re-select the original element, to prevent closure memory leaks 347 // in (older?) versions of IE. 348 $('#' + elementId).find('.richContent').each(function() { 349 tinyMCE.execCommand('mceAddControl', false, 350 $(this).attr('id').toString()); 351 }); 352 }, 500); 353 } 354 }; 355 356 357 /** 358 * Internal callback called after form validation to handle form 359 * submission. 360 * 361 * NB: Returning from this method without explicitly submitting 362 * the form will cancel form submission. 363 * 364 * @private 365 * 366 * @param {Object} validator The validator plug-in. 367 * @param {HTMLElement} formElement The wrapped HTML form. 368 */ 369 $.pkp.controllers.form.FormHandler.prototype.submitHandler_ = 370 function(validator, formElement) { 371 372 // Notify any nested formWidgets of the submit action. 373 var formSubmitEvent = new $.Event('formSubmitRequested'); 374 $(formElement).find('.formWidget').trigger(formSubmitEvent); 375 376 // If the default behavior was prevented for any reason, stop. 377 if (formSubmitEvent.isDefaultPrevented()) { 378 return; 379 } 380 381 $(formElement).find('.pkp_helpers_progressIndicator').show(); 382 383 this.trigger('unregisterChangedForm'); 384 385 if (this.callerSubmitHandler_ !== null) { 386 this.formChangesTracked = false; 387 // A form submission handler (e.g. Ajax) was provided. Use it. 388 this.callbackWrapper(this.callerSubmitHandler_). 389 call(validator, formElement); 390 } else { 391 // No form submission handler was provided. Use the usual method. 392 this.submitFormWithoutValidation(validator); 393 } 394 }; 395 396 397 /** 398 * Internal callback called to push TinyMCE changes back to fields 399 * so they can be validated. 400 * 401 * @return {boolean} true. 402 * @private 403 */ 404 $.pkp.controllers.form.FormHandler.prototype.pushTinyMCEChanges_ = 405 function() { 406 // ensure that rich content elements have their 407 // values stored before validation. 408 if (typeof tinyMCE !== 'undefined') { 409 tinyMCE.triggerSave(); 410 } 411 return true; 412 }; 413 414 415 /** 416 * Configures the enable/disable pair bindings between a checkbox 417 * and some other form element. 418 * 419 * @return {boolean} true. 420 */ 421 $.pkp.controllers.form.FormHandler.prototype.setupEnableDisablePairs = 422 function() { 423 var formElement, key; 424 425 formElement = this.getHtmlElement(); 426 for (key in this.enableDisablePairs_) { 427 $(formElement).find("[id^='" + key + "']").bind( 428 'click', this.callbackWrapper(this.toggleDependentElement_)); 429 } 430 return true; 431 }; 432 433 434 /** 435 * Enables or disables the item which depends on the state of source of the 436 * Event. 437 * @param {HTMLElement} sourceElement The element which generated the event. 438 * @return {boolean} true. 439 * @private 440 */ 441 $.pkp.controllers.form.FormHandler.prototype.toggleDependentElement_ = 442 function(sourceElement) { 443 var formElement, elementId, targetElement; 444 445 formElement = this.getHtmlElement(); 446 elementId = $(sourceElement).attr('id'); 447 targetElement = $(formElement).find( 448 "[id^='" + this.enableDisablePairs_[elementId] + "']"); 449 450 if ($(sourceElement).is(':checked')) { 451 $(targetElement).attr('disabled', ''); 452 } else { 453 $(targetElement).attr('disabled', 'disabled'); 454 } 455 456 return true; 457 }; 458/** @param {jQuery} $ jQuery closure. */ 459}(jQuery));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.