Skip to content

Commit

Permalink
Merge pull request #2678 from HashNotAdam/hotfix/fix_duplication_bug
Browse files Browse the repository at this point in the history
Issue #2677: Autocomplete fields duplicated when using browser back/forward buttons
  • Loading branch information
mshibuya authored Sep 3, 2016
2 parents 4598ddf + 5da6a54 commit 77d8421
Showing 1 changed file with 205 additions and 110 deletions.
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

0 comments on commit 77d8421

Please sign in to comment.