source: main/trunk/model-sites-dev/cambridge-museum/collect/waikato-independent/pre-import/EditableDatabaseTable/WebContent/scripts/jquery.jeditable.js@ 34493

Last change on this file since 34493 was 34493, checked in by davidb, 4 years ago

Base project for providing jquery/jquery-ui controlled interface to editing a database table

File size: 22.3 KB
Line 
1/*
2 * Jeditable - jQuery in place edit plugin
3 *
4 * Copyright (c) 2006-2008 Mika Tuupola, Dylan Verheul
5 *
6 * Licensed under the MIT license:
7 * http://www.opensource.org/licenses/mit-license.php
8 *
9 * Project home:
10 * http://www.appelsiini.net/projects/jeditable
11 *
12 * Based on editable by Dylan Verheul <dylan_at_dyve.net>:
13 * http://www.dyve.net/jquery/?editable
14 *
15 */
16
17/**
18 * Version 1.6.2
19 *
20 * ** means there is basic unit tests for this parameter.
21 *
22 * @name Jeditable
23 * @type jQuery
24 * @param String target (POST) URL or function to send edited content to **
25 * @param Hash options additional options
26 * @param String options[method] method to use to send edited content (POST or PUT) **
27 * @param Function options[callback] Function to run after submitting edited content **
28 * @param String options[name] POST parameter name of edited content
29 * @param String options[id] POST parameter name of edited div id
30 * @param Hash options[submitdata] Extra parameters to send when submitting edited content.
31 * @param String options[type] text, textarea or select (or any 3rd party input type) **
32 * @param Integer options[rows] number of rows if using textarea **
33 * @param Integer options[cols] number of columns if using textarea **
34 * @param Mixed options[height] 'auto', 'none' or height in pixels **
35 * @param Mixed options[width] 'auto', 'none' or width in pixels **
36 * @param String options[loadurl] URL to fetch input content before editing **
37 * @param String options[loadtype] Request type for load url. Should be GET or POST.
38 * @param String options[loadtext] Text to display while loading external content.
39 * @param Mixed options[loaddata] Extra parameters to pass when fetching content before editing.
40 * @param Mixed options[data] Or content given as paramameter. String or function.**
41 * @param String options[indicator] indicator html to show when saving
42 * @param String options[tooltip] optional tooltip text via title attribute **
43 * @param String options[event] jQuery event such as 'click' of 'dblclick' **
44 * @param String options[submit] submit button value, empty means no button **
45 * @param String options[cancel] cancel button value, empty means no button **
46 * @param String options[cssclass] CSS class to apply to input form. 'inherit' to copy from parent. **
47 * @param String options[style] Style to apply to input form 'inherit' to copy from parent. **
48 * @param String options[select] true or false, when true text is highlighted ??
49 * @param String options[placeholder] Placeholder text or html to insert when element is empty. **
50 * @param String options[onblur] 'cancel', 'submit', 'ignore' or function ??
51 *
52 * @param Function options[onsubmit] function(settings, original) { ... } called before submit
53 * @param Function options[onreset] function(settings, original) { ... } called before reset
54 * @param Function options[onerror] function(settings, original, xhr) { ... } called on error
55 *
56 * @param Hash options[ajaxoptions] jQuery Ajax options. See docs.jquery.com.
57 *
58 */
59
60(function($) {
61
62 $.fn.editable = function(target, options) {
63
64 var settings = {
65 target : target,
66 name : 'value',
67 id : 'id',
68 type : 'text',
69 width : 'auto',
70 height : 'auto',
71 event : 'click',
72 onblur : 'cancel',
73 loadtype : 'GET',
74 loadtext : 'Loading...',
75 placeholder: 'Click to edit',
76 loaddata : {},
77 submitdata : {},
78 ajaxoptions: {}
79 };
80
81 if(options) {
82 $.extend(settings, options);
83 }
84
85 /* setup some functions */
86 var plugin = $.editable.types[settings.type].plugin || function() { };
87 var submit = $.editable.types[settings.type].submit || function() { };
88 var buttons = $.editable.types[settings.type].buttons
89 || $.editable.types['defaults'].buttons;
90 var content = $.editable.types[settings.type].content
91 || $.editable.types['defaults'].content;
92 var element = $.editable.types[settings.type].element
93 || $.editable.types['defaults'].element;
94 var reset = $.editable.types[settings.type].reset
95 || $.editable.types['defaults'].reset;
96 var callback = settings.callback || function() { };
97 var onsubmit = settings.onsubmit || function() { };
98 var onreset = settings.onreset || function() { };
99 var onerror = settings.onerror || reset;
100
101 /* add custom event if it does not exist */
102 if (!$.isFunction($(this)[settings.event])) {
103 $.fn[settings.event] = function(fn){
104 return fn ? this.bind(settings.event, fn) : this.trigger(settings.event);
105 }
106 }
107
108 /* show tooltip */
109 $(this).attr('title', settings.tooltip);
110
111 settings.autowidth = 'auto' == settings.width;
112 settings.autoheight = 'auto' == settings.height;
113
114 return this.each(function() {
115
116 /* save this to self because this changes when scope changes */
117 var self = this;
118
119 /* inlined block elements lose their width and height after first edit */
120 /* save them for later use as workaround */
121 var savedwidth = $(self).width();
122 var savedheight = $(self).height();
123
124 /* if element is empty add something clickable (if requested) */
125 if (!$.trim($(this).html())) {
126 $(this).html(settings.placeholder);
127 }
128
129 $(this)[settings.event](function(e) {
130
131 /* prevent throwing an exeption if edit field is clicked again */
132 if (self.editing) {
133 return;
134 }
135
136 /* remove tooltip */
137 $(self).removeAttr('title');
138
139 /* figure out how wide and tall we are, saved width and height */
140 /* are workaround for http://dev.jquery.com/ticket/2190 */
141 if (0 == $(self).width()) {
142 //$(self).css('visibility', 'hidden');
143 settings.width = savedwidth;
144 settings.height = savedheight;
145 } else {
146 if (settings.width != 'none') {
147 settings.width =
148 //settings.autowidth ? $(self).width() : settings.width;
149 settings.autowidth ? savedwidth : settings.width;
150 }
151 if (settings.height != 'none') {
152 settings.height =
153 settings.autoheight ? $(self).height() : settings.height;
154 }
155 }
156 //$(this).css('visibility', '');
157
158 /* remove placeholder text, replace is here because of IE */
159 if ($(this).html().toLowerCase().replace(/;/, '') ==
160 settings.placeholder.toLowerCase().replace(/;/, '')) {
161 $(this).html('');
162 }
163
164 self.editing = true;
165 self.revert = $(self).html();
166 $(self).html('');
167
168 /* create the form object */
169 var form = $('<form />');
170
171 /* apply css or style or both */
172 if (settings.cssclass) {
173 if ('inherit' == settings.cssclass) {
174 form.attr('class', $(self).attr('class'));
175 } else {
176 form.attr('class', settings.cssclass);
177 }
178 }
179
180 if (settings.style) {
181 if ('inherit' == settings.style) {
182 form.attr('style', $(self).attr('style'));
183 /* IE needs the second line or display wont be inherited */
184 form.css('display', $(self).css('display'));
185 } else {
186 form.attr('style', settings.style);
187 }
188 }
189
190 /* add main input element to form and store it in input */
191 var input = element.apply(form, [settings, self]);
192
193 /* set input content via POST, GET, given data or existing value */
194 var input_content;
195
196 if (settings.loadurl) {
197 var t = setTimeout(function() {
198 input.disabled = true;
199 content.apply(form, [settings.loadtext, settings, self]);
200 }, 100);
201
202 var loaddata = {};
203 loaddata[settings.id] = self.id;
204 if ($.isFunction(settings.loaddata)) {
205 $.extend(loaddata, settings.loaddata.apply(self, [self.revert, settings]));
206 } else {
207 $.extend(loaddata, settings.loaddata);
208 }
209 $.ajax({
210 type : settings.loadtype,
211 url : settings.loadurl,
212 data : loaddata,
213 async : false,
214 success: function(result) {
215 window.clearTimeout(t);
216 input_content = result;
217 input.disabled = false;
218 }
219 });
220 } else if (settings.data) {
221 input_content = settings.data;
222 if ($.isFunction(settings.data)) {
223 input_content = settings.data.apply(self, [self.revert, settings]);
224 }
225 } else {
226 input_content = self.revert;
227 }
228 content.apply(form, [input_content, settings, self]);
229
230 input.attr('name', settings.name);
231
232 /* add buttons to the form */
233 buttons.apply(form, [settings, self]);
234
235 /* add created form to self */
236 $(self).append(form);
237
238 /* attach 3rd party plugin if requested */
239 plugin.apply(form, [settings, self]);
240
241 /* focus to first visible form element */
242 $(':input:visible:enabled:first', form).focus();
243
244 /* highlight input contents when requested */
245 if (settings.select) {
246 input.select();
247 }
248
249 /* discard changes if pressing esc */
250 input.keydown(function(e) {
251 if (e.keyCode == 27) {
252 e.preventDefault();
253 //self.reset();
254 reset.apply(form, [settings, self]);
255 }
256 });
257
258 /* discard, submit or nothing with changes when clicking outside */
259 /* do nothing is usable when navigating with tab */
260 var t;
261 if ('cancel' == settings.onblur) {
262 input.blur(function(e) {
263 /* prevent canceling if submit was clicked */
264 t = setTimeout(function() {
265 reset.apply(form, [settings, self]);
266 }, 500);
267 });
268 } else if ('submit' == settings.onblur) {
269 input.blur(function(e) {
270 /* prevent double submit if submit was clicked */
271 t = setTimeout(function() {
272 form.submit();
273 }, 200);
274 });
275 } else if ($.isFunction(settings.onblur)) {
276 input.blur(function(e) {
277 settings.onblur.apply(self, [input.val(), settings]);
278 });
279 } else {
280 input.blur(function(e) {
281 /* TODO: maybe something here */
282 });
283 }
284
285 form.submit(function(e) {
286
287 if (t) {
288 clearTimeout(t);
289 }
290
291 /* do no submit */
292 e.preventDefault();
293
294 /* call before submit hook. */
295 /* if it returns false abort submitting */
296 if (false !== onsubmit.apply(form, [settings, self])) {
297 /* custom inputs call before submit hook. */
298 /* if it returns false abort submitting */
299 if (false !== submit.apply(form, [settings, self])) {
300
301 /* check if given target is function */
302 if ($.isFunction(settings.target)) {
303 var str = settings.target.apply(self, [input.val(), settings]);
304 $(self).html(str);
305 self.editing = false;
306 callback.apply(self, [self.innerHTML, settings]);
307 /* TODO: this is not dry */
308 if (!$.trim($(self).html())) {
309 $(self).html(settings.placeholder);
310 }
311 } else {
312 /* add edited content and id of edited element to POST */
313 var submitdata = {};
314 submitdata[settings.name] = input.val();
315 submitdata[settings.id] = self.id;
316 /* add extra data to be POST:ed */
317 if ($.isFunction(settings.submitdata)) {
318 $.extend(submitdata, settings.submitdata.apply(self, [self.revert, settings]));
319 } else {
320 $.extend(submitdata, settings.submitdata);
321 }
322
323 /* quick and dirty PUT support */
324 if ('PUT' == settings.method) {
325 submitdata['_method'] = 'put';
326 }
327
328 /* show the saving indicator */
329 $(self).html(settings.indicator);
330
331 /* defaults for ajaxoptions */
332 var ajaxoptions = {
333 type : 'POST',
334 data : submitdata,
335 url : settings.target,
336 success : function(result, status) {
337 $(self).html(result);
338 self.editing = false;
339 callback.apply(self, [self.innerHTML, settings]);
340 if (!$.trim($(self).html())) {
341 $(self).html(settings.placeholder);
342 }
343 },
344 error : function(xhr, status, error) {
345 onerror.apply(form, [settings, self, xhr]);
346 }
347 }
348
349 /* override with what is given in settings.ajaxoptions */
350 $.extend(ajaxoptions, settings.ajaxoptions);
351 $.ajax(ajaxoptions);
352
353 }
354 }
355 }
356
357 /* show tooltip again */
358 $(self).attr('title', settings.tooltip);
359
360 return false;
361 });
362 });
363
364 /* privileged methods */
365 this.reset = function(form) {
366 /* prevent calling reset twice when blurring */
367 if (this.editing) {
368 /* before reset hook, if it returns false abort reseting */
369 if (false !== onreset.apply(form, [settings, self])) {
370 $(self).html(self.revert);
371 self.editing = false;
372 if (!$.trim($(self).html())) {
373 $(self).html(settings.placeholder);
374 }
375 /* show tooltip again */
376 $(self).attr('title', settings.tooltip);
377 }
378 }
379 }
380 });
381
382 };
383
384
385 $.editable = {
386 types: {
387 defaults: {
388 element : function(settings, original) {
389 var input = $('<input type="hidden"></input>');
390 $(this).append(input);
391 return(input);
392 },
393 content : function(string, settings, original) {
394 $(':input:first', this).val(string);
395 },
396 reset : function(settings, original) {
397 original.reset(this);
398 },
399 buttons : function(settings, original) {
400 var form = this;
401 if (settings.submit) {
402 /* if given html string use that */
403 if (settings.submit.match(/>$/)) {
404 var submit = $(settings.submit).click(function() {
405 if (submit.attr("type") != "submit") {
406 form.submit();
407 }
408 });
409 /* otherwise use button with given string as text */
410 } else {
411 var submit = $('<button type="submit" />');
412 submit.html(settings.submit);
413 }
414 $(this).append(submit);
415 }
416 if (settings.cancel) {
417 /* if given html string use that */
418 if (settings.cancel.match(/>$/)) {
419 var cancel = $(settings.cancel);
420 /* otherwise use button with given string as text */
421 } else {
422 var cancel = $('<button type="cancel" />');
423 cancel.html(settings.cancel);
424 }
425 $(this).append(cancel);
426
427 $(cancel).click(function(event) {
428 //original.reset();
429 if ($.isFunction($.editable.types[settings.type].reset)) {
430 var reset = $.editable.types[settings.type].reset;
431 } else {
432 var reset = $.editable.types['defaults'].reset;
433 }
434 reset.apply(form, [settings, original]);
435 return false;
436 });
437 }
438 }
439 },
440 text: {
441 element : function(settings, original) {
442 var input = $('<input />');
443 if (settings.width != 'none') { input.width(settings.width); }
444 if (settings.height != 'none') { input.height(settings.height); }
445 /* https://bugzilla.mozilla.org/show_bug.cgi?id=236791 */
446 //input[0].setAttribute('autocomplete','off');
447 input.attr('autocomplete','off');
448 $(this).append(input);
449 return(input);
450 }
451 },
452 textarea: {
453 element : function(settings, original) {
454 var textarea = $('<textarea />');
455 if (settings.rows) {
456 textarea.attr('rows', settings.rows);
457 } else {
458 textarea.height(settings.height);
459 }
460 if (settings.cols) {
461 textarea.attr('cols', settings.cols);
462 } else {
463 textarea.width(settings.width);
464 }
465 $(this).append(textarea);
466 return(textarea);
467 }
468 },
469 select: {
470 element : function(settings, original) {
471 var select = $('<select />');
472 $(this).append(select);
473 return(select);
474 },
475 content : function(string, settings, original) {
476 if (String == string.constructor) {
477 eval ('var json = ' + string);
478 for (var key in json) {
479 if (!json.hasOwnProperty(key)) {
480 continue;
481 }
482 if ('selected' == key) {
483 continue;
484 }
485 var option = $('<option />').val(key).append(json[key]);
486 $('select', this).append(option);
487 }
488 }
489 /* Loop option again to set selected. IE needed this... */
490 $('select', this).children().each(function() {
491 if ($(this).val() == json['selected'] ||
492 $(this).text() == original.revert) {
493 $(this).attr('selected', 'selected');
494 };
495 });
496 }
497 }
498 },
499
500 /* Add new input type */
501 addInputType: function(name, input) {
502 $.editable.types[name] = input;
503 }
504 };
505
506})(jQuery);
Note: See TracBrowser for help on using the repository browser.