/*
* forbject
* http://github.com/yawetse/forbject
* inspired by: https://github.com/serbanghita/formToObject.js
* Copyright (c) 2014 Yaw Joseph Etse. All rights reserved.
*/
'use strict';
var events = require('events'),
util = require('util');
/**
* A module that represents a forbject object, a componentTab is a page composition tool.
* @{@link https://github.com/typesettin/forbject}
* @author Yaw Joseph Etse
* @copyright Copyright (c) 2014 Typesettin. All rights reserved.
* @license MIT
* @constructor forbject
* @requires module:events
* @requires module:util
* @param {object} formRef element selector of form element or actual form element
*/
var forbject = function (formRef) {
events.EventEmitter.call(this);
if (!formRef) {
return false;
}
this.formRef = formRef;
this.keyRegex = /[^\[\]]+/g;
this.$form = null;
this.$formElements = [];
this.formObj = {};
this.refresh = this._refresh;
this.getObject = this._getObject;
if (!this.setForm()) {
return false;
}
if (!this.setFormElements()) {
return false;
}
this.setFormObj();
};
util.inherits(forbject, events.EventEmitter);
/**
* refresh form object key/value pair mapping
* @return {object} form object
* @emits refresh
*/
forbject.prototype._refresh = function () {
this.formObj = {};
this.setFormObj();
this.emit('refresh');
};
/**
* returns form object
* @return {object} form object
*/
forbject.prototype._getObject = function () {
return this.formObj;
};
/**
* Set the main form object we are working on.
* @return {object} Form Element Object
*/
forbject.prototype.setForm = function () {
try {
switch (typeof this.formRef) {
case 'string':
this.$form = document.querySelector(this.formRef);
break;
case 'object':
if (this.isDomNode(this.formRef)) {
this.$form = this.formRef;
}
break;
}
this.emit('init');
return this.$form;
}
catch (e) {
throw new Error(e);
}
};
/**
* Set the elements we need to parse.
* @return {number} number of form elements
*/
forbject.prototype.setFormElements = function () {
this.$formElements = this.$form.querySelectorAll('input, button, textarea, select');
return this.$formElements.length;
};
/**
* Check to see if the object is a HTML node.
* @param {object} node dom element
* @return {Boolean} if object is a dom node
*/
forbject.prototype.isDomNode = function (node) {
return typeof node === 'object' && 'nodeType' in node && node.nodeType === 1;
};
/**
* Iteration through arrays and objects. Compatible with IE.
* @param {Array} arr array to iterate through
* @param {Function} callback async callback
*/
forbject.prototype.forEach = function (arr, callback) {
if ([].forEach) {
return [].forEach.call(arr, callback);
}
var i;
for (i in arr) {
// Object.prototype.hasOwnProperty instead of arr.hasOwnProperty for IE8 compatibility.
if (Object.prototype.hasOwnProperty.call(arr, i)) {
callback.call(arr, arr[i]);
}
}
return;
};
/**
* Recursive method that adds keys and values of the corresponding fields.
* @param {object} result form object
* @param {object} domNode element in form object
* @param {string} keys regex result of form elements
* @param {object} value value of domNode
*/
forbject.prototype.addChild = function (result, domNode, keys, value) {
// #1 - Single dimensional array.
if (keys.length === 1) {
// We're only interested in the radio that is checked.
if (domNode.nodeName === 'INPUT' && domNode.type === 'radio') {
if (domNode.checked) {
return result[keys] = value;
}
else {
return;
}
}
// Checkboxes are a special case. We have to grab each checked values
// and put them into an array.
if (domNode.nodeName === 'INPUT' && domNode.type === 'checkbox') {
if (domNode.checked) {
if (!result[keys]) {
result[keys] = [];
}
return result[keys].push(value);
}
else {
return;
}
}
// Multiple select is a special case.
// We have to grab each selected option and put them into an array.
if (domNode.nodeName === 'SELECT' && domNode.type === 'select-multiple') {
result[keys] = [];
var DOMchilds = domNode.querySelectorAll('option[selected]');
if (DOMchilds) {
this.forEach(DOMchilds, function (child) {
result[keys].push(child.value);
});
}
return;
}
// Fallback. The default one to one assign.
result[keys] = value;
}
// #2 - Multi dimensional array.
if (keys.length > 1) {
if (!result[keys[0]]) {
result[keys[0]] = {};
}
return this.addChild(result[keys[0]], domNode, keys.splice(1, keys.length), value);
}
return result;
};
/**
* iterate through form element items and append enabled elements to form object
*/
forbject.prototype.setFormObj = function () {
var test, i = 0;
for (i = 0; i < this.$formElements.length; i++) {
// Ignore the element if the 'name' attribute is empty.
// Ignore the 'disabled' elements.
if (this.$formElements[i].name && !this.$formElements[i].disabled) {
test = this.$formElements[i].name.match(this.keyRegex);
this.addChild(this.formObj, this.$formElements[i], test, this.$formElements[i].value);
}
}
this.emit('serialized', this.formObj);
return this.formObj;
};
module.exports = forbject;