Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 205 additions & 110 deletions app/assets/javascripts/rails_admin/ra.filtering-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
* jquery.ui.autocomplete.js
*/
(function($) {
$.widget("ra.filteringSelect", {
'use strict';

$.widget('ra.filteringSelect', {
options: {
createQuery: function(query) {
return { query: query };
Expand All @@ -26,143 +28,98 @@
xhr: false
},

button: null,
input: null,
select: null,

_create: function() {
var self = this,
select = this.element.hide(),
selected = select.children(":selected"),
value = selected.val() ? selected.text() : "";
var filtering_select;

if (this.options.xhr) {
this.options.source = this.options.remote_source;
// When using the browser back and forward buttons, it is possible that
// the autocomplete field will be cached which causes duplicate fields
// to be generated.
if (this.element.is(':visible')) {
this.element.hide();
filtering_select = this._inputGroup(this.element.attr('id'));
this.input = this._inputField();
this.button = this._buttonField();
} else {
this.options.source = select.children("option").map(function() {
return { label: $(this).text(), value: this.value };
}).toArray();
filtering_select = this.element.siblings(
'[data-input-for="' + this.element.attr('id') + '"]'
);
this.input = filtering_select.children('input');
this.button = filtering_select.children('.input-group-btn');
}
var filtering_select = $('<div class="input-group filtering-select col-sm-2" style="float:left"></div>')
var input = this.input = $('<input type="text">')
.val(value)
.addClass("form-control ra-filtering-select-input")
.attr('style', select.attr('style'))
.show()
.autocomplete({
delay: this.options.searchDelay,
minLength: this.options.minLength,
source: this._getSourceFunction(this.options.source),
select: function(event, ui) {
var option = $('<option></option>').attr('value', ui.item.id).attr('selected', 'selected').text(ui.item.value);
select.html(option);
select.trigger("change", ui.item.id);
self._trigger("selected", event, {
item: option
});
$(self.element.parents('.controls')[0]).find('.update').removeClass('disabled');
},
change: function(event, ui) {
if (!ui.item) {
var matcher = new RegExp("^" + $.ui.autocomplete.escapeRegex($(this).val()) + "$", "i"),
valid = false;
select.children("option").each(function() {
if ($(this).text().match(matcher)) {
this.selected = valid = true;
return false;
}
});
if (!valid || $(this).val() == '') {
// remove invalid value, as it didn't match anything
$(this).val(null);
select.html($('<option value="" selected="selected"></option>'));
input.data("ui-autocomplete").term = "";
$(self.element.parents('.controls')[0]).find('.update').addClass('disabled');
return false;
}

}
}
})
.keyup(function() {
/* Clear select options and trigger change if selected item is deleted */
if ($(this).val().length == 0) {
select.html($('<option value="" selected="selected"></option>'));
select.trigger("change");
}
})

if(select.attr('placeholder'))
input.attr('placeholder', select.attr('placeholder'))

input.data("ui-autocomplete")._renderItem = function(ul, item) {
return $("<li></li>")
.data("ui-autocomplete-item", item)
.append( $( "<a></a>" ).html( item.html || item.id ) )
.appendTo(ul);
};

var button = this.button = $('<span class="input-group-btn"><label class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-expanded="false" title="Show All Items" role="button"><span class="caret"></span><span class="ui-button-text">&nbsp;</span></label></span>')
.click(function() {
// close if already visible
if (input.autocomplete("widget").is(":visible")) {
input.autocomplete("close");
return;
}

// pass empty string as value to search for, displaying all results
input.autocomplete("search", "");
input.focus();
});

filtering_select.append(input).append(button).insertAfter(select);

this._setOptionsSource();
this._initAutocomplete();
this._initKeyEvent();
this._overloadRenderItem();
this._autocompleteDropdownEvent(this.button);

return filtering_select.append(this.input)
.append(this.button)
.insertAfter(this.element);
},

_getResultSet: function(request, data, xhr) {
var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i");
var highlighter = function(label, word){
if(word.length > 0){
return $.map(label.split(word), function(el, i){
return $('<span></span>').text(el).html();
}).join($('<strong></strong>').text(word)[0].outerHTML);
}else{
return $('<span></span>').text(label).html();
}
var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), 'i');

var spannedContent = function(content) {
return $('<span>').text(content).html();
};

return $.map(data, function(el, i) {
// match regexp only for local requests, remote ones are already filtered, and label may not contain filtered term.
if ((el.id || el.value) && (xhr || matcher.test(el.label))) {
return {
html: highlighter(el.label || el.id, request.term),
value: el.label || el.id,
id: el.id || el.value
};
var highlighter = function(label, word) {
if(word.length) {
return $.map(
label.split(word),
function(el) {
return spannedContent(el);
})
.join($('<strong>')
.text(word)[0]
.outerHTML
);
} else {
return spannedContent(label);
}
};

return $.map(
data,
function(el) {
var id = el.id || el.value;
var value = el.label || el.id;
// match regexp only for local requests, remote ones are already
// filtered, and label may not contain filtered term.
if (id && (xhr || matcher.test(el.label))) {
return {
html: highlighter(value, request.term),
value: value,
id: id
};
}
});
},

_getSourceFunction: function(source) {

var self = this,
requestIndex = 0;
var self = this;
var requestIndex = 0;

if ($.isArray(source)) {

return function(request, response) {
response(self._getResultSet(request, source, false));
};

} else if (typeof source === "string") {

} else if (typeof source === 'string') {
return function(request, response) {

if (this.xhr) {
this.xhr.abort();
}

this.xhr = $.ajax({
url: source,
data: self.options.createQuery(request.term),
dataType: "json",
dataType: 'json',
autocompleteRequest: ++requestIndex,
success: function(data, status) {
if (this.autocompleteRequest === requestIndex) {
Expand All @@ -176,11 +133,149 @@
}
});
};
} else {
return source;
}
},

_setOptionsSource: function() {
if (this.options.xhr) {
this.options.source = this.options.remote_source;
} else {
this.options.source = this.element.children('option').map(function() {
return { label: $(this).text(), value: this.value };
}).toArray();
}
},

return source;
_buttonField: function() {
return $(
'<span class="input-group-btn">' +
'<label class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-expanded="false" title="Show All Items" role="button">' +
'<span class="caret"></span>' +
'<span class="ui-button-text">&nbsp;</span>' +
'</label>' +
'</span>'
);
},

_autocompleteDropdownEvent: function(element) {
var self = this;

return element.click(function() {
// close if already visible
if (self.input.autocomplete('widget').is(':visible')) {
self.input.autocomplete('close');
return;
}

// pass empty string as value to search for, displaying all results
self.input.autocomplete('search', '');
self.input.focus();
});
},

_inputField: function() {
var input;
var selected = this.element.children(':selected');
var value = selected.val() ? selected.text() : '';

input = $('<input type="text">')
.val(value)
.addClass('form-control ra-filtering-select-input')
.attr('style', this.element.attr('style'))
.show();

if (this.element.attr('placeholder')) {
input.attr('placeholder', this.element.attr('placeholder'));
}

return input;
},

_inputGroup: function(inputFor) {
return $('<div>')
.addClass('input-group filtering-select col-sm-2')
.attr('data-input-for', inputFor)
.css('float', 'left');
},

_initAutocomplete: function() {
var self = this;

return this.input.autocomplete({
delay: this.options.searchDelay,
minLength: this.options.minLength,
source: this._getSourceFunction(this.options.source),
select: function(event, ui) {
var option = $('<option>')
.attr('value', ui.item.id)
.attr('selected', 'selected')
.text(ui.item.value);
self.element.html(option)
.trigger('change', ui.item.id);
self._trigger('selected', event, {
item: option
});
$(self.element.parents('.controls')[0])
.find('.update')
.removeClass('disabled');
},
change: function(event, ui) {
if (ui.item) {
return;
}

var matcher = new RegExp('^' + $.ui.autocomplete.escapeRegex($(this).val()) + '$', 'i');
var valid = false;

self.element.children('option')
.each(function() {
if ($(this).text().match(matcher)) {
valid = true;
return false;
}
});

if (valid || $(this).val() !== '') {
return;
}

// remove invalid value, as it didn't match anything
$(this).val(null);
self.element.html($('<option value="" selected="selected"></option>'));
self.input.data('ui-autocomplete').term = '';
$(self.element.parents('.controls')[0])
.find('.update')
.addClass('disabled');
return false;
}
});
},

_initKeyEvent: function() {
var self = this;

return this.input.keyup(function() {
if ($(this).val().length) {
return;
}

/* Clear select options and trigger change if selected item is deleted */
return self.element
.html($('<option value="" selected="selected"></option>'))
.trigger('change');
});
},

_overloadRenderItem: function() {
this.input.data('ui-autocomplete')._renderItem = function(ul, item) {
return $('<li></li>')
.data('ui-autocomplete-item', item)
.append($('<a></a>')
.html(item.html || item.id))
.appendTo(ul);
};
},

destroy: function() {
Expand Down