├── .gitignore ├── demos ├── robots.txt ├── json2.php ├── json.php ├── save.php ├── main.css ├── img │ └── spinner.svg ├── css │ ├── main.css │ └── prism.css ├── js │ ├── jquery.charcounter.js │ ├── jquery.autogrowtextarea.js │ ├── prism.js │ └── jquery.maskedinput.js ├── index.js └── index.html ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── .dockerignore ├── FUNDING.yml ├── CONTRIBUTING.md ├── dist ├── jquery.jeditable.autogrow.min.js ├── jquery.jeditable.masked.min.js ├── jquery.jeditable.charcounter.min.js ├── jquery.jeditable.datepicker.min.js ├── jquery.jeditable.time.min.js └── jquery.jeditable.min.js ├── tests ├── index.html └── tests.js ├── LICENSE ├── Gruntfile.js ├── package.json ├── apache └── 000-default.conf ├── src ├── jquery.jeditable.autogrow.js ├── jquery.jeditable.masked.js ├── jquery.jeditable.checkbox.js ├── jquery.jeditable.charcounter.js ├── jquery.jeditable.datepicker.js ├── jquery.jeditable.time.js └── jquery.jeditable.js ├── Dockerfile ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | api 3 | -------------------------------------------------------------------------------- /demos/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please make sure to target the "experimental" branch! 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | node_modules 4 | README.md 5 | CHANGELOG.md 6 | CONTRIBUTING.md 7 | LICENSE 8 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | liberapay: NicolasCARPi 3 | github: NicolasCARPi 4 | -------------------------------------------------------------------------------- /demos/json2.php: -------------------------------------------------------------------------------- 1 | ");return settings.rows?textarea.attr("rows",settings.rows):textarea.height(settings.height),settings.cols?textarea.attr("cols",settings.cols):textarea.width(settings.width),$(this).append(textarea),textarea},plugin:function(settings,original){$("textarea",this).autoGrow()}})}(jQuery); -------------------------------------------------------------------------------- /demos/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align: center; 3 | font-size: 120%; 4 | background-color: #eee; 5 | } 6 | 7 | .jumbotron { 8 | background-color: #fff; 9 | } 10 | 11 | .editable input[type=submit] { 12 | color: #F00; 13 | font-weight: bold; 14 | } 15 | .editable input[type=button] { 16 | color: #0F0; 17 | font-weight: bold; 18 | } 19 | 20 | .inline { 21 | display: inline; 22 | } 23 | 24 | h4 { 25 | text-align: left; 26 | } 27 | 28 | /* for the full example */ 29 | label { 30 | margin-right: 5px; 31 | } 32 | 33 | .custom-class { 34 | font-size: 110%; 35 | } 36 | -------------------------------------------------------------------------------- /dist/jquery.jeditable.masked.min.js: -------------------------------------------------------------------------------- 1 | /*! jquery-jeditable https://github.com/NicolasCARPi/jquery_jeditable#readme */ 2 | 3 | "use strict";!function($){$.editable.addInputType("masked",{element:function(settings,original){var input=$("").attr({autocomplete:"off",list:settings.list,maxlength:settings.maxlength,pattern:settings.pattern,placeholder:settings.placeholder,tooltip:settings.tooltip,type:"text"}).mask(settings.mask);return"none"!==settings.width&&input.css("width",settings.width),"none"!==settings.height&&input.css("height",settings.height),settings.size&&input.attr("size",settings.size),settings.maxlength&&input.attr("maxlength",settings.maxlength),$(this).append(input),input}})}(jQuery); -------------------------------------------------------------------------------- /dist/jquery.jeditable.charcounter.min.js: -------------------------------------------------------------------------------- 1 | /*! jquery-jeditable https://github.com/NicolasCARPi/jquery_jeditable#readme */ 2 | 3 | "use strict";!function($){$.editable.addInputType("charcounter",{element:function(settings,original){var textarea=$("");return settings.rows?textarea.attr("rows",settings.rows):"none"!==settings.height&&textarea.height(settings.height),settings.cols?textarea.attr("cols",settings.cols):"none"!==settings.width&&textarea.width(settings.width),settings.maxlength&&textarea.attr("maxlength",settings.maxlength),$(this).append(textarea),textarea}},select:{element:function(settings,original){var select=$("").attr({maxlength:settings.maxlength,placeholder:settings.placeholder,min:settings.min,max:settings.max,step:settings.step,tooltip:settings.tooltip,type:_supportInType("number")});return"none"!==settings.width&&input.css("width",settings.width),$(this).append(input),input}},email:{element:function(settings,original){var input=$("").attr({maxlength:settings.maxlength,placeholder:settings.placeholder,tooltip:settings.tooltip,type:_supportInType("email")});return"none"!==settings.width&&input.css("width",settings.width),$(this).append(input),input}},url:{element:function(settings,original){var input=$("").attr({maxlength:settings.maxlength,pattern:settings.pattern,placeholder:settings.placeholder,tooltip:settings.tooltip,type:_supportInType("url")});return"none"!==settings.width&&input.css("width",settings.width),$(this).append(input),input}}},addInputType:function(name,input){$.editable.types[name]=input}},$.fn.editable.defaults={name:"value",id:"id",type:"text",width:"auto",height:"auto",event:"click.editable keydown.editable",onblur:"cancel",tooltip:"Click to edit",loadtype:"GET",loadtext:"Loading...",placeholder:"Click to edit",sortselectoptions:!1,loaddata:{},submitdata:{},ajaxoptions:{}}}(jQuery); -------------------------------------------------------------------------------- /demos/js/jquery.maskedinput.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Masked Input Plugin 3 | Copyright (c) 2007 - 2015 Josh Bush (digitalbush.com) 4 | Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license) 5 | Version: 1.4.1 6 | */ 7 | !function(factory) { 8 | "function" == typeof define && define.amd ? define([ "jquery" ], factory) : factory("object" == typeof exports ? require("jquery") : jQuery); 9 | }(function($) { 10 | var caretTimeoutId, ua = navigator.userAgent, iPhone = /iphone/i.test(ua), chrome = /chrome/i.test(ua), android = /android/i.test(ua); 11 | $.mask = { 12 | definitions: { 13 | "9": "[0-9]", 14 | a: "[A-Za-z]", 15 | "*": "[A-Za-z0-9]" 16 | }, 17 | autoclear: !0, 18 | dataName: "rawMaskFn", 19 | placeholder: "_" 20 | }, $.fn.extend({ 21 | caret: function(begin, end) { 22 | var range; 23 | if (0 !== this.length && !this.is(":hidden")) return "number" == typeof begin ? (end = "number" == typeof end ? end : begin, 24 | this.each(function() { 25 | this.setSelectionRange ? this.setSelectionRange(begin, end) : this.createTextRange && (range = this.createTextRange(), 26 | range.collapse(!0), range.moveEnd("character", end), range.moveStart("character", begin), 27 | range.select()); 28 | })) : (this[0].setSelectionRange ? (begin = this[0].selectionStart, end = this[0].selectionEnd) : document.selection && document.selection.createRange && (range = document.selection.createRange(), 29 | begin = 0 - range.duplicate().moveStart("character", -1e5), end = begin + range.text.length), 30 | { 31 | begin: begin, 32 | end: end 33 | }); 34 | }, 35 | unmask: function() { 36 | return this.trigger("unmask"); 37 | }, 38 | mask: function(mask, settings) { 39 | var input, defs, tests, partialPosition, firstNonMaskPos, lastRequiredNonMaskPos, len, oldVal; 40 | if (!mask && this.length > 0) { 41 | input = $(this[0]); 42 | var fn = input.data($.mask.dataName); 43 | return fn ? fn() : void 0; 44 | } 45 | return settings = $.extend({ 46 | autoclear: $.mask.autoclear, 47 | placeholder: $.mask.placeholder, 48 | completed: null 49 | }, settings), defs = $.mask.definitions, tests = [], partialPosition = len = mask.length, 50 | firstNonMaskPos = null, $.each(mask.split(""), function(i, c) { 51 | "?" == c ? (len--, partialPosition = i) : defs[c] ? (tests.push(new RegExp(defs[c])), 52 | null === firstNonMaskPos && (firstNonMaskPos = tests.length - 1), partialPosition > i && (lastRequiredNonMaskPos = tests.length - 1)) : tests.push(null); 53 | }), this.trigger("unmask").each(function() { 54 | function tryFireCompleted() { 55 | if (settings.completed) { 56 | for (var i = firstNonMaskPos; lastRequiredNonMaskPos >= i; i++) if (tests[i] && buffer[i] === getPlaceholder(i)) return; 57 | settings.completed.call(input); 58 | } 59 | } 60 | function checkVal(allow) { 61 | var i, c, pos, test = input.val(), lastMatch = -1; 62 | for (i = 0, pos = 0; len > i; i++) if (tests[i]) { 63 | for (buffer[i] = getPlaceholder(i); pos++ < test.length; ) if (c = test.charAt(pos - 1), 64 | tests[i].test(c)) { 65 | buffer[i] = c, lastMatch = i; 66 | break; 67 | } 68 | if (pos > test.length) { 69 | clearBuffer(i + 1, len); 70 | break; 71 | } 72 | } else buffer[i] === test.charAt(pos) && pos++, partialPosition > i && (lastMatch = i); 73 | return allow ? writeBuffer() : partialPosition > lastMatch + 1 ? settings.autoclear || buffer.join("") === defaultBuffer ? (input.val() && input.val(""), 74 | clearBuffer(0, len)) : writeBuffer() : (writeBuffer(), input.val(input.val().substring(0, lastMatch + 1))), 75 | partialPosition ? i : firstNonMaskPos; 76 | } 77 | function getPlaceholder(i) { 78 | return settings.placeholder.charAt(i < settings.placeholder.length ? i : 0); 79 | } 80 | function seekNext(pos) { 81 | for (;++pos < len && !tests[pos]; ) ; 82 | return pos; 83 | } 84 | function seekPrev(pos) { 85 | for (;--pos >= 0 && !tests[pos]; ) ; 86 | return pos; 87 | } 88 | function shiftL(begin, end) { 89 | var i, j; 90 | if (!(0 > begin)) { 91 | for (i = begin, j = seekNext(end); len > i; i++) if (tests[i]) { 92 | if (!(len > j && tests[i].test(buffer[j]))) break; 93 | buffer[i] = buffer[j], buffer[j] = getPlaceholder(j), j = seekNext(j); 94 | } 95 | writeBuffer(), input.caret(Math.max(firstNonMaskPos, begin)); 96 | } 97 | } 98 | function shiftR(pos) { 99 | var i, c, j, t; 100 | for (i = pos, c = getPlaceholder(pos); len > i; i++) { 101 | if (tests[i]) { 102 | if (j = seekNext(i), t = buffer[i], buffer[i] = c, !(len > j && tests[j].test(t))) { 103 | break; 104 | } 105 | c = t; 106 | } 107 | } 108 | } 109 | function androidInputEvent() { 110 | var curVal = input.val(), pos = input.caret(); 111 | if (oldVal && oldVal.length && oldVal.length > curVal.length) { 112 | for (checkVal(!0); pos.begin > 0 && !tests[pos.begin - 1];) { 113 | pos.begin--; 114 | } 115 | if (0 === pos.begin) { 116 | for (;pos.begin < firstNonMaskPos && !tests[pos.begin];) { 117 | pos.begin++; 118 | } 119 | } 120 | input.caret(pos.begin, pos.begin); 121 | } else { 122 | for (checkVal(!0); pos.begin < len && !tests[pos.begin];) { 123 | pos.begin++; 124 | } 125 | input.caret(pos.begin, pos.begin); 126 | } 127 | tryFireCompleted(); 128 | } 129 | function blurEvent() { 130 | checkVal(), input.val() != focusText && input.change(); 131 | } 132 | function clearBuffer(start, end) { 133 | var i; 134 | for (i = start; end > i && len > i; i++) { 135 | tests[i] && (buffer[i] = getPlaceholder(i)); 136 | } 137 | } 138 | function keydownEvent(e) { 139 | if (!input.prop("readonly")) { 140 | var pos, begin, end, k = e.which || e.keyCode; 141 | oldVal = input.val(), 8 === k || 46 === k || iPhone && 127 === k ? (pos = input.caret(), 142 | begin = pos.begin, end = pos.end, end - begin === 0 && (begin = 46 !== k ? seekPrev(begin) : end = seekNext(begin - 1), 143 | end = 46 === k ? seekNext(end) : end), clearBuffer(begin, end), shiftL(begin, end - 1), 144 | e.preventDefault()) : 13 === k ? blurEvent.call(this, e) : 27 === k && (input.val(focusText), 145 | input.caret(0, checkVal()), e.preventDefault()); 146 | } 147 | } 148 | function keypressEvent(e) { 149 | if (!input.prop("readonly")) { 150 | var p, c, next, k = e.which || e.keyCode, pos = input.caret(); 151 | if (!(e.ctrlKey || e.altKey || e.metaKey || 32 > k) && k && 13 !== k) { 152 | if (pos.end - pos.begin !== 0 && (clearBuffer(pos.begin, pos.end), shiftL(pos.begin, pos.end - 1)), 153 | p = seekNext(pos.begin - 1), len > p && (c = String.fromCharCode(k), tests[p].test(c))) { 154 | if (shiftR(p), buffer[p] = c, writeBuffer(), next = seekNext(p), android) { 155 | var proxy = function() { 156 | $.proxy($.fn.caret, input, next)(); 157 | }; 158 | setTimeout(proxy, 0); 159 | } else input.caret(next); 160 | pos.begin <= lastRequiredNonMaskPos && tryFireCompleted(); 161 | } 162 | e.preventDefault(); 163 | } 164 | } 165 | } 166 | function writeBuffer() { 167 | input.val(buffer.join("")); 168 | } 169 | 170 | 171 | var input = $(this), buffer = $.map(mask.split(""), function(c, i) { 172 | return "?" != c ? defs[c] ? getPlaceholder(i) : c : void 0; 173 | }), defaultBuffer = buffer.join(""), focusText = input.val(); 174 | input.data($.mask.dataName, function() { 175 | return $.map(buffer, function(c, i) { 176 | return tests[i] && c != getPlaceholder(i) ? c : null; 177 | }).join(""); 178 | }), input.one("unmask", function() { 179 | input.off(".mask").removeData($.mask.dataName); 180 | }).on("focus.mask", function() { 181 | if (!input.prop("readonly")) { 182 | clearTimeout(caretTimeoutId); 183 | var pos; 184 | focusText = input.val(), pos = checkVal(), caretTimeoutId = setTimeout(function() { 185 | input.get(0) === document.activeElement && (writeBuffer(), pos == mask.replace("?", "").length ? input.caret(0, pos) : input.caret(pos)); 186 | }, 10); 187 | } 188 | }).on("blur.mask", blurEvent).on("keydown.mask", keydownEvent).on("keypress.mask", keypressEvent).on("input.mask paste.mask", function() { 189 | input.prop("readonly") || setTimeout(function() { 190 | var pos = checkVal(!0); 191 | input.caret(pos), tryFireCompleted(); 192 | }, 0); 193 | }), chrome && android && input.off("input.mask").on("input.mask", androidInputEvent), 194 | checkVal(); 195 | }); 196 | } 197 | }); 198 | }); 199 | -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | jQuery-jeditable Demos 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 |

jQuery-jeditable

52 |

Edit in place plugin for jQuery

53 |

This is a demo page, have a look at the source to see how each example is implemented!

54 |

GitHub repository

55 |

Documentation

56 |

API Documentation

57 |
58 | 59 |

Core features

60 |
<!-- include this in your HTML -->
 61 | <script src=/path/to/jquery.jeditable.min.js></script>
 62 | 
63 | 64 |
65 | 66 | 67 |
68 |
69 |

Basic minimal example

70 |
The most basic example (press enter to validate). No options.
71 |

Click this text to edit it.

72 |

Show source code

73 |
74 |
$(".editable-text").editable("save.php");
75 |
76 |
77 |
78 | 79 | 80 |
81 |
82 |

Complete example

83 |
A more complete example with a bunch of options.
84 |

Click this text to edit it.

85 |

Show source code

86 |
87 |
/* data that will be sent along */
 88 | var submitdata = {}
 89 | /* this will make the save.php script take a long time so you can see the spinner ;) */
 90 | submitdata['slow'] = true;
 91 | submitdata['pwet'] = 'youpla';
 92 | 
 93 | $(".editable-text-full").editable("save.php", {
 94 |     indicator : "<img src='img/spinner.svg' />",
 95 |     type : "text",
 96 |     // only limit to three letters example
 97 |     //pattern: "[A-Za-z]{3}",
 98 |     onedit : function() { console.log('If I return false edition will be canceled'); return true;},
 99 |     before : function() { console.log('Triggered before form appears')},
100 |     callback : function(result, settings, submitdata) {
101 |         console.log('Triggered after submit');
102 |         console.log('Result: ' + result);
103 |         console.log('Settings.width: ' + settings.width);
104 |         console.log('Submitdata: ' + submitdata.pwet);
105 |     },
106 |     cancel : 'Cancel',
107 |     cssclass : 'custom-class',
108 |     cancelcssclass : 'btn btn-danger',
109 |     submitcssclass : 'btn btn-success',
110 |     maxlength : 200,
111 |     // select all text
112 |     select : true,
113 |     label : 'This is a label',
114 |     onreset : function() { console.log('Triggered before reset') },
115 |     onsubmit : function() { console.log('Triggered before submit') },
116 |     showfn : function(elem) { elem.fadeIn('slow') },
117 |     submit : 'Save',
118 |     submitdata : submitdata,
119 |     /* submitdata as a function example
120 |     submitdata : function(revert, settings, submitdata) {
121 |         console.log("Revert text: " + revert);
122 |         console.log(settings);
123 |         console.log("User submitted text: " + submitdata.value);
124 |     },
125 |     */
126 |     tooltip : "Click to edit...",
127 |     width : 160
128 | });
129 | 
130 | /* target as a function example
131 | $(".editable-text-full").editable(function(input, settings, elem) {
132 |     console.log(input);
133 |     console.log(settings);
134 |     console.log($(elem).data('test'));
135 | }, {});
136 | */
137 | 
138 |
139 |
140 |
141 | 142 | 143 |
144 |
145 |

Normal textarea

146 |
Example of the 'textarea' type.
147 |

If you click anywhere on this text, it will become a textarea that you can edit. And then you can click OK to save it.

148 |
149 |

Show source code

150 |
151 |
$(".editable_textarea").editable("save.php", {
152 |     type   : 'textarea',
153 |     submit : 'OK',
154 |     cancel : 'Nope'
155 | });
156 | 
157 |
158 |
159 | 160 | 161 |
162 |
163 |

Select

164 |
Example of the 'select' type.
165 |
166 |

THIS IS A SELECT, click it to get a select menu. Options are from the "data" setting.

167 |

THIS IS A SELECT WITH JSON FEED, click it to get a select menu. Options are loaded from an ajax request getting JSON.

168 |

THIS IS A MULTIPLE SELECT.You can select several values (try holding Shift or Ctrl).

169 |
170 |
171 |

Show source code

172 |
173 |
// inline select
174 | $(".editable-select").editable("save.php", {
175 |     type   : "select",
176 |     // this data will be sorted by value
177 |     data   : '{"Wiki":"Wiki","Banana":"Banana","Apple":"Apple", "Pear":"Pear", "selected":"Pear"}',
178 |     submitdata : function() { return {id : 42, something: 'else'};},
179 |     style  : "inherit",
180 | });
181 | // with JSON feed
182 | $(".editable-select-json").editable("save.php", {
183 |     type   : "select",
184 |     loadurl : "json.php",
185 |     submit : "OK",
186 |     style  : "inherit"
187 | });
188 | // MULTIPLE SELECT
189 | $(".multiple-select").editable("save.php", {
190 |     type : "select",
191 |     data   : '{"Wiki":"Wiki","Banana":"Banana","Apple":"Apple", "Pear":"Pear"}',
192 |     submit: 'OK',
193 |     multiple : true,
194 |     onblur: function() { return true; },
195 |     // use intercept to display the results as we want it
196 |     intercept: function(result, status) {
197 |         return "You selected: " + result + ". ";
198 |     },
199 |     onerror: function(settings, self, xhr) {
200 |         console.log("Error with status code: " + xhr.status);
201 |     },
202 |     submitdata : function(revert, settings, result) {
203 |         console.log("User selected values: " + result.value);
204 |     },
205 | });
206 | 
207 |
208 |
209 | 210 |
211 | 212 |
213 | 214 | 215 |
216 |
217 |

