/* global app, Lang */ 'use strict'; define('form', ['jquery'], function($) { /** * Class FormInput, represents any input in a form * * @class FormInput * @param {Object} field The input parameters * @param {Form} form The form the input is asssociated with **/ var FormInput = function(field, form) { this.form = form; for (var key in field) { if (field.hasOwnProperty(key)) { this[key] = field[key]; } } this.node = $('[id=\'' + this.id + '\']'); if (this.type === 'submit') { this.node.click(function() { // Ask for confirmation if (this.name === 'delete' && !confirm(Lang.get('form.confirm-delete'))) { // The user finally doesn't want to delete the record return false; } // The user confirmed this.form.setObjectAction(this.name); return true; }.bind(this)); } }; /** * Get or set the value of the field * * @memberOf FormInput * @param {string} value If this variable is set, it will be set to the input * @returns {string} The input value or the value that has been set */ FormInput.prototype.val = function(value) { if (value === undefined) { // Get the input value switch (this.type) { case 'checkbox' : return this.node.prop('checked'); case 'radio' : return this.node.find(':checked').val(); case 'html' : return this.node.html(); default : return this.node.val(); } } else { switch (this.type) { case 'checkbox' : this.node.prop('checked', value); break; case 'radio' : this.node.find('[value="' + value + '"]').prop('checked', true); break; case 'html' : this.node.html(value); break; default : this.node.val(value); break; } } return value; }; /** * Get a property data of the field * * @memberOf FormInput * @param {string} prop - the property to get the data value * @returns {styring} The value of the property */ FormInput.prototype.data = function(prop) { return this.node.data(prop); }; /** * Check the value of the field is valid * * @memberOf FormInput * @returns {bool} True if the field is valid, false else */ FormInput.prototype.isValid = function() { // If the field is required, the field can't be empty if (this.required) { var emptyValue = this.emptyValue || ''; if (this.val() === emptyValue) { this.addError(Lang.get('form.required-field')); return false; } } // If the field has a specific pattern, test the value with this pattern if (this.pattern) { var regex = new RegExp(this.pattern.substr(1, -1)); if (this.val() && !regex.test(this.val())) { this.addError(Lang.exists('form.' + this.type + '-format') ? Lang.get('form.' + this.type + '-format') : Lang.get('form.field-format') ); return false; } } if (this.minimum) { if (this.val() && this.val() < this.minimum) { this.addError(Lang.get('form.number-minimum', {value: this.minimum})); return false; } } if (this.maximum) { if (this.val() && this.val() > this.maximum) { this.addError(Lang.get('form.number-maximum', {value: this.maximum})); return false; } } // If the field has to be compared with another one, compare the two values if (this.compare) { if (this.val() !== this.form.inputs[this.compare].val()) { this.addError(Lang.get('form.' + this.type + '-comparison')); return false; } } return true; }; /** * Display an error on the input * * @memberOf FormInput * @param {string} text The error message to set to the input */ FormInput.prototype.addError = function(text) { if (this.errorAt) { this.form.inputs[this.errorAt].addError(text); } else { this.node.addClass('error').after('<span class="input-error-message">' + text + '</span>'); } }; /** * Remove the errors on the input * * @memberOf FormInput */ FormInput.prototype.removeError = function() { this.node.removeClass('error').next('.input-error-message').remove(); }; /** * This class is used to validate and submit forms client side. * forms are accessible to window by app.formrs[id] * * @class Form * @param {string} id - the id of the form * @param {Object} fields - The list of all fields in the form */ var Form = function(id, fields) { this.id = id; this.node = $('[id=\'' + this.id + '\']'); this.upload = this.node.hasClass('upload-form'); this.action = this.node.attr('action'); this.method = this.node.attr('method').toLowerCase(); this.inputs = {}; for (var name in fields) { if (fields.hasOwnProperty(name)) { this.inputs[name] = new FormInput(fields[name], this); } } // Listen for form submission this.node.submit(function() { this.submit(); return false; }.bind(this)); // Listen for form change this.onchange = null; this.node.change(function(event) { if (this.onchange) { this.onchange.call(this, event); } }.bind(this)); }; /** * Check the dat of the form * * @memberOf Form * @returns {bool} - true if the form data is correct, false else */ Form.prototype.isValid = function() { var valid = true; this.removeErrors(); for (var name in this.inputs) { if (!this.inputs[name].isValid()) { valid = false; } } return valid; }; /** * Remove all the form errors * * @memberOf Form */ Form.prototype.removeErrors = function() { this.node.find('.form-result-message').removeClass('alert alert-danger').text(''); for (var name in this.inputs) { if (this.inputs.hasOwnProperty(name)) { this.inputs[name].removeError(); } } }; /** * Display an error message to the form * * @memberOf Form * @param {string} text The message to display */ Form.prototype.displayErrorMessage = function(text) { this.node.find('.form-result-message') .addClass('alert alert-danger') .html('<i class=\'icon icon-exclamation-circle\'></i> ' + text); }; /** * Display the errors on the form inputs * * @memberOf Form * @param {Object} errors The errors to display, where keys are inputs names, and values the error messages */ Form.prototype.displayErrors = function(errors) { if (typeof errors === 'object' && !(errors instanceof Array)) { for (var id in errors) { if (errors.hasOwnProperty(id)) { this.inputs[id].addError(errors[id]); } } } }; /** * Set the object action of the form. The object action can be "register" or "delete", * and represents the action that will be performed server side * * @memberOf Form * @param {string} action - The action value to set */ Form.prototype.setObjectAction = function(action) { if (action.toLowerCase() === 'delete') { this.method = action; } }; /** * Submit the form * * @returns {boolean} False * @memberOf Form */ Form.prototype.submit = function() { // Remove all Errors on this form this.removeErrors(); if (this.objectAction === 'delete' || this.isValid()) { app.loading.start(); // Send an Ajax request to submit the form var data; if (this.method === 'get') { data = $(this.node).serlialize(); } else { data = new FormData(this.node.get(0)); } var options = { xhr : app.xhr, url : this.action, type : this.method, dataType : 'json', data : data, processData : false, contentType : false }; $.ajax(options) .done(function(results) { // treat the response if (results.message) { app.notify('success', results.message); } // Trigger a form_success event to the form if (this.onsuccess) { this.onsuccess(results.data); } }.bind(this)) .fail(function(xhr) { if (!xhr.responseJSON) { // The returned result is not a JSON this.displayErrorMessage(xhr.responseText); } else { var response = xhr.responseJSON; switch (xhr.status) { case 412 : // The form has not been checked correctly this.displayErrorMessage(response.message); this.displayErrors(response.errors); break; case 424 : // An error occured in the form treatment this.displayErrorMessage(response.message); break; default : this.displayErrorMessage(Lang.get('main.technical-error')); break; } if (this.onerror) { this.onerror(response.data); } } }.bind(this)) .always(function() { app.loading.stop(); }); } else { this.displayErrorMessage(Lang.get('form.error-fill')); } return false; }; /** * Reset the form values * * @memberOf Form */ Form.prototype.reset = function() { this.node.get(0).reset(); }; /** * Get the form data as Object * * @memberOf Form * @returns {Object} The object containing the form inputs data */ Form.prototype.valueOf = function() { var result = {}; for (var name in this.inputs) { if (this.inputs.hasOwnProperty(name)) { var item = this.inputs[name], matches = (/^(.+?)((?:\[(.*?)\])+)$/).exec(name); if (matches !== null) { var params = matches[2]; if (!result[matches[1]]) { result[matches[1]] = {}; } var tmp = result[matches[1]], m; do { m = (/^(\[(.*?)\])(\[(.*?)\])?/).exec(params); if (m !== null) { if (m[3]) { if (!tmp[m[2]]) { tmp[m[2]] = m[4] ? {} : []; } tmp = tmp[m[2]]; params = m[3]; } else if (tmp instanceof Array) { tmp.push(item.val()); } else { tmp[m[2]] = item.val(); } } } while (m && m[3]); } else { result[name] = item.val(); } } } return result; }; /** * Display the content of the form * * @memberOf Form * @returns {string} The JSON representing the form inputs data */ Form.prototype.toString = function() { return JSON.stringify(this.valueOf()); }; return Form; });