/**
* @file:
* Converts textfield to a autocomplete deluxe widget.
*/
(function($) {
Drupal.autocomplete_deluxe = Drupal.autocomplete_deluxe || {};
Drupal.behaviors.autocomplete_deluxe = {
attach: function(context) {
var autocomplete_settings = Drupal.settings.autocomplete_deluxe;
$('input.autocomplete-deluxe-form').once( function() {
if (autocomplete_settings[$(this).attr('id')].multiple === true) {
new Drupal.autocomplete_deluxe.MultipleWidget(this, autocomplete_settings[$(this).attr('id')]);
} else {
new Drupal.autocomplete_deluxe.SingleWidget(autocomplete_settings[$(this).attr('id')]);
}
});
}
};
/**
* Autogrow plugin which auto resizes the input of the multiple value.
*
* http://stackoverflow.com/questions/931207/is-there-a-jquery-autogrow-plugin-for-text-fields
*
*/
$.fn.autoGrowInput = function(o) {
o = $.extend({
maxWidth: 1000,
minWidth: 0,
comfortZone: 70
}, o);
this.filter('input:text').each(function(){
var minWidth = o.minWidth || $(this).width(),
val = '',
input = $(this),
testSubject = $('').css({
position: 'absolute',
top: -9999,
left: -9999,
width: 'auto',
fontSize: input.css('fontSize'),
fontFamily: input.css('fontFamily'),
fontWeight: input.css('fontWeight'),
letterSpacing: input.css('letterSpacing'),
whiteSpace: 'nowrap'
}),
check = function() {
if (val === (val = input.val())) {return;}
// Enter new content into testSubject
var escaped = val.replace(/&/g, '&').replace(/\s/g,' ').replace(//g, '>');
testSubject.html(escaped);
// Calculate new width + whether to change
var testerWidth = testSubject.width(),
newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
currentWidth = input.width(),
isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
|| (newWidth > minWidth && newWidth < o.maxWidth);
// Animate width
if (isValidWidthChange) {
input.width(newWidth);
}
};
testSubject.insertAfter(input);
$(this).bind('keyup keydown blur update', check);
});
return this;
};
/**
* Unescapes the given string.
*/
Drupal.autocomplete_deluxe.unescape = function (input) {
// Unescaping is done via a textarea, since the text inside of it is never
// executed. This method also allows us to support older browsers like
// IE 9 and below.
var textArea = document.createElement('textarea');
textArea.innerHTML = input;
var decoded = textArea.value;
if ('remove' in Element.prototype) {
textArea.remove();
}
return decoded;
};
/**
* If there is no result this label will be shown.
* @type {{label: string, value: string}}
*/
Drupal.autocomplete_deluxe.empty = {label: '- ' + Drupal.t('None') + ' -', value: "" };
/**
* EscapeRegex function from jquery autocomplete, is not included in drupal.
*/
Drupal.autocomplete_deluxe.escapeRegex = function(value) {
return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/gi, "\\$&");
};
/**
* Filter function from jquery autocomplete, is not included in drupal.
*/
Drupal.autocomplete_deluxe.filter = function(array, term) {
var matcher = new RegExp(Drupal.autocomplete_deluxe.escapeRegex(term), "i");
return $.grep(array, function(value) {
return matcher.test(value.label || value.value || value);
});
};
Drupal.autocomplete_deluxe.Widget = function() {
};
/**
* Url for the callback.
*/
Drupal.autocomplete_deluxe.Widget.prototype.uri = null;
/**
* Allows widgets to filter terms.
* @param term
* A term that should be accepted or not.
* @return {Boolean}
* True if the term should be accepted.
*/
Drupal.autocomplete_deluxe.Widget.prototype.acceptTerm = function(term) {
return true;
};
Drupal.autocomplete_deluxe.Widget.prototype.init = function(settings) {
if(navigator.appVersion.indexOf("MSIE 6.") != -1) {
return;
}
this.id = settings.input_id;
this.jqObject = $('#' + this.id);
this.uri = settings.uri;
this.multiple = settings.multiple;
this.required = settings.required;
this.limit = settings.limit;
this.synonyms = typeof settings.use_synonyms == 'undefined' ? false : settings.use_synonyms;
this.not_found_message = typeof settings.use_synonyms == 'undefined' ? "The term '@term' will be added." : settings.not_found_message;
this.wrapper = '""';
if (typeof settings.delimiter == 'undefined') {
this.delimiter = true;
} else {
this.delimiter = settings.delimiter.charCodeAt(0);
}
this.items = {};
var self = this;
var parent = this.jqObject.parent();
var parents_parent = this.jqObject.parent().parent();
parents_parent.append(this.jqObject);
parent.remove();
parent = parents_parent;
var generateValues = function(data, term) {
var result = new Array();
for (var terms in data) {
if (self.acceptTerm(terms)) {
result.push({
label: data[terms],
value: terms
});
}
}
if ($.isEmptyObject(result)) {
result.push({
label: Drupal.t(self.not_found_message, {'@term' : term}),
value: term,
newTerm: true
});
}
return result;
};
var cache = {};
var lastXhr = null;
this.source = function(request, response) {
var term = request.term;
if (term in cache) {
response(generateValues(cache[term], term));
return;
}
// Some server collapse two slashes if the term is empty, so insert at
// least a whitespace. This whitespace will later on be trimmed in the
// autocomplete callback.
if (!term) {
term = " ";
}
request.synonyms = self.synonyms;
var url = settings.uri + '/' + term +'/' + self.limit;
lastXhr = $.getJSON(url, request, function(data, status, xhr) {
cache[term] = data;
if (xhr === lastXhr) {
response(generateValues(data, term));
}
});
};
this.jqObject.autocomplete({
'source' : this.source,
'minLength': settings.min_length
});
var jqObject = this.jqObject;
var autocompleteDataKey = typeof(this.jqObject.data('autocomplete')) === 'object' ? 'item.autocomplete' : 'ui-autocomplete';
var throbber = $('
').insertAfter(jqObject);
this.jqObject.bind("autocompletesearch", function(event, ui) {
throbber.removeClass('autocomplete-deluxe-closed');
throbber.addClass('autocomplete-deluxe-open');
});
this.jqObject.bind("autocompleteopen", function(event, ui) {
throbber.addClass('autocomplete-deluxe-closed');
throbber.removeClass('autocomplete-deluxe-open');
});
// Monkey patch the _renderItem function jquery so we can highlight the
// text, that we already entered.
$.ui.autocomplete.prototype._renderItem = function( ul, item) {
var t = item.label;
if (this.term != "") {
var escapedValue = Drupal.autocomplete_deluxe.escapeRegex( this.term );
var re = new RegExp('()*""' + escapedValue + '""|' + escapedValue + '()*', 'gi');
var t = item.label.replace(re,"$&");
}
return $( "" )
.data(autocompleteDataKey, item)
.append( "" + t + "" )
.appendTo( ul );
};
};
Drupal.autocomplete_deluxe.Widget.prototype.generateValues = function(data) {
var result = new Array();
for (var index in data) {
result.push(data[index]);
}
return result;
};
/**
* Generates a single selecting widget.
*/
Drupal.autocomplete_deluxe.SingleWidget = function(settings) {
this.init(settings);
this.setup();
this.jqObject.addClass('autocomplete-deluxe-form-single');
};
Drupal.autocomplete_deluxe.SingleWidget.prototype = new Drupal.autocomplete_deluxe.Widget();
Drupal.autocomplete_deluxe.SingleWidget.prototype.setup = function() {
var jqObject = this.jqObject;
var parent = jqObject.parent();
parent.mousedown(function() {
if (parent.hasClass('autocomplete-deluxe-single-open')) {
jqObject.autocomplete('close');
} else {
jqObject.autocomplete('search', '');
}
});
};
/**
* Creates a multiple selecting widget.
*/
Drupal.autocomplete_deluxe.MultipleWidget = function(input, settings) {
this.init(settings);
this.setup();
};
Drupal.autocomplete_deluxe.MultipleWidget.prototype = new Drupal.autocomplete_deluxe.Widget();
Drupal.autocomplete_deluxe.MultipleWidget.prototype.items = new Object();
Drupal.autocomplete_deluxe.MultipleWidget.prototype.acceptTerm = function(term) {
// Accept only terms, that are not in our items list.
return !(term in this.items);
};
Drupal.autocomplete_deluxe.MultipleWidget.Item = function (widget, item) {
if (item.newTerm === true) {
item.label = item.value;
}
this.value = item.value;
this.element = $('');
this.element.text(item.label);
this.widget = widget;
this.item = item;
var self = this;
var close = $('').appendTo(this.element);
// Use single quotes because of the double quote encoded stuff.
var input = $('')
input.val(this.value);
input.appendTo(this.element);
close.mousedown(function() {
self.remove(item);
});
};
Drupal.autocomplete_deluxe.MultipleWidget.Item.prototype.remove = function() {
this.element.remove();
var values = this.widget.valueForm.val();
var escapedValue = Drupal.autocomplete_deluxe.escapeRegex( this.item.value );
var regex = new RegExp('()*""' + escapedValue + '""()*', 'gi');
this.widget.valueForm.val(values.replace(regex, ''));
delete this.widget.items[this.value];
};
Drupal.autocomplete_deluxe.MultipleWidget.prototype.setup = function() {
var jqObject = this.jqObject;
var parent = jqObject.parents('.autocomplete-deluxe-container');
var value_container = parent.next();
var value_input = value_container.find('input');
var items = this.items;
var self = this;
this.valueForm = value_input;
// Override the resize function, so that the suggestion list doesn't resizes
// all the time.
var autocompleteDataKey = typeof(this.jqObject.data('autocomplete')) === 'object' ? 'autocomplete' : 'ui-autocomplete';
jqObject.data(autocompleteDataKey)._resizeMenu = function() {};
jqObject.show();
value_container.hide();
// Add the default values to the box.
var default_values = value_input.val();
default_values = $.trim(default_values);
default_values = default_values.substr(2, default_values.length-4);
default_values = default_values.split('"" ""');
for (var index in default_values) {
var value = default_values[index];
if (value != '') {
// If a terms is encoded in double quotes, then the label should have
// no double quotes.
var label = value.match(/["][\w|\s|\D|]*["]/gi) !== null ? value.substr(1, value.length-2) : value;
var item = {
label : label,
value : value
};
var item = new Drupal.autocomplete_deluxe.MultipleWidget.Item(self, item);
item.element.insertBefore(jqObject);
items[item.value] = item;
}
}
jqObject.addClass('autocomplete-deluxe-multiple');
parent.addClass('autocomplete-deluxe-multiple');
// Adds a value to the list.
this.addValue = function(ui_item) {
var item = new Drupal.autocomplete_deluxe.MultipleWidget.Item(self, ui_item);
item.element.insertBefore(jqObject);
items[ui_item.value] = item;
var new_value = ' ' + self.wrapper + ui_item.value + self.wrapper;
var values = value_input.val();
value_input.val(values + new_value);
jqObject.val('');
};
parent.mouseup(function() {
jqObject.autocomplete('search', '');
jqObject.focus();
});
jqObject.bind("autocompleteselect", function(event, ui) {
// JQuery ui autocomplete needs the terms escaped, otherwise it would be
// open to XSS issues. Drupal.autocomplete.Item also escapes on rendering
// the DOM elements. Thus we have to unescape the label here before adding
// the new item.
var item = ui.item;
item.label = Drupal.autocomplete_deluxe.unescape(item.label);
self.addValue(item);
jqObject.width(25);
// Return false to prevent setting the last term as value for the jqObject.
return false;
});
jqObject.bind("autocompletechange", function(event, ui) {
jqObject.val('');
});
jqObject.blur(function() {
var last_element = jqObject.parent().children('.autocomplete-deluxe-item').last();
last_element.removeClass('autocomplete-deluxe-item-focus');
});
var clear = false;
jqObject.keypress(function (event) {
var value = jqObject.val();
// If a comma was entered and there is none or more then one comma,or the
// enter key was entered, then enter the new term.
if ((event.which == self.delimiter && (value.split('"').length - 1) != 1) || (event.which == 13 && jqObject.val() != "")) {
value = value.substr(0, value.length);
if (typeof self.items[value] == 'undefined' && value != '') {
var ui_item = {
label: value,
value: value
};
self.addValue(ui_item);
}
clear = true;
if (event.which == 13) {
return false;
}
}
// If the Backspace key was hit and the input is empty
if (event.which == 8 && value == '') {
var last_element = jqObject.parent().children('.autocomplete-deluxe-item').last();
// then mark the last item for deletion or deleted it if already marked.
if (last_element.hasClass('autocomplete-deluxe-item-focus')) {
var value = last_element.children('input').val();
self.items[value].remove(self.items[value]);
jqObject.autocomplete('search', '');
} else {
last_element.addClass('autocomplete-deluxe-item-focus');
}
} else {
// Remove the focus class if any other key was hit.
var last_element = jqObject.parent().children('.autocomplete-deluxe-item').last();
last_element.removeClass('autocomplete-deluxe-item-focus');
}
});
jqObject.autoGrowInput({
comfortZone: 50,
minWidth: 10,
maxWidth: 460
});
jqObject.keyup(function (event) {
if (clear) {
// Trigger the search, so it display the values for an empty string.
jqObject.autocomplete('search', '');
jqObject.val('');
clear = false;
// Return false to prevent entering the last character.
return false;
}
});
};
})(jQuery);