├── README.md ├── ajax-form.js ├── index.html └── server.py /README.md: -------------------------------------------------------------------------------- 1 | # AJAX Form 2 | 3 | * Adds support for PUT, PATCH, DELETE and OPTIONS methods in HTML forms. 4 | * Adds support for submitting JSON and other content types in HTML forms. 5 | 6 | See [index.html](https://github.com/tomchristie/ajax-form/blob/master/index.html) for example usage. 7 | 8 | ## Requirements 9 | 10 | jQuery 1.9+ 11 | 12 | ## Restrictions 13 | 14 | * Multipart (file upload) is only supported in browsers that support the FormData API. 15 | * Browsers that do not support the history API will result in a subsequent `GET` request. 16 | * Cross-domain requests will result in a subsequent `GET` request. 17 | * Non-HTML responses will result in a subsequent `GET` request. 18 | 19 | ## Usage - PUT, PATCH, DELETE, OPTIONS methods 20 | 21 | Use `data-method="***"`. 22 | 23 |
24 | 25 | 26 | 27 |
28 | 33 | 34 | Or using button controls. 35 | 36 |
37 | 38 | 39 | 40 | 41 |
42 | 47 | 48 | ## Usage - Content type overriding 49 | 50 | Use `data-override="content"` and `data-override="content-type"`. 51 | 52 | Using an input control: 53 | 54 |
55 | 56 | 57 | 58 |
59 | 64 | 65 | Using a select control: 66 | 67 |
68 | 72 | 73 | 74 |
75 | 80 | -------------------------------------------------------------------------------- /ajax-form.js: -------------------------------------------------------------------------------- 1 | function replaceDocument(docString) { 2 | var doc = document.open("text/html"); 3 | doc.write(docString); 4 | doc.close(); 5 | } 6 | 7 | 8 | function doAjaxSubmit(e) { 9 | var form = $(this); 10 | var btn = $(this.clk); 11 | var method = btn.data('method') || form.data('method') || form.attr('method') || 'GET'; 12 | method = method.toUpperCase() 13 | if (method === 'GET') { 14 | // GET requests can always use standard form submits. 15 | return; 16 | } 17 | 18 | var contentType = 19 | form.find('input[data-override="content-type"]').val() || 20 | form.find('select[data-override="content-type"] option:selected').text(); 21 | if (method === 'POST' && !contentType) { 22 | // POST requests can use standard form submits, unless we have 23 | // overridden the content type. 24 | return; 25 | } 26 | 27 | // At this point we need to make an AJAX form submission. 28 | e.preventDefault(); 29 | 30 | var url = form.attr('action'); 31 | var data; 32 | if (contentType) { 33 | data = form.find('[data-override="content"]').val() || '' 34 | } else { 35 | contentType = form.attr('enctype') || form.attr('encoding') 36 | if (contentType === 'multipart/form-data') { 37 | if (!window.FormData) { 38 | alert('Your browser does not support AJAX multipart form submissions'); 39 | return; 40 | } 41 | // Use the FormData API and allow the content type to be set automatically, 42 | // so it includes the boundary string. 43 | // See https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects 44 | contentType = false; 45 | data = new FormData(form[0]); 46 | } else { 47 | contentType = 'application/x-www-form-urlencoded; charset=UTF-8' 48 | data = form.serialize(); 49 | } 50 | } 51 | 52 | var ret = $.ajax({ 53 | url: url, 54 | method: method, 55 | data: data, 56 | contentType: contentType, 57 | processData: false, 58 | headers: {'Accept': 'text/html; q=1.0, */*'}, 59 | }); 60 | ret.always(function(data, textStatus, jqXHR) { 61 | if (textStatus != 'success') { 62 | jqXHR = data; 63 | } 64 | var responseContentType = jqXHR.getResponseHeader("content-type") || ""; 65 | if (responseContentType.toLowerCase().indexOf('text/html') === 0) { 66 | replaceDocument(jqXHR.responseText); 67 | try { 68 | // Modify the location and scroll to top, as if after page load. 69 | history.replaceState({}, '', url); 70 | scroll(0,0); 71 | } catch(err) { 72 | // History API not supported, so redirect. 73 | window.location = url; 74 | } 75 | } else { 76 | // Not HTML content. We can't open this directly, so redirect. 77 | window.location = url; 78 | } 79 | }); 80 | return ret; 81 | } 82 | 83 | 84 | function captureSubmittingElement(e) { 85 | var target = e.target; 86 | var form = this; 87 | form.clk = target; 88 | } 89 | 90 | 91 | $.fn.ajaxForm = function() { 92 | var options = {} 93 | return this 94 | .unbind('submit.form-plugin click.form-plugin') 95 | .bind('submit.form-plugin', options, doAjaxSubmit) 96 | .bind('click.form-plugin', options, captureSubmittingElement); 97 | }; 98 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example 6 | 7 | 8 |
9 |

PUT

10 |
11 | 12 | 13 | 14 |
15 |
16 |
17 |

PUT or PATCH, using button controls

18 |
19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |

PUT to different URL

27 |
28 | 29 | 30 | 31 |
32 |
33 |
34 |

Multipart PUT

35 |
36 | 37 | 38 | 39 |
40 |
41 |
42 |

PUT with JSON ContentType

43 |
44 | 45 | 46 | 47 |
48 |
49 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | # This file provided by Facebook is for non-commercial testing and evaluation 2 | # purposes only. Facebook reserves all rights not expressly granted. 3 | # 4 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 5 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 6 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 7 | # FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 8 | # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 9 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | import json 12 | import os 13 | from flask import Flask, Response, request 14 | 15 | app = Flask(__name__, static_url_path='', static_folder='.') 16 | index = open('index.html', 'r').read() 17 | 18 | @app.route('/', methods=['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) 19 | def index(): 20 | return open('index.html', 'r').read() 21 | 22 | 23 | @app.route('/', methods=['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) 24 | def catch_all(path): 25 | return open('index.html', 'r').read() 26 | 27 | 28 | if __name__ == '__main__': 29 | app.run(port=int(os.environ.get("PORT",3000))) 30 | --------------------------------------------------------------------------------