Different events

218 |
By default a click will trigger an edit. But you can bind other events as shown below.
219 |

220 | Click me if you dare! or maybe you should 221 | doubleclick instead? Really lazy people can just 222 | mouseover me... 223 |

224 |

Show source code

225 |
226 |
// click
227 | $(".click").editable("save.php", {
228 |     tooltip   : "Click to edit...",
229 |     style  : "inherit"
230 | });
231 | 
232 | // double click
233 | $(".dblclick").editable("save.php", {
234 |     tooltip   : "Doubleclick to edit...",
235 |     event     : "dblclick",
236 |     style  : "inherit"
237 | });
238 | 
239 | // mouseover
240 | $(".mouseover").editable("save.php", {
241 |     tooltip   : "Move mouseover to edit...",
242 |     event     : "mouseover",
243 |     style  : "inherit"
244 | });
245 | 
246 |
247 |
248 |
249 | 250 | 251 |
252 |
253 |

Intercepting the data sent back

254 |

Useful if you want to process the returned data before it hits the page.

255 |

Click here to test the intercept option.

256 |

Show source code

257 |
258 |
$(".intercept").editable("json2.php", {
259 |     submit : 'OK',
260 |     intercept : function(jsondata) {
261 |         json = JSON.parse(jsondata);
262 |         console.log(json.status);
263 |         console.log(json.other);
264 |         return json.result;
265 |     },
266 | });
267 | 
268 |
269 |
270 |
271 | 272 | 273 | 274 |
275 |
276 |

E-mail, numbers, URL

277 |

You can restrict input to emails, numbers or urls

278 | 279 |

-2.06

280 |

https://www.example.com

281 |

Show source code

282 |
283 |
// EMAIL
284 | $(".email").editable("save.php", {
285 |     type: "email",
286 |     tooltip: "Enter a valid email address",
287 |     placeholder: "nico.tesla@example.com",
288 | });
289 | // NUMBER
290 | $(".number").editable("save.php", {
291 |     type: "number",
292 |     tooltip: "Click to edit: number",
293 |     placeholder: "0",
294 |     min: 0,
295 |     max: 10,
296 |     step: 1
297 | });
298 | // URL
299 | $(".url").editable("save.php", {
300 |     type: "url",
301 |     tooltip: "Enter a valid URL",
302 |     placeholder: "https://www.example.com"
303 | });
304 | 
305 |
306 |
307 |
308 | 309 | 310 | 311 |
312 |
313 |

Styling the buttons

314 |

If you want to use different css class for the submit and cancel button. Also show how to add an ID to the form.

315 |

Click here to show the buttons

316 |

Show source code

317 |
318 |
$(".css-buttons").editable("save.php", {
319 |     submit : 'OK',
320 |     cancel : 'Cancel',
321 |     cssclass : 'custom-class',
322 |     cancelcssclass : 'btn btn-danger',
323 |     submitcssclass : 'btn btn-success',
324 |     formid : 'abc-123'
325 | });
326 | 
327 |
328 |
329 |
330 | 331 | 332 |
333 |
334 |

Checkbox

335 |

Click here to test checkbox input

336 |

Show source code

337 |
338 |
$(".checkbox").editable("save.php", {
339 |     type      : "checkbox",
340 |     submit : 'ok'
341 | });
342 | 
343 |
344 |
345 |
346 | 347 |

Features requiring plugins

348 | 349 | 350 |
351 |
352 |

Date Picker (requires jQuery-ui datepicker)

353 |
<!-- also include this in your HTML -->
354 | <script src=/path/to/jquery.jeditable.datepicker.min.js></script>
355 | <!-- also include jQuery-UI JS and CSS files -->
356 | 
357 |

16-09-2018

358 |

Show source code

