├── README.md └── jquery-ui.triggeredAutocomplete.js /README.md: -------------------------------------------------------------------------------- 1 | jQuery UI Triggered Autocomplete 2 | ==================== 3 | 4 | This widget lets you search for users to @mention in your posts. It works very much like Facebook and Google+ in that it supports users with spaces in their name. It writes to a hidden field with the user ID's formatted in this way: @[12345] while showing @username in the input box. You can save the encoded string for easier parsing at display time. 5 | 6 | ``` 7 | $('#inputbox').triggeredAutocomplete({ 8 | hidden: '#hidden_inputbox, 9 | source: "/search.php", 10 | trigger: "@", 11 | maxLength: 25 12 | }); 13 | ``` 14 | 15 | You can use a predefined array or json as a source. Example json result: 16 | 17 | ``` 18 | [{"value":"1234","label":"Beef"},{"value":"98765","label":"Chicken"}] 19 | ``` 20 | 21 | To use the hidden field without an ajax call you need to pass an associative array: 22 | 23 | ``` 24 | $('#inputbox').triggeredAutocomplete({ 25 | hidden: '#hidden_inputbox, 26 | source: new Array({ "value": "1234", "label": 'Geech'}, {"value": "5312", "label": "Marf"}) 27 | }); 28 | ``` 29 | 30 | This also supports an optional img to appear beside each result. You just need to pass an img URL for each value and label. Here is the CSS for the image: 31 | 32 | ``` 33 | .ui-menu-item img { padding-right: 10px; width: 32px; height: 32px; } 34 | .ui-menu-item span { color: #444; font-size: 12px; vertical-align: top } 35 | ``` 36 | 37 | If you want editable posts, you need to pass an id_map as an attr tag of the input box. This is also json encoded and is simply an associative array of the included user_id => username pairs in the existing post. This is so when you change the post the original @mentions are preserved in their @[12345] format. 38 | 39 | Demo: http://jsfiddle.net/vq6MH/146/ 40 | 41 | Discussion at Hawkee: http://www.hawkee.com/snippet/9391/ -------------------------------------------------------------------------------- /jquery-ui.triggeredAutocomplete.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************** 2 | /* 3 | * triggeredAutocomplete (jQuery UI autocomplete widget) 4 | * 2012 by Hawkee.com (hawkee@gmail.com) 5 | * 6 | * Version 1.4.5 7 | * 8 | * Requires jQuery 1.7 and jQuery UI 1.8 9 | * 10 | * Dual licensed under MIT or GPLv2 licenses 11 | * http://en.wikipedia.org/wiki/MIT_License 12 | * http://en.wikipedia.org/wiki/GNU_General_Public_License 13 | * 14 | */ 15 | 16 | ;(function ( $, window, document, undefined ) { 17 | $.widget("ui.triggeredAutocomplete", $.extend(true, {}, $.ui.autocomplete.prototype, { 18 | 19 | options: { 20 | trigger: "@", 21 | allowDuplicates: true, 22 | maxLength: 0 23 | }, 24 | 25 | _create:function() { 26 | 27 | var self = this; 28 | this.id_map = new Object(); 29 | this.stopIndex = -1; 30 | this.stopLength = -1; 31 | this.contents = ''; 32 | this.cursorPos = 0; 33 | 34 | /** Fixes some events improperly handled by ui.autocomplete */ 35 | this.element.bind('keydown.autocomplete.fix', function (e) { 36 | switch (e.keyCode) { 37 | case $.ui.keyCode.ESCAPE: 38 | self.close(e); 39 | e.stopImmediatePropagation(); 40 | break; 41 | case $.ui.keyCode.UP: 42 | case $.ui.keyCode.DOWN: 43 | if (!self.menu.element.is(":visible")) { 44 | e.stopImmediatePropagation(); 45 | } 46 | } 47 | }); 48 | 49 | // Check for the id_map as an attribute. This is for editing. 50 | 51 | var id_map_string = this.element.attr('id_map'); 52 | if(id_map_string) this.id_map = jQuery.parseJSON(id_map_string); 53 | 54 | this.ac = $.ui.autocomplete.prototype; 55 | this.ac._create.apply(this, arguments); 56 | 57 | this.updateHidden(); 58 | 59 | // Select function defined via options. 60 | this.options.select = function(event, ui) { 61 | var contents = self.contents; 62 | var cursorPos = self.cursorPos; 63 | 64 | // Save everything following the cursor (in case they went back to add a mention) 65 | // Separate everything before the cursor 66 | // Remove the trigger and search 67 | // Rebuild: start + result + end 68 | 69 | var end = contents.substring(cursorPos, contents.length); 70 | var start = contents.substring(0, cursorPos); 71 | start = start.substring(0, start.lastIndexOf(self.options.trigger)); 72 | 73 | var top = self.element.scrollTop(); 74 | this.value = start + self.options.trigger+ui.item.label+' ' + end; 75 | self.element.scrollTop(top); 76 | 77 | // Create an id map so we can create a hidden version of this string with id's instead of labels. 78 | 79 | self.id_map[ui.item.label] = ui.item.value; 80 | self.updateHidden(); 81 | 82 | /** Places the caret right after the inserted item. */ 83 | var index = start.length + self.options.trigger.length + ui.item.label.length + 2; 84 | if (this.createTextRange) { 85 | var range = this.createTextRange(); 86 | range.move('character', index); 87 | range.select(); 88 | } else if (this.setSelectionRange) { 89 | this.setSelectionRange(index, index); 90 | } 91 | 92 | return false; 93 | }; 94 | 95 | // Don't change the input as you browse the results. 96 | this.options.focus = function(event, ui) { return false; } 97 | this.menu.options.blur = function(event, ui) { return false; } 98 | 99 | // Any changes made need to update the hidden field. 100 | this.element.focus(function() { self.updateHidden(); }); 101 | this.element.change(function() { self.updateHidden(); }); 102 | }, 103 | 104 | // If there is an 'img' then show it beside the label. 105 | 106 | _renderItem: function( ul, item ) { 107 | if(item.img != undefined) { 108 | return $( "
" ) 109 | .data( "item.autocomplete", item ) 110 | .append( "" + "