comparison plugins/jqueryui/js/jquery.tagedit.js @ 0:4681f974d28b

vanilla 1.3.3 distro, I hope
author Charlie Root
date Thu, 04 Jan 2018 15:52:31 -0500
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4681f974d28b
1 /*
2 * Tagedit - jQuery Plugin
3 * The Plugin can be used to edit tags from a database the easy way
4 *
5 * Examples and documentation at: tagedit.webwork-albrecht.de
6 *
7 * License:
8 * This work is licensed under a MIT License
9 *
10 * @licstart The following is the entire license notice for the
11 * JavaScript code in this file.
12 *
13 * Copyright (c) 2010 Oliver Albrecht <info@webwork-albrecht.de>
14 * Copyright (c) 2014 Thomas BrĂ¼derli <thomas@roundcube.net>
15 *
16 * Licensed under the MIT licenses
17 *
18 * Permission is hereby granted, free of charge, to any person obtaining
19 * a copy of this software and associated documentation files (the
20 * "Software"), to deal in the Software without restriction, including
21 * without limitation the rights to use, copy, modify, merge, publish,
22 * distribute, sublicense, and/or sell copies of the Software, and to
23 * permit persons to whom the Software is furnished to do so, subject to
24 * the following conditions:
25 *
26 * The above copyright notice and this permission notice shall be
27 * included in all copies or substantial portions of the Software.
28 *
29 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 *
37 * @licend The above is the entire license notice
38 * for the JavaScript code in this file.
39 *
40 * @author Oliver Albrecht Mial: info@webwork-albrecht.de Twitter: @webworka
41 * @version 1.5.2 (06/2014)
42 * Requires: jQuery v1.4+, jQueryUI v1.8+, jQuerry.autoGrowInput
43 *
44 * Example of usage:
45 *
46 * $( "input.tag" ).tagedit();
47 *
48 * Possible options:
49 *
50 * autocompleteURL: '', // url for a autocompletion
51 * deleteEmptyItems: true, // Deletes items with empty value
52 * deletedPostfix: '-d', // will be put to the Items that are marked as delete
53 * addedPostfix: '-a', // will be put to the Items that are choosem from the database
54 * additionalListClass: '', // put a classname here if the wrapper ul shoud receive a special class
55 * allowEdit: true, // Switch on/off edit entries
56 * allowDelete: true, // Switch on/off deletion of entries. Will be ignored if allowEdit = false
57 * allowAdd: true, // switch on/off the creation of new entries
58 * direction: 'ltr' // Sets the writing direction for Outputs and Inputs
59 * animSpeed: 500 // Sets the animation speed for effects
60 * autocompleteOptions: {}, // Setting Options for the jquery UI Autocomplete (http://jqueryui.com/demos/autocomplete/)
61 * breakKeyCodes: [ 13, 44 ], // Sets the characters to break on to parse the tags (defaults: return, comma)
62 * checkNewEntriesCaseSensitive: false, // If there is a new Entry, it is checked against the autocompletion list. This Flag controlls if the check is (in-)casesensitive
63 * texts: { // some texts
64 * removeLinkTitle: 'Remove from list.',
65 * saveEditLinkTitle: 'Save changes.',
66 * deleteLinkTitle: 'Delete this tag from database.',
67 * deleteConfirmation: 'Are you sure to delete this entry?',
68 * deletedElementTitle: 'This Element will be deleted.',
69 * breakEditLinkTitle: 'Cancel'
70 * }
71 */
72
73 (function($) {
74
75 $.fn.tagedit = function(options) {
76 /**
77 * Merge Options with defaults
78 */
79 options = $.extend(true, {
80 // default options here
81 autocompleteURL: null,
82 checkToDeleteURL: null,
83 deletedPostfix: '-d',
84 addedPostfix: '-a',
85 additionalListClass: '',
86 allowEdit: true,
87 allowDelete: true,
88 allowAdd: true,
89 direction: 'ltr',
90 animSpeed: 500,
91 autocompleteOptions: {
92 select: function( event, ui ) {
93 $(this).val(ui.item.value).trigger('transformToTag', [ui.item.id]);
94 return false;
95 }
96 },
97 breakKeyCodes: [ 13, 44 ],
98 checkNewEntriesCaseSensitive: false,
99 texts: {
100 removeLinkTitle: 'Remove from list.',
101 saveEditLinkTitle: 'Save changes.',
102 deleteLinkTitle: 'Delete this tag from database.',
103 deleteConfirmation: 'Are you sure to delete this entry?',
104 deletedElementTitle: 'This Element will be deleted.',
105 breakEditLinkTitle: 'Cancel',
106 forceDeleteConfirmation: 'There are more records using this tag, are you sure do you want to remove it?'
107 },
108 tabindex: false
109 }, options || {});
110
111 // no action if there are no elements
112 if(this.length == 0) {
113 return;
114 }
115
116 // set the autocompleteOptions source
117 if(options.autocompleteURL) {
118 options.autocompleteOptions.source = options.autocompleteURL;
119 }
120
121 // Set the direction of the inputs
122 var direction= this.attr('dir');
123 if(direction && direction.length > 0) {
124 options.direction = this.attr('dir');
125 }
126
127 var elements = this;
128 var focusItem = null;
129
130 var baseNameRegexp = new RegExp("^(.*)\\[([0-9]*?("+options.deletedPostfix+"|"+options.addedPostfix+")?)?\]$", "i");
131
132 var baseName = elements.eq(0).attr('name').match(baseNameRegexp);
133 if(baseName && baseName.length == 4) {
134 baseName = baseName[1];
135 }
136 else {
137 // Elementname does not match the expected format, exit
138 alert('elementname dows not match the expected format (regexp: '+baseNameRegexp+')')
139 return;
140 }
141
142 // read tabindex from source element
143 var ti;
144 if (!options.tabindex && (ti = elements.eq(0).attr('tabindex')))
145 options.tabindex = ti;
146
147 // init elements
148 inputsToList();
149
150 /**
151 * Creates the tageditinput from a list of textinputs
152 *
153 */
154 function inputsToList() {
155 var html = '<ul class="tagedit-list '+options.additionalListClass+'">';
156
157 elements.each(function(i) {
158 var element_name = $(this).attr('name').match(baseNameRegexp);
159 if(element_name && element_name.length == 4 && (options.deleteEmptyItems == false || $(this).val().length > 0)) {
160 if(element_name[1].length > 0) {
161 var elementId = typeof element_name[2] != 'undefined'? element_name[2]: '',
162 domId = 'tagedit-' + baseName + '-' + (elementId || i);
163
164 html += '<li class="tagedit-listelement tagedit-listelement-old" aria-labelledby="'+domId+'">';
165 html += '<span dir="'+options.direction+'" id="'+domId+'">' + $(this).val() + '</span>';
166 html += '<input type="hidden" name="'+baseName+'['+elementId+']" value="'+$(this).val()+'" />';
167 if (options.allowDelete)
168 html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'" aria-label="'+options.texts.removeLinkTitle+' '+$(this).val()+'">x</a>';
169 html += '</li>';
170 }
171 }
172 });
173
174 // replace Elements with the list and save the list in the local variable elements
175 elements.last().after(html)
176 var newList = elements.last().next();
177 elements.remove();
178 elements = newList;
179
180 // Check if some of the elementshav to be marked as deleted
181 if(options.deletedPostfix.length > 0) {
182 elements.find('input[name$="'+options.deletedPostfix+'\]"]').each(function() {
183 markAsDeleted($(this).parent());
184 });
185 }
186
187 // put an input field at the End
188 // Put an empty element at the end
189 html = '<li class="tagedit-listelement tagedit-listelement-new">';
190 if (options.allowAdd)
191 html += '<input type="text" name="'+baseName+'[]" value="" id="tagedit-input" disabled="disabled" class="tagedit-input-disabled" dir="'+options.direction+'"/>';
192 html += '</li>';
193 html += '</ul>';
194
195 elements
196 .append(html)
197 .attr('tabindex', options.tabindex) // set tabindex to <ul> to recieve focus
198
199 // Set function on the input
200 .find('#tagedit-input')
201 .attr('tabindex', options.tabindex)
202 .each(function() {
203 $(this).autoGrowInput({comfortZone: 15, minWidth: 15, maxWidth: 20000});
204
205 // Event is triggert in case of choosing an item from the autocomplete, or finish the input
206 $(this).bind('transformToTag', function(event, id) {
207 var oldValue = (typeof id != 'undefined' && (id.length > 0 || id > 0));
208
209 var checkAutocomplete = oldValue == true || options.autocompleteOptions.noCheck ? false : true;
210 // check if the Value ist new
211 var isNewResult = isNew($(this).val(), checkAutocomplete);
212 if(isNewResult[0] === true || (isNewResult[0] === false && typeof isNewResult[1] == 'string')) {
213
214 if(oldValue == false && typeof isNewResult[1] == 'string') {
215 oldValue = true;
216 id = isNewResult[1];
217 }
218
219 if(options.allowAdd == true || oldValue) {
220 var domId = 'tagedit-' + baseName + '-' + id;
221 // Make a new tag in front the input
222 html = '<li class="tagedit-listelement tagedit-listelement-old" aria-labelledby="'+domId+'">';
223 html += '<span dir="'+options.direction+'" id="'+domId+'">' + $(this).val() + '</span>';
224 var name = oldValue? baseName + '['+id+options.addedPostfix+']' : baseName + '[]';
225 html += '<input type="hidden" name="'+name+'" value="'+$(this).val()+'" />';
226 html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'" aria-label="'+options.texts.removeLinkTitle+' '+$(this).val()+'">x</a>';
227 html += '</li>';
228
229 $(this).parent().before(html);
230 }
231 }
232 $(this).val('');
233
234 // close autocomplete
235 if(options.autocompleteOptions.source) {
236 if($(this).is(':ui-autocomplete'))
237 $(this).autocomplete( "close" );
238 }
239
240 })
241 .keydown(function(event) {
242 var code = event.keyCode > 0? event.keyCode : event.which;
243
244 switch(code) {
245 case 46:
246 if (!focusItem)
247 break;
248 case 8: // BACKSPACE
249 if(focusItem) {
250 focusItem.fadeOut(options.animSpeed, function() {
251 $(this).remove();
252 })
253 unfocusItem();
254 event.preventDefault();
255 return false;
256 }
257 else if($(this).val().length == 0) {
258 // delete Last Tag
259 var elementToRemove = elements.find('li.tagedit-listelement-old').last();
260 elementToRemove.fadeOut(options.animSpeed, function() {elementToRemove.remove();})
261 event.preventDefault();
262 return false;
263 }
264 break;
265 case 9: // TAB
266 if($(this).val().length > 0 && $('ul.ui-autocomplete #ui-active-menuitem').length == 0) {
267 $(this).trigger('transformToTag');
268 event.preventDefault();
269 return false;
270 }
271 break;
272 case 37: // LEFT
273 case 39: // RIGHT
274 if($(this).val().length == 0) {
275 // select previous Tag
276 var inc = code == 37 ? -1 : 1,
277 items = elements.find('li.tagedit-listelement-old')
278 x = items.length, next = 0;
279 items.each(function(i, elem) {
280 if ($(elem).hasClass('tagedit-listelement-focus')) {
281 x = i;
282 return true;
283 }
284 });
285 unfocusItem();
286 next = Math.max(0, x + inc);
287 if (items.get(next)) {
288 focusItem = items.eq(next).addClass('tagedit-listelement-focus');
289 $(this).attr('aria-activedescendant', focusItem.attr('aria-labelledby'))
290
291 if(options.autocompleteOptions.source != false) {
292 $(this).autocomplete('close').autocomplete('disable');
293 }
294 }
295 event.preventDefault();
296 return false;
297 }
298 break;
299 default:
300 // ignore input if an item is focused
301 if (focusItem !== null) {
302 event.preventDefault();
303 event.bubble = false;
304 return false;
305 }
306 }
307 return true;
308 })
309 .keypress(function(event) {
310 var code = event.keyCode > 0? event.keyCode : event.which;
311 if($.inArray(code, options.breakKeyCodes) > -1) {
312 if($(this).val().length > 0 && $('ul.ui-autocomplete #ui-active-menuitem').length == 0) {
313 $(this).trigger('transformToTag');
314 }
315 event.preventDefault();
316 return false;
317 }
318 else if($(this).val().length > 0){
319 unfocusItem();
320 }
321 return true;
322 })
323 .bind('paste', function(e){
324 var that = $(this);
325 if (e.type == 'paste'){
326 setTimeout(function(){
327 that.trigger('transformToTag');
328 }, 1);
329 }
330 })
331 .blur(function() {
332 if($(this).val().length == 0) {
333 // disable the field to prevent sending with the form
334 $(this).attr('disabled', 'disabled').addClass('tagedit-input-disabled');
335 }
336 else {
337 // Delete entry after a timeout
338 var input = $(this);
339 $(this).data('blurtimer', window.setTimeout(function() {input.val('');}, 500));
340 }
341 unfocusItem();
342 // restore tabindex when widget looses focus
343 if (options.tabindex)
344 elements.attr('tabindex', options.tabindex);
345 })
346 .focus(function() {
347 window.clearTimeout($(this).data('blurtimer'));
348 // remove tabindex on <ul> because #tagedit-input now has it
349 elements.attr('tabindex', '-1');
350 });
351
352 if(options.autocompleteOptions.source != false) {
353 $(this).autocomplete(options.autocompleteOptions);
354 }
355 })
356 .end()
357 .click(function(event) {
358 switch(event.target.tagName) {
359 case 'A':
360 $(event.target).parent().fadeOut(options.animSpeed, function() {
361 $(event.target).parent().remove();
362 elements.find('#tagedit-input').focus();
363 });
364 break;
365 case 'INPUT':
366 case 'SPAN':
367 case 'LI':
368 if($(event.target).hasClass('tagedit-listelement-deleted') == false &&
369 $(event.target).parent('li').hasClass('tagedit-listelement-deleted') == false) {
370 // Don't edit an deleted Items
371 return doEdit(event);
372 }
373 default:
374 $(this).find('#tagedit-input')
375 .removeAttr('disabled')
376 .removeClass('tagedit-input-disabled')
377 .focus();
378 }
379 return false;
380 })
381 // forward focus event (on tabbing through the form)
382 .focus(function(e){ $(this).click(); })
383 }
384
385 /**
386 * Remove class and reference to currently focused tag item
387 */
388 function unfocusItem() {
389 if(focusItem){
390 if(options.autocompleteOptions.source != false) {
391 elements.find('#tagedit-input').autocomplete('enable');
392 }
393 focusItem.removeClass('tagedit-listelement-focus');
394 focusItem = null;
395 elements.find('#tagedit-input').removeAttr('aria-activedescendant');
396 }
397 }
398
399 /**
400 * Sets all Actions and events for editing an Existing Tag.
401 *
402 * @param event {object} The original Event that was given
403 * return {boolean}
404 */
405 function doEdit(event) {
406 if(options.allowEdit == false) {
407 // Do nothing
408 return;
409 }
410
411 var element = event.target.tagName == 'SPAN'? $(event.target).parent() : $(event.target);
412
413 var closeTimer = null;
414
415 // Event that is fired if the User finishes the edit of a tag
416 element.bind('finishEdit', function(event, doReset) {
417 window.clearTimeout(closeTimer);
418
419 var textfield = $(this).find(':text');
420 var isNewResult = isNew(textfield.val(), true);
421 if(textfield.val().length > 0 && (typeof doReset == 'undefined' || doReset === false) && (isNewResult[0] == true)) {
422 // This is a new Value and we do not want to do a reset. Set the new value
423 $(this).find(':hidden').val(textfield.val());
424 $(this).find('span').html(textfield.val());
425 }
426
427 textfield.remove();
428 $(this).find('a.tagedit-save, a.tagedit-break, a.tagedit-delete').remove(); // Workaround. This normaly has to be done by autogrow Plugin
429 $(this).removeClass('tagedit-listelement-edit').unbind('finishEdit');
430 return false;
431 });
432
433 var hidden = element.find(':hidden');
434 html = '<input type="text" name="tmpinput" autocomplete="off" value="'+hidden.val()+'" class="tagedit-edit-input" dir="'+options.direction+'"/>';
435 html += '<a class="tagedit-save" title="'+options.texts.saveEditLinkTitle+'">o</a>';
436 html += '<a class="tagedit-break" title="'+options.texts.breakEditLinkTitle+'">x</a>';
437
438 // If the Element is one from the Database, it can be deleted
439 if(options.allowDelete == true && element.find(':hidden').length > 0 &&
440 typeof element.find(':hidden').attr('name').match(baseNameRegexp)[3] != 'undefined') {
441 html += '<a class="tagedit-delete" title="'+options.texts.deleteLinkTitle+'">d</a>';
442 }
443
444 hidden.after(html);
445 element
446 .addClass('tagedit-listelement-edit')
447 .find('a.tagedit-save')
448 .click(function() {
449 $(this).parent().trigger('finishEdit');
450 return false;
451 })
452 .end()
453 .find('a.tagedit-break')
454 .click(function() {
455 $(this).parent().trigger('finishEdit', [true]);
456 return false;
457 })
458 .end()
459 .find('a.tagedit-delete')
460 .click(function() {
461 window.clearTimeout(closeTimer);
462 if(confirm(options.texts.deleteConfirmation)) {
463 var canDelete = checkToDelete($(this).parent());
464 if (!canDelete && confirm(options.texts.forceDeleteConfirmation)) {
465 markAsDeleted($(this).parent());
466 }
467
468 if(canDelete) {
469 markAsDeleted($(this).parent());
470 }
471
472 $(this).parent().find(':text').trigger('finishEdit', [true]);
473 }
474 else {
475 $(this).parent().find(':text').trigger('finishEdit', [true]);
476 }
477 return false;
478 })
479 .end()
480 .find(':text')
481 .focus()
482 .autoGrowInput({comfortZone: 10, minWidth: 15, maxWidth: 20000})
483 .keypress(function(event) {
484 switch(event.keyCode) {
485 case 13: // RETURN
486 event.preventDefault();
487 $(this).parent().trigger('finishEdit');
488 return false;
489 case 27: // ESC
490 event.preventDefault();
491 $(this).parent().trigger('finishEdit', [true]);
492 return false;
493 }
494 return true;
495 })
496 .blur(function() {
497 var that = $(this);
498 closeTimer = window.setTimeout(function() {that.parent().trigger('finishEdit', [true])}, 500);
499 });
500 }
501
502 /**
503 * Verifies if the tag select to be deleted is used by other records using an Ajax request.
504 *
505 * @param element
506 * @returns {boolean}
507 */
508 function checkToDelete(element) {
509 // if no URL is provide will not verify
510 if(options.checkToDeleteURL === null) {
511 return false;
512 }
513
514 var inputName = element.find('input:hidden').attr('name');
515 var idPattern = new RegExp('\\d');
516 var tagId = inputName.match(idPattern);
517 var checkResult = false;
518
519 $.ajax({
520 async : false,
521 url : options.checkToDeleteURL,
522 dataType: 'json',
523 type : 'POST',
524 data : { 'tagId' : tagId},
525 complete: function (XMLHttpRequest, textStatus) {
526
527 // Expected JSON Object: { "success": Boolean, "allowDelete": Boolean}
528 var result = $.parseJSON(XMLHttpRequest.responseText);
529 if(result.success === true){
530 checkResult = result.allowDelete;
531 }
532 }
533 });
534
535 return checkResult;
536 }
537
538 /**
539 * Marks a single Tag as deleted.
540 *
541 * @param element {object}
542 */
543 function markAsDeleted(element) {
544 element
545 .trigger('finishEdit', [true])
546 .addClass('tagedit-listelement-deleted')
547 .attr('title', options.deletedElementTitle);
548 element.find(':hidden').each(function() {
549 var nameEndRegexp = new RegExp('('+options.addedPostfix+'|'+options.deletedPostfix+')?\]');
550 var name = $(this).attr('name').replace(nameEndRegexp, options.deletedPostfix+']');
551 $(this).attr('name', name);
552 });
553
554 }
555
556 /**
557 * Checks if a tag is already choosen.
558 *
559 * @param value {string}
560 * @param checkAutocomplete {boolean} optional Check also the autocomplet values
561 * @returns {Array} First item is a boolean, telling if the item should be put to the list, second is optional the ID from autocomplete list
562 */
563 function isNew(value, checkAutocomplete) {
564 checkAutocomplete = typeof checkAutocomplete == 'undefined'? false : checkAutocomplete;
565 var autoCompleteId = null;
566
567 var compareValue = options.checkNewEntriesCaseSensitive == true? value : value.toLowerCase();
568
569 var isNew = true;
570 elements.find('li.tagedit-listelement-old input:hidden').each(function() {
571 var elementValue = options.checkNewEntriesCaseSensitive == true? $(this).val() : $(this).val().toLowerCase();
572 if(elementValue == compareValue) {
573 isNew = false;
574 }
575 });
576
577 if (isNew == true && checkAutocomplete == true && options.autocompleteOptions.source != false) {
578 var result = [];
579 if ($.isArray(options.autocompleteOptions.source)) {
580 result = options.autocompleteOptions.source;
581 }
582 else if ($.isFunction(options.autocompleteOptions.source)) {
583 options.autocompleteOptions.source({term: value}, function (data) {result = data});
584 }
585 else if (typeof options.autocompleteOptions.source === "string") {
586 // Check also autocomplete values
587 var autocompleteURL = options.autocompleteOptions.source;
588 if (autocompleteURL.match(/\?/)) {
589 autocompleteURL += '&';
590 } else {
591 autocompleteURL += '?';
592 }
593 autocompleteURL += 'term=' + value;
594 $.ajax({
595 async: false,
596 url: autocompleteURL,
597 dataType: 'json',
598 complete: function (XMLHttpRequest, textStatus) {
599 result = $.parseJSON(XMLHttpRequest.responseText);
600 }
601 });
602 }
603
604 // If there is an entry for that already in the autocomplete, don't use it (Check could be case sensitive or not)
605 for (var i = 0; i < result.length; i++) {
606 var resultValue = result[i].label? result[i].label : result[i];
607 var label = options.checkNewEntriesCaseSensitive == true? resultValue : resultValue.toLowerCase();
608 if (label == compareValue) {
609 isNew = false;
610 autoCompleteId = typeof result[i] == 'string' ? i : result[i].id;
611 break;
612 }
613 }
614 }
615
616 return new Array(isNew, autoCompleteId);
617 }
618 }
619 })(jQuery);
620
621 (function($){
622
623 // jQuery autoGrowInput plugin by James Padolsey
624 // See related thread: http://stackoverflow.com/questions/931207/is-there-a-jquery-autogrow-plugin-for-text-fields
625
626 $.fn.autoGrowInput = function(o) {
627
628 o = $.extend({
629 maxWidth: 1000,
630 minWidth: 0,
631 comfortZone: 70
632 }, o);
633
634 this.filter('input:text').each(function(){
635
636 var minWidth = o.minWidth || $(this).width(),
637 val = '',
638 input = $(this),
639 testSubject = $('<tester/>').css({
640 position: 'absolute',
641 top: -9999,
642 left: -9999,
643 width: 'auto',
644 fontSize: input.css('fontSize'),
645 fontFamily: input.css('fontFamily'),
646 fontWeight: input.css('fontWeight'),
647 letterSpacing: input.css('letterSpacing'),
648 whiteSpace: 'nowrap'
649 }),
650 check = function() {
651
652 if (val === (val = input.val())) {return;}
653
654 // Enter new content into testSubject
655 var escaped = val.replace(/&/g, '&amp;').replace(/\s/g,'&nbsp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
656 testSubject.html(escaped);
657
658 // Calculate new width + whether to change
659 var testerWidth = testSubject.width(),
660 newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
661 currentWidth = input.width(),
662 isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
663 || (newWidth > minWidth && newWidth < o.maxWidth);
664
665 // Animate width
666 if (isValidWidthChange) {
667 input.width(newWidth);
668 }
669
670 };
671
672 testSubject.insertAfter(input);
673
674 $(this).bind('keyup keydown blur update', check);
675
676 check();
677 });
678
679 return this;
680
681 };
682
683 })(jQuery);