'use strict'; /*@ Ox.Form Form Object options Options object error error id id items [] self Shared private variable ([options[, self]]) -> Form Object change change validate validate submit submit @*/ Ox.Form = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ error: '', id: '', items: [], validate: function(valid) { return Ox.every(valid); } }) .options(options || {}) .addClass('OxForm'); Ox.extend(self, { $items: [], $messages: [], itemIds: [], itemIsValid: [] }); self.options.items.forEach(function(item, i) { validateItem(i, function(valid) { self.itemIsValid[i] = valid; }); self.itemIds[i] = item.options('id') || item.id; self.$items[i] = Ox.FormItem({element: item}).appendTo(that); item.bindEvent({ autovalidate: function(data) { validateForm(i, data.valid); data.valid && self.$items[i].setMessage(''); }, /* // fixme: should't inputs also trigger a change event? blur: function(data) { that.triggerEvent('change', { id: self.itemIds[i], data: data }); }, */ change: function(data) { // fixme: shouldn't this be key/value instead of id/data? that.triggerEvent('change', { id: self.itemIds[i], data: data }); validateItem(i, function(valid) { validateForm(i, valid); }); }, submit: function(data) { self.formIsValid && that.submit(); }, validate: function(data) { validateForm(i, data.valid); // timeout needed for cases where the form is removed // from the DOM, triggering blur of an empty item - // in this case, we don't want the message to appear setTimeout(function() { self.$items[i].setMessage(data.valid ? '' : data.message); }, 0); } }); }); self.formIsValid = self.options.validate(self.itemIsValid); function getItemIndexById(id) { return self.itemIds.indexOf(id); } function validateForm(pos, valid) { self.itemIsValid[pos] = valid; if (self.options.validate(self.itemIsValid) != self.formIsValid) { self.formIsValid = !self.formIsValid; that.triggerEvent('validate', { valid: self.formIsValid }); } } function validateItem(pos, callback) { var item = self.options.items[pos], validate = item.options('validate'); if (validate) { validate(item.value(), function(data) { callback(data.valid); }); } else { callback(item.value && !Ox.isEmpty(item.value)); } } /*@ addItem addItem (pos, item) -> add item at position @*/ that.addItem = function(pos, item) { Ox.Log('Form', 'addItem', pos) self.options.items.splice(pos, 0, item); self.$items.splice(pos, 0, Ox.FormItem({element: item})); pos == 0 ? self.$items[pos].insertBefore(self.$items[0]) : self.$items[pos].insertAfter(self.$items[pos - 1]); } /*@ removeItem removeItem (pos) -> remove item from position @*/ that.removeItem = function(pos) { Ox.Log('Form', 'removeItem', pos); self.$items[pos].remove(); self.options.items.splice(pos, 1); self.$items.splice(pos, 1); } that.setMessages = function(messages) { Ox.forEach(messages, function(v) { self.$items[getItemIndexById(v.id)].setMessage(v.message); }); }; /*@ submit submit @*/ that.submit = function() { that.triggerEvent('submit', {values: that.values()}); }; /*@ valid valid @*/ that.valid = function() { return self.formIsValid; }; /*@ values values @*/ that.values = function() { // FIXME: this should accept a single string argument to get a single value /* get/set form values call without arguments to get current form values pass values as array to set values (not implemented) */ var values = {}; if (arguments.length == 0) { self.$items.forEach(function($item, i) { values[self.itemIds[i]] = self.$items[i].value(); }); //Ox.Log('Form', 'VALUES', values) return values; } else { Ox.Log('Form', 'SET FORM VALUES', arguments[0]) Ox.forEach(arguments[0], function(value, key) { var index = getItemIndexById(key); index > -1 && self.options.items[index].value(value); }); return that; } }; return that; };