Source: formie.js

/*
 * formie
 * http://github.com/yawetse/formie
 *
 * Copyright (c) 2014 Yaw Joseph Etse. All rights reserved.
 */
'use strict';

var async = require('async'),
	classie = require('classie'),
	events = require('events'),
	forbject = require('forbject'),
	querystring = require('querystring'),
	request = require('superagent'),
	util = require('util'),
	extend = require('util-extend'),
	jsonpscript,
	documentHeadElement;

/**
 * A module that represents a formie object, a componentTab is a page composition tool.
 * @{@link https://github.com/typesettin/formie}
 * @author Yaw Joseph Etse
 * @copyright Copyright (c) 2014 Typesettin. All rights reserved.
 * @license MIT
 * @constructor formie
 * @requires module:async
 * @requires module:classie
 * @requires module:events
 * @requires module:forbject
 * @requires module:querystring
 * @requires module:superagent
 * @requires module:util-extend
 * @requires module:util
 * @param {object} options configuration options
 * @example 
		ajaxsubmitclassname: 'formie',
		ajaxsubmitfileuploadclassname: 'formie-file',
		ajaxformselector: '#formie',
		jsonp: false,
		autosubmitselectors: '.autoFormSubmit',
		autosubmitelements: [],
		preventsubmitselectors: '.noFormSubmit',
		preventsubmitelements: [],
		headers: {},
		queryparameters: {},
		postdata: {},
		beforesubmitcallback: null,
		errorcallback: null,
		successcallback: null
 */
var formie = function (options) {
	events.EventEmitter.call(this);

	var defaultOptions = {
		//ajaxsubmitclassname: 'formie',
		ajaxsubmitfileuploadclassname: 'formie-file',
		ajaxformselector: '#formie',
		autosubmitselectors: '.autoFormSubmit',
		autosubmitelements: [],
		preventsubmitselectors: '.noFormSubmit',
		preventsubmitelements: [],
		headers: {},
		queryparameters: {},
		postdata: {},
		beforesubmitcallback: null,
		errorcallback: null,
		successcallback: null
	};
	this.options = extend(defaultOptions, options);

	this.init = this._init;
	this.ajaxSubmitFormie = this.__ajaxSubmitFormie;
	this.submitOnChangeListeners = this.__submitOnChangeListeners;
	this.autoSubmitFormOnChange = this.__autoSubmitFormOnChange;
	this.preventEnterSubmitListeners = this.__preventEnterSubmitListeners;
	this.preventSubmitOnEnter = this.__preventSubmitOnEnter;
	this.ajaxFormEventListers = this.__ajaxFormEventListers;
	this.submit = this.__submit;
	this.init();
	// this.render = this._render;
	// this.addBinder = this._addBinder;
};

util.inherits(formie, events.EventEmitter);

/**
 * asynchronously submit from data, supports, POST, GET, and GET JSONP
 * @param  {object} e       form submit event
 * @param  {object} element form html element
 * @return {Function} ajaxResponseHandler(error, response)
 * @emits submitted(formieData)
 */
