UNPKG

alpaca

Version:

Alpaca provides the easiest and fastest way to generate interactive forms for the web and mobile devices. It runs simply as HTML5 or more elaborately using Bootstrap, jQuery Mobile or jQuery UI. Alpaca uses Handlebars to process JSON schema and provide

630 lines (533 loc) 17.7 kB
(function($) { var Alpaca = $.alpaca; Alpaca.Form = Base.extend( /** * @lends Alpaca.Form.prototype */ { /** * @constructs * * @class This class is for managing HTML form control. * * @param {Object} domEl Field container. * @param {Object} options Field options. * @param {Object|String} view Field view. * @param {Alpaca.Connector} connector Field connector. * @param {Function} errorCallback Error callback. */ constructor: function(domEl, options, viewId, connector, errorCallback) { // container this.domEl = domEl; // parent this.parent = null; this.connector = connector; this.errorCallback = errorCallback; // options this.options = options; if (this.options.attributes) { this.attributes = this.options.attributes; } else { this.attributes = {}; } if (this.options.buttons) { if (this.options.buttons.submit) { if (!this.options.buttons.submit.type) { this.options.buttons.submit.type = 'submit'; } if (!this.options.buttons.submit.name) { this.options.buttons.submit.name = 'submit'; } if (!this.options.buttons.submit.value) { this.options.buttons.submit.value = 'Submit'; } } if (this.options.buttons.reset) { if (!this.options.buttons.reset.type) { this.options.buttons.reset.type = 'reset'; } if (!this.options.buttons.reset.name) { this.options.buttons.reset.name = 'reset'; } if (!this.options.buttons.reset.value) { this.options.buttons.reset.value = 'Reset'; } } // some general correction for (var k in this.options.buttons) { if (this.options.buttons[k].label) { this.options.buttons[k].value = this.options.buttons[k].label; } if (this.options.buttons[k].title) { this.options.buttons[k].value = this.options.buttons[k].title; } if (!this.options.buttons[k].type) { this.options.buttons[k].type = "button"; } } } if (this.attributes.id) { this.id = this.attributes.id; } else { this.id = Alpaca.generateId(); this.attributes.id = this.id; } // if we have a submit button specified, and toggleSubmitValidState isn't defined, set to true by default // don't allow the form to submit unless valid if (this.options.buttons && this.options.buttons.submit && Alpaca.isUndefined(this.options.toggleSubmitValidState)) { this.options.toggleSubmitValidState = true; } this.viewType = options.viewType; // set a runtime view this.view = new Alpaca.RuntimeView(viewId, this); // for each button, make sure that classes is set minimally to view.styles.button for (var k in this.options.buttons) { if (!this.options.buttons[k].styles) { this.options.buttons[k].styles = this.view.styles.button; } } }, /** * Renders this form into the container. * * @param {Function} callback */ render: function(callback) { var self = this; // load the appropriate template and render it this.processRender(this.domEl, function() { // bind our field dom element into the domEl self.form.appendTo(self.domEl); // add default class self.form.addClass("alpaca-form"); // CALLBACK: "form" self.fireCallback("form"); // execute callback callback(self); }); }, afterInitialize: function() { var self = this; if (self.options.toggleSubmitValidState) { // adjust submit button state self.adjustSubmitButtonState(); } }, /** * Determines whether the top control is entirely valid. * * @return {*} */ isFormValid: function() { // re-compute validation for the full control set this.topControl.validate(true); var valid = this.topControl.isValid(true); //this.refreshValidationState(true); return valid; }, isValid: function() { return this.isFormValid(); }, validate: function(children) { return this.topControl.validate(children); }, enableSubmitButton: function() { $(".alpaca-form-button-submit").attrProp("disabled", false); if ($.mobile) { try { $(".alpaca-form-button-submit").button('refresh'); } catch (e) { } } }, disableSubmitButton: function() { $(".alpaca-form-button-submit").attrProp("disabled", true); if ($.mobile) { try { $(".alpaca-form-button-submit").button('refresh'); } catch (e) { } } }, adjustSubmitButtonState: function() { this.disableSubmitButton(); if (this.isFormValid()) { this.enableSubmitButton(); } }, /** * Responsible for fetching any templates needed so as to render the * current mode for this field. * * Once completed, the onSuccess method is called. * * @param {Object} parentEl Field container. * @param {Function} callback */ processRender: function(parentEl, callback) { var self = this; // lookup the template we should use to render this.formDescriptor = this.view.getTemplateDescriptor("form"); if (!this.formDescriptor) { return Alpaca.throwErrorWithCallback("Could not find template descriptor: form"); } var renderedDomElement = Alpaca.tmpl(this.formDescriptor, { id: this.getId(), options: this.options, view: this.view }); renderedDomElement.appendTo(parentEl); this.form = renderedDomElement; // find our insertion point // this is marked by the handlebars helper this.formFieldsContainer = $(this.form).find("." + Alpaca.MARKER_CLASS_FORM_ITEMS_FIELD); this.formFieldsContainer.removeClass(Alpaca.MARKER_CLASS_FORM_ITEMS_FIELD); if (Alpaca.isEmpty(this.form.attr("id"))) { this.form.attr("id", this.getId() + "-form-outer"); } if (Alpaca.isEmpty(this.form.attr("data-alpaca-form-id"))) { this.form.attr("data-alpaca-form-id", this.getId()); } // the form field $(parentEl).find("form").attr(this.attributes); // populate the buttons as well this.buttons = {}; $(parentEl).find(".alpaca-form-button").each(function() { $(this).click(function(e) { $(this).attr("button-pushed", true); }); // custom click handler? var key = $(this).attr("data-key"); if (key) { var buttonConfig = self.options.buttons[key]; if (buttonConfig) { if (buttonConfig.click) { $(this).click(function(form, handler) { return function(e) { e.preventDefault(); handler.call(form, e); } }(self, buttonConfig.click)); } } } }); callback(); }, /** * Returns the id of the form. * * @returns {String} Form id */ getId: function() { return this.id; }, /** * Returns form type. * * @returns {String} Form type. */ getType: function() { return this.type; }, /** * Returns this form's parent. * * @returns {Object} Form parent. */ getParent: function() { return this.parent; }, /** * Returns the value of the JSON rendered by this form. * * @returns {Any} Value of the JSON rendered by this form. */ getValue: function() { return this.topControl.getValue(); }, /** * Sets the value of the JSON to be rendered by this form. * * @param {Any} value Value to be set. */ setValue: function(value) { this.topControl.setValue(value); }, /** * Initializes events handling (Form Submission) for this form. */ initEvents: function() { var _this = this; var formTag = $(this.domEl).find("form"); var v = this.getValue(); $(formTag).submit(v, function(e) { return _this.onSubmit(e, _this); }); // listen for fieldupdates and determine whether the form is valid. // if so, enable the submit button... // otherwise, disable it if (this.options.toggleSubmitValidState) { $(_this.topControl.getFieldEl()).bind("fieldupdate", function() { _this.adjustSubmitButtonState(); }); $(_this.topControl.getFieldEl()).bind("fieldkeyup", function() { _this.adjustSubmitButtonState(); }); $(_this.topControl.getFieldEl()).bind("fieldblur", function() { _this.adjustSubmitButtonState(); }); } }, getButtonEl: function(buttonId) { return $(this.domEl).find(".alpaca-form-button-" + buttonId); }, /** * Handles form submit events. * * @param {Object} e Submit event. * @param {Object} form the form */ onSubmit: function(e, form) { if (!this.isFormValid()) { e.stopPropagation(); this.refreshValidationState(true); return false; } if (this.submitHandler) { e.stopPropagation(); var v = this.submitHandler(e, form); if (Alpaca.isUndefined(v)) { v = false; } return v; } }, /** * Registers a custom submit handler. * * @param {Object} func Submit handler to be registered. */ registerSubmitHandler: function (func) { if (Alpaca.isFunction(func)) { this.submitHandler = func; } }, /** * Displays validation information of all fields of this form. * * @param {Boolean} children whether to render validation state for child fields * * @returns {Object} Form validation state. */ refreshValidationState: function(children, callback) { this.topControl.refreshValidationState(children, callback); }, /** * Disables this form. */ disable: function() { this.topControl.disable(); }, /** * Enables this form. */ enable: function() { this.topControl.enable(); }, /** * Focuses on this form. * * If a callback is provided, the callback receives the focused control. */ focus: function(onFocusCallback) { this.topControl.focus(function(controlWithFocus) { if (onFocusCallback) { onFocusCallback(controlWithFocus); } }); }, /** * Purge any event listeners and remove the form from the DOM. * * @param [Boolean] skipParent when true, the form cleans up without traversing through parent child controls */ destroy: function(skipParent) { this.getFormEl().remove(); // we allow form.destroy() which tells parent control to destroy // if skipParent == true, then we do not call up (invoked from container) if (!skipParent && this.parent) { this.parent.destroy(); } }, /** * Shows the form. */ show: function() { this.getFormEl().css({ "display": "" }); }, /** * Hides the form. */ hide: function() { this.getFormEl().css({ "display": "none" }); }, /** * Clears the form and resets values of its fields. * * @param stopUpdateTrigger If false, triggers the update event of this event. */ clear: function(stopUpdateTrigger) { this.topControl.clear(stopUpdateTrigger); }, /** * Checks if form is empty. * * @returns {Boolean} True if the form is empty, false otherwise. */ isEmpty: function() { return this.topControl.isEmpty(); }, /** * Fires a view callback for the current form. * * @param id * @param arg1 * @param arg2 * @param arg3 * @param arg4 * @param arg5 */ fireCallback: function(id, arg1, arg2, arg3, arg4, arg5) { this.view.fireCallback(this, id, arg1, arg2, arg3, arg4, arg5); }, /** * Retrieves the form element. * * @returns {Object} The rendered DOM element. */ getFormEl: function() { return this.form; }, /** * Performs a regular old submit. */ submit: function() { this.form.submit(); }, /** * Fires the submit in the background and hands back the jQuery promise. * * An optional config can be passed in to control the underlying jQuery ajax XHR. * * @returns {*} */ ajaxSubmit: function(config) { var self = this; if (!config) { config = {}; } config.url = self.options.attributes.action; config.type = self.options.attributes.method; if (!config.data) { config.data = this.getValue(); } if (!config.dataType) { config.dataType = "json"; } if (!config.headers) { config.headers = {}; } // support CSRF here var csrfToken = self.determineCsrfToken(); if (csrfToken) { config.headers[Alpaca.CSRF_HEADER_NAME] = csrfToken; } return $.ajax(config); }, determineCsrfToken: function() { // is there a direct token specified? var csrfToken = Alpaca.CSRF_TOKEN; if (!csrfToken) { // is there a cookie that we can pull the value from? for (var t = 0; t < Alpaca.CSRF_COOKIE_NAMES.length; t++) { var cookieName = Alpaca.CSRF_COOKIE_NAMES[t]; var cookieValue = Alpaca.readCookie(cookieName); if (cookieValue) { csrfToken = cookieValue; break; } } } return csrfToken; } }); })(jQuery);