Create a gist now

Instantly share code, notes, and snippets.

Embed
To give <select/> autocompletition super-power.
/*
* MIT License
*
* Copyright (c) 2017 Rémi Blaise
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// To give <select/> autocompletition super-power.
// Options:
// - orientation: 'bottom' or 'top'
// - serverSide: true
// - ajax: url
// - valueAttr: 'id'
// - displayAttr: 'name'
// - length: -1 // Max nb of results display
// - minLength: 0 // Nb of letter to trigger autocomplete
// Server-side processing use the same API than DataTables, so can be handled by AbstractDataTablesController.
jQuery(function($){
$.widget( "ui.combobox", {
_create: function(position) {
this.wrapper = $( "<span>" )
.addClass( "input-group" )
.insertAfter( this.element );
this.element.hide();
this._createAutocomplete();
this._createShowAllButton();
this.position = position;
},
_createAutocomplete: function() {
var selected = this.element.children( ":selected" ),
value = selected.val() ? selected.text() : "";
this.input = $( "<input>" )
.appendTo( this.wrapper )
.val( value )
.attr( "placeholder", "John Snow" )
.addClass( "form-control" )
.autocomplete({
delay: 0,
minLength: this.options.minLength || 0,
source: this._source.bind( this ),
position: this.options.orientation === 'top' ? { my: "left bottom", at: "left top" } : {}
})
.tooltip({
classes: {
"ui-tooltip": "ui-state-highlight"
}
});
this._on( this.input, {
autocompleteselect: function( event, ui ) {
ui.item.option.selected = true;
this._trigger( "select", event, {
item: ui.item.option
});
},
autocompletechange: "_removeIfInvalid"
});
},
_createShowAllButton: function() {
var input = this.input,
wasOpen = false,
minLength = this.options.minLength
;
$( '<div class="input-group-btn"><button type="button" class="btn btn-default"><i class="fa fa-angle-down" aria-hidden="true"></i></button></div>' )
.attr( "tabIndex", -1 )
.tooltip()
.appendTo( this.wrapper )
.button({
icons: {
primary: "ui-icon-triangle-1-s"
},
text: false
})
.removeClass( "ui-corner-all" )
.on( "mousedown", function() {
wasOpen = input.autocomplete( "widget" ).is( ":visible" );
})
.on( "click", function() {
input.trigger( "focus" );
// Close if already visible
if ( wasOpen ) {
return;
}
// Pass empty string as value to search for, displaying all results
input
.autocomplete("option", "minLength", 0)
.autocomplete("search", "")
.autocomplete("option", "minLength", minLength)
;
});
},
_source: function(request, response) {
if (!this.options.serverSide) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
response( this.element.children( "option" ).map(function() {
var text = $( this ).text();
if ( this.value && ( !request.term || matcher.test(text) ) )
return {
label: text,
value: text,
option: this
};
}) );
} else {
var url = this.options.ajax,
value = this.options.value,
display = this.options.display,
length = this.options.length || -1,
search = this.input.val(),
draw
;
if (this.draw) {
draw = --this.draw;
} else {
draw = this.draw = -1;
}
$.ajax({
url : url,
dataType : 'json',
data: {
draw: draw,
length: length,
'search[value]': search,
'search[regex]': false,
'order[0][column]': 1,
'order[0][dir]': 'asc',
'columns[0][name]': value,
'columns[1][name]': display
},
success: function(data, textStatus, jqXHR) {
if (jqXHR.status == 200) {
if (data.draw === draw) {
response($.map(data.data, function(row){
return {
value: row[0],
label: row[1]
};
}));
} else {
console.log('draw is not corresponding');
}
} else {
console.log('An unexpected behavior happened:\n' + jqXHR.status);
}
},
error: function(jqXHR, textStatus, errorThrown) {
alert('An error ' + jqXHR.status + ' happened:\n' + errorThrown);
}
});
}
},
_removeIfInvalid: function( event, ui ) {
// Selected an item, nothing to do
if ( ui.item ) {
return;
}
// Search for a match (case-insensitive)
var value = this.input.val(),
valueLowerCase = value.toLowerCase(),
valid = false;
this.element.children( "option" ).each(function() {
if ( $( this ).text().toLowerCase() === valueLowerCase ) {
this.selected = valid = true;
return false;
}
});
// Found a match, nothing to do
if ( valid ) {
return;
}
// Remove invalid value
this.input
.val( "" )
.attr( "title", value + " didn't match any item" )
.tooltip( "open" );
this.element.val( "" );
this._delay(function() {
this.input.tooltip( "close" ).attr( "title", "" );
}, 2500 );
this.input.autocomplete( "instance" ).term = "";
},
_destroy: function() {
this.wrapper.remove();
this.element.show();
}
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment