Source: forbject.js

/*
 * 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;