kpsul/static/autocomplete_light/text_widget.js
2016-05-21 23:57:36 +02:00

275 lines
8.5 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
This script enables TextWidget, a widget for CharField that supports
autocomplete for comma-separated values.
It's organization is not final, there are a couple of things that are also used
in widget.js that will be re-factored probably in a script called lib.js.
The API however, is consistent with widget.js, and is not meant to change.
For now, the script is composed of these parts:
- a handful of jQuery extensions to ease treatment of comma separated values in
an input,
- yourlabs.TextWidget is stripped version of yourlabs.Widget, to handle the
behavior of a comma separated autocompleted input,
- yourlabsTextWidget jQuery extension which role is to manage TextWidget instances,
- yourlabsTextWidget initialization system, which supports dynamically added
autocompletes (ie. admin inlines)
*/
jQuery.fn.getSelectionStart = function(){
// Written by jQuery4U
// http://www.jquery4u.com/snippets/6-jquery-cursor-functions/#.UDPQ9xXtFw8
    if(this.lengh == 0) return -1;
    input = this[0];
 
    var pos = input.value.length;
 
    if (input.createTextRange) {
if (window.getSelection) {
var r = window.getSelection(); //IE11
} else {
var r = document.selection.createRange().duplicate();
r.moveEnd('character', input.value.length);
}
        if (r.text == '')
        pos = input.value.length;
        pos = input.value.lastIndexOf(r.text);
    } else if(typeof(input.selectionStart)!="undefined")
    pos = input.selectionStart;
 
    return pos;
}
jQuery.fn.getCursorPosition = function(){
// Written by jQuery4U
    if(this.lengh == 0) return -1;
    return $(this).getSelectionStart();
}
// Return the word on which the cursor is on.
//
// Consider the pipe "|" as an ASCII representation of the cursor, with such an
// input value::
//
// foo, bar|, baz
//
// getCursorWord would return 'bar'.
jQuery.fn.getCursorWord = function() {
var value = $(this).val();
var positions = $(this).getCursorWordPositions();
return value.substring(positions[0], positions[1]);
}
// Return the offsets of the word on which the cursor is on.
//
// Consider the pipe "|" as an ASCII representation of the cursor, with such an
// input value::
//
// foo, bar|, baz
//
// getCursorWord would return [6, 8].
jQuery.fn.getCursorWordPositions = function() {
var position = $(this).getCursorPosition();
var value = $(this).val();
var word = '';
// find start of word
for(var start=position - 1; start >= 0; start--) {
if (value[start] == ',') {
break;
}
}
start = start < 0 ? 0 : start;
// find end of word
for(var end=position; end <= value.length - 1; end++) {
if (value[end] == ',') {
break;
}
}
while(value[start] == ',' || value[start] == ' ') start++;
while(value[end] == ',' || value[end] == ' ') end--;
return [start, end + 1];
}
// TextWidget ties an input with an autocomplete.
yourlabs.TextWidget = function(input) {
this.input = input;
this.autocompleteOptions = {
getQuery: function() {
return this.input.getCursorWord();
}
}
}
// The widget is in charge of managing its Autocomplete.
yourlabs.TextWidget.prototype.initializeAutocomplete = function() {
this.autocomplete = this.input.yourlabsAutocomplete(
this.autocompleteOptions);
// Add a class to ease css selection of autocompletes for widgets
this.autocomplete.box.addClass(
'autocomplete-light-text-widget');
};
// Bind Autocomplete.selectChoice signal to TextWidget.selectChoice()
yourlabs.TextWidget.prototype.bindSelectChoice = function() {
this.input.bind('selectChoice', function(e, choice) {
if (!choice.length)
return // placeholder: create choice here
$(this).yourlabsTextWidget().selectChoice(choice);
});
};
// Called when a choice is selected from the Autocomplete.
yourlabs.TextWidget.prototype.selectChoice = function(choice) {
var inputValue = this.input.val();
var choiceValue = this.getValue(choice);
var positions = this.input.getCursorWordPositions();
var newValue = inputValue.substring(0, positions[0]);
newValue += choiceValue;
newValue += inputValue.substring(positions[1]);
this.input.val(newValue);
this.input.focus();
}
// Return the value of an HTML choice, used to fill the input.
yourlabs.TextWidget.prototype.getValue = function(choice) {
return $.trim(choice.html().replace(/(<([^>]+)>)/ig,""));
}
// Initialize the widget.
yourlabs.TextWidget.prototype.initialize = function() {
this.initializeAutocomplete();
this.bindSelectChoice();
}
// Destroy the widget. Takes a widget element because a cloned widget element
// will be dirty, ie. have wrong .input and .widget properties.
yourlabs.TextWidget.prototype.destroy = function(input) {
input
.unbind('selectChoice')
.yourlabsAutocomplete('destroy');
}
// TextWidget factory, registry and destroyer, as jQuery extension.
$.fn.yourlabsTextWidget = function(overrides) {
var overrides = overrides ? overrides : {};
if (overrides == 'destroy') {
var widget = this.data('widget');
if (widget) {
widget.destroy(this);
this.removeData('widget');
}
return
}
if (this.data('widget') == undefined) {
// Instanciate the widget
var widget = new yourlabs.TextWidget(this);
// Pares data-*
var data = this.data();
var dataOverrides = {
autocompleteOptions: {
// workaround a display bug
minimumCharacters: 0,
getQuery: function() {
// Override getQuery since we need the autocomplete to filter
// choices based on the word the cursor is on, rather than the full
// input value.
return this.input.getCursorWord();
}
}
};
for (var key in data) {
if (!key) continue;
if (key.substr(0, 12) == 'autocomplete') {
if (key == 'autocomplete') continue;
var newKey = key.replace('autocomplete', '');
newKey = newKey.replace(newKey[0], newKey[0].toLowerCase())
dataOverrides['autocompleteOptions'][newKey] = data[key];
} else {
dataOverrides[key] = data[key];
}
}
// Allow attribute overrides
widget = $.extend(widget, dataOverrides);
// Allow javascript object overrides
widget = $.extend(widget, overrides);
this.data('widget', widget);
// Setup for usage
widget.initialize();
// Widget is ready
widget.input.attr('data-widget-ready', 1);
widget.input.trigger('widget-ready');
}
return this.data('widget');
}
$(document).ready(function() {
$('body').on('initialize', 'input[data-widget-bootstrap=text]', function() {
/*
Only setup autocompletes on inputs which have
data-widget-bootstrap=text, if you want to initialize some
autocompletes with custom code, then set
data-widget-boostrap=yourbootstrap or something like that.
*/
$(this).yourlabsTextWidget();
});
// Solid initialization, usage::
//
// $(document).bind('yourlabsTextWidgetReady', function() {
// $('body').on('initialize', 'input[data-widget-bootstrap=text]', function() {
// $(this).yourlabsTextWidget({
// yourCustomArgs: // ...
// })
// });
// });
$(document).trigger('yourlabsTextWidgetReady');
$('.autocomplete-light-text-widget:not([id*="__prefix__"])').each(function() {
$(this).trigger('initialize');
});
$(document).bind('DOMNodeInserted', function(e) {
var widget = $(e.target).find('.autocomplete-light-text-widget');
if (!widget.length) {
widget = $(e.target).is('.autocomplete-light-text-widget') ? $(e.target) : false;
if (!widget) {
return;
}
}
// Ignore inserted autocomplete box elements.
if (widget.is('.yourlabs-autocomplete')) {
return;
}
// Ensure that the newly added widget is clean, in case it was cloned.
widget.yourlabsWidget('destroy');
widget.find('input').yourlabsAutocomplete('destroy');
widget.trigger('initialize');
});
})