359 |
360 |
$(".datepicker").editable("save.php", {
361 |     type      : 'datepicker',
362 |     submit : 'OK',
363 |     datepicker : {
364 |         format: "dd-mm-yy"
365 |     },
366 |     tooltip   : "Click to edit..."
367 | });
368 | 
369 |
370 |
371 |
372 | 373 | 374 |
375 |
376 |

Autogrow textarea

377 |
<!-- also include this in your HTML -->
378 | <script src=/path/to/jquery.jeditable.autogrow.min.js></script>
379 | <script src=/path/to/jquery.autogrowtextarea.js></script>
380 | 
381 |

Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.

382 |

Depends on Autogrow-Textarea by Jevin O. Sewaruth.

383 |

Show source code

384 |
385 |
$(".autogrow").editable("save.php", {
386 |     type      : "autogrow",
387 |     submit    : 'OK',
388 |     cancel    : 'cancel',
389 |     tooltip   : "Click to edit...",
390 |     onblur    : "ignore"
391 | });
392 | 
393 |
394 |
395 |
396 | 397 |
398 | 399 |
400 | 401 | 402 |
403 |
404 |

Masked Input

405 |
<!-- also include this in your HTML -->
406 | <script src=/path/to/jquery.jeditable.masked.min.js></script>
407 | <script src=/path/to/jquery.maskedinput.js></script>
408 | 
409 |

19/12/2017

410 |

Depends on 411 | Masked Input by Josh Bush. 412 |

413 |

Show source code

414 |
415 |
$(".masked").editable("save.php", {
416 |     type      : "masked",
417 |     mask      : "99/99/9999",
418 |     submit    : 'OK',
419 |     tooltip   : "Click to edit..."
420 | });
421 | 
422 |
423 |
424 |
425 | 426 | 427 | 428 |
429 |
430 |

Time Picker

431 |
<!-- also include this in your HTML -->
432 | <script src=/path/to/jquery.jeditable.time.min.js></script>
433 | 
434 |

16:30

435 |

Show source code

436 |
437 |
$(".timepicker").editable("save.php", {
438 |     type      : 'time',
439 |     submit    : 'OK',
440 |     tooltip   : "Click to edit..."
441 | });
442 | 
443 |
444 |
445 |
446 | 447 | 448 |
449 |
450 |

Character counter

451 |
<!-- also include this in your HTML -->
452 | <script src=/path/to/jquery.jeditable.charcounter.min.js></script>
453 | <script src=/path/to/jquery.charcounter.js></script>
454 | 
455 |

The number of characters in the textarea will be counted.

456 |

Show source code

