/** * @author Rafael Ostertag <rafael.ostertag@math.uzh.ch> */ /* global $ */ /* global EventEmitter */ /* @depend QfqEvents.js */ /* @depend ElementUpdate.js */ /* @depend Dirty.js */ /** * Qfq Namespace * * @namespace QfqNS */ var QfqNS = QfqNS || {}; (function (n) { 'use strict'; // TODO: This object is getting its own gravitational field. Start refactoring. /** * Represents a QFQ Form. * * QfqForm will autonomously fire an lock `extend` request when the lock expired, but the last change `t_c` has * been made during the lock period `t_l`. I.e. let `t_{current}` be the current time, an `extend` request is made * when * * t_c + t_l > t_{current} * * holds. * * @param formId {string} value of the form's id attribute * @param submitTo {string} url where data will be submitted to * @param deleteUrl {string} url to call upon record deletion * @param dataRefreshUrl {string} url where to fetch new element values from * @param fileUploadTo {string} url used for file uploads * @param fileDeleteUrl {string} url used to delete files * @param dirtyUrl {string} url used to query * @constructor * * @name QfqNS.QfqForm */ n.QfqForm = function (formId, submitTo, deleteUrl, dataRefreshUrl, fileUploadTo, fileDeleteUrl, dirtyUrl) { this.formId = formId; this.submitTo = submitTo; this.deleteUrl = deleteUrl; this.dataRefreshUrl = dataRefreshUrl; this.fileUploadTo = fileUploadTo; this.fileDeleteUrl = fileDeleteUrl; this.dirtyUrl = dirtyUrl; this.dirtyFired = false; this.lockAcquired = false; this.formImmutableDueToConcurrentAccess = false; this.lockRenewalPhase = false; this.goToAfterSave = false; this.skipRequiredCheck = false; this.activateFirstRequiredTab = true; this.additionalQueryParameters = { 'recordHashMd5': this.getRecordHashMd5() }; if (!!$('#' + QfqNS.escapeJqueryIdSelector(this.formId)).data('enable-save-button')) { this.form = new n.Form(this.formId, false); this.getSaveButton().removeClass("disabled").removeAttr("disabled"); } else { this.getSaveButton().addClass("disabled").attr("disabled", "disabled"); this.form = new n.Form(this.formId, false); } if ($('#' + QfqNS.escapeJqueryIdSelector(this.formId)).data('required-off-but-mark')) { this.skipRequiredCheck = true; } else { this.skipRequiredCheck = false; } if (typeof $('#' + QfqNS.escapeJqueryIdSelector(this.formId)).data('activate-first-required-tab') !== 'undefined') { this.activateFirstRequiredTab = $('#' + QfqNS.escapeJqueryIdSelector(this.formId)).data('activate-first-required-tab'); } this.infoLockedButton = this.infoLockedButton.bind(this); // This is required when displaying validation messages, in order to activate the tab, which has validation // issues this.bsTabs = null; this.lastButtonPress = null; this.eventEmitter = new EventEmitter(); this.dirty = new n.Dirty(this.dirtyUrl); this.dirty.on(n.Dirty.EVENTS.SUCCESS, this.dirtyNotifySuccess.bind(this)); this.dirty.on(n.Dirty.EVENTS.DENIED, this.dirtyNotifyDenied.bind(this)); this.dirty.on(n.Dirty.EVENTS.FAILED, this.dirtyNotifyFailed.bind(this)); this.dirty.on(n.Dirty.EVENTS.SUCCESS_TIMEOUT, this.dirtyTimeout.bind(this)); this.dirty.on(n.Dirty.EVENTS.RENEWAL_DENIED, this.dirtyRenewalDenied.bind(this)); this.dirty.on(n.Dirty.EVENTS.RENEWAL_SUCCESS, this.dirtyRenewalSuccess.bind(this)); this.form.on('form.changed', this.changeHandler.bind(this)); this.form.on('form.reset', this.resetHandler.bind(this)); this.form.on('form.submit.successful', this.submitSuccessDispatcher.bind(this)); this.form.on('form.submit.failed', function (obj) { n.Helper.showAjaxError(null, obj.textStatus, obj.errorThrown); }); this.getSaveButton().click(this.handleSaveClick.bind(this)); this.getCloseButton().click(this.handleCloseClick.bind(this)); this.getNewButton().click(this.handleNewClick.bind(this)); this.getDeleteButton().click(this.handleDeleteClick.bind(this)); var that = this; $('.external-save').click(function(e) { var uri = $(this).data('target'); that.callSave(uri); }); this.setupFormUpdateHandler(); if (!!$('#' + QfqNS.escapeJqueryIdSelector(this.formId)).data('disable-return-key-submit')) { // Nothing to do } else { this.setupEnterKeyHandler(); } this.fileUploader = new n.FileUpload('#' + this.formId, this.fileUploadTo); this.startUploadHandler = this.startUploadHandler.bind(this); this.fileUploader.on('fileupload.started', this.startUploadHandler); this.fileUploader.on('fileupload.upload.successful', that.fileUploadSuccessHandler); this.fileUploader.on('fileupload.upload.failed', function (obj) { n.Helper.showAjaxError(null, obj.textStatus, obj.errorThrown); }); this.endUploadHandler = this.endUploadHandler.bind(this); this.fileUploader.on('fileupload.ended', this.endUploadHandler); this.fileDeleter = new n.FileDelete("#" + this.formId, this.fileDeleteUrl); this.fileDeleter.on('filedelete.delete.successful', this.fileDeleteSuccessHandler.bind(this)); this.fileDeleter.on('filedelete.delete.failed', function (obj) { n.Helper.showAjaxError(null, obj.textStatus, obj.errorThrown); }); var configurationData = this.readFormConfigurationData(); this.applyFormConfiguration(configurationData); // Initialize jqxDateTimeInput elements. n.Helper.jqxDateTimeInput(); // Initialize jqxComboBox elements. n.Helper.jqxComboBox(); // Deprecated //n.Helper.jqxEditor(); n.Helper.tinyMce(); n.Helper.codemirror(); this.form.on('form.submit.before', n.Helper.tinyMce.prepareSave); this.form.on('form.validation.before', n.Helper.tinyMce.prepareSave); this.form.on('form.validation.failed', this.validationError); this.form.on('form.validation.success', this.validationSuccess); $(".radio-inline").append($("<span>", { class: "checkmark", aria: "hidden"})); $(".checkbox-inline").append($("<span>", { class: "checkmark", aria: "hidden"})); $(".radio").append($("<span>", { class: "checkmark", aria: "hidden"})); $(".checkbox").append($("<span>", { class: "checkmark", aria: "hidden"})); }; n.QfqForm.prototype.on = n.EventEmitter.onMixin; n.QfqForm.prototype.dirtyNotifySuccess = function (obj) { this.lockAcquired = true; // Intentionally empty. May be used later on. }; n.QfqForm.prototype.dirtyRenewalSuccess = function (obj) { this.lockAcquired = true; }; /** * @public */ n.QfqForm.prototype.releaseLock = function (async) { if (!this.lockAcquired) { n.Log.debug("releaseLock(): no lock acquired or already released."); return; } n.Log.debug("releaseLock(): releasing lock."); this.dirty.release(this.getSip(), this.getRecordHashMd5AsQueryParameter(), async); this.resetLockState(); }; n.QfqForm.prototype.resetLockState = function () { this.dirty.clearSuccessTimeoutTimerIfSet(); this.dirtyFired = false; this.formImmutableDueToConcurrentAccess = false; this.lockRenewalPhase = false; this.lockAcquired = false; }; n.QfqForm.prototype.dirtyRenewalDenied = function (obj) { var that = this; var messageButtons = [{ label: "Reload", eventName: 'reload' }]; if (obj.data.status == "conflict_allow_force") { messageButtons.push({ label: "Ignore", eventName: 'ignore' }); } var alert = new n.Alert( { type: "error", message: obj.data.message, modal: true, buttons: messageButtons } ); alert.on('alert.reload', function () { that.eventEmitter.emitEvent('qfqform.close-intentional', n.EventEmitter.makePayload(that, null)); window.location.reload(true); }); alert.on('alert.ignore', function () { console.log("Ignored Recordlock"); }); alert.show(); }; n.QfqForm.prototype.dirtyTimeout = function (obj) { this.dirtyFired = false; this.lockAcquired = false; this.lockRenewalPhase = true; // Figure out whether the user made changes in the lock timeout period if (this.form.formChangedTimestampInMilliseconds + this.dirty.lockTimeoutInMilliseconds > Date.now()) { // Renew without user intervention. this.fireDirtyRequestIfRequired(); // and bail out return; } var alert = new n.Alert( { message: "Exclusive access to document timed out.", type: "warning" } ); alert.show(); }; n.QfqForm.prototype.dirtyNotifyDenied = function (obj) { var messageType; var isModal = true; var messageButtons = [{ label: "Reload", eventName: 'reload' }]; var message; var that = this; switch (obj.data.status) { case "conflict": messageType = "error"; this.setButtonEnabled(this.getSaveButton(), false); this.getSaveButton().removeClass(this.getSaveButtonAttentionClass()); this.setButtonEnabled(this.getDeleteButton(), false); this.formImmutableDueToConcurrentAccess = true; this.lockAcquired = false; break; case "conflict_allow_force": messageType = "warning"; messageButtons.push({ label: "Ignore", eventName: 'ignore' }); break; case "error": messageType = "error"; this.setButtonEnabled(this.getSaveButton(), false); this.getSaveButton().removeClass(this.getSaveButtonAttentionClass()); this.setButtonEnabled(this.getDeleteButton(), false); // Do not make the form ask for saving changes. this.form.formChanged = false; this.formImmutableDueToConcurrentAccess = true; this.lockAcquired = false; break; default: n.Log.error('Invalid dirty status: \'' + obj.data.status + '\'. Assume messageType \'error\''); messageType = "error"; break; } message = new n.Alert({ message: obj.data.message, type: messageType, timeout: n.Alert.constants.NO_TIMEOUT, modal: isModal, buttons: messageButtons }); message.on('alert.reload', function () { that.eventEmitter.emitEvent('qfqform.close-intentional', n.EventEmitter.makePayload(that, null)); window.location.reload(true); }); message.show(); }; n.QfqForm.prototype.dirtyNotifyFailed = function () { this.dirtyFired = false; this.lockAcquired = false; }; n.QfqForm.prototype.validationError = function (info) { var $formControl = $(info.data.element); var $messageContainer = $formControl.siblings('.hidden.with-errors'); if ($messageContainer.length === 0) { if ($formControl.parent().hasClass('input-group')) { $messageContainer = $formControl.parent().siblings('.hidden.with-errors'); } } $messageContainer.data('qfq.hidden.message', true); $messageContainer.removeClass('hidden'); }; n.QfqForm.prototype.validationSuccess = function (info) { var $formControl = $(info.data.element); var $messageContainer = $formControl.siblings('.with-errors'); if ($messageContainer.length === 0) { if ($formControl.parent().hasClass('input-group')) { $messageContainer = $formControl.parent().siblings('.hidden.with-errors'); } } if ($messageContainer.data('qfq.hidden.message') === true) { $messageContainer.addClass('hidden'); } }; /** * @private */ n.QfqForm.prototype.setupEnterKeyHandler = function () { $("input").keyup(function (event) { if (this.formImmutableDueToConcurrentAccess) { return; } if (event.which === 13 && this.submitOnEnter()) { if (this.isFormChanged()) { this.lastButtonPress = "save&close"; n.Log.debug("save&close click"); this.submit(); } event.preventDefault(); } }.bind(this)); }; /** * * @private */ n.QfqForm.prototype.readFormConfigurationData = function () { var $configuredElements = $("#" + this.formId + " [data-hidden],#" + this.formId + " [data-disabled],#" + this.formId + " [data-required]"); var configurationArray = []; $configuredElements.each(function (index, element) { try { var $element = $(element); if (!element.hasAttribute("name")) { n.Log.warning("Element has configuration data, but no name. Skipping"); return; } var configuration = {}; configuration['form-element'] = $element.attr('name'); var hiddenVal = $element.data('hidden'); if (hiddenVal !== undefined) { configuration.hidden = n.Helper.stringToBool(hiddenVal); } var disabledVal = $element.data('disabled'); if (disabledVal !== undefined) { configuration.disabled = n.Helper.stringToBool(disabledVal); } var requiredVal = $element.data("required"); if (requiredVal !== undefined) { configuration.required = n.Helper.stringToBool(requiredVal); } configurationArray.push(configuration); } catch (e) { n.Log.error(e.message); } }); return configurationArray; }; /** * @public * @param bsTabs */ n.QfqForm.prototype.setBsTabs = function (bsTabs) { this.bsTabs = bsTabs; }; n.QfqForm.prototype._createError = function (message) { var messageButtons = [{ label: "Ok", eventName: 'close' }]; var alert = new n.Alert({ "message": message, "type": "error", modal: true, buttons: messageButtons}); alert.show(); }; /** * @private */ n.QfqForm.prototype.fileDeleteSuccessHandler = function (obj) { if (!obj.data.status) { throw Error("Response on file upload missing status"); } if (obj.data.status === "error") { this._createError(obj.data.message); return; } var $button = $(obj.target); $button.prop("disabled", true); var $buttonParent = $button.parent(); $buttonParent.addClass('hidden'); var $inputFile = $buttonParent.siblings('label'); $inputFile.children(':file').prop("disabled", false); $inputFile.removeClass('hidden'); $inputFile.children(':file').removeClass('hidden'); $inputFile.children(':file').val(""); if ($inputFile.children(':file').data('required') == 'required') { $inputFile.children(':file').prop("required", true); } this.form.markChanged(); }; /** * @private */ n.QfqForm.prototype.fileUploadSuccessHandler = function (obj) { if (!obj.data.status) { throw Error("Response on file upload missing status"); } if (obj.data.status === "error") { //this._createError(obj.data.message); var messageButtons = [{ label: "Ok", eventName: 'close' }]; var alert = new n.Alert({ "message": obj.data.message, "type": obj.data.status, modal: true, buttons: messageButtons}); alert.show(); return false; } var $fileInput = $(obj.target); $fileInput.prop("disabled", true); $fileInput.addClass("hidden"); $fileInput.parent().addClass("hidden"); var $deleteContainer = $fileInput.parent().siblings('div.uploaded-file'); var fileNamesString = obj.target.files[0].name; var $fileNameSpan = $deleteContainer.find("span.uploaded-file-name"); $fileNameSpan.empty().append(fileNamesString); var $deleteButton = $deleteContainer.find("button"); $deleteButton.prop("disabled", false); $deleteContainer.removeClass("hidden"); }; /** * * @param $button * @param enabled {boolean} * * @private */ n.QfqForm.prototype.setButtonEnabled = function ($button, enabled) { if (!$button) { n.Log.error("QfqForm#setButtonEnabled(): no button provided."); return; } if (!enabled) { $button.addClass("disabled"); $button.prop("disabled", true); } else { $button.removeClass("disabled"); $button.prop("disabled", false); } }; /* Dynamic Update Trigger */ n.QfqForm.prototype.setupFormUpdateHandler = function () { $('textarea[data-load],input[data-load],select[data-load]').on('change', this.formUpdateHandler.bind(this)); }; n.QfqForm.prototype.formUpdateHandler = function () { var that = this; if (this.formImmutableDueToConcurrentAccess) { return; } $.post(this.dataRefreshUrl, this.form.serialize(), "json") .fail(n.Helper.showAjaxError) .done(function (data) { this.handleFormUpdate(data); }.bind(that)); }; n.QfqForm.prototype.handleFormUpdate = function (data) { if (!data.status) { throw new Error("Expected 'status' attribute to be present."); } if (data.status === "error") { this._createError("Error while updating form:<br>" + (data.message ? data.message : "No reason given")); return; } if (data.status === "success") { if (!data['form-update']) { throw new Error("'form-update' attribute missing in form update data"); } this.applyFormConfiguration(data['form-update']); this.applyElementConfiguration(data['element-update']); return; } throw new Error("Unexpected status: '" + data.status + "'"); }; /** * @private */ n.QfqForm.prototype.destroyFormAndSetText = function (text) { this.form = null; $('#' + this.formId).replaceWith($("<p>").append(text)); this.eventEmitter.emitEvent('qfqform.destroyed', n.EventEmitter.makePayload(this, null)); }; /** * @private */ n.QfqForm.prototype.handleSaveClick = function () { this.lastButtonPress = "save"; n.Log.debug("save click"); this.getSaveButton().removeClass('btn-info'); this.getSaveButton().addClass('btn-warning active disabled'); if (!this.form.saveInProgress) { this.submit(); } }; n.QfqForm.prototype.callSave = function(uri) { if(this.isFormChanged()) { this.handleSaveClick(); this.goToAfterSave = uri; } else { window.location = uri; return; } }; /** * @private */ n.QfqForm.prototype.handleCloseClick = function () { this.lastButtonPress = "close"; if (this.form.getFormChanged()) { var alert = new n.Alert({ message: "You have unsaved changes. Do you want to save first?", type: "warning", modal: true, buttons: [ {label: "Yes", eventName: "yes"}, {label: "No", eventName: "no", focus: true}, {label: "Cancel", eventName: "cancel"} ] }); var that = this; alert.on('alert.yes', function () { that.submit(); }); alert.on('alert.no', function () { that.releaseLock(); that.eventEmitter.emitEvent('qfqform.close-intentional', n.EventEmitter.makePayload(that, null)); that.goBack(); }); alert.show(); } else { this.goBack(); } }; n.QfqForm.prototype.submit = function (queryParameters) { var submitQueryParameters; var alert; var submitReason; if (this.form.validate() !== true) { var element = this.form.getFirstNonValidElement(); if (element.hasAttribute('name') && this.bsTabs) { var tabId = this.bsTabs.getContainingTabIdForFormControl(element.getAttribute('name')); if (tabId && this.activateFirstRequiredTab) { this.bsTabs.activateTab(tabId); } var form = document.getElementById(this.form.formId); var inputs = form.elements; for (var i = 0; i < inputs.length; i++) { var e = inputs[i]; if(!e.willValidate) { continue; } if(!e.checkValidity()) { var updateTabId = this.bsTabs.getContainingTabIdForFormControl(e.getAttribute('name')); if(updateTabId != tabId) { this.bsTabs.addDot(updateTabId); } } } } // Since we might have switched the tab, re-validate to highlight errors this.form.$form.validator('update'); this.form.$form.validator('validate'); this.form.$form.each(function() { if (!$(this).validate) { } }); if (!this.skipRequiredCheck) { alert = new n.Alert("Form is incomplete.", "warning"); alert.timeout = 3000; alert.show(); return; } } // First, remove all validation states, in case a previous submit has set a validation state, thus we're not // stockpiling them. if (!this.skipRequiredCheck) { this.clearAllValidationStates(); } submitReason = { "submit_reason": this.lastButtonPress === "close" ? "save,close" : this.lastButtonPress }; submitQueryParameters = $.extend({}, queryParameters, submitReason); this.form.submitTo(this.submitTo, submitQueryParameters); this.form.saveInProgress = true; }; /** * @private */ n.QfqForm.prototype.handleNewClick = function (event) { event.preventDefault(); this.lastButtonPress = "new"; if (this.form.getFormChanged()) { var alert = new n.Alert({ message: "You have unsaved changes. Do you want to save first?", type: "warning", modal: true, buttons: [ {label: "Yes", eventName: "yes", focus: true}, {label: "No", eventName: "no"}, {label: "Cancel", eventName: "cancel"} ] }); var that = this; alert.on('alert.no', function () { that.eventEmitter.emitEvent('qfqform.close-intentional', n.EventEmitter.makePayload(that, null)); var anchorTarget = that.getNewButtonTarget(); window.location = anchorTarget; }); alert.on('alert.yes', function () { that.submit(); }); alert.show(); } else { var anchorTarget = this.getNewButtonTarget(); window.location = anchorTarget; } n.Log.debug("new click"); }; /** * @private */ n.QfqForm.prototype.handleDeleteClick = function () { this.lastButtonPress = "delete"; n.Log.debug("delete click"); var alert = new n.Alert({ message: "Do you really want to delete the record?", type: "warning", modal: true, buttons: [ {label: "Yes", eventName: "ok"}, {label: "No", eventName: "cancel", focus: true} ] }); var that = this; alert.on('alert.ok', function () { $.post(that.appendQueryParametersToUrl(that.deleteUrl, that.getRecordHashMd5AsQueryParameter())) .done(that.ajaxDeleteSuccessDispatcher.bind(that)) .fail(n.Helper.showAjaxError); }); alert.show(); }; n.QfqForm.prototype.getRecordHashMd5AsQueryParameter = function () { return { 'recordHashMd5': this.getRecordHashMd5(), 'tabUniqId': this.getTabUniqId() }; }; /** * * @param data * @param textStatus * @param jqXHR * * @private */ n.QfqForm.prototype.ajaxDeleteSuccessDispatcher = function (data, textStatus, jqXHR) { if (!data.status) { throw new Error("No 'status' property 'data'"); } switch (data.status) { case "error": this.handleLogicDeleteError(data); break; case "success": this.handleDeleteSuccess(data); break; default: throw new Error("Status '" + data.status + "' unknown."); } }; /** * * @param data * * @private */ n.QfqForm.prototype.handleDeleteSuccess = function (data) { this.setButtonEnabled(this.getCloseButton(), false); this.setButtonEnabled(this.getDeleteButton(), false); this.setButtonEnabled(this.getSaveButton(), false); this.setButtonEnabled(this.getNewButton(), false); this.destroyFormAndSetText("Record has been deleted!"); if (!data.redirect || data.redirect === "auto") { this.goBack(); return; } if (data.redirect === "no") { this._createError("redirect=='no' not allowed"); return; } if (data.redirect === "url" && data['redirect-url']) { window.location = data['redirect-url']; } if (data.redirect === "url-skip-history" && data['redirect-url']) { window.location.replace(data['redirect-url']); } }; /** * * @param data * * @private */ n.QfqForm.prototype.handleLogicDeleteError = function (data) { if (!data.message) { throw Error("Status is 'error' but required 'message' attribute is missing."); } this._createError(data.message); this.setButtonEnabled(this.getDeleteButton(), false); }; /** * Called when form is changed. * * @param obj {n.QfqForm} * * @private */ n.QfqForm.prototype.changeHandler = function (obj) { if (this.formImmutableDueToConcurrentAccess) { return; } this.getSaveButton().removeClass("disabled"); this.getSaveButton().addClass(this.getSaveButtonAttentionClass()); this.getSaveButton().removeAttr("disabled"); this.fireDirtyRequestIfRequired(); }; n.QfqForm.prototype.fireDirtyRequestIfRequired = function () { if (this.dirtyFired) { return; } if (this.lockRenewalPhase) { this.dirty.renew(this.getSip(), this.getRecordHashMd5AsQueryParameter()); } else { this.dirty.notify(this.getSip(), this.getRecordHashMd5AsQueryParameter()); } this.dirtyFired = true; }; /** * * @param obj {n.QfqForm} * * @private */ n.QfqForm.prototype.resetHandler = function (obj) { this.getSaveButton().removeClass(this.getSaveButtonAttentionClass()); this.getSaveButton().addClass("disabled"); this.getSaveButton().attr("disabled", "disabled"); this.resetLockState(); }; n.QfqForm.prototype.deactivateSaveButton = function () { this.getSaveButton().addClass("disabled"); //this.getSaveButton().attr("disabled", "disabled"); this.getSaveButton().off('click'); this.getSaveButton().on('click', this.infoLockedButton); this.getSaveButton().css("color", "#fff"); }; n.QfqForm.prototype.infoLockedButton = function(e) { var alert = new n.Alert({ message: "Please wait until the upload finishes to save this form", buttons: [{ label: "Ok", eventName: "ok"}], modal: true }); alert.show(); e.preventDefault(); return false; }; n.QfqForm.prototype.activateSaveButton = function () { this.getSaveButton().off('click'); this.getSaveButton().removeClass("disabled"); //this.getSaveButton().removeAttr("disabled"); this.getSaveButton().css("color", ""); this.getSaveButton().click(this.handleSaveClick.bind(this)); }; n.QfqForm.prototype.getSaveButtonAttentionClass = function () { var $saveButton = this.getSaveButton(); return $saveButton.data('class-on-change') || 'alert-warning'; }; /** * * @returns {jQuery|HTMLElement} * * @private */ n.QfqForm.prototype.getSaveButton = function () { return $("#save-button"); }; /** * * @returns {jQuery|HTMLElement} * * @private */ n.QfqForm.prototype.getCloseButton = function () { return $("#close-button"); }; /** * * @returns {jQuery|HTMLElement} * * @private */ n.QfqForm.prototype.getDeleteButton = function () { return $("#delete-button"); }; /** * * @returns {jQuery|HTMLElement} * * @private */ n.QfqForm.prototype.getNewButton = function () { return $("#form-new-button"); }; /** * @private */ n.QfqForm.prototype.submitSuccessDispatcher = function (obj) { if (!obj.data.status) { throw new Error("No 'status' property in 'data'"); } switch (obj.data.status) { case "error": this.handleLogicSubmitError(obj.target, obj.data); break; case "success": this.handleSubmitSuccess(obj.target, obj.data); break; case "conflict": this.handleConflict(obj.target, obj.data); break; case "conflict_allow_force": this.handleOverrideableConflict(obj.target, obj.data); break; default: throw new Error("Status '" + obj.data.status + "' unknown."); } }; /** * * @param form * @param data * * @private */ n.QfqForm.prototype.handleLogicSubmitError = function (form, data) { if (!data.message) { throw Error("Status is 'error' but required 'message' attribute is missing."); } this._createError(data.message); if (data["field-name"] && this.bsTabs) { var tabId = this.bsTabs.getContainingTabIdForFormControl(data["field-name"]); if (tabId) { this.bsTabs.activateTab(tabId); } this.setValidationState(data["field-name"], "error"); this.setHelpBlockValidationMessage(data["field-name"], data["field-message"]); } }; /** * */ n.QfqForm.prototype.handleConflict = function (form, data) { this.setButtonEnabled(this.getSaveButton(), false); this.getSaveButton().removeClass(this.getSaveButtonAttentionClass()); this.setButtonEnabled(this.getDeleteButton(), false); this.formImmutableDueToConcurrentAccess = true; this.lockAcquired = false; this._createError(data.message); }; n.QfqForm.prototype.handleOverrideableConflict = function (form, data) { var that = this; var alert = new n.Alert({ message: data.message + 'Save anyway?', type: "warning", modal: true, buttons: [ {label: "Yes", eventName: "yes"}, {label: "No", eventName: "no", focus: true} ] }); alert.on('alert.yes', function () { if (data.tokenForce) { that.submit({ tokenForce: data.tokenForce }); } else { that.submit(); } }); alert.show(); }; /** * * @param form * @param data * * @private */ n.QfqForm.prototype.handleSubmitSuccess = function (form, data) { n.Log.debug('Reset form state'); form.resetFormChanged(); this.resetLockState(); switch (this.lastButtonPress) { case 'save&close': this.goBack(); break; case 'save': if (data.message) { var alert = new n.Alert(data.message); alert.timeout = 3000; alert.show(); } // Skip other checks if external Save is called if (this.goToAfterSave) { console.log("Called goToAfterSave = " + this.goToAfterSave); window.location = this.goToAfterSave; return; } // do we have to update the HTML Form? if (data['form-update']) { this.applyFormConfiguration(data['form-update']); } if (data['element-update']) {this.applyElementConfiguration(data['element-update']); } if (data.redirect === "url" && data['redirect-url']) { window.location = data['redirect-url']; return; } if (data.redirect === "url-skip-history" && data['redirect-url']) { window.location.replace(data['redirect-url']); return; } if (data.redirect === "close") { this.goBack(); return; } break; case 'close': if (!data.redirect || data.redirect === "no") { return; } if (data.redirect === "auto" || data.redirect === "close") { this.goBack(); return; } if (data.redirect === "url" && data['redirect-url']) { window.location = data['redirect-url']; return; } if (data.redirect === "url-skip-history" && data['redirect-url']) { window.location.replace(data['redirect-url']); return; } break; case 'new': var target = this.getNewButtonTarget(); window.location.replace(target); return; default: if (data.redirect === "auto") { this.goBack(); return; } if (data.redirect === "url" && data['redirect-url']) { window.location = data['redirect-url']; return; } if (data.redirect === "url-skip-history" && data['redirect-url']) { window.location.replace(data['redirect-url']); return; } break; } if(this.skipRequiredCheck) { this.form.$form.validator('update'); this.form.$form.validator('validate'); } }; n.QfqForm.prototype.getNewButtonTarget = function () { return $('#form-new-button').attr('href'); }; n.QfqForm.prototype.getFormGroupByControlName = function (formControlName) { console.log("Form Control Name: " + formControlName); var $formControl = $("[name='" + formControlName + "']"); if ($formControl.length === 0) { n.Log.debug("QfqForm.setValidationState(): unable to find form control with name '" + formControlName + "'"); return null; } var iterator = $formControl[0]; while (iterator !== null) { var $iterator = $(iterator); if ($iterator.hasClass('form-group')) { return $iterator; } iterator = iterator.parentElement; } return null; }; n.QfqForm.prototype.setValidationState = function (formControlName, state) { var $formGroup = this.getFormGroupByControlName(formControlName); if ($formGroup) { $formGroup.addClass("has-" + state); $formGroup.addClass("testitest"); } }; n.QfqForm.prototype.resetValidationState = function (formControlName) { var $formGroup = this.getFormGroupByControlName(formControlName).find('input'); $formGroup.removeClass("has-danger"); $formGroup.removeClass("has-error"); $formGroup.removeClass("has-success"); $formGroup.removeClass("has-danger"); }; n.QfqForm.prototype.clearAllValidationStates = function () { // Reset any messages/states added by bootstrap-validator. this.form.$form.validator('reset'); // Reset any states added by a call to QfqForm#setValidationState() $('.has-warning,.has-error,.has-success,.has-danger').removeClass("has-warning has-error has-success" + " has-danger"); // Remove all messages received from server upon form submit. $('[data-qfq=validation-message]').remove(); }; /** * * @param formControlName * @param text */ n.QfqForm.prototype.setHelpBlockValidationMessage = function (formControlName, text) { /* * Why is this method here and not in FormGroup? Adding this particular method to FormGroup is easy, however * QfqForm.clearAllValidationStates() would not find its proper place in FormGroup, since FormGroup operates * on one element. We would end up having the responsibilities spread across several classes, which would be * confusing. */ var $formGroup = this.getFormGroupByControlName(formControlName); if (!$formGroup) { return; } var $helpBlockColumn; var $formGroupSubDivs = $formGroup.find("div"); if ($formGroupSubDivs.length < 3) { $helpBlockColumn = $("<div>").addClass("col-md-4"); $formGroup.append($helpBlockColumn); } else { $helpBlockColumn = $($formGroupSubDivs[2]); } $helpBlockColumn.append( $("<p>") .addClass("help-block") .attr("data-qfq", "validation-message") .append(text) .prepend($("<div>", { class: "arrow arrow-up"})) ); }; /** * * @param configuration {array} array of objects. */ n.QfqForm.prototype.applyFormConfiguration = function (configuration) { var arrayLength = configuration.length; for (var i = 0; i < arrayLength; i++) { var configurationItem = configuration[i]; var formElementName = configurationItem["form-element"]; if (formElementName === undefined) { n.Log.error("configuration lacks 'form-element' attribute. Skipping."); continue; } try { var element = n.Element.getElement(formElementName); // Cleaner way to set states for tinymce // This triggers the event on the unaccesable textarea // The tinymce registers a listener on the textarea // See helper/tinyMCE.js for details if(element.$element.hasClass('qfq-tinymce')) { element.$element.trigger("blur", [configurationItem]); } if (configurationItem.value !== undefined) { element.setValue(configurationItem.value); } if (configurationItem.readonly !== undefined) { // Readonly and disabled is the same in our domain element.setEnabled(!configurationItem.readonly); } if (configurationItem.disabled !== undefined) { // Readonly and disabled is the same in our domain element.setEnabled(!configurationItem.disabled); } if (configurationItem.hidden !== undefined) { element.setHidden(configurationItem.hidden); } if (configurationItem.required !== undefined) { element.setRequired(configuration.required); if(element.$element) { if(element.$element.is("select")) { element.$element.prop('required', configurationItem.required); element.$element.attr('data-required', 'yes'); } if(element.$element.is("input[type=hidden]")) { console.log("Update Hidden"); element.$element.prop("required", configurationItem.required); } } } } catch (e) { n.Log.error(e.message); } } }; n.QfqForm.prototype.applyElementConfiguration = function (configuration) { if (!configuration) { console.error("No configuration for Element Update found"); return; } n.ElementUpdate.updateAll(configuration); }; /** * @private * @param obj */ n.QfqForm.prototype.startUploadHandler = function (obj) { $(obj.target).after( $('<i>').addClass('spinner') ); this.deactivateSaveButton(); }; /** * @private * @param obj */ n.QfqForm.prototype.endUploadHandler = function (obj) { var $siblings = $(obj.target).siblings(); $siblings.filter("i").remove(); this.activateSaveButton(); }; /** * Retrieve SIP as stored in hidden input field. * * @returns {string} sip */ n.QfqForm.prototype.getSip = function () { return this.getValueOfHiddenInputField('s'); }; /** * Retrieve recordHashMd5 as stored in hidden input field. * * @returns {string} sip */ n.QfqForm.prototype.getRecordHashMd5 = function () { return this.getValueOfHiddenInputField('recordHashMd5'); }; /** * Misuse the window.name attribute to set/get a tab uniq identifier. * Use millisecond timestamp as identifier: hopefully there are never more than one tab opened per millisecond in a single browser session. * * @returns {string} tab identifier */ n.QfqForm.prototype.getTabUniqId = function () { if (!window.name.toString()) { // Misuse window.name as tab uniq identifier. Set window.name if it is empty. window.name = Date.now().toString(); } return window.name; }; n.QfqForm.prototype.getValueOfHiddenInputField = function (fieldName) { return $('#' + this.formId + ' input[name=' + fieldName + ']').val(); }; /** * @public */ n.QfqForm.prototype.isFormChanged = function () { return this.form.formChanged; }; /** * @private */ n.QfqForm.prototype.submitOnEnter = function () { return !(!!this.form.$form.data('disable-return-key-submit')); }; n.QfqForm.prototype.appendQueryParametersToUrl = function (url, queryParameterObject) { var queryParameterString = $.param(queryParameterObject); if (url.indexOf('?') !== -1) { return url + "&" + queryParameterString; } return url + "?" + queryParameterString; }; /** * @private * * Go back in the history, or pop an alert when no history. */ n.QfqForm.prototype.goBack = function () { var alert; if (window.history.length < 2) { alert = new n.Alert( { type: "info", message: "Please close the tab/window." } ); alert.show(); return; } window.history.back(); }; })(QfqNS);