/* Picasso * A Framework to build dinamic forms using MVC * Build date: Sun, 11 Oct 2015 03:00:09 GMT * @author Rubens Pinheiro Gonçalves Cavalcante * @version 0.9.4 * @license GPL-3.0 */ /** * Main namespace * @namespace */ var Picasso = Picasso || {}; /** * @alias Picasso */ var P = P || Picasso; // -- Namespace virtual comments -- // /** * Declares the plain javascript object constructors * @namespace {Object} Picasso.pjo */ /** * All the system core objects * @namespace {Object} Picasso.core */ /** * All the system errors objects * @namespace {Object} Picasso.error */ /** * All the utils modules * @namespace {Object} Picasso.utils */ /** * Reunites functions to augment Picasso functionality * @namespace {Object} Picasso.extend */ /** * The dynamic form builder namespace * @namespace {Object} Picasso.form */ /** * All the field constructors are stores here * @namespace {Object} Picasso.form.field */ // -- End of load virtual comments -- // /** * Shows the information about the framework * @type {{author: string, version: string, build: string, license: string}} */ Picasso.info = { author: "%author%", version: "%version%", build: "%buildDate%", license: "%license%" }; /** * Declares or loads already declared module/namespace * @example * // Declaring * Picasso.load("utils.array"); * // Using * var arrayUtils = Picasso.load("utils.array"); * * @param {string} namespace The module complete namespace * @return {Object} The loaded module */ Picasso.load = function (namespace) { var parts = namespace.split('.'); var currentObj = Picasso; // strip redundant leading global if (parts[0] === "Picasso") { parts = parts.slice(1); } for (var i = 0; i < parts.length; i++) { if (typeof currentObj[parts[i]] === "undefined") { currentObj[parts[i]] = {}; } currentObj = currentObj[parts[i]]; } return currentObj; }; Picasso.load("loadField"); /** * Loads a field of the given type * @param {Picasso.pjo.Field} field * @returns {Picasso.form.field.PicassoField} */ Picasso.loadField = function (field) { var fieldFactory = new Picasso.form.FieldFactory(); try { return fieldFactory.create(field); } catch (e) { var log = Picasso.load("utils.log"); log.error(e.message); return null; } }; Picasso.load("extend"); /** * Adds a extra field to the Picasso form * @param {string} fieldName The name to access this type of field * @param {function} Field Constructor of the field * @throws {Picasso.error.InvalidParameters} */ Picasso.extend.field = function (fieldName, Field) { var objUtil = Picasso.load("utils.object"); var log = Picasso.load("utils.log"); if (typeof Field == "function") { if (!objUtil.isEmpty(Field.prototype)) { log.warn("Overriding the field prototype from given constructor", Field); } Field.prototype = new Picasso.form.field.PicassoField; if (Picasso.form.FieldFactory.constructors.hasOwnProperty(fieldName)) { log.warn("Overriding a already registered Picasso field " + fieldName); } Picasso.form.FieldFactory.constructors[fieldName] = Field; } else { throw new Picasso.error.InvalidParameters("Picasso.extend.field", {Field: "Invalid constructor"}, this); } }; /** * Adds a extra validator to the Picasso fields * @param {string} fieldName * @param {function} validator * @throws {Picasso.error.InvalidParameters} */ Picasso.extend.validator = function (fieldName, validator) { var objUtil = Picasso.load("utils.object"); var log = Picasso.load("utils.log"); if (typeof validator == "function") { if (Picasso.form.validators.hasOwnProperty(fieldName)) { log.warn("Overriding a already registered Picasso field validator " + fieldName); } Picasso.form.validators[fieldName] = validator; } else { throw new Picasso.error.InvalidParameters("Picasso.extend.validator", {validator: "Invalid function"}, this); } }; Picasso.load("pjo.Event"); /** * The default event object * @param {string} name * @param {*} data * @param {Object} target * @constructor */ Picasso.pjo.Event = function PicassoEvent(name, data, target) { /** @type String */ this.name = name || "any"; /** @type * */ this.data = data || null; /** @type Object */ this.target = target || null; }; Picasso.load("pjo.EventHandler"); /** * Default event handler * @param {string} eventName * @param {Function|String} callback * @param {Object} context * @constructor */ Picasso.pjo.EventHandler = function (eventName, callback, context) { /** @type String */ this.eventName = eventName || ""; /** @type Function */ this.callback = callback || new Function(); /** @type Object */ this.context = context || null; }; Picasso.load("pjo.Column"); /** * An form fieldset * @constructor */ Picasso.pjo.Column = function(){ /** @type {string|number} */ this.id = null; /** @type {number} */ this.index = 0; /** @type {Picasso.pjo.Column.colSize} */ this.colXSize = 3; /** @type {Picasso.pjo.Field[]} */ this.fields = []; }; /** * The size of the grid columns * @enum {string} * @readonly */ Picasso.pjo.Column.colSize = { SMALL: 2, MEDIUM: 3, LARGE: 4 }; Picasso.load("pjo.Field"); /** * A form field * @constructor */ Picasso.pjo.Field = function(){ /** @type {string|number} */ this.id = null; /** @type {string} */ this.label = ""; /** @type {Picasso.pjo.Field.type} */ this.type = null; /** @type {boolean} */ this.required = false; /** @type {boolean} */ this.formIgnore = false; /** @type {*} */ this.value = null; /** * The field attributes * @type {{name: string}} */ this.attrs = { name: "" }; }; /** * Available default field types * @readonly * @enum {string} */ Picasso.pjo.Field.type = { TEXT: "text", TEXTAREA: "textarea", NUMBER: "number", EMAIL: "email", PASSWORD: "password", SUBMIT: "submit", CANCEL: "cancel" }; Picasso.load("pjo.Form"); /** * A form object representation * @constructor */ Picasso.pjo.Form = function(){ /** @type {string|number} */ this.id = null; /** * The form attributes * @type {{action: string, method: string, name: string}} */ this.attrs = { action: "", method: "", name: "" }; /** @type {Picasso.pjo.Grid} */ this.grid = null; }; Picasso.load("pjo.Grid"); Picasso.pjo.Grid = function(){ /** @type {string|number} */ this.id = null; /** @type {string} */ this.legend = null; /** * The element attributes * @type {{name: string}} */ this.attrs = {}; /** @type {Picasso.pjo.Column[]} */ this.columns = []; }; Picasso.load("pjo.Validation"); /** * A validation object * @constructor */ Picasso.pjo.Validation = function () { /** @type {Picasso.form.field.PicassoField} */ this.field = null; /** @type {string[]} */ this.errorMessages = []; /**0@type {boolean} */ this.valid = false; }; Picasso.load("error.InvalidFieldType"); /** * Invalid field type used * @param {string} fieldType * @constructor * @extends Error */ Picasso.error.InvalidFieldType = function(fieldType){ this.name = "InvalidFieldType"; this.message = "Can't find a constructor. Invalid field type " + String(fieldType); }; Picasso.error.InvalidFieldType.prototype = Error.prototype; Picasso.error.InvalidFieldType.constructor = Picasso.error.InvalidFieldType; Picasso.load("error.InvalidParameters"); /** * Invalid parameters error * @param {string} funcName The name of the function that are throwing this error * @param {Object} errorParameters A map of parameter/error message type * @param {Object} context The context that the error occurred * @extends {Error} * @constructor */ Picasso.error.InvalidParameters = function (funcName, errorParameters, context) { this.name = "InvalidParameters"; this.message = "The function " + funcName + " has received invalid parameters\n"; var template = "\tparameter %param%: %dependence%;\n"; for(var i in errorParameters){ if(errorParameters.hasOwnProperty(i)){ this.message += template.replace("%param%", i).replace("%dependence%", errorParameters[i]); } } this.errorParameters = errorParameters || null; this.context = context || null; }; Picasso.error.InvalidParameters.prototype = Error.prototype; Picasso.error.InvalidParameters.constructor = Picasso.error.InvalidParameters; Picasso.load("error.NotImplementedError"); /** * Invalid field type used * @param {string} constructor * @param {string} method * @constructor * @extends Error */ Picasso.error.NotImplementedError = function(constructor, method){ this.name = "NotImplementedError"; this.message = constructor + " object child must implement the " + method + " method"; }; Picasso.error.NotImplementedError.prototype = Error.prototype; Picasso.error.NotImplementedError.constructor = Picasso.error.NotImplementedError; Picasso.load("utils.array"); Picasso.utils.array = ( /** * A set of array utils * @exports utils/array */ function () { /** * Finds a element into the array * @param {Array} arr A array to search into * @param {*} element The element to find * @return {boolean} If the element was found * @public */ var find = function (arr, element) { /** * Object utils * @type module:utils/object * @private */ var objUtil = Picasso.load("Picasso.utils.object"); for (var i = 0; i < arr.length; i++) { if (typeof arr[i] == 'object' && typeof element == 'object') { if (objUtil.equals(arr[i], element)) { return true; } } else { if (arr[i] === element) { return true; } } } return false; }; /** * Compares two arrays and verify if their are equals * @param {Array} arr1 First array * @param {Array} arr2 Second array * @return {boolean} If they are equals * @public */ var equals = function (arr1, arr2) { if (arr1.length != arr2.length) return false; for (var i = 0; i < arr2.length; i++) { if (!find(arr1, arr2[i])) { return false; } } return true; }; /** * 'For each' callback * @callback module:utils/array.eachCallback * @param {*} element Current element of the iteration * @param {number} index The current index */ /** * Iterates over a array, executing a callback * to each element * @param {Array} arr * @param {module:utils/array.eachCallback} call * @public */ var each = function (arr, call) { if(typeof arr === "undefined"){ return; } var i, l; l = arr.length; for (i = 0; i < l; i++) { call(arr[i], i); } }; // Public API return { find: find, equals: equals, each: each } }() ); Picasso.load("utils.html"); Picasso.utils.html = ( /** * HTML and DOM utils * @exports utils/html */ function () { /** * Set the given elements to the HTML object * @param {HTMLElement} element * @param {Object} attrs * @public */ var setAttributes = function (element, attrs) { for (var attr in attrs) { if (attrs.hasOwnProperty(attr)) { element.setAttribute(attr, attrs[attr]); } } }; /** * Add a class to a element * @param {HTMLElement} element An HTML element to add the classes * @param {string} _class One or more classes separated by space */ var addClass = function (element, _class) { var classes = element.classList; var toAppend, i; if (_class.indexOf(' ') != -1) { toAppend = _class.split(' '); } else { toAppend = [_class] } for (i = 0; i < toAppend.length; i++) { if (!classes.contains(toAppend[i])) { classes.add(toAppend[i]); } } }; /** * Remove a class of a element * @param {HTMLElement} element * @param {string} _class */ var removeClass = function (element, _class) { var classes = element.classList; var toRemove, i; if (_class.indexOf(' ') != -1) { toRemove = _class.split(' '); } else { toRemove = [_class] } for (i = 0; i < toRemove.length; i++) { if (!classes.contains(toRemove[i])) { classes.remove(toRemove[i]); } } }; // Public API return { setAttributes: setAttributes, addClass: addClass, removeClass: removeClass }; }() ); Picasso.load("utils.log"); Picasso.utils.log = ( /** * Defines a set of functions to log messages * @exports utils/log */ function () { /** * The log levels * @enum {string} */ var lvs = { ERROR: "error", WARN: "warn", INFO: "info", DISABLED: "disabled" }; var currentLevel = lvs.DISABLED; /** * Shows info in the console * @param {string} msg * @param {object} context */ var info = function (msg, context) { if (currentLevel == lvs.INFO) { context = context || "(no context informed)"; console.info(msg, context); } }; /** * Warning in the console * @param {string} msg * @param {object} context */ var warn = function (msg, context) { if (currentLevel == lvs.INFO || currentLevel == lvs.WARN) { context = context || "(no context informed)"; console.warn(msg, context); } }; /** * Error in the console * @param {string} msg * @param {object} context */ var error = function (msg, context) { if (currentLevel == lvs.INFO || currentLevel == lvs.WARN || currentLevel == lvs.ERROR) { context = context || "(no context informed)"; console.error(msg, context); } }; /** * Sets the level of the logger * @param {lvs} level */ var setLogLevel = function(level){ currentLevel = level; }; /** * Get the log level * @return {lvs} */ var getLogLevel = function(){ return currentLevel; }; return { lvs: lvs, info: info, warn: warn, error: error, setLogLevel: setLogLevel, getLogLevel: getLogLevel }; }()); Picasso.load("utils.object"); Picasso.utils.object = ( /** * A set of object utils * @exports utils/object */ function () { /** * Transforms a string delimited by "-" * to a camel case notation * @param {string} property * @returns {string} * @private */ var _toCamelCase = function (property) { return property.toLowerCase().replace(/-(.)/g, function (match, g1) { return g1.toUpperCase(); }); }; /** * Extends a constructor * @param {Function} Class The object constructor * @param {Function} Parent The parent object constructor * @returns {Function} The Class constructor */ var extend = function (Class, Parent) { //Rent a prototype var Rented = new Function(); Rented.prototype = Parent.prototype; Class.prototype = new Rented(); Class._super = Parent.prototype; Class.prototype.constructor = Class; return Class; }; /** * 'For each' callback * @callback utils/object.eachCallback * @param {*} value * @param {string} property */ /** * Iterates over a object properties * @param {Object} obj * @param {module:utils/object.eachCallback} call */ var each = function (obj, call) { if (obj instanceof Object) { var property; for (property in obj) { if (obj.hasOwnProperty(property)) { call(obj[property], property); } } } }; /** * Compares two objects and * verify if they are equals * @param {Object} obj1 * @param {Object} obj2 * @return {Boolean} * @public */ var equals = function (obj1, obj2) { var path; for (path in obj1) { if (typeof(obj2[path]) == 'undefined') { return false; } } for (path in obj1) { if (obj1[path]) { switch (typeof(obj1[path])) { case 'object': if (!obj1[path].equals(obj2[path])) { return false; } break; case 'function': if (typeof(obj2[path]) == 'undefined' || (path != 'equals' && obj1[path].toString() != obj2[path].toString()) ) { return false; } break; default: if (obj1[path] != obj2[path]) { return false; } } } else { if (obj2[path]) { return false; } } } for (path in obj2) { if (typeof(obj1[path]) == 'undefined') { return false; } } return true; }; /** * Converts the given object to the strict properties of * the plain object constructor * @param {Object} obj * @param {Object.constructor} plainObjectConstructor */ var deserialize = function (obj, plainObjectConstructor) { var pjo = new plainObjectConstructor(); var formattedObj = {}; for (var i in obj) { if (obj.hasOwnProperty(i)) { if (i.indexOf("-") != -1) { formattedObj[_toCamelCase(i)] = obj[i]; } else { formattedObj[i] = obj[i]; } } } for (var property in pjo) { if (pjo.hasOwnProperty(property) && formattedObj.hasOwnProperty(property)) { pjo[property] = formattedObj[property]; } } return pjo; }; /** * Tests if a object is empty * @param {Object} obj * @returns {boolean} */ var isEmpty = function(obj){ for(var key in obj){ if(obj.hasOwnProperty(key)){ return false; } } return true; }; // Public API return { extend: extend, equals: equals, each: each, deserialize: deserialize, isEmpty: isEmpty } }() ); Picasso.load("core.Sequence"); Picasso.core.Sequence = (function () { /** * Stores all the sequences * @type {Object} * @private * @static */ var _registeredEntities = {}; /** * Validates and if necessary starts a new sequence * based on the given entity name * @param {string} entityName The entity name * @return {boolean} * @private * @static */ var _validateAndStartSequence = function (entityName) { if (typeof entityName == "undefined" || typeof entityName != "string") { return false; } if (!_registeredEntities.hasOwnProperty(entityName)) { _registeredEntities[entityName] = null; } return true; }; /** * Controls a sequence of the given entity.
* Take note that this is the real Sequence constructor. * * @param {string} entity The entity name * @constructor * @alias Picasso.core.Sequence * @example * var userSeq = new Picasso.core.Sequence("User"); * var secUserSeq = new Picasso.core.Sequence("User"); * * secUserSeq.currentVal(); // returns 0 * userSeq.nextVal(); // returns 1 * secUserSeq.currentVal(); // returns 1 */ var SeqConstructor = function (entity) { this._entity = entity; }; /** * View the sequence current value * @return {?number} The current sequence value or null * to a invalid entity name * @public */ SeqConstructor.prototype.currentVal = function () { if (_validateAndStartSequence(this._entity)) { return _registeredEntities[this._entity]; } return null; }; /** * Get the next val of a sequence and increments it * @return {number} The current sequence value * @public */ SeqConstructor.prototype.nextVal = function () { if (_validateAndStartSequence(this._entity)) { if (_registeredEntities[this._entity] == null) { _registeredEntities[this._entity] = 0; return 0; } return ++_registeredEntities[this._entity]; } return null; }; return SeqConstructor }()); Picasso.load("core.Subject"); /** * The subject constructor * See the observer design pattern * @constructor */ Picasso.core.Subject = function () { /** * All the event handlers (Observers) are stored here * @type {Object} */ var handlers = {}; /** * Visits all the associated handlers to the given event * and call it or remove it * @param {string} action * @param {function} callback * @param {Picasso.pjo.Event} event * @private */ var _visit = function (action, event, callback) { var evListeners = handlers[event.name] || []; for (var i = 0; i < evListeners.length; i++) { if (action == "fire") { var cbk = evListeners[i].callback; if(typeof cbk == "string" && evListeners[i].context.hasOwnProperty(cbk)){ evListeners[i].context[cbk](event); } else{ evListeners[i].callback.call(evListeners.context, event); } } else if (evListeners[i].callback === callback && (event.context == null || evListeners.context === event.context)) { evListeners.splice(i, 1); } } }; /** * Subscribes a new observer * @param {string} eventType * @param {Function} callback * @param {Object} context * @throws Picasso.error.InvalidParameters * @protected */ this._subscribe = function (eventType, callback, context) { if (typeof eventType == "undefined") { throw new Picasso.error.InvalidParameters("_subscribe", {eventType: "obrigatory"}, this._subscribe); } if (!handlers.hasOwnProperty(eventType)) { handlers[eventType] = []; } var handler = new Picasso.pjo.EventHandler(eventType, callback, context || this); handlers[eventType].push(handler); }; /** * Removes a observer of a event. * If, only the eventType is given, removes all observers of * this event type. If callback is given, removes all observers * that calls this callback. And finnaly, if context is given too, * removes if match the eventType, callback and context. * @param {string} eventType * @param {Function} callback * @param {Object} context * @throws {Picasso.error.InvalidParameters} * @protected */ this._unsubscribe = function (eventType, callback, context) { if (typeof eventType == "undefined") { throw new Picasso.error.InvalidParameters("usubscribe", {eventType: "obrigatory"}, this._unsubscribe); } if (typeof callback == "undefined" && typeof context == "undefined") { delete handlers[eventType]; } else { _visit("_unsubscribe", new Picasso.pjo.Event(eventType, [], context), callback); } }; /** * Fires a event, calling all the observers * of this event * @param {string} eventType * @param {*} eventData * @param {Object} context * @throws {Picasso.error.InvalidParameters} */ this.fire = function (eventType, eventData, context) { if (typeof eventType == "undefined") { throw new Picasso.error.InvalidParameters("fire", {eventType: "String"}, this.fire); } _visit("fire", new Picasso.pjo.Event(eventType, eventData, context || this)); } }; Picasso.load("Collection"); Picasso.Collection = function (ModelConstructor) { var Collection = Picasso.utils.object.extend(Picasso.Collection.MetaConstructor, Array); var collection = new Collection(); collection.setCollectionType(ModelConstructor); return collection; }; /** * A model collection constructor * @extends {Array} * @constructor */ Picasso.Collection.MetaConstructor = function () { /** * Sets the type of the collection * @param {Picasso.Model} ModelConstructor */ this.setCollectionType = function (ModelConstructor) { this.ModelConstructor = ModelConstructor; }; /** * Clears the collection */ this.clear = function () { for (var i = 0, l = this.length; i < l; i++) { this.pop(); } }; /** * Iterates over each element of the collection * @param {function} callback */ this.each = function (callback) { for (var i = 0, l = this.length; i < l; i++) { callback(this[i], i); } }; /** * Gets a element of the collection * @param {number} id * @returns {?Picasso.Model} */ this.getElement = function (id) { for (var i = 0; i < this.length; i++) { if (this[i].id == id) { return this[i]; } } return null; }; /** * Adds a element to the collection * @param element */ this.addElement = function (element) { var model = new this.ModelConstructor(); model.update(element); this.push(model); }; /** * Adds elements to the collection * @param elements */ this.addElements = function (elements) { for (var i = 0; i < elements.length; i++) { this.addElement(elements[i]); } }; /** * Removes a element from the collection * @param {number} id * @returns {Picasso.Model} */ this.removeElement = function (id) { for (var i = 0; i < this.length; i++) { if (this[i].id == id) { return this.splice(i, 1); } } }; /** * Update a element from collection * @param {Picasso.Model} element * @param {string} [property='id'] */ this.updateElement = function (element, property) { if (!element.hasOwnProperty(property) || typeof property === 'undefined') { property = 'id'; } for (var i = 0; i < this.length; i++) { if (this[i].hasOwnProperty(property) && element.hasOwnProperty(property) && element[property] === this[i][property]) { this[i].update(element); } } }; }; Picasso.load("Controller"); /** * The picasso Controller entity. * To create an application controller use the extend static method * @example: * var MyCustomController = Picasso.Controller.extend(function (model, view) { * this.construct(model, view); * // ... * }); * * @param {Picasso.Model} model A model to associate to this controller * @param {Picasso.View} view A view to associate to this controller * @constructor */ Picasso.Controller = function (model, view) { /** * All the UI action events are stored here * @type {Picasso.pjo.EventHandler[]} * @private */ this._UIActions = []; /** * @type {Picasso.Model} * @protected */ this._model = null; /** * @type {Object} * @protected */ this._views = {}; /** * Autowired form validator * @type {Picasso.form.Validator} * @public */ this.validator = new Picasso.form.Validator(); }; /** * Listen all registered UIActions to a view * @param {Picasso.View} view * @private */ Picasso.Controller.prototype._registerUIAction = function (view) { var l = this._UIActions.length; for (var i = 0; i < l; i++) { var evHandler = this._UIActions[i]; view._subscribe(evHandler.eventName, evHandler.callback); } }; /** * The Default Controller constructor * @param {Picasso.Model} model * @param {...Picasso.View} */ Picasso.Controller.prototype.construct = function (model) { Picasso.Controller.apply(this, arguments); this._model = model; var l = arguments.length; if (l > 1) { for (var i = 1; i < l; i++) { if (arguments[i] instanceof Picasso.View) { this.registerView(arguments[i]); arguments[i].setModel(model); } } } }; /** * Registers a view to this controller * @param {Picasso.View} view */ Picasso.Controller.prototype.registerView = function (view) { view.setModel(this._model); this._views[view._seq] = view; this._registerUIAction(view); }; /** * Register a uiAction (event) to a controller callback * @param {string} uiActionName * @param {Function} callback */ Picasso.Controller.prototype.listen = function (uiActionName, callback) { var uiAcion = new Picasso.pjo.EventHandler(uiActionName, callback, this); this._UIActions.push(uiAcion); for (var i in this._views) { if (this._views.hasOwnProperty(i)) { this._views[i]._subscribe(uiActionName, callback); } } }; /** * Gets the model associated with this controller * @return {Picasso.Model|Picasso.Collection} */ Picasso.Controller.prototype.getModel = function(){ return this._model; }; Picasso.Controller.prototype.setModel = function(model){ for(var i in this._views){ if(this._views.hasOwnProperty(i)){ this._views[i].setModel(model); } } }; /** * Extends from a Controller * @static * @param {Function} constructor The constructor to extend * @returns {Function} The updated constructor */ Picasso.Controller.extend = function (constructor) { return Picasso.utils.object.extend(constructor, Picasso.Controller); }; Picasso.load("Model"); /** * The picasso Model entity * @constructor * @extends Picasso.core,Subject */ Picasso.Model = function () {}; Picasso.Model.prototype = new Picasso.core.Subject(); /** * Sets a property of the model * @param {string} property * @param {*} value */ Picasso.Model.prototype.set = function (property, value) { if (this.hasOwnProperty(property)) { if (this[property] instanceof Picasso.Model) { this[property].update(value); } else { this[property] = value; this.fire("propertyChange", {property: property, value: value}); } } }; /** * Gets a model property value * @param {string} property * @return {*} */ Picasso.Model.prototype.get = function (property) { if (this.hasOwnProperty(property)) { return this[property]; } }; /** * Updates the properties of the model * @param {Object} plainModel */ Picasso.Model.prototype.update = function (plainModel) { for (var i in plainModel) { if (plainModel.hasOwnProperty(i)) { this.set(i, plainModel[i]); } } }; /** * Returns the plain object of this model with * all the values * @return {Object} */ Picasso.Model.prototype.toPlainObject = function () { var pjo = new this.constructor(); for (var property in pjo) { if (pjo.hasOwnProperty(property) && typeof pjo[property] != "function") { pjo[property] = this[property]; } } return pjo; }; /** * Extends from a Model * @static * @param {Function} Constructor The constructor to extend * @returns {Function} The updated constructor */ Picasso.Model.extend = function (Constructor) { var metaData = []; var sampleModel = new Constructor(); for (var attr in sampleModel) { if (sampleModel.hasOwnProperty(attr)) { metaData.push(attr); } } var Model = Picasso.utils.object.extend(Constructor, Picasso.Model); for (var i = 0; i < metaData.length; i++) { (function (attr) { var methodSuffix = attr.charAt(0).toUpperCase() + attr.slice(1); Model.prototype['set' + methodSuffix] = function (val) { this.set(attr, val); }; Model.prototype['get' + methodSuffix] = function () { return this.get(attr); }; }(metaData[i])); } return Model; }; Picasso.load("View"); /** * The picasso View entity * @constructor * @extends Picasso.core.Subject */ Picasso.View = function () { var that = this; /** * @type {Picasso.Model} * @protected */ this._model = null; /** * The view form * @type {Picasso.form.PicassoForm} * @private */ this._form = null; /** * Stores the models events callbacks * @type {Object} * @protected */ this._modelEvents = {}; /** * The dynamic form builder * @type {Picasso.form.Builder} * @protected */ this._formBuilder = new Picasso.form.Builder(); /** * The main object of the view * @type {HTMLObjectElement} * @public */ this.dom = null; }; Picasso.View.prototype = new Picasso.core.Subject(); /** * Default constructor of a view * @param {HTMLObjectElement} dom */ Picasso.View.prototype.construct = function(dom){ Picasso.View.apply(this, arguments); this.dom = dom || document; }; /** * Set a model to this view * @param {Picasso.Model} model */ Picasso.View.prototype.setModel = function(model){ this._model = model; for(var i in this._modelEvents){ if(this._modelEvents.hasOwnProperty(i)){ this._model._subscribe(i, this._modelEvents[i], this); } } }; /** * Binds the model properties to the form * fields. */ Picasso.View.prototype.bindFormData = function(){ var that = this; this._model._subscribe("propertyChange", function(ev) { var property = ev.data.property; var value = ev.data.value; if (that._form != null) { var field = that._form.getField(property); field.value(value); } }); }; /** * Builds a picasso form object from the given JSON * @param {Object} formJSON * @returns {Picasso.form.PicassoForm} */ Picasso.View.prototype.buildForm = function(formJSON){ return this._formBuilder.buildForm(formJSON); }; /** * Sets the view form * @param {Picasso.form.PicassoForm} pForm */ Picasso.View.prototype.setForm = function(pForm){ this._form = pForm; }; /** * Gets the view form * @return {Picasso.form.PicassoForm} */ Picasso.View.prototype.getForm = function(){ return this._form; }; /** * Registers a model event * @param {string} eventName * @param {Function} method */ Picasso.View.prototype.register = function(eventName, method){ this._modelEvents[eventName] = method; }; /** * Extends from a View * @static * @param {Function} constructor The constructor to extend * @returns {Function} The updated constructor */ Picasso.View.extend = function(constructor){ return Picasso.utils.object.extend(constructor, Picasso.View); }; Picasso.load("form.validators.text"); /** * Default validation for text fields * @param {Picasso.form.field.PicassoField} emailField * @returns {Picasso.pjo.Validation} */ Picasso.form.validators.email = function (emailField) { var validation = new Picasso.pjo.Validation(); validation.field = emailField; validation.valid = typeof emailField.value() != "undefined"; if (!validation.valid && emailField.isRequired()) { validation.errorMessages.push("Field value is undefined"); } var emailRegex = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i; if (!emailRegex.test(emailField.value()) && emailField.isRequired()) { validation.valid = false; validation.errorMessages.push("Field contains a invalid email"); } return validation; }; Picasso.load("form.validators.hidden"); /** * Default validation for hidden fields * @param {Picasso.form.field.PicassoField} hiddenField * @returns {Picasso.pjo.Validation} */ Picasso.form.validators.hidden = function(hiddenField){ var validation = new Picasso.pjo.Validation(); validation.valid = true; return validation; }; Picasso.load("form.validators.number"); /** * Default validation for number fields * @param {Picasso.form.field.PicassoField} numberField * @returns {Picasso.pjo.Validation} */ Picasso.form.validators.number = function (numberField) { var val = numberField.value(); var validation = new Picasso.pjo.Validation(); validation.field = numberField; validation.valid = !isNaN(Number(val)); if (!validation.valid) { validation.errorMessages.push("Field value is not a number"); } }; Picasso.load("form.validators.password"); /** * Default validation for password fields * @param {Picasso.form.field.PicassoField} passwordField * @returns {Picasso.pjo.Validation} */ Picasso.form.validators.password = function(passwordField){ var validation = new Picasso.pjo.Validation(); validation.field = passwordField; validation.valid = typeof passwordField.value() != "undefined"; if(!validation.valid){ validation.errorMessages.push("Field value is undefined"); } return validation; }; Picasso.load("form.validators.text"); /** * Default validation for text fields * @param {Picasso.form.field.PicassoField} textField * @returns {Picasso.pjo.Validation} */ Picasso.form.validators.text = function(textField){ var validation = new Picasso.pjo.Validation(); validation.field = textField; validation.valid = typeof textField.value() != "undefined"; if(!validation.valid){ validation.errorMessages.push("Field value is undefined"); } return validation; }; Picasso.load("form.field.PicassoField"); /** * The default field implementation * @constructor */ Picasso.form.field.PicassoField = function (label, type, required, formIgnore) { /** * Sequence manager * @type {Picasso.core.Sequence} */ var Sequence = Picasso.load("core.Sequence"); /** * The html utils * @type {utils/html} */ var htmlUtils = Picasso.load("utils.html"); /** * The id of the field * @type {string|number} * @protected */ this._id = null; /** * The html of the field * @type {HTMLElement} * @protected */ this._element = null; /** * The field label * @type {string} * @protected */ this._label = ""; /** * If this field is ignored in * a form final value * @type {boolean} * @protected */ this._formIgnore = false; /** * The flag to mark this field as required * @type {boolean} * @protected */ this._required = false; /** * The type of this field * @type {string} * @protected */ this._type = ""; /** * Calls a 'post constructor' to this object * @param {string} label * @param {string} type * @param {boolean} required * @param {boolean} formIgnore * @private */ this.__postConstructor__ = function(label, type, required, formIgnore){ this._label = label; this._type = type; this._required = required; this._formIgnore = formIgnore; }; /** * Builds the field * @param {Picasso.pjo.Field} field * @abstract * @throws {Picasso.error.NotImplementedError} */ this.build = function (field) { throw new Picasso.error.NotImplementedError("PicassoField", "build"); }; /** * Verifies if the field is empty or not * @returns {boolean} * @abstract * @throws {Picasso.error.NotImplementedError} */ this.isEmpty = function () { throw new Picasso.error.NotImplementedError("PicassoField", "isEmpty"); }; /** * Returns or sets the value of a field * @param {*} val * @abstract * @throws {Picasso.error.NotImplementedError} */ this.value = function (val) { throw new Picasso.error.NotImplementedError("PicassoField", "value"); }; /** * Resets the field * @abstract * @throws {Picasso.error.NotImplementedError} */ this.reset = function () { throw new Picasso.error.NotImplementedError("PicassoField", "reset"); }; /** * Get this field id * @returns {string|number} */ this.getId = function () { return this._id; }; /** * Get the field label * @return {string} */ this.getLabel = function(){ return this._label; }; /** * Verify if the field is required * @return {boolean} */ this.isRequired = function(){ return this._required; }; /** * Verify if the field is ignored in the form * @return {boolean} */ this.isFormIgnored = function(){ return this._formIgnore; }; /** * Get the field type * @return {string} */ this.getType = function(){ return this._type; }; /** * Sets this field id, if not is given * generates the id based on a sequence * @param {string|number} id */ this.setId = function (id) { if (id == null || id == "") { var entity = this.type || "PicassoField"; this._id = new Sequence(entity).nextVal(); } else { if (this._element != null) { this._element.setAttribute("id", String(id)); } this._id = id; } }; /** * Add one or more classes (separated by space) to the field * @param {string} _class */ this.addClass = function (_class) { htmlUtils.addClass(this._element, _class); }; /** * Removes one or more classes (separated by space) to the field * @param {string} _class */ this.removeClass = function (_class) { htmlUtils.removeClass(this._element, _class); }; /** * Get the HTMLElement of this field * @return {HTMLElement} */ this.getHTMLElement = function () { return this._element; }; /** * Sets the HTMLElement of this field * @param {HTMLElement} element */ this.setHTMLElement = function (element) { if (element instanceof HTMLElement) { this._element = element; } }; }; Picasso.load("form.field.ButtonField"); /** * Default buttons constructor * @constructor * @extends {Picasso.form.field.PicassoField} */ Picasso.form.field.ButtonField = function () { /** @type {utils/html} */ var htmlUtils = Picasso.load("utils.html"); /** * Verify if the button is empty * @returns {boolean} */ this.isEmpty = function () { return false; }; /** * Gets the value of the button * @returns {*} */ this.value = function () { return this._element.value; }; /** * Builds the button field * @param {Picasso.pjo.Field} field */ this.build = function (field) { this.setId(field.id); var buttomElement = document.createElement("button"); htmlUtils.setAttributes(buttomElement, { id: this.getId(), type: field.type || "button", class: "btn btn-default" }); htmlUtils.setAttributes(buttomElement, field.attrs); buttomElement.innerHTML = field.value; this.formIgnore = true; this.setHTMLElement(buttomElement); }; }; Picasso.form.field.ButtonField.prototype = new Picasso.form.field.PicassoField(); Picasso.load("form.field.HiddenField"); /** * The default hidden input builder * @constructor * @extends {Picasso.form.field.PicassoField} */ Picasso.form.field.HiddenField = function () { /** @type {utils/html} */ var htmlUtils = Picasso.load("utils.html"); /** * Verify if the input is empty * @returns {boolean} */ this.isEmpty = function () { return this.value() == ""; }; /** * Gets/Sets the value of t he input * @param {*} val * @returns {*} */ this.value = function (val) { var el = this._element; if (typeof val != "undefined") { el.value = val; } else { var res = el.value; return res == ""? null : res; } }; /** * The HTMLElement builder * @param {Picasso.pjo.Field} field * @return {HTMLElement} */ this.build = function (field) { var fieldElement = document.createElement("input"); htmlUtils.setAttributes(fieldElement, { name: field.id || "", type: "hidden" }); htmlUtils.setAttributes(fieldElement, field.attrs); this.setHTMLElement(fieldElement); }; }; Picasso.form.field.HiddenField.prototype = new Picasso.form.field.PicassoField(); Picasso.load("form.field.InputField"); /** * The default 'input' builder * @constructor * @extends {Picasso.form.field.PicassoField} */ Picasso.form.field.InputField = function () { /** @type {utils/html} */ var htmlUtils = Picasso.load("utils.html"); /** * Verify if the input is empty * @returns {boolean} */ this.isEmpty = function () { return this.value() == ""; }; /** * Gets/Sets the value of the input * @param {*} val * @returns {*} */ this.value = function (val) { var el = this._element.getElementsByTagName("input")[0]; if (typeof val != "undefined") { el.value = val; } else { return el.value; } }; /** * The HTMLElement builder * @param {Picasso.pjo.Field} field * @return {HTMLElement} */ this.build = function (field) { var formGroup = document.createElement("div"); formGroup.setAttribute("class", "form-group"); var fieldElement = document.createElement("input"); htmlUtils.setAttributes(fieldElement, { name: field.id || "", type: field.type || "text" }); htmlUtils.setAttributes(fieldElement, field.attrs); htmlUtils.addClass(fieldElement, "form-control"); var labelElement = document.createElement("label"); labelElement.setAttribute("class", "control-label"); labelElement.innerHTML = field.label; formGroup.appendChild(labelElement); formGroup.appendChild(fieldElement); this.setHTMLElement(formGroup); }; }; Picasso.form.field.InputField.prototype = new Picasso.form.field.PicassoField(); Picasso.load("form.Builder"); /** * Translates and builds a form from * a object to HTML elements * @constructor */ Picasso.form.Builder = function () { //Load dependencies /** @type {utils/array} */ this.arrayUtils = Picasso.load("utils.array"); /** @type {utils/html} */ this.htmlUtils = Picasso.load("utils.html"); /** @type {utils/object} */ this.objUtils = Picasso.load("utils.object"); /**@type {Picasso.form.FieldFactory} */ this.fieldFactory = new Picasso.form.FieldFactory(); }; /** * Translates a columns object into a set of HTML elements * @param {Picasso.pjo.Column} col * @param {Picasso.form.PicassoForm} pForm * @returns {HTMLDivElement} */ Picasso.form.Builder.prototype.buildColumn = function (col, pForm) { col = this.objUtils.deserialize(col, Picasso.pjo.Column); var column = document.createElement("div"); this.htmlUtils.setAttributes(column, col.attrs); column.setAttribute("id", col.id); var colSizeClass = "col-xs-"; colSizeClass += col.colXSize || Picasso.pjo.Column.colSize.MEDIUM; this.htmlUtils.addClass(column, "column " + colSizeClass); var that = this; this.arrayUtils.each(col.fields, function (field) { var picassoField = that.fieldFactory.create(field); pForm.addField(picassoField); column.appendChild(picassoField.getHTMLElement()); }); return column; }; /** * Translates a serialized grid into a HTML div * @param {Picasso.pjo.Grid} grid * @param {Picasso.form.PicassoForm} pForm */ Picasso.form.Builder.prototype.buildGrid = function (grid, pForm) { grid = this.objUtils.deserialize(grid, Picasso.pjo.Grid); var that = this; var divElement = document.createElement("div"); if (grid.legend != null || grid.legend != "") { var legend = document.createElement("p"); legend.innerHTML = grid.legend; this.htmlUtils.addClass(legend, "bg-info"); divElement.appendChild(legend); } this.htmlUtils.setAttributes(divElement, grid.attrs); divElement.setAttribute('id', grid.id); this.htmlUtils.addClass(divElement, "grid-block"); this.arrayUtils.each(grid.columns, function (fieldSet) { divElement.appendChild(that.buildColumn(fieldSet, pForm)); }); return divElement; }; /** * Translates a serialized form to a HTML form * @param {Picasso.pjo.Form} form * @returns {Picasso.form.PicassoForm} */ Picasso.form.Builder.prototype.buildForm = function (form) { form = this.objUtils.deserialize(form, Picasso.pjo.Form); var pForm = new Picasso.form.PicassoForm(); var formElement = document.createElement("form"); formElement.setAttribute("id", form.id); formElement.setAttribute("role", "form"); formElement.setAttribute("novalidate", "novalidate"); this.htmlUtils.setAttributes(formElement, form.attrs); var that = this; this.arrayUtils.each(form.grid, function (block) { formElement.appendChild(that.buildGrid(block, pForm)); }); pForm.setHTMLElement(formElement); return pForm; }; Picasso.load("form.FieldFactory"); /** * A field factory * @constructor */ Picasso.form.FieldFactory = function () {}; /** * All the available field constructors * Can be a method name or the function itself * @type {Object} * @static */ Picasso.form.FieldFactory.constructors = (function(){ return { hidden: Picasso.form.field.HiddenField, text: Picasso.form.field.InputField, textArea: Picasso.form.field.InputField, email: Picasso.form.field.InputField, password: Picasso.form.field.InputField, submit: Picasso.form.field.ButtonField, cancel: Picasso.form.field.ButtonField, button: Picasso.form.field.ButtonField } })(); /** * Sets some picasso attributes to the html field element * @param {Picasso.form.field.PicassoField} pField * @private */ Picasso.form.FieldFactory.prototype._setPicassoAttributes = function (pField) { /** @type {utils/html} */ var htmlUtils = Picasso.load("utils.html"); if (pField.required) { htmlUtils.addClass(pField.getHTMLElement(), "prequired"); } if (pField.formIgnore) { htmlUtils.addClass(pField.getHTMLElement(), "pform-ignore"); } }; /** * Strategy pattern to choose the right * field builder method * @param {string} fieldType * @returns {Picasso.form.field.PicassoField.constructor} * @throws {Picasso.error.InvalidFieldType} * @private */ Picasso.form.FieldFactory.prototype._getFieldConstructorByFieldType = function (fieldType) { var constructors = this.constructor.constructors; if (constructors.hasOwnProperty(fieldType)) { var fieldConstructor = constructors[fieldType]; if (typeof fieldConstructor === 'string') { return this[fieldConstructor]; } else { return fieldConstructor; } } throw new Picasso.error.InvalidFieldType(fieldType); }; /** * Builds a field element * @param {Picasso.pjo.Field} field * @returns {Picasso.form.field.PicassoField} The picasso field object */ Picasso.form.FieldFactory.prototype.create = function (field) { var objUtils = Picasso.load("utils.object"); field = objUtils.deserialize(field, Picasso.pjo.Field); var FieldConstructor = this._getFieldConstructorByFieldType(field.type); var picassoField = new FieldConstructor(); picassoField.__postConstructor__(field.label, field.type, field.required, field.formIgnore); picassoField.build(field); if (field.hasOwnProperty("id")) { picassoField.setId(field.id); } if(field.value != null){ picassoField.value(field.value); } this._setPicassoAttributes(picassoField); return picassoField; }; Picasso.load("form.PicassoForm"); /** * The Picasso representation of a form * @constructor */ Picasso.form.PicassoForm = function () { /** * The form fields * @type {Object} * @private */ var fields = {}; /** * The HTML representation of this object * @type {HTMLFormElement} * @private */ var element = null; /** * Describes the valid state of the form * @type {boolean} */ this.valid = false; /** * Adds a field to the form * @param {Picasso.form.field.PicassoField} pField */ this.addField = function (pField) { fields[pField.getId()] = pField; }; /** * Gets the form fields * @returns {Picasso.form.field.PicassoField[]} */ this.getFields = function () { var res = []; for (var i in fields) { if (fields.hasOwnProperty(i)) { res.push(fields[i]); } } return res; }; /** * Gets a field by the Id * @param {string} fieldId * @return {Picasso.form.field.PicassoField} */ this.getField = function (fieldId) { if (fields.hasOwnProperty(fieldId)) { return fields[fieldId]; } return null; }; /** * Sets the html element * @param {HTMLFormElement} htmlForm */ this.setHTMLElement = function (htmlForm) { element = htmlForm; }; /** * Gets the html element * @returns {HTMLFormElement} */ this.getHTMLElement = function () { return element; }; /** * Gets/Sets the form value * @param {Object} data * returns {Object} */ this.value = function (data) { var fields = this.getFields(); if (typeof data == "undefined") { var val = {}; for (var i = 0; i < fields.length; i++) { var id = fields[i].getId(); if (typeof id != "undefined" && !fields[i].formIgnore) { val[id] = fields[i].value(); } } return val; } else { for (var key in data) { if (data.hasOwnProperty(key)) { this.getField(key).value(data[key]); } } } }; }; /** * A alias to the Picasso Form object constructor * @alias {Picasso.form.PicassoForm} */ Picasso.Form = Picasso.form.PicassoForm; Picasso.load("form.Renderer"); /** * Manage the render of a dynamic form * @param {HTMLElement} container * @constructor */ Picasso.form.Renderer = function (container) { this.container = container; }; /** * Builds and renders the given form * @param {Picasso.pjo.Form} form */ Picasso.form.Renderer.prototype.render = function(form) { }; Picasso.load("form.Validator"); /** * Validates fields * @param {Picasso.form.Form} _form * @constructor */ Picasso.form.Validator = function (_form) { var log = Picasso.load("utils.log"); var form = _form; /** * Validates a field * @param {Picasso.form.field.PicassoField} pField * @returns {Picasso.pjo.Validation} */ this.validate = function (pField) { var validation = new Picasso.pjo.Validation(); validation.field = pField; validation.valid = false; if (!pField.isRequired() || !pField.isEmpty()) { if (Picasso.form.validators.hasOwnProperty(pField.getType())) { return Picasso.form.validators[pField.getType()](pField); } else { log.warn("No validator found to the field type " + pField.getType(), pField); validation.valid = null; return validation; } } validation.errorMessages.push("Field is required"); return validation; }; /** * Validates a entire form, returning the id * and the validation value * @param {Picasso.form.PicassoForm} pForm * @returns {{string: boolean}} */ this.validateForm = function (pForm) { var fields = pForm.getFields(); var validation = {}; var formValid = true; for (var i = 0; i < fields.length; i++) { var f = fields[i]; if (!f.isFormIgnored()) { validation[f.getId()] = this.validate(f); //If there's no validator registered, the valid property is always null if (validation[f.getId()].valid !== null) { formValid = formValid && !!validation[f.getId()].valid; } } } pForm.valid = formValid; return validation; }; };