1/* 2 Tagator jQuery Plugin 3 A plugin to make input elements, tag holders 4 version 1.4, Apr 25th, 2017 5 by Ingi á Steinamørk 6 7 The MIT License (MIT) 8 9 Copyright (c) 2014 QODIO 10 11 Permission is hereby granted, free of charge, to any person obtaining a copy of 12 this software and associated documentation files (the "Software"), to deal in 13 the Software without restriction, including without limitation the rights to 14 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 15 the Software, and to permit persons to whom the Software is furnished to do so, 16 subject to the following conditions: 17 18 The above copyright notice and this permission notice shall be included in all 19 copies or substantial portions of the Software. 20 21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 23 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 24 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 25 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 26 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 */ 28 29(function ($) { 30 $.tagator = function (source_element, options) { 31 var defaults = { 32 prefix: 'tagator_', 33 height: 'auto', 34 useDimmer: false, 35 showAllOptionsOnFocus: false, 36 allowAutocompleteOnly: false, 37 autocomplete: [] 38 }; 39 40 var self = this; 41 var selected_index = -1; 42 var $source_element = $(source_element); 43 var $tagator_element = null; 44 var $tags_element = null; 45 var $placeholder_element = null; 46 var $input_element = null; 47 var $textlength_element = null; 48 var $options_element = null; 49 var key = { 50 backspace: 8, 51 enter: 13, 52 escape: 27, 53 left: 37, 54 up: 38, 55 right: 39, 56 down: 40, 57 comma: 188 58 }; 59 self.settings = {}; 60 61 62 // INITIALIZE PLUGIN 63 self.init = function () { 64 self.settings = $.extend({}, defaults, options); 65 66 //// ================== CREATE ELEMENTS ================== //// 67 // dimmer 68 if (self.settings.useDimmer) { 69 if ($('#' + self.settings.prefix + 'dimmer').length === 0) { 70 var $dimmer_element = $(document.createElement('div')); 71 $dimmer_element.attr('id', self.settings.prefix + 'dimmer'); 72 $dimmer_element.hide(); 73 $(document.body).prepend($dimmer_element); 74 } 75 } 76 // box element 77 $tagator_element = $(document.createElement('div')); 78 if ($source_element[0].id !== undefined) { 79 $tagator_element.attr('id', self.settings.prefix + $source_element[0].id); 80 } 81 $tagator_element.addClass(self.settings.prefix + 'element options-hidden'); 82 $tagator_element.css({ 83 padding: $source_element.css('padding'), 84 'flex-grow': $source_element.css('flex-grow'), 85 position: 'relative' 86 }); 87 if (parseInt($source_element.css('width')) !== 0) { 88 $tagator_element.css({ 89 width: $source_element.css('width') 90 }); 91 } 92 if (self.settings.height === 'element') { 93 $tagator_element.css({ 94 height: $source_element.outerHeight + 'px' 95 }); 96 } 97 $source_element.after($tagator_element); 98 $source_element.hide(); 99 // textlength element 100 $textlength_element = $(document.createElement('span')); 101 $textlength_element.addClass(self.settings.prefix + 'textlength'); 102 $textlength_element.css({ 103 position: 'absolute', 104 visibility: 'hidden' 105 }); 106 $tagator_element.append($textlength_element); 107 // tags element 108 $tags_element = $(document.createElement('div')); 109 $tags_element.addClass(self.settings.prefix + 'tags'); 110 $tagator_element.append($tags_element); 111 // placeholder element 112 $placeholder_element = $(document.createElement('div')); 113 $placeholder_element.addClass(self.settings.prefix + 'placeholder'); 114 $tagator_element.append($placeholder_element); 115 // input element 116 $input_element = $(document.createElement('input')); 117 $input_element.addClass(self.settings.prefix + 'input'); 118 $input_element.width(20); 119 $input_element.attr('autocomplete', 'false'); 120 $tagator_element.append($input_element); 121 // options element 122 $options_element = $(document.createElement('ul'));
123 $options_element.addClass(self.settings.prefix + 'options'); 124 125 $tagator_element.append($options_element); 126 127 //// ================== BIND ELEMENTS EVENTS ================== //// 128 // source element 129 $source_element.change(function () { 130 refreshTags(); 131 }); 132 // box element 133 $tagator_element.on('focus', function (e) { 134 e.preventDefault(); 135 e.stopPropagation(); 136 showOptions(); 137 $input_element.focus(); 138 }); 139 $tagator_element.on('mousedown', function (e) { 140 e.preventDefault(); 141 e.stopPropagation(); 142 $input_element.focus(); 143 if ($input_element[0].setSelectionRange) { 144 $input_element.focus(); 145 $input_element[0].setSelectionRange($input_element.val().length, $input_element.val().length); 146 } else if ($input_element[0].createTextRange) { 147 var range = $input_element[0].createTextRange(); 148 range.collapse(true); 149 range.moveEnd('character', $input_element.val().length); 150 range.moveStart('character', $input_element.val().length); 151 range.select(); 152 } 153 }); 154 $tagator_element.on('mouseup', function (e) { 155 e.preventDefault(); 156 e.stopPropagation(); 157 }); 158 $tagator_element.on('click', function (e) { 159 e.preventDefault(); 160 e.stopPropagation(); 161 if (self.settings.showAllOptionsOnFocus) { 162 //showOptions(); 163 searchOptions(); 164 } 165 $input_element.focus(); 166 }); 167 $tagator_element.on('dblclick', function (e) { 168 e.preventDefault(); 169 e.stopPropagation(); 170 $input_element.focus(); 171 $input_element.select(); 172 }); 173 // input element 174 $input_element.on('click', function (e) { 175 e.preventDefault(); 176 e.stopPropagation(); 177 }); 178 $input_element.on('dblclick', function (e) { 179 e.preventDefault(); 180 e.stopPropagation(); 181 }); 182 $input_element.on('keydown', function (e) { 183 e.stopPropagation(); 184 var keyCode = e.keyCode || e.which; 185 switch (keyCode) { 186 case key.up: 187 e.preventDefault(); 188 if (selected_index > -1) { 189 selected_index = selected_index - 1; 190 } else { 191 selected_index = $options_element.find('.' + self.settings.prefix + 'option').length - 1; 192 } 193 refreshActiveOption(); 194 scrollToActiveOption(); 195 break; 196 case key.down: 197 e.preventDefault(); 198 if (selected_index < $options_element.find('.' + self.settings.prefix + 'option').length - 1) { 199 selected_index = selected_index + 1; 200 } else { 201 selected_index = -1; 202 } 203 refreshActiveOption(); 204 scrollToActiveOption(); 205 break; 206 case key.escape: 207 e.preventDefault(); 208 break; 209 case key.comma: 210 e.preventDefault(); 211 if (selected_index === -1) { 212 if ($input_element.val() !== '') { 213 addTag($input_element.val()); 214 } 215 } 216 resizeInput(); 217 break; 218 case key.enter: 219 e.preventDefault(); 220 if (selected_index !== -1) { 221 selectOption(); 222 } else { 223 if ($input_element.val() !== '') { 224 addTag($input_element.val()); 225 } 226 } 227 resizeInput(); 228 break; 229 case key.backspace: 230 if ($input_element.val() === '') { 231 $source_element.val($source_element.val().substring(0, $source_element.val().lastIndexOf(','))); 232 $source_element.trigger('change'); 233 searchOptions(); 234 } 235 resizeInput(); 236 break; 237 default: 238 resizeInput(); 239 break; 240 } 241 refreshPlaceholder(); 242 }); 243 $input_element.on('keyup', function (e) { 244 e.preventDefault(); 245 e.stopPropagation(); 246 var keyCode = e.keyCode || e.which; 247 if (keyCode === key.escape || keyCode === key.enter) { 248 hideOptions(); 249 } else if (keyCode < 37 || keyCode > 40) { 250 searchOptions(); 251 } 252 if ($tagator_element.hasClass('options-hidden') && (keyCode === key.left || keyCode === key.right || keyCode === key.up || keyCode === key.down)) { 253 searchOptions(); 254 } 255 resizeInput(); 256 refreshPlaceholder(); 257 }); 258 $input_element.on('focus', function (e) { 259 e.preventDefault(); 260 e.stopPropagation(); 261 $tagator_element.addClass('focused'); 262 if (!$options_element.is(':empty') || self.settings.showAllOptionsOnFocus) { 263 searchOptions(); 264 showOptions(); 265 } 266 }); 267 $input_element.on('blur', function (e) { 268 e.preventDefault(); 269 e.stopPropagation(); 270 $tagator_element.removeClass('focused'); 271 if ($input_element.val() !== '') { 272 addTag($input_element.val()); 273 } 274 hideOptions(); 275 }); 276 refreshTags(); 277 }; 278 279 280 // RESIZE INPUT 281 var resizeInput = function () { 282 $textlength_element.html($input_element.val()); 283 $input_element.css({ width: ($textlength_element.width() + 30) + 'px' }); 284 }; 285 286 287 // SET AUTOCOMPLETE LIST 288 self.autocomplete = function (autocomplete) { 289 self.settings.autocomplete = autocomplete !== undefined ? autocomplete : []; 290 }; 291 292 293 // REFRESH TAGS 294 self.refresh = function () { 295 refreshTags(); 296 }; 297 var refreshTags = function () { 298 $tags_element.empty(); 299 var tags = $source_element.val().split(','); 300 $.each(tags, function (key, value) { 301 if (value !== '' && checkAllowedTag(value)) { 302 var $tag_element = $(document.createElement('div')); 303 $tag_element.addClass(self.settings.prefix + 'tag'); 304 $tag_element.html(value); 305 // remove button 306 var $button_remove_element = $(document.createElement('div')); 307 $button_remove_element.data('text', value); 308 $button_remove_element.addClass(self.settings.prefix + 'tag_remove'); 309 $button_remove_element.on('mousedown', function (e) { 310 e.preventDefault(); 311 e.stopPropagation(); 312 });
313 $button_remove_element.on('mouseup', function (e) { 314 e.preventDefault(); 315 e.stopPropagation(); 316 removeTag($(this).data('text')); 317 $source_element.trigger('change'); 318 }); 319 $button_remove_element.html('X'); 320 $tag_element.append($button_remove_element); 321 // clear 322 var $clear_element = $(document.createElement('div')); 323 $clear_element.css('clear', 'both'); 324 $tag_element.append($clear_element); 325 326 $tags_element.append($tag_element); 327 } 328 }); 329 refreshPlaceholder(); 330 searchOptions(); 331 }; 332 333 // REFRESH PLACEHOLDER 334 var refreshPlaceholder = function () { 335 if ($tags_element.is(':empty') && !$input_element.val() && $source_element.attr('placeholder')) { 336 $placeholder_element.html($source_element.attr('placeholder')); 337 $placeholder_element.show(); 338 } else { 339 $placeholder_element.hide(); 340 } 341 }; 342 343 // REMOVE TAG FROM ORIGINAL ELEMENT 344 var removeTag = function (text) { 345 var tagsBefore = $source_element.val().split(','); 346 var tagsAfter = []; 347 $.each(tagsBefore, function (key, value) { 348 if (value !== text && value !== '') { 349 tagsAfter.push(value); 350 } 351 }); 352 $source_element.val(tagsAfter.join(',')); 353 }; 354 355 // CHECK IF TAG IS PRESENT 356 var hasTag = function (text) { 357 var tags = $source_element.val().split(','); 358 var hasTag = false; 359 $.each(tags, function (key, value) { 360 if ($.trim(value) === $.trim(text)) { 361 hasTag = true; 362 } 363 }); 364 return hasTag; 365 }; 366 367 // CHECK IF TAG IS ALLOWED 368 var checkAllowedTag = function (text) { 369 if (!self.settings.allowAutocompleteOnly) { 370 return true; 371 } 372 373 var checkAllowedTag = false; 374 $.each(self.settings.autocomplete, function (key, value) { 375 if ($.trim(value) === $.trim(text)) { 376 checkAllowedTag = true; 377 } 378 }); 379 return checkAllowedTag; 380 }; 381 382 // ADD TAG TO ORIGINAL ELEMENT 383 var addTag = function (text) { 384 if (!hasTag(text) && checkAllowedTag(text)) { 385 $source_element.val($source_element.val() + ($source_element.val() !== '' ? ',' : '') + text); 386 $source_element.trigger('change'); 387 } 388 $input_element.val(''); 389 $tagator_element.focus(); 390 hideOptions(); 391 }; 392 393 // OPTIONS SEARCH METHOD 394 var searchOptions = function () { 395 $options_element.empty(); 396 if ($input_element.val().replace(/\s/g, '') !== '' || self.settings.showAllOptionsOnFocus) { 397 var optionsArray = []; 398 $.each(self.settings.autocomplete, function (key, value) { 399 if (value.toLowerCase().indexOf($input_element.val().toLowerCase()) !== -1) { 400 if (!hasTag(value)) { 401 optionsArray.push(value); 402 } 403 } 404 }); 405 generateOptions(optionsArray); 406 } 407 if ($input_element.is(':focus')) { 408 if (!$options_element.is(':empty')) { 409 showOptions(); 410 } else { 411 hideOptions(); 412 } 413 } else { 414 hideOptions(); 415 } 416 selected_index = -1; 417 }; 418 419 // GENERATE OPTIONS 420 var generateOptions = function (optionsArray) { 421 var index = -1; 422 $(optionsArray).each(function (key, value) { 423 index++; 424 var option = createOption(value, index); 425 $options_element.append(option); 426 }); 427 refreshActiveOption(); 428 }; 429 430 // CREATE RESULT OPTION 431 var createOption = function (text, index) { 432 // holder li 433 var option = document.createElement('li'); 434 $(option).data('index', index); 435 $(option).data('text', text); 436 $(option).html(text); 437 $(option).addClass(self.settings.prefix + 'option'); 438 if (this.selected) { 439 $(option).addClass('active'); 440 } 441 442 // BIND EVENTS 443 $(option).on('mouseover', function (e) { 444 e.stopPropagation(); 445 e.preventDefault(); 446 selected_index = index; 447 refreshActiveOption(); 448 }); 449 $(option).on('mousedown', function (e) { 450 e.stopPropagation(); 451 e.preventDefault(); 452 }); 453 $(option).on('click', function (e) { 454 e.preventDefault(); 455 e.stopPropagation(); 456 selectOption(); 457 }); 458 459 460 return option; 461 }; 462 463 // SHOW OPTIONS AND DIMMER 464 var showOptions = function () { 465 $tagator_element.removeClass('options-hidden').addClass('options-visible'); 466 if (self.settings.useDimmer) { 467 $('#' + self.settings.prefix + 'dimmer').show(); 468 } 469 $options_element.css('top', ($tagator_element.outerHeight - 2) + 'px'); 470 if ($tagator_element.hasClass('single')) { 471 selected_index = $options_element.find('.' + self.settings.prefix + 'option').index($options_element.find('.' + self.settings.prefix + 'option.active')); 472 } 473 scrollToActiveOption(); 474 }; 475 476 // HIDE OPTIONS AND DIMMER 477 var hideOptions = function () { 478 $tagator_element.removeClass('options-visible').addClass('options-hidden'); 479 if (self.settings.useDimmer) { 480 $('#' + self.settings.prefix + 'dimmer').hide(); 481 } 482 }; 483 484 // REFRESH ACTIVE IN OPTIONS METHOD 485 var refreshActiveOption = function () { 486 $options_element.find('.active').removeClass('active'); 487 if (selected_index !== -1) { 488 $options_element.find('.' + self.settings.prefix + 'option').eq(selected_index).addClass('active'); 489 } 490 }; 491 492 // SCROLL TO ACTIVE OPTION IN OPTIONS LIST 493 var scrollToActiveOption = function () { 494 var $active_element = $options_element.find('.' + self.settings.prefix + 'option.active'); 495 if ($active_element.length > 0) { 496 $options_element.scrollTop($options_element.scrollTop() + $active_element.position().top - $options_element.height()/2 + $active_element.height()/2); 497 } 498 499 }; 500 501 // SELECT ACTIVE OPTION 502 var selectOption = function () { 503 addTag($options_element.find('.' + self.settings.prefix + 'option').eq(selected_index).data('text'));
504 }; 505 506 507 // REMOVE PLUGIN AND REVERT INPUT ELEMENT TO ORIGINAL STATE 508 self.destroy = function () { 509 $tagator_element.remove(); 510 $source_element.removeData('tagator'); 511 $source_element.attr('style', '').show(); 512 if ($('.tagator').length === 0) { 513 $('#' + self.settings.prefix + 'dimmer').remove(); 514 } 515 }; 516 517 // Initialize plugin 518 self.init(); 519 }; 520 521 //// ================== INSTANTIATE PLUGIN METHOD ================== //// 522 $.fn.tagator = function(options) { 523 options = options !== undefined ? options : {}; 524 return this.each(function () { 525 if (typeof(options) === 'object') { 526 if ($(this).data('tagator') === undefined ) { 527 var self = new $.tagator(this, options); 528 $(this).data('tagator', self); 529 } 530 } else if (typeof $(this).data('tagator') !== 'undefined' && $(this).data('tagator')[options]) { 531 $(this).data('tagator')[options].apply(this, Array.prototype.slice.call(arguments, 1)); 532 } else { 533 //$.error('Method ' + options + ' does not exist in $.tagator'); 534 } 535 }); 536 }; 537}(jQuery)); 538 539 540$(function () { 541 $('.tagator').each(function () { 542 var $this = $(this); 543 var options = {}; 544 $.each($this.data(), function (key, value) { 545 if (key.substring(0, 7) === 'tagator') { 546 var value_temp = value.toString().replace(/'/g, '"'); 547 value_temp = $.parseJSON(value_temp); 548 if (typeof value_temp === 'object') { 549 value = value_temp; 550 } 551 options[key.substring(7, 8).toLowerCase() + key.substring(8)] = value; 552 } 553 }); 554 $this.tagator(options); 555 }); 556});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.