Source: Form.js

/**
                * @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
                */

                /* global $ */
                /* global EventEmitter */
                /* @depend QfqEvents.js */

                /**
                * Qfq Namespace
                *
                * @namespace QfqNS
                */
                var QfqNS = QfqNS || {};

                (function (n) {
                'use strict';

                /**
                *
                * @param formId
                * @constructor
                * @name QfqNS.Form
                */
                n.Form = function (formId) {
                this.formId = formId;
                this.eventEmitter = new EventEmitter();

                if (!document.forms[this.formId]) {
                throw new Error("Form '" + formId + "' does not exist.");
                }

                this.formChanged = false;
                this.$form = $(document.forms[this.formId]);
                this.$form.on("change", this.changeHandler.bind(this));

                // On <input> elements, we specifically bind this events, in order to update the formChanged property
                // immediately, not only after loosing focus. Same goes for <textarea>
                this.$form.find("input, textarea").on("input paste", this.changeHandler.bind(this));

                this.$form.on('submit', function (event) {
                event.preventDefault();
                });
                };

                n.Form.prototype.on = n.EventEmitter.onMixin;

                /**
                *
                * @param event
                *
                * @private
                */
                n.Form.prototype.changeHandler = function (event) {
                this.formChanged = true;
                this.eventEmitter.emitEvent('form.changed', n.EventEmitter.makePayload(this, null));
                };

                n.Form.prototype.getFormChanged = function () {
                return this.formChanged;
                };

                n.Form.prototype.markChanged = function () {
                this.changeHandler(null);
                };

                n.Form.prototype.resetFormChanged = function () {
                this.formChanged = false;
                this.eventEmitter.emitEvent('form.reset', n.EventEmitter.makePayload(this, null));

                };

                n.Form.prototype.submitTo = function (to) {
                $.post(to, this.$form.serialize())
                .done(this.ajaxSuccessHandler.bind(this))
                .fail(this.submitFailureHandler.bind(this));
                };

                n.Form.prototype.serialize = function () {
                return this.$form.serialize();
                };

                /**
                *
                * @param data
                * @param textStatus
                * @param jqXHR
                *
                * @private
                */
                n.Form.prototype.ajaxSuccessHandler = function (data, textStatus, jqXHR) {
                this.eventEmitter.emitEvent('form.submit.successful',
                n.EventEmitter.makePayload(this, data, {
                textStatus: textStatus,
                jqXHR: jqXHR
                }));
                };

                /**
                *
                *
                * @private
                */
                n.Form.prototype.submitFailureHandler = function (jqXHR, textStatus, errorThrown) {
                this.eventEmitter.emitEvent('form.submit.failed', n.EventEmitter.makePayload(this, null, {
                textStatus: textStatus,
                errorThrown: errorThrown,
                jqXHR: jqXHR
                }));
                // REMOVE: this.userSubmitFailureHandlers.call(this, textStatus, jqXHR, errorThrown);
                };

                /**
                * @public
                * @returns {*}
                */
                n.Form.prototype.validate = function () {
                // uncommented because bootstrap-validator sets novalidate="true" on form.
                //if (this.$form.attr('novalidate')) {
                // return true;
                //}

                return document.forms[this.formId].checkValidity();
                };

                /**
                * @public
                */
                n.Form.prototype.getFirstNonValidElement = function () {
                var index;
                var elementNumber = document.forms[this.formId].length;

                for (index = 0; index < elementNumber; index++) {
                var element = document.forms[this.formId][index];
                if (!element.willValidate) {
                continue;
                }

                if (!element.checkValidity()) {
                return element;
                }
                }

                return null;
                };

                })(QfqNS);