/
formium.js
118 lines (111 loc) · 3.3 KB
/
formium.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
'use strict';
// jshint scripturl:true
var $ = require('dominus');
var queso = require('queso');
var safeson = require('safeson');
var state = require('./lib/state');
var transformers = [];
var formium = {
submit: submit,
transform: transform,
configure: state.configure
};
function noop () {}
function transform (fn) {
transformers.push(fn);
}
/* AJAX form submissions use an intermediary <iframe> to help browsers remember
* autocompletion suggestions. We submit the form against the <iframe> and then
* grab the response as plain-text JSON that we need to parse into JSON.
* Afterwards we can hand that JSON to the response handler, business as usual.
* Since we've submitted the form "organically", the browser stores suggestions.
*/
function frame (form) {
var name = 'ff-' + new Date().valueOf();
$(form)
.attr('autocomplete', 'on')
.attr('target', name);
return $('<iframe>')
.css('display', 'none')
.attr('id', name)
.attr('name', name)
.attr('src', 'javascript:void 0')
.afterOf(form);
}
function submit (form, done) {
var textareaCloneValue = 'data-clone-value';
var iframe = frame(form);
var content = iframe[0].contentWindow;
var restore = transformers.map(run);
var textareas = $('textarea', form);
textareas.forEach(preserveValue);
var formClone = form.cloneNode(true);
var textareaClones = $('textarea', formClone);
disable(form);
$('button', form).forEach(disable);
$('[autofocus]', formClone).attr('autofocus', null);
textareaClones.forEach(updateValue);
textareas.forEach(restoreTextarea);
var formCloneId = 'f' + iframe[0].id;
formClone.id = formCloneId;
iframe.once('load', grabResponse);
content.document.body.appendChild(formClone);
var frameForm = content.document.getElementById(formCloneId);
var amp;
if (state.qs) {
amp = frameForm.action.indexOf('?') !== -1;
frameForm.action += queso.stringify(state.qs(form), amp);
}
frameForm.onsubmit = null;
frameForm.submit();
restore.forEach(run);
function preserveValue (textarea) {
var ta = $(textarea);
ta.attr(textareaCloneValue, ta.value());
}
function updateValue (textarea) {
var ta = $(textarea);
ta.value(ta.attr(textareaCloneValue)).attr(textareaCloneValue, null);
}
function restoreTextarea (textarea) {
$(textarea).attr(textareaCloneValue, null);
}
function grabResponse () {
var html = readResponse(content);
if (!html) {
gotResponse(new Error('Internal Server Error')); return;
}
var json = decodeResponse(html);
var err = json ? null : new Error('Malformed response');
gotResponse(err, json);
function gotResponse (err, data) {
$('button', form).forEach(enable);
enable(form);
iframe.remove();
(done || noop).call(form, err, data);
$(form).emit('formium', { error: err, data: data });
}
}
function readResponse (content) {
try {
return content.document.body.innerHTML;
} catch (e) { // failure to read (most likely) means server crashed or response timed out
}
}
function decodeResponse (html) {
try {
return safeson.decode(html);
} catch (e) {
}
}
function run (fn) {
return (fn || noop)(form);
}
function disable (el) {
el.disabled = true;
}
function enable (el) {
el.disabled = false;
}
}
module.exports = formium;