formie.prototype.__ajaxSubmitFormie = function (e, element) {

	if (e) {
		e.preventDefault();
	}
	var f = (element) ? element : e.target,
		beforefn,
		errorfn,
		successfn,
		formieDataFromForm,
		formieData,
		ajaxResponseHandler = function (err, response) {
			if (err && this.options.errorcallback) {
				errorfn = this.options.errorcallback;
				if (typeof errorfn === 'function') {
					errorfn(err, response);
				}
				else if (typeof window[errorfn] === 'function') {
					errorfn = window[errorfn];
					errorfn(err, response);
				}
			}
			else if (this.options.successcallback) {
				successfn = this.options.successcallback;
				if (typeof successfn === 'function') {
					successfn(response);
				}
				else if (typeof window[successfn] === 'function') {
					successfn = window[successfn];
					successfn(response);
				}
			}
			this.emit('submitted', formieData);
		}.bind(this);

	if (this.options.beforesubmitcallback) {
		beforefn = this.options.beforesubmitcallback;
		if (typeof beforefn === 'function') {
			beforefn(e, f);
		}
		else if (typeof window[beforefn] === 'function') {
			beforefn = window[beforefn];
			beforefn(e, f);
		}
	}

	formieDataFromForm = new forbject(f).getObject();
	// console.log('f.getAttribute("enctype")', f.getAttribute('enctype'));

	this.options.method = (f.getAttribute('method')) ? f.getAttribute('method').toLowerCase() : this.options.method.toLowerCase();
	this.options.action = (f.getAttribute('action')) ? f.getAttribute('action') : (this.options.action) ? this.options.action : window.location.href;

	if (this.options.jsonp) {
		formieData = extend(formieDataFromForm, this.options.queryparameters);
		// head element
		documentHeadElement = (documentHeadElement) ? documentHeadElement : document.getElementsByTagName('head')[0];

		// remove existing 
		if (document.querySelector('#formie-jsonp')) {
			documentHeadElement.removeChild(document.querySelector('#formie-jsonp'));
		}

		// Create a new script element
		jsonpscript = document.createElement('script');

		// Set its source to the JSONP API
		jsonpscript.src = this.options.action + '?' + querystring.stringify(formieData);
		jsonpscript.id = 'formie-jsonp';

		window[formieData.callback] = this.options.successcallback;

		// Stick the script element in the page <head>
		documentHeadElement.appendChild(jsonpscript);
	}
	else if (f.getAttribute('enctype') === 'multipart/form-data') {
		var formData = new FormData(f),
			fileInputs = f.querySelectorAll('input[type="file"]'),
			// reader = new FileReader(),
			client = new XMLHttpRequest(),
			asyncFunctions = [],
			asyncFileReaderCB = function (fileinputname, file) {
				return function (cb) {
					var filereader = new FileReader();
					filereader.readAsDataURL(file);
					filereader.onload = function () {
						// console.log('e', e);
						// console.log('filereader', filereader);
						formData.append(fileinputname, filereader.result, file.name);
						cb(null, file);
					};
				};
			};

		formieData = formieDataFromForm;
		//loop through file inputs and append files to formData
		if (fileInputs) {
			for (var r = 0; r < fileInputs.length; r++) {
				if (fileInputs[r].files.length > 0) {
					for (var s = 0; s < fileInputs[r].files.length; s++) {
						// reader.readAsDataURL(fileInputs[r].files[s]);
						asyncFunctions.push(asyncFileReaderCB(fileInputs[r].name, fileInputs[r].files[s]));
					}
				}
			}
			async.parallel(
				asyncFunctions,
				function (err) { //, results) {
					// console.log('async results', results);
					try {
						client.open(this.options.method, this.options.action + '?' + querystring.stringify(this.options.queryparameters), true);
						if (this.options.headers) {
							for (var i in this.options.headers) {
								client.setRequestHeader(i, this.options.headers[i]);
							}
						}
						client.send(formData); /* Send to server */
						// client.onreadystatechange = function () {}
						client.onabort = function (err) {
							ajaxResponseHandler(err);
						};
						client.onerror = function (err) {
							ajaxResponseHandler(err);
						};
						client.onloadend = function () {
							if (client.readyState === 4) {
								if (client.status !== 200) {
									ajaxResponseHandler(client.statusText, client);
								}
								else {
									var res = {};
									res.body = JSON.parse(client.response);
									ajaxResponseHandler(null, res);
								}
							}
						};
					}
					catch (e) {
						ajaxResponseHandler(e);
					}
				}.bind(this)
			);

		}
		// console.log('fileInputs', fileInputs, f, formData);
	}
	else if (this.options.method === 'get') {
		formieData = extend(formieDataFromForm, this.options.queryparameters);
		request
			.get(this.options.action)
			.set(this.options.headers)
			.query(formieData)
			.end(ajaxResponseHandler);
	}
	else if (this.options.method === 'delete' || this.options.method === 'del') {
		formieData = extend(formieDataFromForm, this.options.queryparameters);
		request
			.del(this.options.action)
			.set(this.options.headers)
			.send(formieData)
			.end(ajaxResponseHandler);
	}
	else if (this.options.method === 'post') {
		formieData = extend(formieDataFromForm, this.options.postdata);
		request
			.post(this.options.action)
			.set(this.options.headers)
			.query(this.options.queryparameters)
			.send(formieData)
			.end(ajaxResponseHandler);
	}
	return false;
};