457 |
458 |
$(".charcounter").editable("save.php", {
459 |     type      : "charcounter",
460 |     submit    : 'OK',
461 |     tooltip   : "Click to edit...",
462 |     onblur    : "ignore",
463 |     charcounter : {
464 |         characters : 60
465 |     }
466 | });
467 | 
468 |
469 |
470 |
471 | 472 |
473 |
474 | 475 | 476 | 477 | -------------------------------------------------------------------------------- /src/jquery.jeditable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Jeditable - jQuery in place edit plugin 3 | * @home https://github.com/NicolasCARPi/jquery_jeditable 4 | * @author Mika Tuupola, Dylan Verheul, Nicolas CARPi 5 | * @copyright © 2006 Mika Tuupola, Dylan Verheul, Nicolas CARPi 6 | * @licence MIT (see LICENCE file) 7 | * @name Jquery-jeditable 8 | * @type jQuery 9 | * 10 | * @param {String|Function} target - URL or Function to send edited content to. Can also be 'disable', 'enable', or 'destroy' 11 | * @param {Object} [options] - Additional options 12 | * @param {Object} [options.ajaxoptions] - jQuery Ajax options. See https://api.jquery.com/jQuery.ajax/ 13 | * @param {Function} [options.before] - Function to be executed before going into edit mode 14 | * @param {Function} [options.callback] - function(result, settings, submitdata) Function to run after submitting edited content 15 | * @param {String} [options.cancel] - Cancel button value, empty means no button 16 | * @param {String} [options.cancelcssclass] - CSS class to apply to cancel button 17 | * @param {Number} [options.cols] - Number of columns if using textarea 18 | * @param {String} [options.cssclass] - CSS class to apply to input form; use 'inherit' to copy from parent 19 | * @param {String} [options.inputcssclass] - CSS class to apply to input. 'inherit' to copy from parent 20 | * @param {Function} [options.intercept] - Intercept the returned data so you have a chance to process it before returning it in the page 21 | * @param {String|Function} [options.data] - Content loaded in the form 22 | * @param {String} [options.event='click'] - jQuery event such as 'click' or 'dblclick'. See https://api.jquery.com/category/events/ 23 | * @param {String} [options.formid] - Give an id to the form that is produced 24 | * @param {String|Number} [options.height='auto'] - Height of the element in pixels or 'auto' or 'none' 25 | * @param {String} [options.id='id'] - POST parameter name of edited div id 26 | * @param {String} [options.indicator] - Indicator html to show when saving 27 | * @param {String} [options.label] - Label for the form 28 | * @param {String} [options.list] - HTML5 attribute for text input. Will suggest from a datalist with id of the list option 29 | * @param {String|Function} [options.loaddata] - Extra parameters to pass when fetching content before editing 30 | * @param {String} [options.loadtext='Loading…'] - Text to display while loading external content 31 | * @param {String} [options.loadtype='GET'] - Request type for loadurl (GET or POST) 32 | * @param {String} [options.loadurl] - URL to fetch input content before editing 33 | * @param {Number} [options.max] - Maximum value for number type 34 | * @param {String} [options.maxlength] - The maximum number of character in the text field 35 | * @param {String} [options.method] - Method to use to send edited content (POST or PUT) 36 | * @param {Number} [options.min] - Minimum value for number type 37 | * @param {Boolean} [options.multiple] - Allow multiple selections in a select input 38 | * @param {String} [options.name='value'] - POST parameter name of edited content 39 | * @param {String|Function} [options.onblur='cancel'] - Use 'cancel', 'submit', 'ignore' or function. If function returns false, the form is cancelled. 40 | * @param {Function} [options.onedit] - function triggered upon edition; will cancel edition if it returns false 41 | * @param {Function} [options.onerror] - function(settings, original, xhr) { ... } called on error 42 | * @param {Function} [options.onreset] - function(settings, original) { ... } called before reset 43 | * @param {Function} [options.onsubmit] - function(settings, original) { ... } called before submit 44 | * @param {String} [options.pattern] - HTML5 attribute for text or URL input 45 | * @param {String} [options.placeholder='Click to edit'] - Placeholder text or html to insert when element is empty 46 | * @param {Number} [options.rows] - number of rows if using textarea 47 | * @param {Boolean} [options.select] - When true text is selected 48 | * @param {Function} [options.showfn]- Function that can animate the element when switching to edit mode 49 | * @param {String} [options.size] - The size of the text field 50 | * @param {String} [options.sortselectoptions] - Sort the options of a select form 51 | * @param {Number} [options.step] - Step size for number type 52 | * @param {String} [options.style] - Style to apply to input form; 'inherit' to copy from parent 53 | * @param {String} [options.submit] - submit button value, empty means no button 54 | * @param {String} [options.submitcssclass] - CSS class to apply to submit button 55 | * @param {Object|Function} [options.submitdata] - Extra parameters to send when submitting edited content. function(revert, settings, submitdata) 56 | * @param {String} [options.tooltip] - Tooltip text that appears on hover (via title attribute) 57 | * @param {String} [options.type='text'] - text, textarea, select, email, number, url (or any 3rd party input type) 58 | * @param {String|Number} [options.width='auto'] - The width of the element in pixels or 'auto' or 'none' 59 | * 60 | * @example Simple usage example: 61 | * $(".editable").editable("save.php", { 62 | * cancel : 'Cancel', 63 | * submit : 'Save', 64 | * tooltip : "Click to edit...", 65 | * }); 66 | */ 67 | (function($) { 68 | 69 | 'use strict'; 70 | 71 | // Keyboard accessibility/WAI-ARIA - allow users to navigate to an editable element using TAB/Shift+TAB 72 | $.fn.editableAriaShim = function () { 73 | this.attr({ 74 | role: 'button', 75 | tabindex: 0 76 | }); 77 | return this; // <-- object chaining. 78 | }; 79 | 80 | // EDITABLE function 81 | $.fn.editable = function(target, options) { 82 | 83 | if ('disable' === target) { 84 | $(this).data('disabled.editable', true); 85 | return; 86 | } 87 | if ('enable' === target) { 88 | $(this).data('disabled.editable', false); 89 | return; 90 | } 91 | if ('destroy' === target) { 92 | $(this) 93 | .off($(this).data('event.editable')) 94 | .removeData('disabled.editable') 95 | .removeData('event.editable'); 96 | return; 97 | } 98 | var settings = $.extend({}, $.fn.editable.defaults, {target:target}, options); 99 | 100 | /* setup some functions */ 101 | var plugin = $.editable.types[settings.type].plugin || function() { }; 102 | var submit = $.editable.types[settings.type].submit || function() { }; 103 | var buttons = $.editable.types[settings.type].buttons || $.editable.types.defaults.buttons; 104 | var content = $.editable.types[settings.type].content || $.editable.types.defaults.content; 105 | var element = $.editable.types[settings.type].element || $.editable.types.defaults.element; 106 | var reset = $.editable.types[settings.type].reset || $.editable.types.defaults.reset; 107 | var destroy = $.editable.types[settings.type].destroy || $.editable.types.defaults.destroy; 108 | var callback = settings.callback || function() { }; 109 | var intercept = settings.intercept || function(s) { return s; }; 110 | var onedit = settings.onedit || function() { }; 111 | var onsubmit = settings.onsubmit || function() { }; 112 | var onreset = settings.onreset || function() { }; 113 | var onerror = settings.onerror || reset; 114 | var before = settings.before || false; 115 | 116 | // TOOLTIP 117 | if (settings.tooltip) { 118 | $(this).attr('title', settings.tooltip); 119 | } 120 | 121 | return this.each(function() { 122 | 123 | /* Save this to self because this changes when scope changes. */ 124 | var self = this; 125 | 126 | /* Save so it can be later used by $.editable('destroy') */ 127 | $(this).data('event.editable', settings.event); 128 | 129 | /* If element is empty add something clickable (if requested) */ 130 | if (!$(this).html().trim()) { 131 | $(this).html(settings.placeholder); 132 | } 133 | 134 | if ('destroy' === target) { 135 | destroy.apply($(this).find('form'), [settings, self]); 136 | return; 137 | } 138 | 139 | // EVENT IS FIRED 140 | $(this).on(settings.event, function(e) { 141 | 142 | /* Abort if element is disabled. */ 143 | if (true === $(this).data('disabled.editable')) { 144 | return; 145 | } 146 | 147 | // do nothing if user press Tab again, just go to next element, not into edit mode 148 | if (e.which === 9) { 149 | return; 150 | } 151 | 152 | /* Prevent throwing an exception if edit field is clicked again. */ 153 | if (self.editing) { 154 | return; 155 | } 156 | 157 | /* Abort if onedit hook returns false. */ 158 | if (false === onedit.apply(this, [settings, self, e])) { 159 | return; 160 | } 161 | 162 | /* execute the before function if any was specified */ 163 | if (settings.before && (typeof settings.before === 'function')) { 164 | settings.before(e); 165 | } else if (settings.before && !(typeof settings.before === 'function')) { 166 | throw "The 'before' option needs to be provided as a function!"; 167 | } 168 | 169 | /* Prevent default action and bubbling. */ 170 | e.preventDefault(); 171 | e.stopPropagation(); 172 | 173 | /* Remove tooltip. */ 174 | if (settings.tooltip) { 175 | $(self).removeAttr('title'); 176 | } 177 | 178 | /* Remove placeholder text, replace is here because of IE. */ 179 | if ($(this).html().toLowerCase().replace(/(;|"|\/)/g, '') === 180 | settings.placeholder.toLowerCase().replace(/(;|"|\/)/g, '')) { 181 | $(this).html(''); 182 | } 183 | 184 | self.editing = true; 185 | self.revert = $(self).text(); 186 | $(self).html(''); 187 | 188 | /* Create the form object. */ 189 | var form = $('
'); 190 | 191 | /* Apply css or style or both. */ 192 | if (settings.cssclass) { 193 | if ('inherit' === settings.cssclass) { 194 | form.attr('class', $(self).attr('class')); 195 | } else { 196 | form.attr('class', settings.cssclass); 197 | } 198 | } 199 | 200 | if (settings.style) { 201 | if ('inherit' === settings.style) { 202 | form.attr('style', $(self).attr('style')); 203 | /* IE needs the second line or display won't be inherited. */ 204 | form.css('display', $(self).css('display')); 205 | } else { 206 | form.attr('style', settings.style); 207 | } 208 | } 209 | 210 | // add a label if it exists 211 | if (settings.label) { 212 | form.append(''); 213 | } 214 | 215 | // add an ID to the form 216 | if (settings.formid) { 217 | form.attr('id', settings.formid); 218 | } 219 | 220 | /* Add main input element to form and store it in input. */ 221 | var input = element.apply(form, [settings, self]); 222 | 223 | if (settings.inputcssclass) { 224 | if ('inherit' === settings.inputcssclass) { 225 | input.attr('class', $(self).attr('class')); 226 | } else { 227 | input.attr('class', settings.inputcssclass); 228 | } 229 | } 230 | 231 | /* Set input content via POST, GET, given data or existing value. */ 232 | var input_content; 233 | 234 | // timeout function 235 | var t; 236 | var isSubmitting = false; 237 | 238 | if (settings.loadurl) { 239 | t = self.setTimeout(function() { 240 | input.disabled = true; 241 | }, 100); 242 | $(self).html(settings.loadtext); 243 | 244 | var loaddata = {}; 245 | loaddata[settings.id] = self.id; 246 | if (typeof settings.loaddata === 'function') { 247 | $.extend(loaddata, settings.loaddata.apply(self, [self.revert, settings])); 248 | } else { 249 | $.extend(loaddata, settings.loaddata); 250 | } 251 | $.ajax({ 252 | type : settings.loadtype, 253 | url : settings.loadurl, 254 | data : loaddata, 255 | async: false, 256 | cache : false, 257 | success: function(result) { 258 | self.clearTimeout(t); 259 | input_content = result; 260 | input.disabled = false; 261 | } 262 | }); 263 | } else if (settings.data) { 264 | input_content = settings.data; 265 | if (typeof settings.data === 'function') { 266 | input_content = settings.data.apply(self, [self.revert, settings]); 267 | } 268 | } else { 269 | input_content = self.revert; 270 | } 271 | content.apply(form, [input_content, settings, self]); 272 | 273 | input.attr('name', settings.name); 274 | 275 | /* adjust the width of the element to account for the margin/padding/border */ 276 | if (settings.width !== 'none') { 277 | var adj_width = settings.width - (input.outerWidth(true) - settings.width); 278 | input.width(adj_width); 279 | } 280 | 281 | /* Add buttons to the form. */ 282 | buttons.apply(form, [settings, self]); 283 | 284 | /* Add created form to self. */ 285 | if (settings.showfn && (typeof settings.showfn === 'function')) { 286 | form.hide(); 287 | } 288 | 289 | // clear the loadtext that we put here before 290 | $(self).html(''); 291 | 292 | $(self).append(form); 293 | 294 | // execute the showfn 295 | if (settings.showfn && (typeof settings.showfn === 'function')) { 296 | settings.showfn(form); 297 | } 298 | 299 | /* Attach 3rd party plugin if requested. */ 300 | plugin.apply(form, [settings, self]); 301 | 302 | /* Focus to first visible form element. */ 303 | form.find(':input:visible:enabled:first').trigger('focus'); 304 | 305 | /* Highlight input contents when requested. */ 306 | if (settings.select) { 307 | input.trigger('select'); 308 | } 309 | 310 | /* discard changes if pressing esc */ 311 | $(this).on('keydown', function(e) { 312 | if (e.which === 27) { 313 | e.preventDefault(); 314 | reset.apply(form, [settings, self]); 315 | /* allow shift+enter to submit form (required for textarea) */ 316 | } else if (e.which == 13 && e.shiftKey){ 317 | e.preventDefault(); 318 | form.trigger('submit'); 319 | } 320 | }); 321 | 322 | /* Discard, submit or nothing with changes when clicking outside. */ 323 | /* Do nothing is usable when navigating with tab. */ 324 | if ('cancel' === settings.onblur) { 325 | input.on('blur', function(e) { 326 | /* Prevent canceling if submit was clicked. */ 327 | t = self.setTimeout(function() { 328 | reset.apply(form, [settings, self]); 329 | }, 500); 330 | }); 331 | } else if ('submit' === settings.onblur) { 332 | input.on('blur', function(e) { 333 | /* Prevent double submit if submit was clicked. */ 334 | t = self.setTimeout(function() { 335 | form.trigger('submit'); 336 | }, 200); 337 | }); 338 | } else if (typeof settings.onblur === 'function') { 339 | input.on('blur', function(e) { 340 | // reset the form if the onblur function returns false 341 | if (false === settings.onblur.apply(self, [input.val(), settings, form])) { 342 | reset.apply(form, [settings, self]); 343 | } 344 | }); 345 | } 346 | 347 | form.on('submit', function(e) { 348 | 349 | /* Do no submit. */ 350 | e.preventDefault(); 351 | e.stopPropagation(); 352 | 353 | if (isSubmitting) { 354 | // we are already submitting! Stop right here. 355 | return false; 356 | } else { 357 | isSubmitting = true; 358 | } 359 | 360 | if (t) { 361 | self.clearTimeout(t); 362 | } 363 | 364 | /* Call before submit hook. */ 365 | /* If it returns false abort submitting. */ 366 | isSubmitting = false !== onsubmit.apply(form, [settings, self]); 367 | if (isSubmitting) { 368 | /* Custom inputs call before submit hook. */ 369 | /* If it returns false abort submitting. */ 370 | isSubmitting = false !== submit.apply(form, [settings, self]); 371 | if (isSubmitting) { 372 | 373 | /* Check if given target is function */ 374 | if (typeof settings.target === 'function') { 375 | /* Callback function to handle the target response */ 376 | var responseHandler = function(value, complete) { 377 | isSubmitting = false; 378 | if (false !== complete) { 379 | $(self).html(value); 380 | self.editing = false; 381 | callback.apply(self, [self.innerText, settings]); 382 | if (!$(self).html().trim()) { 383 | $(self).html(settings.placeholder); 384 | } 385 | } 386 | }; 387 | /* Call the user target function */ 388 | var userTarget = settings.target.apply(self, [input.val(), settings, responseHandler]); 389 | /* Handle the target function return for compatibility */ 390 | if (false !== userTarget && undefined !== userTarget) { 391 | responseHandler(userTarget, userTarget); 392 | } 393 | 394 | } else { 395 | /* Add edited content and id of edited element to POST. */ 396 | var submitdata = {}; 397 | submitdata[settings.name] = input.val(); 398 | submitdata[settings.id] = self.id; 399 | /* Add extra data to be POST:ed. */ 400 | if (typeof settings.submitdata === 'function') { 401 | $.extend(submitdata, settings.submitdata.apply(self, [self.revert, settings, submitdata])); 402 | } else { 403 | $.extend(submitdata, settings.submitdata); 404 | } 405 | 406 | /* Quick and dirty PUT support. */ 407 | if ('PUT' === settings.method) { 408 | submitdata._method = 'put'; 409 | } 410 | 411 | // SHOW INDICATOR 412 | $(self).html(settings.indicator); 413 | 414 | /* Defaults for ajaxoptions. */ 415 | var ajaxoptions = { 416 | type : 'POST', 417 | complete: function (xhr, status) { 418 | isSubmitting = false; 419 | }, 420 | data : submitdata, 421 | dataType: 'html', 422 | url : settings.target, 423 | success : function(result, status) { 424 | 425 | // INTERCEPT 426 | result = intercept.apply(self, [result, status]); 427 | 428 | if (ajaxoptions.dataType === 'html') { 429 | $(self).html(result); 430 | } 431 | self.editing = false; 432 | callback.apply(self, [result, settings, submitdata]); 433 | if (!$(self).html().trim()) { 434 | $(self).html(settings.placeholder); 435 | } 436 | }, 437 | error : function(xhr, status, error) { 438 | onerror.apply(form, [settings, self, xhr]); 439 | } 440 | }; 441 | 442 | /* Override with what is given in settings.ajaxoptions. */ 443 | $.extend(ajaxoptions, settings.ajaxoptions); 444 | $.ajax(ajaxoptions); 445 | } 446 | } 447 | } 448 | 449 | /* Show tooltip again. */ 450 | if (settings.tooltip) { 451 | $(self).attr('title', settings.tooltip); 452 | } 453 | return false; 454 | }); 455 | }); 456 | 457 | // PRIVILEGED METHODS 458 | 459 | // RESET 460 | self.reset = function(form) { 461 | /* Prevent calling reset twice when blurring. */ 462 | if (self.editing) { 463 | /* Before reset hook, if it returns false abort resetting. */ 464 | if (false !== onreset.apply(form, [settings, self])) { 465 | $(self).text(self.revert); 466 | self.editing = false; 467 | if (!$(self).html().trim()) { 468 | $(self).html(settings.placeholder); 469 | } 470 | /* Show tooltip again. */ 471 | if (settings.tooltip) { 472 | $(self).attr('title', settings.tooltip); 473 | } 474 | } 475 | } 476 | }; 477 | 478 | // DESTROY 479 | self.destroy = function(form) { 480 | $(self) 481 | .off($(self).data('event.editable')) 482 | .removeData('disabled.editable') 483 | .removeData('event.editable'); 484 | 485 | self.clearTimeouts(); 486 | 487 | if (self.editing) { 488 | reset.apply(form, [settings, self]); 489 | } 490 | }; 491 | 492 | // CLEARTIMEOUT 493 | self.clearTimeout = function(t) { 494 | var timeouts = $(self).data('timeouts'); 495 | clearTimeout(t); 496 | if(timeouts) { 497 | var i = timeouts.indexOf(t); 498 | if(i > -1) { 499 | timeouts.splice(i, 1); 500 | if(timeouts.length <= 0) { 501 | $(self).removeData('timeouts'); 502 | } 503 | } else { 504 | console.warn('jeditable clearTimeout could not find timeout '+t); 505 | } 506 | } 507 | }; 508 | 509 | // CLEAR ALL TIMEOUTS 510 | self.clearTimeouts = function () { 511 | var timeouts = $(self).data('timeouts'); 512 | if(timeouts) { 513 | for(var i = 0, n = timeouts.length; i < n; ++i) { 514 | clearTimeout(timeouts[i]); 515 | } 516 | timeouts.length = 0; 517 | $(self).removeData('timeouts'); 518 | } 519 | }; 520 | 521 | // SETTIMEOUT 522 | self.setTimeout = function(callback, time) { 523 | var timeouts = $(self).data('timeouts'); 524 | var t = setTimeout(function() { 525 | callback(); 526 | self.clearTimeout(t); 527 | }, time); 528 | if(!timeouts) { 529 | timeouts = []; 530 | $(self).data('timeouts', timeouts); 531 | } 532 | timeouts.push(t); 533 | return t; 534 | }; 535 | }); 536 | }; 537 | 538 | var _supportInType = function (type) { 539 | var i = document.createElement('input'); 540 | i.setAttribute('type', type); 541 | return i.type !== 'text' ? type : 'text'; 542 | }; 543 | 544 | 545 | $.editable = { 546 | types: { 547 | defaults: { 548 | element : function(settings, original) { 549 | var input = $(''); 550 | $(this).append(input); 551 | return(input); 552 | }, 553 | content : function(string, settings, original) { 554 | $(this).find(':input:first').val(string); 555 | }, 556 | reset : function(settings, original) { 557 | original.reset(this); 558 | }, 559 | destroy: function(settings, original) { 560 | original.destroy(this); 561 | }, 562 | buttons : function(settings, original) { 563 | var form = this; 564 | var submit; 565 | if (settings.submit) { 566 | /* If given html string use that. */ 567 | if (settings.submit.match(/>$/)) { 568 | submit = $(settings.submit).on('click', function() { 569 | if (submit.attr('type') !== 'submit') { 570 | form.trigger('submit'); 571 | } 572 | }); 573 | /* Otherwise use button with given string as text. */ 574 | } else { 575 | submit = $('