├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── dist
└── ajaxable.min.js
├── docs
└── API.md
├── package.json
├── src
├── Ajaxable.js
└── index.js
├── test
├── helper.js
└── index.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 | .eslintrc
4 | *.html
5 |
6 | private/
7 | coverage/
8 | node_modules/
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5.6"
4 | after_script: babel-node ./node_modules/istanbul/lib/cli cover node_modules/.bin/_mocha -- --compilers js:babel-core/register --require ./test/helper.js --recursive -R spec ./test/*.js && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Artur Arseniev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Ajaxable](https://ajaxable.js.org/)
2 |
3 | [](https://travis-ci.org/artf/ajaxable)
4 | [](https://coveralls.io/github/artf/ajaxable?branch=master)
5 |
6 | This library simply takes the standard HTML forms as an input and make them send requests via AJAX keeping HTML5 validations.
7 |
8 | [Demo](https://ajaxable.js.org/)
9 |
10 |
11 | ## Installation
12 |
13 | Download the file from [here](https://cdn.rawgit.com/artf/ajaxable/master/dist/ajaxable.min.js), via `npm i ajaxable` or get it directly from the `/dist` folder
14 |
15 |
16 | ## Usage
17 |
18 | Basic
19 |
20 | ```html
21 |
22 |
23 |
28 |
29 |
32 | ```
33 |
34 | Listen events
35 |
36 | ```js
37 | ajaxable('#myform')
38 | .onStart(function(params) {
39 | // Make stuff before each request, eg. start 'loading animation'
40 | })
41 | .onEnd(function(params) {
42 | // Make stuff after each request, eg. stop 'loading animation'
43 | })
44 | .onResponse(function(res, params) {
45 | // Make stuff after on response of each request
46 | })
47 | .onError(function(err, params) {
48 | // Make stuff on errors
49 | });
50 | ```
51 | The `params` argument is an object containing additional data about the specific request. For example, `el` is the form element which made the request and `activeRequests` indicates how many requests are still pending (useful with multiple forms)
52 |
53 |
54 | ## Development
55 |
56 | Clone the repository and enter inside the folder
57 |
58 | ```sh
59 | $ git clone https://github.com/artf/ajaxable.git
60 | $ cd ajaxable
61 | ```
62 |
63 | Install it
64 |
65 | ```sh
66 | $ npm i
67 | ```
68 |
69 | Start the dev server
70 |
71 | ```sh
72 | $ npm start
73 | ```
74 |
75 | Build before the commit. This will also increase the patch level version of the package
76 |
77 | ```sh
78 | $ npm run build
79 | ```
80 |
81 |
82 | ## API
83 |
84 | [API Reference here](./docs/API.md)
85 |
86 |
87 | ## Testing
88 |
89 | Run tests
90 |
91 | ```sh
92 | $ npm test
93 | ```
94 |
95 | Run and watch tests
96 |
97 | ```sh
98 | $ npm run test:dev
99 | ```
100 |
101 | ## Compatibility
102 |
103 | All modern browsers (IE > 9)
104 |
105 | ## License
106 |
107 | MIT
108 |
--------------------------------------------------------------------------------
/dist/ajaxable.min.js:
--------------------------------------------------------------------------------
1 | /*! ajaxable - 0.2.3 */
2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.ajaxable=t():e.ajaxable=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}var o=n(1),i=r(o);e.exports=function(e,t){return new i["default"](e,t)}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var a=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:"";o(this,t);var r=i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));if(!e)throw new Error("The element is empty");var s={responseType:"json",headers:{}},a=n||{};for(var u in s)u in a||(a[u]=s[u]);var f="X-Requested-With";""!=a.headers[f]&&(a.headers[f]="XMLHttpRequest"),r.els=r.parseEl(e),r.opts=a;for(var c=0;c
2 |
3 | # constructor
4 |
5 | Init the form by providing the element, it can be either HTML selector or the form element (HTMLFormElement).
6 |
7 | **Parameters**
8 |
9 | - `el` **([string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [HTMLFormElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement))**
10 | - `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** Options
11 | - `options.responseType` **\[[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** Define the response type, eg. `json`(default), `blob`, `arraybuffer`, leave empty if undefined
12 | - `options.headers` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** Define custom headers
13 |
14 | **Examples**
15 |
16 | ```javascript
17 | ajaxable('form.ajaxable', {
18 | responseType: '',
19 | headers: {
20 | 'Content-Type': 'text/html; charset=UTF-8'
21 | }
22 | });
23 | ```
24 |
25 | # onStart
26 |
27 | Bind a callback and execute it on start of each request
28 | The callback accepts parameters object as argument
29 |
30 | **Parameters**
31 |
32 | - `clb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** Callback function
33 |
34 | **Examples**
35 |
36 | ```javascript
37 | ajaxable('...').onStart((params) => {
38 | // do stuff
39 | })
40 | ```
41 |
42 | # onEnd
43 |
44 | Bind a callback and execute it on end of each request
45 | The callback accepts parameters object as argument
46 |
47 | **Parameters**
48 |
49 | - `clb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** Callback function
50 |
51 | **Examples**
52 |
53 | ```javascript
54 | ajaxable('...').onEnd((params) => {
55 | // do stuff
56 | })
57 | ```
58 |
59 | # onResponse
60 |
61 | Bind a callback and execute it on response of each request
62 | The callback accepts the response and parameters as arguments
63 |
64 | **Parameters**
65 |
66 | - `clb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** Callback function
67 |
68 | **Examples**
69 |
70 | ```javascript
71 | ajaxable('...').onResponse((res, params) => {
72 | // do stuff
73 | })
74 | ```
75 |
76 | # onError
77 |
78 | Bind a callback and execute it on error of each request
79 | The callback accepts the error and parameters as arguments
80 |
81 | **Parameters**
82 |
83 | - `clb` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** Callback function
84 |
85 | **Examples**
86 |
87 | ```javascript
88 | ajaxable('...').onError((err, params) => {
89 | // do stuff
90 | })
91 | ```
92 |
93 | # submit
94 |
95 | Submit the request
96 |
97 | **Examples**
98 |
99 | ```javascript
100 | ajaxable('...').submit();
101 | ```
102 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ajaxable",
3 | "version": "0.2.3",
4 | "description": "Make your form instantly ajaxable",
5 | "main": "dist/ajaxable.min.js",
6 | "homepage": "https://github.com/artf/ajaxable",
7 | "author": "Artur Arseniev",
8 | "license": "MIT",
9 | "bugs": {
10 | "url": "https://github.com/artf/ajaxable/issues"
11 | },
12 | "babel": {
13 | "presets": [
14 | "es2015"
15 | ]
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/artf/ajaxable.git"
20 | },
21 | "keywords": [
22 | "form",
23 | "ajax",
24 | "ajaxable",
25 | "request"
26 | ],
27 | "scripts": {
28 | "lint": "eslint src",
29 | "test": "mocha --compilers js:babel-core/register --require ./test/helper.js --recursive ./test/*.js",
30 | "test:dev": "npm test -- -R min -w",
31 | "build": "WEBPACK_ENV=prod && npm run test && npm run build:doc && npm version --no-git-tag-version patch && webpack",
32 | "build:doc": "documentation build src/index.js -o ./docs/API.md -f md",
33 | "cover": "babel-node ./node_modules/istanbul/lib/cli cover node_modules/.bin/_mocha -- --compilers js:babel-core/register --require ./test/helper.js --recursive -R spec ./test/*.js",
34 | "start": "WEBPACK_ENV=dev webpack-dev-server --progress --colors"
35 | },
36 | "dependencies": {
37 | "tiny-emitter": "^1.1.0"
38 | },
39 | "devDependencies": {
40 | "babel-cli": "^6.16.0",
41 | "babel-core": "^6.17.0",
42 | "babel-loader": "^6.2.5",
43 | "babel-preset-es2015": "^6.16.0",
44 | "coveralls": "^2.11.14",
45 | "documentation": "^4.0.0-beta11",
46 | "eslint": "^3.7.1",
47 | "expect": "^1.20.2",
48 | "istanbul": "^1.1.0-alpha.1",
49 | "jsdom": "^9.8.0",
50 | "mocha": "^3.1.1",
51 | "sinon": "^1.17.6",
52 | "webpack": "^1.13.2",
53 | "webpack-dev-server": "^1.16.2"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Ajaxable.js:
--------------------------------------------------------------------------------
1 | import EventEmitter from 'tiny-emitter';
2 |
3 | class Ajaxable extends EventEmitter{
4 |
5 | /**
6 | * Init the form by providing the element, it can be either HTML selector or the form element (HTMLFormElement).
7 | * @param {string|HTMLFormElement} el
8 | * @param {Object} [options] Options
9 | * @param {string} [options.responseType] Define the response type, eg. `json`(default), `blob`, `arraybuffer`, leave empty if undefined
10 | * @param {Object} [options.headers] Define custom headers
11 | * @example
12 | * ajaxable('form.ajaxable', {
13 | * responseType: '',
14 | * headers: {
15 | * 'Content-Type': 'text/html; charset=UTF-8'
16 | * }
17 | * });
18 | */
19 | constructor(el, options = '') {
20 | super();
21 | if(!el){
22 | throw new Error('The element is empty');
23 | }
24 | // Default options
25 | const defaults = {
26 | responseType: 'json',
27 | headers: {},
28 | };
29 |
30 | let opts = options || {};
31 | for (let name in defaults) {
32 | if (!(name in opts))
33 | opts[name] = defaults[name];
34 | }
35 |
36 | // Set default X-Requested-With if not setted
37 | const reqHead = 'X-Requested-With';
38 | if(opts.headers[reqHead] != '')
39 | opts.headers[reqHead] = 'XMLHttpRequest';
40 |
41 | this.els = this.parseEl(el);
42 | this.opts = opts;
43 |
44 | for (let i = 0; i < this.els.length; i++) {
45 | this.bindForm(this.els[i]);
46 | }
47 | }
48 |
49 | /**
50 | * Bind a callback and execute it on start of each request
51 | * The callback accepts parameters object as argument
52 | * @param {Function} clb Callback function
53 | * @example
54 | * ajaxable('...').onStart((params) => {
55 | * // do stuff
56 | * })
57 | */
58 | onStart(clb){
59 | return this.on('start', clb);
60 | }
61 |
62 | /**
63 | * Bind a callback and execute it on end of each request
64 | * The callback accepts parameters object as argument
65 | * @param {Function} clb Callback function
66 | * @example
67 | * ajaxable('...').onEnd((params) => {
68 | * // do stuff
69 | * })
70 | */
71 | onEnd(clb){
72 | return this.on('end', clb);
73 | }
74 |
75 | /**
76 | * Bind a callback and execute it on response of each request
77 | * The callback accepts the response and parameters as arguments
78 | * @param {Function} clb Callback function
79 | * @example
80 | * ajaxable('...').onResponse((res, params) => {
81 | * // do stuff
82 | * })
83 | */
84 | onResponse(clb){
85 | return this.on('response', clb);
86 | }
87 |
88 | /**
89 | * Bind a callback and execute it on error of each request
90 | * The callback accepts the error and parameters as arguments
91 | * @param {Function} clb Callback function
92 | * @example
93 | * ajaxable('...').onError((err, params) => {
94 | * // do stuff
95 | * })
96 | */
97 | onError(clb){
98 | return this.on('error', clb);
99 | }
100 |
101 | /**
102 | * Submit the request
103 | * @example
104 | * ajaxable('...').submit();
105 | */
106 | submit() {
107 | for (let i = 0; i < this.els.length; i++) {
108 | this.send(this.els[i]);
109 | }
110 | }
111 |
112 | /**
113 | * Trigger form submit.
114 | * If I need to submit form programmatically and trigger
115 | * HTML5 Validation .submit() doesn't work, it's necessary
116 | * to 'click()' on a submitable element.
117 | * @param {HTMLFormElement} el Form element
118 | * @private
119 | */
120 | send(el) {
121 | const id = '_aj_btn';
122 | let subEl = el.querySelector('#' + id);
123 | if(!subEl){
124 | subEl = el.appendChild(document.createElement('button'));
125 | subEl.id = id;
126 | subEl.style.display = 'none';
127 | }
128 | subEl.click();
129 | }
130 |
131 | /**
132 | * Parse element data and return iterable object
133 | * @param {string|HTMLFormElement} el Form/s element
134 | * @return {Array|NodeList}
135 | * @private
136 | */
137 | parseEl(el) {
138 | if(typeof el === 'string'){
139 | el = document.querySelectorAll(el);
140 | }if(!el.length ||
141 | el instanceof window.HTMLElement){
142 | el = [el];
143 | }
144 | return el;
145 | }
146 |
147 | /**
148 | * Bind submit event to the form
149 | * @param {HTMLFormElement} el Form element
150 | * @private
151 | */
152 | bindForm(el) {
153 | this.checkForm(el);
154 | const ev = 'submit';
155 | const checkForm = (e) => {
156 | if(el.checkValidity()){
157 | e.preventDefault();
158 | this.sendForm(el);
159 | }
160 | };
161 | this.removeListeners(el, ev);
162 | this.addListener(el, ev, checkForm);
163 | }
164 |
165 | /**
166 | * Send form data
167 | * @param {HTMLFormElement} el Form element
168 | * @private
169 | */
170 | sendForm(el) {
171 | const formData = this.fetchData(el);
172 | let req = new XMLHttpRequest();
173 | let headers = this.opts.headers;
174 | this._ar++;
175 | let params = {
176 | el,
177 | req,
178 | activeRequests: this._ar,
179 | requestData: this.fetchFormData(formData)
180 | };
181 | this.emit('start', params);
182 | req.addEventListener("progress", (e) =>
183 | this.emit('progress', e, el, req)
184 | );
185 | req.addEventListener("load", (e) => {
186 | const toJson = this.opts.responseType == 'json';
187 | let response = '';
188 | try{
189 | response = toJson ? JSON.parse(req.responseText) : req.response;
190 | }catch(err){
191 | this.emit('error', err, params);
192 | return;
193 | }
194 | this.emit('response', response, params);
195 | });
196 | req.addEventListener("error", (e) =>
197 | this.emit('error', e, params)
198 | );
199 | req.addEventListener("loadend", (e) => {
200 | this._ar--;
201 | params.activeRequests = this._ar;
202 | this.emit('end', params)
203 | });
204 | req.open(el.method, el.action);
205 |
206 | // Set headers
207 | for (let head in headers){
208 | req.setRequestHeader(head, headers[head]);
209 | }
210 |
211 | req.send(formData);
212 | }
213 |
214 | /**
215 | * Fetch data from the form element
216 | * @param {HTMLFormElement} el Form element
217 | * @return {FormData}
218 | * @private
219 | */
220 | fetchData(el) {
221 | this.checkForm(el);
222 | let formData = new window.FormData(el);
223 | return formData;
224 | }
225 |
226 | /**
227 | * Fetch data from the FormData object
228 | * @param {FormData} fd
229 | * @return {Object}
230 | * @private
231 | */
232 | fetchFormData(fd) {
233 | let obj = {};
234 | if(fd.entries){
235 | for(let pair of fd.entries()) {
236 | obj[pair[0]] = pair[1];
237 | }
238 | }
239 | return obj;
240 | }
241 |
242 | /**
243 | * Check if the element is a valid form node
244 | * @param {HTMLFormElement} el
245 | * @private
246 | */
247 | checkForm(el) {
248 | if(!el || !(el instanceof window.HTMLFormElement)){
249 | let name = el.constructor.name;
250 | throw new Error(`The element is not a valid form, ${name} given`);
251 | }
252 | }
253 |
254 | /**
255 | * Add listener helper
256 | * @param {HTMLElement} node
257 | * @param {string} event
258 | * @param {function} handler
259 | * @private
260 | */
261 | addListener(node, event, handler) {
262 | if(!(node in this._eh))
263 | this._eh[node] = {};
264 | if(!(event in this._eh[node]))
265 | this._eh[node][event] = [];
266 | this._eh[node][event].push(handler);
267 | node.addEventListener(event, handler);
268 | }
269 |
270 | /**
271 | * Remove listener helper
272 | * @param {HTMLElement} node
273 | * @param {string} event
274 | * @private
275 | */
276 | removeListeners(node, event) {
277 | if(node in this._eh) {
278 | let handlers = this._eh[node];
279 | if(event in handlers) {
280 | let eventHandlers = handlers[event];
281 | for(let i = eventHandlers.length; i--;) {
282 | let handler = eventHandlers[i];
283 | node.removeEventListener(event, handler);
284 | }
285 | }
286 | }
287 | }
288 | }
289 |
290 | // Define static event handler
291 | Ajaxable.prototype._eh = {};
292 | // Define static variable which indicates active requests
293 | Ajaxable.prototype._ar = 0;
294 |
295 | export default Ajaxable;
296 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Ajaxable from './Ajaxable';
2 |
3 | module.exports = (el, opt) => new Ajaxable(el, opt);
4 |
--------------------------------------------------------------------------------
/test/helper.js:
--------------------------------------------------------------------------------
1 | import jsdom from 'jsdom';
2 |
3 | //const virtualConsole = jsdom.createVirtualConsole();
4 | //virtualConsole.on('log', console.log);
5 |
6 | const doc = jsdom.jsdom('');
7 | const win = doc.defaultView;
8 |
9 |
10 | global.document = doc;
11 | global.window = win;
12 |
13 | Object.keys(window).forEach((key) => {
14 | if (!(key in global)) {
15 | global[key] = window[key];
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 | import sinon from 'sinon';
3 | import ajaxable from './../src';
4 |
5 | describe('Ajaxable', () => {
6 | let obj;
7 | let form;
8 | let d = document;
9 |
10 | beforeEach(() => {
11 | form = `
12 |
13 | `;
17 | d.body.innerHTML = form;
18 | });
19 |
20 | it('Throws error without element', () => {
21 | expect(() => {
22 | obj = ajaxable();
23 | }).toThrow();
24 | });
25 |
26 | it('Init with string, single element', () => {
27 | const el = ajaxable('#form1').els;
28 | expect(el.length).toEqual(1);
29 | });
30 |
31 | it('Init with string, multiple elements', () => {
32 | const el = ajaxable('.forms').els;
33 | expect(el.length).toEqual(3);
34 | });
35 |
36 | it('Init with the DOM', () => {
37 | const el = ajaxable(d.getElementById('form1')).els;
38 | expect(el.length).toEqual(1);
39 | });
40 |
41 | it('Init with the DOM which has children', () => {
42 | const el = ajaxable(d.getElementById('form3')).els;
43 | expect(el.length).toEqual(1);
44 | });
45 |
46 | it('Throw on wrong element with checkForm', () => {
47 | obj = ajaxable('#form1');
48 | expect(() => {
49 | obj.checkForm();
50 | }).toThrow();
51 | });
52 |
53 | describe('Ajaxable methods', () => {
54 | let formEl;
55 | let formEl2;
56 | let requests;
57 | let xhr;
58 |
59 | beforeEach(() => {
60 | form = `
63 | `;
67 | d.body.innerHTML = form;
68 | formEl = d.getElementById('formel1');
69 | formEl2 = d.getElementById('formel2');
70 | obj = ajaxable('#formel2');
71 | xhr = sinon.useFakeXMLHttpRequest();
72 | requests = [];
73 | xhr.onCreate = function(xhr) {
74 | requests.push(xhr);
75 | };
76 | });
77 |
78 | afterEach(function() {
79 | xhr.restore();
80 | });
81 |
82 | it('Adds event listener correctly', (done) => {
83 | obj.addListener(formEl, 'submit', (e) => {
84 | done();
85 | });
86 | d.getElementById('send-data').click();
87 | });
88 |
89 | it('Removes event listener correctly', (done) => {
90 | obj.addListener(formEl, 'submit', (e) => {
91 | done(new Error('Not expected to submit'));
92 | });
93 | obj.removeListeners(formEl, 'submit');
94 | d.getElementById('send-data').onclick = () => {
95 | done();
96 | };
97 | d.getElementById('send-data').click();
98 | });
99 |
100 | it('Bind form correctly', (done) => {
101 | obj.sendForm = () => {done()};
102 | formEl2.checkValidity = () => true;
103 | obj.bindForm(formEl2);
104 | obj.submit();
105 | });
106 |
107 | it('Fetch data from the FormData', () => {
108 | let fd = new window.FormData();
109 | obj.fetchFormData(fd);
110 | fd.entries = () => [['test', 'value']];
111 | let res = obj.fetchFormData(fd);
112 | expect(res).toEqual({test:'value'});
113 | });
114 |
115 | it('Bind form correctly, with fail on checkValidity', (done) => {
116 | obj.sendForm = () => {done()};
117 | formEl2.checkValidity = () => false;
118 | obj.bindForm(formEl2);
119 | obj.submit();
120 | done();
121 | });
122 |
123 | it('Triggers onStart on sendForm', (done) => {
124 | let formEl = d.getElementById('formel2');
125 | obj.onStart((params) => {
126 | expect(params.el.id).toEqual(formEl.id);
127 | done();
128 | });
129 | obj.sendForm(formEl);
130 | });
131 |
132 | it('Triggers onEnd on sendForm', (done) => {
133 | let formEl = d.getElementById('formel2');
134 | obj.onEnd((params) => {
135 | expect(params.el.id).toEqual(formEl.id);
136 | done();
137 | });
138 | obj.sendForm(formEl);
139 | requests[0].respond();
140 | });
141 |
142 | it('Triggers onResponse on sendForm, auto parse JSON', (done) => {
143 | const data = {test: 'value'};
144 | obj.onResponse((res, params) => {
145 | expect(res).toEqual(data);
146 | done();
147 | });
148 | obj.sendForm(formEl2);
149 | requests[0].respond(200,
150 | {'Content-Type': 'text/json'},
151 | JSON.stringify(data));
152 | });
153 |
154 | it('Triggers onResponse on sendForm, do not parse JSON', (done) => {
155 | obj = ajaxable('#formel2', {responseType: ''});
156 | const data = {test: 'value'};
157 | obj.onResponse((res, params) => {
158 | expect(res).toEqual(JSON.stringify(data));
159 | done();
160 | });
161 | obj.sendForm(formEl2);
162 | requests[0].respond(200,
163 | {'Content-Type': 'text/json'},
164 | JSON.stringify(data));
165 | });
166 |
167 | it('Triggers onError on sendForm, with wrong JSON', (done) => {
168 | const data = {test: 'value'};
169 | obj.onError((err, params) => {
170 | expect(err).toExist();
171 | done();
172 | });
173 | obj.sendForm(formEl2);
174 | requests[0].respond(200,
175 | {'Content-Type': 'text/json'},
176 | JSON.stringify(data)+'test');
177 | });
178 |
179 | it('Triggers onError on sendForm', (done) => {
180 | const data = {test: 'value'};
181 | obj.onError((err, params) => {
182 | expect(err).toExist();
183 | done();
184 | });
185 | obj.sendForm(formEl2);
186 | requests[0].respond();
187 | });
188 |
189 | it('Sends X-Requested-With in the headers', () => {
190 | obj.sendForm(formEl2);
191 | const reqHead = requests[0].requestHeaders;
192 | expect(reqHead['X-Requested-With']).toExist();
193 | expect(reqHead['X-Requested-With']).toEqual('XMLHttpRequest');
194 | });
195 |
196 | it('Sends empty X-Requested-With headers', () => {
197 | obj = ajaxable('#formel2', {
198 | headers: {'X-Requested-With': ''}
199 | });
200 | obj.sendForm(formEl2);
201 | const reqHead = requests[0].requestHeaders;
202 | expect(reqHead['X-Requested-With']).toNotExist();
203 | });
204 |
205 |
206 | })
207 |
208 | });
209 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var pkg = require('./package.json');
4 | var name = 'ajaxable';
5 | var env = process.env.WEBPACK_ENV;
6 | var plugins = [];
7 |
8 | if(env !== 'dev'){
9 | plugins.push(new webpack.optimize.UglifyJsPlugin({ compressor: { warnings: false } }));
10 | plugins.push(new webpack.BannerPlugin(pkg.name + ' - ' + pkg.version));
11 | }
12 |
13 | module.exports = {
14 | entry: './src/index',
15 | output: {
16 | filename: './dist/' + name + '.min.js',
17 | library: name,
18 | libraryTarget: 'umd',
19 | },
20 | module: {
21 | loaders: [
22 | {
23 | test: /\.jsx?$/,
24 | loader: 'babel-loader',
25 | include: /src/,
26 | query: {
27 | presets: ['es2015']
28 | }
29 | },
30 | ],
31 | },
32 | plugins: plugins
33 | };
34 |
--------------------------------------------------------------------------------