-
Rafael Ostertag authoredRafael Ostertag authored
QfqForm.js 18.44 KiB
/**
* @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
*/
/* global $ */
/* global EventEmitter */
/* @depend QfqEvents.js */
var QfqNS = QfqNS || {};
(function (n) {
'use strict';
// TODO: This object is getting too big. Start refactoring.
/**
* Represents a QFQ Form.
*
* @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
* @constructor
*/
n.QfqForm = function (formId, submitTo, deleteUrl, dataRefreshUrl, fileUploadTo) {
this.formId = formId;
this.submitTo = submitTo;
this.deleteUrl = deleteUrl;
this.dataRefreshUrl = dataRefreshUrl;
this.fileUploadTo = fileUploadTo;
this.form = new n.Form(this.formId);
// This is required when displaying validation messages, in to activate the tab, which has validation issues
this.bsTabs = null;
this.lastButtonPress = null;
this.eventEmitter = new EventEmitter();
this.getSaveButton().addClass("disabled").attr("disabled", "disabled");
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));
this.setupFormUpdateHandler();
this.fileUploader = new n.FileUpload('#' + this.formId, this.fileUploadTo, this.getSip());
this.fileUploader.on('fileupload.started', this.startUploadHandler);
this.fileUploader.on('fileupload.upload.success', this.fileUploadSuccessHandler);
this.fileUploader.on('fileupload.upload.failed',
function (obj) {
n.Helper.showAjaxError(null, obj.textStatus, obj.errorThrown);
});
this.fileUploader.on('fileupload.ended', this.endUploadHandler);
};
n.QfqForm.prototype.on = n.EventEmitter.onMixin;
/**
* @public
* @param bsTabs
*/
n.QfqForm.prototype.setBsTabs = function (bsTabs) {
this.bsTabs = bsTabs;
};
/**
* @private
*/
n.QfqForm.prototype.fileUploadSuccessHandler = function (obj) {
if (!obj.data.status) {
throw Error("Response on file upload missing status");
}
if (obj.data.status === "error") {
var alert = new n.Alert(obj.data.message, "error");
alert.show();
}
};
/**
*
* @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.attr("disabled", "disabled");
} else {
$button.removeClass("disabled");
$button.removeAttr("disabled");
}
};
n.QfqForm.prototype.setupFormUpdateHandler = function () {
$('input[data-load],select[data-load]').on('change', this.formUpdateHandler.bind(this));
};
n.QfqForm.prototype.formUpdateHandler = function () {
var that = this;
$.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") {
var alert = new n.Alert("Error while updating form:<br>" + (data.message ? data.message : "No reason" +
" given"), "error");
alert.show();
return;
}
if (data.status === "success") {
if (!data['form-update']) {
throw new Error("'form-update' attribute missing in form update data");
}
this.applyElementConfiguration(data['form-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");
// First, remove all validation states, in case a previous submit has set a validation state, thus we're not
// stockpiling them.
this.clearAllValidationStates();
this.submit();
};
/**
* @private
*/
n.QfqForm.prototype.handleCloseClick = function () {
this.lastButtonPress = "close";
if (this.form.getFormChanged()) {
var alert = new n.Alert("You have unsaved changes. Do you want to close?", "warning", "yesnosave");
var that = this;
alert.on('alert.save', function () {
that.submit();
});
alert.on('alert.ok', function () {
window.history.back();
});
alert.show();
} else {
window.history.back();
}
};
n.QfqForm.prototype.submit = function () {
if (this.form.validate() !== true) {
this.form.$form.validator('validate');
var alert = new n.Alert("Form is incomplete.", "warning");
alert.show();
return;
}
this.form.submitTo(this.submitTo);
};
/**
* @private
*/
n.QfqForm.prototype.handleNewClick = function (event) {
event.preventDefault();
this.lastButtonPress = "new";
if (this.form.getFormChanged()) {
var alert = new n.Alert("You have unsaved changes. Do you want to close?", "warning", "yesnosave");
var that = this;
alert.on('alert.save', function () {
that.submit();
});
alert.on('alert.ok', function () {
var anchorTarget = that.getNewButtonTarget();
window.location = anchorTarget;
});
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("Do you really want to delete the record?", "warning", "yesno");
var that = this;
alert.on('alert.ok', function () {
$.post(that.deleteUrl)
.done(that.ajaxDeleteSuccessDispatcher.bind(that))
.fail(n.Helper.showAjaxError);
});
alert.show();
};
/**
*
* @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 === "client") {
window.history.back();
return;
}
if (data.redirect === "no") {
var alert = new n.Alert("redirect=='no' not allowed", "error");
alert.show();
return;
}
if (data.redirect === "url" || data['redirect-url']) {
window.location = data['redirect-url'];
return;
}
};
/**
*
* @param data
*
* @private
*/
n.QfqForm.prototype.handleLogicDeleteError = function (data) {
if (!data.message) {
throw Error("Status is 'error' but required 'message' attribute is missing.");
}
var alert = new n.Alert(data.message, "error");
alert.show();
};
/**
*
* @param form {n.QfqForm}
*
* @private
*/
n.QfqForm.prototype.changeHandler = function (obj) {
this.getSaveButton().removeClass("disabled");
this.getSaveButton().addClass("alert-warning");
this.getSaveButton().removeAttr("disabled");
};
/**
*
* @param form {n.QfqForm}
*
* @private
*/
n.QfqForm.prototype.resetHandler = function (obj) {
this.getSaveButton().removeClass("alert-warning");
this.getSaveButton().addClass("disabled");
this.getSaveButton().attr("disabled", "disabled");
};
/**
*
* @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;
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.");
}
var alert = new n.Alert(data.message, "error");
alert.show();
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"]);
}
};
/**
*
* @param form
* @param data
*
* @private
*/
n.QfqForm.prototype.handleSubmitSuccess = function (form, data) {
n.Log.debug('Reset form state');
form.resetFormChanged();
switch (this.lastButtonPress) {
case 'save':
if (data.message) {
var alert = new n.Alert(data.message);
alert.timeout = 1500;
alert.show();
}
// do we have to update the HTML Form?
if (data['form-update']) {
this.applyElementConfiguration(data['form-update']);
}
if (data.redirect === "url" || data['redirect-url']) {
window.location = data['redirect-url'];
return;
}
break;
case 'close':
if (!data.redirect || data.redirect === "no") {
return;
}
if (data.redirect === "client") {
window.history.back();
return;
}
if (data.redirect === "url" || data['redirect-url']) {
window.location = data['redirect-url'];
return;
}
break;
case 'new':
var target = this.getNewButtonTarget();
window.location = target;
return;
default:
if (data.redirect === "client") {
window.history.back();
return;
}
if (data.redirect === "url" || data['redirect-url']) {
window.location = data['redirect-url'];
return;
}
break;
}
};
n.QfqForm.prototype.getNewButtonTarget = function () {
return $('#form-new-button').attr('href');
};
n.QfqForm.prototype.getFormGroupByControlName = function (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);
}
};
n.QfqForm.prototype.resetValidationState = function (formControlName) {
var $formGroup = this.getFormGroupByControlName(formControlName);
$formGroup.removeClass("has-warning");
$formGroup.removeClass("has-error");
$formGroup.removeClass("has-success");
};
n.QfqForm.prototype.clearAllValidationStates = function () {
$('.has-warning,.has-error,.has-success').removeClass("has-warning has-error has-success");
$('[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)
);
};
/**
*
* @param configuration {array} array of objects.
*/
n.QfqForm.prototype.applyElementConfiguration = 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);
if (configurationItem.value !== undefined) {
element.setValue(configurationItem.value);
}
if (configurationItem.readonly !== undefined) {
element.setReadOnly(configurationItem.readonly);
}
if (configurationItem.disabled !== undefined) {
element.setEnabled(!configurationItem.disabled);
}
} catch (e) {
n.Log.error(e.message);
}
}
};
/**
* @private
* @param triggeredBy
*/
n.QfqForm.prototype.startUploadHandler = function (obj) {
$(obj.target).after(
$('<i>').addClass('spinner')
);
};
/**
* @private
* @param triggeredBy
*/
n.QfqForm.prototype.endUploadHandler = function (obj) {
var $siblings = $(obj.target).siblings();
$siblings.filter("i").remove();
};
/**
* Retrieve SIP as stored in hidden input field.
*
* @returns {string} sip
*/
n.QfqForm.prototype.getSip = function () {
return $('#' + this.formId + ' input[name=s]').val();
};
})(QfqNS);