/**
 * submit formie via ajax
 */
formie.prototype.__submit = function () {
	this.ajaxSubmitFormie(null, this.options.form);
};

/**
 * submit current form if html element has ajaxsubmitclassname class
 * @emits autosubmitelement(element)
 */
formie.prototype.__autoSubmitFormOnChange = function () {
	var formElement = (this.form) ? this.form : this.options.form;

	this.ajaxSubmitFormie(null, formElement);
	this.emit('autosubmitelement', formElement);
	/*
	// console.log('formElement', formElement);
	if (classie.hasClass(formElement, this.options.ajaxsubmitclassname)) {
		this.ajaxSubmitFormie(null, formElement);
		this.emit('autosubmitelement', formElement);
	}
	else {
		formElement.submit();
	}
	*/
};

/**
 * add change listener for form elements with autosubmitselectors class
 */
formie.prototype.__submitOnChangeListeners = function () {
	this.options.autosubmitelements = this.options.form.querySelectorAll(this.options.autosubmitselectors);

	for (var x in this.options.autosubmitelements) {
		if (typeof this.options.autosubmitelements[x] === 'object') {
			this.options.autosubmitelements[x].addEventListener('change', this.autoSubmitFormOnChange.bind(this), false);
		}
	}
};

/**
 * prevent element from submitting form when pressing enter key
 * @param  {object} e keypress event
 * @return {boolean}   also e.preventDefault();
 * @emits prevententer(e.target)
 */
formie.prototype.__preventSubmitOnEnter = function (e) {
	if (e.which === 13 || e.keyCode === 13) {
		e.preventDefault();
		this.emit('prevententer', e.target);
		return false;
	}
};

/**
 * add keypress listeners to form elements that have preventsubmitselectors class to prevent submitting form on enter key
 */
formie.prototype.__preventEnterSubmitListeners = function () {

	this.options.preventsubmitelements = this.options.form.querySelectorAll(this.options.preventsubmitselectors);
	// console.log(this.options.preventsubmitelements);
	for (var x in this.options.preventsubmitelements) {
		if (typeof this.options.preventsubmitelements[x] === 'object') {
			this.options.preventsubmitelements[x].addEventListener('keypress', this.preventSubmitOnEnter.bind(this), false);
			this.options.preventsubmitelements[x].addEventListener('keydown', this.preventSubmitOnEnter.bind(this), false);
		}
	}
	// document.addEventListener('keypress', preventSubmitOnEnter, false);
};

/**
 * add submit event listener to formie form
 */
formie.prototype.__ajaxFormEventListers = function () {
	this.options.form.addEventListener('submit', this.ajaxSubmitFormie.bind(this), false);
};

/**
 * sets this.options.form, also adds event listener for formie form [this.ajaxFormEventListers()], adds auto submit form listeners [this.submitOnChangeListeners()], and prevent submit listeners [this.preventEnterSubmitListeners()]
 * @emits initialized
 */
formie.prototype._init = function () {
	this.options.form = (this.options.form) ? this.options.form : document.querySelector(this.options.ajaxformselector);

	this.ajaxFormEventListers();
	this.submitOnChangeListeners();
	this.preventEnterSubmitListeners();
	this.emit('initialized');
};
module.exports = formie;