From d6d14ab4c2b5fbffdf009b42b61a48064fc420ea Mon Sep 17 00:00:00 2001 From: gabriel-dehan Date: Sat, 17 Nov 2012 20:34:50 +0100 Subject: [PATCH 1/8] Strip the term before fetching the model --- lib/rails3-jquery-autocomplete/autocomplete.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/rails3-jquery-autocomplete/autocomplete.rb b/lib/rails3-jquery-autocomplete/autocomplete.rb index fefc9146..22c2a385 100644 --- a/lib/rails3-jquery-autocomplete/autocomplete.rb +++ b/lib/rails3-jquery-autocomplete/autocomplete.rb @@ -46,7 +46,7 @@ def autocomplete(object, method, options = {}) method = options[:column_name] if options.has_key?(:column_name) - term = params[:term] + term = params[:term].strip if term && !term.blank? #allow specifying fully qualified class name for model object @@ -93,4 +93,3 @@ def json_for_autocomplete(items, method, extra_data=[]) end end end - From 06671736b04223b490f56bfec1cd91775c3cc4a5 Mon Sep 17 00:00:00 2001 From: gabriel-dehan Date: Sat, 17 Nov 2012 20:35:19 +0100 Subject: [PATCH 2/8] Add helpers for textareas --- lib/rails3-jquery-autocomplete/form_helper.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/rails3-jquery-autocomplete/form_helper.rb b/lib/rails3-jquery-autocomplete/form_helper.rb index 006a14df..50891b27 100644 --- a/lib/rails3-jquery-autocomplete/form_helper.rb +++ b/lib/rails3-jquery-autocomplete/form_helper.rb @@ -12,6 +12,13 @@ def autocomplete_field(object_name, method, source, options ={}) options["data-autocomplete"] = source text_field(object_name, method, rewrite_autocomplete_option(options)) end + + # Returns an text area tailored for accessing a specified attribute (identified by +method+) and + # that is populated with jQuery's autocomplete plugin. See autocomplete_field + def autocomplete_area(object_name, method, source, options ={}) + options["data-autocomplete"] = source + text_area(object_name, method, rewrite_autocomplete_option(options)) + end end module FormTagHelper @@ -25,6 +32,13 @@ def autocomplete_field_tag(name, value, source, options ={}) options["data-autocomplete"] = source text_field_tag(name, value, rewrite_autocomplete_option(options)) end + + # Creates a standard text area that can be populated with jQuery's autocomplete plugin, see autocomplete_field_tag + def autocomplete_area_tag(name, value, source, options ={}) + options["data-autocomplete"] = source + text_area_tag(name, value, rewrite_autocomplete_option(options)) + end + end # @@ -44,4 +58,7 @@ class ActionView::Helpers::FormBuilder #:nodoc: def autocomplete_field(method, source, options = {}) @template.autocomplete_field(@object_name, method, source, objectify_options(options)) end + def autocomplete_area(method, source, options = {}) + @template.autocomplete_area(@object_name, method, source, objectify_options(options)) + end end From 7d2a678f8956bb7f5cacff2006aa1f2917de5600 Mon Sep 17 00:00:00 2001 From: gabriel-dehan Date: Sun, 18 Nov 2012 17:25:39 +0100 Subject: [PATCH 3/8] Added one subversion as there has been many many changes --- lib/rails3-jquery-autocomplete/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rails3-jquery-autocomplete/version.rb b/lib/rails3-jquery-autocomplete/version.rb index a679fb20..639277d4 100644 --- a/lib/rails3-jquery-autocomplete/version.rb +++ b/lib/rails3-jquery-autocomplete/version.rb @@ -1,3 +1,3 @@ module Rails3JQueryAutocomplete - VERSION = '1.0.10' + VERSION = '1.0.11' end From 004386ebcef9ae79daec4a234776039965a37f88 Mon Sep 17 00:00:00 2001 From: gabriel-dehan Date: Sun, 18 Nov 2012 17:26:03 +0100 Subject: [PATCH 4/8] Updated readme to reflect modifications --- README.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4fc263a7..b439c076 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,20 @@ Supports both ActiveRecord, [mongoid](http://github.com/mongoid/mongoid), and [M Works with [Formtastic](http://github.com/justinfrench/formtastic) and [SimpleForm](https://github.com/plataformatec/simple_form) +## Logs + +* *1.1.0* : + * Code refactoring; + * Now handle textareas; + * Can now be set up via Javascript : `$('#my_element').railsAutocomplete({ /* options */ });`; + * With a new set of options; + * Term autocompletion now works on previous terms, and the autocompletion will no longer happen at the end of the input/textarea, but were the cursor stands; + * Can now handle writing terms among normal non completed text through the use of a delimiter. The autocompletion is disabled while writing normal text and is triggered when one presses the delimiter key (like '#' or ',', ...). There are still a few glitches; + * Terms are now using a comma delimiter by default, enabling multiple terms autocompletion in a same input; + * Delimiters can be at the beginning or the end of the term string. Allowing for "#term1 #term2" or "term1, term2"; + * is now used for autocompletion and an option allows to keep the focus in the current element; + * The current autocompleted term can now be filtered before processing. + ## ActiveRecord You can find a [detailed example](http://github.com/crowdint/rails3-jquery-autocomplete-app) @@ -179,26 +193,71 @@ Autocomplete uses Yajl as JSON encoder/decoder, but you can specify your own ### View -On your view, all you have to do is include the attribute autocomplete on the text field +On your view, all you have to do is include the attribute autocomplete on the field or textarea using the url to the autocomplete action as the value. form_for @product do |f| f.autocomplete_field :brand_name, autocomplete_brand_name_products_path + f.autocomplete_area :brand_name, autocomplete_brand_name_products_path end This will generate an HTML tag that looks like: + If you are not using a FormBuilder (form_for) or you just want to include an autocomplete field without the form, you can use the -*autocomplete_field_tag* helper. +*autocomplete_field_tag* and *autocomplete_area_tag* helper. form_tag 'some/path' autocomplete_field_tag 'address', '', address_autocomplete_path, :size => 75 + autocomplete_area_tag 'address', '', address_autocomplete_path, :size => 75 end Now your autocomplete code is unobtrusive, Rails 3 style. +### Enabling the autocompletion + +By default, a rather basic autocompletion for all fields and text-area with the attribute data-autocomplete takes place. +The default text fields and text area are activated with the following options : + + ```javascript + jQuery('input[data-autocomplete]').railsAutocomplete({ + delimiter : ',', + insertDelimiter : true, + focusOnNext : true + }); + jQuery('textarea[data-autocomplete]').railsAutocomplete({ + delimiter : ',', + insertDelimiter : true + }); + ``` + +To deactivate the default autocompletion use : + + ```javascript + jQuery.railsAutocomplete.configuration.createWithDefaults = false; + ``` + +and call `jQuery(e).railsAutoComplete();` with the options you need. + +Available options are the followings : + + ```javascript + // Default options + { + delimiter: null, + delimiterPosition: 'after', + focusOnNext: false, + url: null, + withText: false, + filter: null + } + ``` + +For more informations please refer to the documented code in the file autocomplete-rails-uncompressed.js, +around line 257 (function _defaultOptions()); + ### Getting the object id If you need to use the id of the selected object, you can use the *id_element* attribute too: @@ -364,4 +423,3 @@ Everyone on [this list](https://github.com/crowdint/rails3-jquery-autocomplete/c [Crowd Interactive](http://www.crowdint.com) is an American web design and development company that happens to work in Colima, Mexico. We specialize in building and growing online retail stores. We don’t work with everyone – just companies we believe in. Call us today to see if there’s a fit. Find more info [here](http://www.crowdint.com)! - From fa112313ed0727b3cc7d7c21bb7439713f120b46 Mon Sep 17 00:00:00 2001 From: gabriel-dehan Date: Sun, 18 Nov 2012 17:26:39 +0100 Subject: [PATCH 5/8] Lots of modification for version 1.1.0, see README.md for the ChangeLog --- .../autocomplete-rails-uncompressed.js | 563 +++++++++++++++--- lib/assets/javascripts/autocomplete-rails.js | 2 +- 2 files changed, 478 insertions(+), 87 deletions(-) diff --git a/lib/assets/javascripts/autocomplete-rails-uncompressed.js b/lib/assets/javascripts/autocomplete-rails-uncompressed.js index edd20c45..b4e52664 100644 --- a/lib/assets/javascripts/autocomplete-rails-uncompressed.js +++ b/lib/assets/javascripts/autocomplete-rails-uncompressed.js @@ -16,115 +16,506 @@ (function(jQuery) { + jQuery.fn.caretPosition = function() { + el = this[0]; + if (el.selectionStart) { + return el.selectionStart; + } else if (document.selection) { + el.focus(); + + var r = document.selection.createRange(); + if (r == null) { + return 0; + } + + var re = el.createTextRange(), + rc = re.duplicate(); + re.moveToBookmark(r.getBookmark()); + rc.setEndPoint('EndToStart', re); + + return rc.text.length; + } + return 0; + }; + var self = null; - jQuery.fn.railsAutocomplete = function() { + jQuery.fn.railsAutocomplete = function(opts) { return this.live('focus',function() { if (!this.railsAutoCompleter) { - this.railsAutoCompleter = new jQuery.railsAutocomplete(this); + this.railsAutoCompleter = new jQuery.railsAutocomplete(this, opts); } }); }; - jQuery.railsAutocomplete = function (e) { - _e = e; + jQuery.railsAutocomplete = function (e, opts) { + _e = e; + e.options = merge(opts, railsAutocompleteConfig.DEFAULT_OPTIONS) + this.init(_e); }; jQuery.railsAutocomplete.fn = jQuery.railsAutocomplete.prototype = { railsAutocomplete: '0.0.1' }; + jQuery.railsAutocomplete.configuration = { createWithDefaults : true } jQuery.railsAutocomplete.fn.extend = jQuery.railsAutocomplete.extend = jQuery.extend; jQuery.railsAutocomplete.fn.extend({ init: function(e) { - e.delimiter = jQuery(e).attr('data-delimiter') || null; - function split( val ) { - return val.split( e.delimiter ); - } - function extractLast( term ) { - return split( term ).pop().replace(/^\s+/,""); + e.options.delimiter = e.options.delimiter || jQuery(e).attr('data-delimiter') || null; + e.options.url = e.options.url || jQuery(e).attr('data-autocomplete') + + railsAutocompleteTag.init(e); + if( e.options.withText ) { + if( !e.options.delimiter ) { + throw 'When working with joined text, autocomplete rails needs a delimiter to be defined.'; + } + railsAutocompleteTag.autocomplete(e.options.delimiter); + } else { + railsAutocompleteTag.autocomplete(); } + } + }); - jQuery(e).autocomplete({ - source: function( request, response ) { - jQuery.getJSON( jQuery(e).attr('data-autocomplete'), { - term: extractLast( request.term ) - }, function() { - if(arguments[0].length == 0) { - arguments[0] = [] - arguments[0][0] = { id: "", label: "no existing match" } - } - jQuery(arguments[0]).each(function(i, el) { - var obj = {}; - obj[el.id] = el; - jQuery(e).data(obj); - }); - response.apply(null, arguments); + jQuery(document).ready(function(){ + if(jQuery.railsAutocomplete.configuration.createWithDefaults) { + jQuery('input[data-autocomplete]').railsAutocomplete({ + delimiter : ',', + insertDelimiter : true, + focusOnNext : true + }); + jQuery('textarea[data-autocomplete]').railsAutocomplete({ + delimiter : ',', + insertDelimiter : true }); - }, - change: function( event, ui ) { - if(jQuery(jQuery(this).attr('data-id-element')).val() == "") { - return; - } - jQuery(jQuery(this).attr('data-id-element')).val(ui.item ? ui.item.id : ""); - var update_elements = jQuery.parseJSON(jQuery(this).attr("data-update-elements")); - var data = ui.item ? jQuery(this).data(ui.item.id.toString()) : {}; - if(update_elements && jQuery(update_elements['id']).val() == "") { - return; + } + }); + + function merge(source, other){ + for(var p in other){ + if(source[p] === undefined) source[p] = other[p]; + } + return source; + } +})(jQuery); + +var railsAutocompleteTag = (function() { + var callbacks, self; + + function _init(e) { + self = e; + callbacks = railsAutocompleteConfig.autocompleteCallbacks(self); + }; + + function _autocomplete(symbol) { + if (symbol) { + _autocompleteWithSymbol(symbol) + } else { + jQuery(self).autocomplete(callbacks); + } + } + + function _autocompleteWithSymbol(symbol) { + var code = symbol.charCodeAt(0), + autocompleter = null; + + jQuery(self).on('keypress', function(e) { + if( e.which === code ) { + /* We don't want to re-assign all the callbacks on each keypress */ + if (null === autocompleter) { + autocompleter = jQuery(self).autocomplete(callbacks); + } else { + jQuery(self).autocomplete("enable"); + } + } else if (e.which === ','.charCodeAt(0)) { + jQuery(self).autocomplete("disable"); } - for (var key in update_elements) { - jQuery(update_elements[key]).val(ui.item ? data[key] : ""); - } - }, - search: function() { - // custom minLength - var term = extractLast( this.value ); - if ( term.length < 2 ) { - return false; - } - }, - focus: function() { - // prevent value inserted on focus - return false; - }, - select: function( event, ui ) { - var terms = split( this.value ); - // remove the current input - terms.pop(); - // add the selected item - terms.push( ui.item.value ); - // add placeholder to get the comma-and-space at the end - if (e.delimiter != null) { - terms.push( "" ); - this.value = terms.join( e.delimiter ); - } else { - this.value = terms.join(""); - if (jQuery(this).attr('data-id-element')) { - jQuery(jQuery(this).attr('data-id-element')).val(ui.item.id); + }); + } + + return { + init : _init, + autocomplete: _autocomplete + }; +})(); + +var railsAutocompleteConfig = (function() { + function _autocompleteCallbacks(e) { + // We instanciate it every time, as the helpers uses the element object + e.helpers = new _railsAutocompleteHelpers(e); + + return { + source: function( request, response ) { + jQuery.getJSON( e.options.url , { + term: e.helpers.extractCurrent( request.term ) + }, function(data) { + if(arguments[0].length == 0) { + arguments[0] = [] + arguments[0][0] = { id: "", label: "no existing match" } + } + jQuery(arguments[0]).each(function(i, el) { + var obj = {}; + obj[el.id] = el; + jQuery(e).data(obj); + }); + response.apply(null, arguments); + }); + }, + change: function( event, ui ) { + if(jQuery(jQuery(this).attr('data-id-element')).val() == "") { + return; + } + jQuery(jQuery(this).attr('data-id-element')).val(ui.item ? ui.item.id : ""); + var update_elements = jQuery.parseJSON(jQuery(this).attr("data-update-elements")), + data = ui.item ? jQuery(this).data(ui.item.id.toString()) : {}; + if(update_elements && jQuery(update_elements['id']).val() == "") { + return; + } + for (var key in update_elements) { + jQuery(update_elements[key]).val(ui.item ? data[key] : ""); + } + }, + search: function() { + // custom minLength + var term = e.helpers.extractCurrent( this.value ); + if ( term.length < 2 ) { + return false; + } + }, + focus: function() { + // prevent value inserted on focus + return false; + }, + close : function() { + /* Disabled because we don't want autocompletion + * when writing normal text + */ + if (e.options.withText) { + jQuery(this).autocomplete("disable"); + /* Simulates a keypress for nothing, + * as the previous line blocked the keyboard input + */ + jQuery(this).focus().trigger( jQuery.Event( 'keypress', { which: 13 } ) ); + } + }, + select: function( event, ui ) { + var terms = e.helpers.split( this.value ); + // Replace the current input with the completion + terms[e.helpers.currentTermIndex(terms)] = ui.item.value + + if (this.options.delimiter != null) { + terms = jQuery.map(terms, function(v, i) { + // If the value is blank, we remove it + // Otherwise, we trim all whitespaces + if(!(v == '' || v == ' ')) { + return jQuery.trim(v); + } + }); + + if (this.options.insertDelimiter == true) { + if (this.options.delimiterPosition === 'after') { + // Add placeholder to get the delimiter-and-space at the end + terms.push( "" ); + } + } + + this.value = e.helpers.getValue(terms); + } else { + this.value = terms.join(""); + if (jQuery(this).attr('data-id-element')) { + jQuery(jQuery(this).attr('data-id-element')).val(ui.item.id); + } + if (jQuery(this).attr('data-update-elements')) { + var data = jQuery(this).data(ui.item.id.toString()); + var update_elements = jQuery.parseJSON(jQuery(this).attr("data-update-elements")); + for (var key in update_elements) { + jQuery(update_elements[key]).val(data[key]); + } + } + } + var remember_string = this.value; + jQuery(this).bind('keyup.clearId', function(){ + if(jQuery(this).val().trim() != remember_string.trim()){ + jQuery(jQuery(this).attr('data-id-element')).val(""); + jQuery(this).unbind('keyup.clearId'); + } + }); + + jQuery(e).trigger('railsAutocomplete.select', ui); + + /* If we hit tab, we stay in the same element + * and fullfil the autocompletion process + */ + if (event.keyCode === 9 && !e.options.focusOnNext) { + event.preventDefault(); + jQuery(e).focus(); + } + return false; } - if (jQuery(this).attr('data-update-elements')) { - var data = jQuery(this).data(ui.item.id.toString()); - var update_elements = jQuery.parseJSON(jQuery(this).attr("data-update-elements")); - for (var key in update_elements) { - jQuery(update_elements[key]).val(data[key]); - } + } + } + function _defaultOptions() { + return { + /* Wether of not to use a delimiter, and in the case + * we do, a String specifying it. + * Can also be specified through the HTML data-delimiter attribute. + * + * Examples: + * + * // Possibles values: + * '@', '#', ',', + */ + delimiter : null, + + /* Were is the delimiter positionned : before or after the term. + * + * Examples: + * + * // Values + * 'after', 'before' + */ + delimiterPosition : 'after', + + /* Wether or not to insert a new blank delimiter when autocompleting + * + * Examples: + * + * // Values + * true, false + */ + insertDelimiter : false, + + /* Wether or not to focus on the next element when pressing tab. + * + * Examples: + * + * // Values + * true, false + */ + focusOnNext : false, + + /* URL to get autocompletion results from. + * Can also be specified through the HTML data-autocomplete attribute. + * + * Examples: + * + * // Possible values: + * 'http://localhost:3000/terms/autocomplete_term', '/terms/autocomplete_term', + */ + url : null, + + /* Wether or not it is possible to write pure non autocompleted text + * and trigger autocompletion when typing the delimiter + * + * Examples: + * + * // Values: + * true, false + */ + withText : false, + + /* Public: a Function to filter the term under the keyboard cursor + * before it is processed. + * + * element - The HTML DOMElement we are autocompleting. + * index - The index of the current term, as a Number. + * terms - The Array of terms. + * + * Examples: + * + * // Mainly, the default behavior is strictly equivalent to a + * // filter function just as this one : + * { + * filter: function() { + * return terms[index]; + * } + * } + */ + filter : null + }; + } + + return { + autocompleteCallbacks : _autocompleteCallbacks, + DEFAULT_OPTIONS : _defaultOptions() + } +})(); + +/* Private: Having the helpers as an instanciable class + * helps having multiple autocomplete fields in a same page + * as they all have a different set (instance) of helpers and options. + * + * e - The element for which the helpers are. + * + * Returns an instance of _railsAutocompleteHelpers. + */ +_railsAutocompleteHelpers = (function(e){ + this.options = e.options; + this.element = e; +}); + +_railsAutocompleteHelpers.prototype = { + split: function( val ) { + return val.split( this.options.delimiter ); + }, + /* Public: Same as Array.join() but joins on the left side of the string. + * An other difference is that it also appends the separator to the first + * character. + * + * array - The Array to join. + * string - The separator, as a String. + * + * Returns a joined String. + */ + leftJoin: function( array, string ) { + var a = ''; + for (var i = 0; i < array.length; i++) { + a += string + array[i]; + } + return a; + }, + /* Public: Chooses between Array.join() and this.leftJoin() according to + * set options. + * + * array - The Array to join. + * string - The separator, as a String. + * + * Returns a joined String. + */ + join: function( array, string ) { + return (this.options.delimiterPosition === 'after') ? array.join(string) : this.leftJoin(array, string); + }, + /* Public: see extractCurrent(), currentIndex(), find the term right under + * the cursor's position in an Array. + * + * array - An Array to search + * onlyIndex - A Boolean indicating if we want to return + * only the index or the whole word (default: false). + * + * Returns a String with the found word or a Number with the index of the found word. + */ + findTermIn: function(array, onlyIndex) { + var position = jQuery(this.element).caretPosition(), + total_length = 0, + string_length = 0; + + /* If position < 0 || position > total array strings' length, + * normalize position. + */ + string_length = array.join('').length; + if(position > string_length) { + position = string_length; + } else if(position < 0) { + position = 0; + } + + for(var i = 0; i < array.length; i++) { + total_length += array[i].length; + if(position <= total_length) { + return onlyIndex ? i : array[i]; } - } - var remember_string = this.value; - jQuery(this).bind('keyup.clearId', function(){ - if(jQuery(this).val().trim() != remember_string.trim()){ - jQuery(jQuery(this).attr('data-id-element')).val(""); - jQuery(this).unbind('keyup.clearId'); + } + }, + /* Private API - Deprecated: Find the last term in a string. + * + * terms - A String containing multiple terms. + * + * Returns the term, as a leading-whitespace-clean String. + */ + extractLast: function( terms ) { + return this.split( terms ).pop().replace(/^\s+/,""); + }, + + /* Public: Returns the term under the cursor. Can be filtered + * through this.options.filter callback; + * + * term - A String from which to extract + * + * Returns a String with the extracted term. + */ + extractCurrent: function( term, filter ) { + var terms_array = this.split( term ), + term = ''; + // We can filter the terms manualy through + if( this.options.filter ) { + term = this.options.filter.call(this.element, this.findTermIn( terms_array, true ), terms_array); + } else { + term = this.findTermIn( terms_array ); + } + return term.replace(/^\s+/,""); + }, + + /* Public: Returns the id of the term under the cursor + * + * - An Array in which to search the current term + * + * Returns a the index as a Number. + */ + currentTermIndex: function( terms ) { + return this.findTermIn( terms, true ) + }, + + /* Public: returns the delimiter according to the options + * + * Examples : + * + * // With this.options.delimiter set to ',' + * // delimiterPosition set to after : 'bla, ' + * // delimiterPosition set to before : ' ,bla' + * + * Returns the delimiter, as a String. + */ + delimiter: function() { + var delimiter = ''; + delimiter += (this.options.delimiterPosition === 'before') ? ' ' : ''; + // " delimiter" || "delimiter" + delimiter += this.options.delimiter; + // "delimiter " || "delimiter" + delimiter += (this.options.delimiterPosition === 'after') ? ' ' : ''; + return delimiter; + }, + + /* Public: Returns the first term of an array if it does not start with a delimiter + * + * terms - An Array of String, the terms. + * + * Returns the first term, as a String. + */ + firstTerm: function(terms) { + var firstTerm = null; + if (this.options.withText) { + /* If the first character of the first term is not a + * delimiter, we extract and store it because we + * don't want the delimiter to be appened to it. + */ + if (this.element.value[0] !== this.options.delimiter) { + firstTerm = terms.shift(); } - }); - jQuery(e).trigger('railsAutocomplete.select', ui); - return false; } - }); - } - }); + return firstTerm; + }, - jQuery(document).ready(function(){ - jQuery('input[data-autocomplete]').railsAutocomplete(); - }); -})(jQuery); + /* Private: Get the new element value from an Array of terms. + * + * terms - An Array of String, the terms. + * + * Returns the joined and processed terms as a String. + */ + getValue: function(terms) { + var firstTerm = this.firstTerm(terms) + value = this.join( terms, this.delimiter() ); + + // If there is no insertion of a delimiter, we append a whitespace + if (this.options.insertDelimiter == false) { + value += ' ' + } + // If, the first element was not a term we added it back + if (firstTerm) { + value = firstTerm + value; + } else if (this.options.delimiterPosition === 'before') { + // Else the first element was a term and we remove the space before + value = value.substring(1) + } + return value; + } +}; diff --git a/lib/assets/javascripts/autocomplete-rails.js b/lib/assets/javascripts/autocomplete-rails.js index 8c54fa34..74cc2410 100644 --- a/lib/assets/javascripts/autocomplete-rails.js +++ b/lib/assets/javascripts/autocomplete-rails.js @@ -13,4 +13,4 @@ * Example: * */ -(function(e){var t=null;e.fn.railsAutocomplete=function(){return this.live("focus",function(){this.railsAutoCompleter||(this.railsAutoCompleter=new e.railsAutocomplete(this))})},e.railsAutocomplete=function(e){_e=e,this.init(_e)},e.railsAutocomplete.fn=e.railsAutocomplete.prototype={railsAutocomplete:"0.0.1"},e.railsAutocomplete.fn.extend=e.railsAutocomplete.extend=e.extend,e.railsAutocomplete.fn.extend({init:function(t){function n(e){return e.split(t.delimiter)}function r(e){return n(e).pop().replace(/^\s+/,"")}t.delimiter=e(t).attr("data-delimiter")||null,e(t).autocomplete({source:function(n,i){e.getJSON(e(t).attr("data-autocomplete"),{term:r(n.term)},function(){arguments[0].length==0&&(arguments[0]=[],arguments[0][0]={id:"",label:"no existing match"}),e(arguments[0]).each(function(n,r){var i={};i[r.id]=r,e(t).data(i)}),i.apply(null,arguments)})},change:function(t,n){if(e(e(this).attr("data-id-element")).val()=="")return;e(e(this).attr("data-id-element")).val(n.item?n.item.id:"");var r=e.parseJSON(e(this).attr("data-update-elements")),i=n.item?e(this).data(n.item.id.toString()):{};if(r&&e(r["id"]).val()=="")return;for(var s in r)e(r[s]).val(n.item?i[s]:"")},search:function(){var e=r(this.value);if(e.length<2)return!1},focus:function(){return!1},select:function(r,i){var s=n(this.value);s.pop(),s.push(i.item.value);if(t.delimiter!=null)s.push(""),this.value=s.join(t.delimiter);else{this.value=s.join(""),e(this).attr("data-id-element")&&e(e(this).attr("data-id-element")).val(i.item.id);if(e(this).attr("data-update-elements")){var o=e(this).data(i.item.id.toString()),u=e.parseJSON(e(this).attr("data-update-elements"));for(var a in u)e(u[a]).val(o[a])}}var f=this.value;return e(this).bind("keyup.clearId",function(){e(this).val().trim()!=f.trim()&&(e(e(this).attr("data-id-element")).val(""),e(this).unbind("keyup.clearId"))}),e(t).trigger("railsAutocomplete.select",i),!1}})}}),e(document).ready(function(){e("input[data-autocomplete]").railsAutocomplete()})})(jQuery); \ No newline at end of file +(function(e){function n(e,t){for(var n in t){if(e[n]===undefined)e[n]=t[n]}return e}e.fn.caretPosition=function(){el=this[0];if(el.selectionStart){return el.selectionStart}else if(document.selection){el.focus();var e=document.selection.createRange();if(e==null){return 0}var t=el.createTextRange(),n=t.duplicate();t.moveToBookmark(e.getBookmark());n.setEndPoint("EndToStart",t);return n.text.length}return 0};var t=null;e.fn.railsAutocomplete=function(t){return this.live("focus",function(){if(!this.railsAutoCompleter){this.railsAutoCompleter=new e.railsAutocomplete(this,t)}})};e.railsAutocomplete=function(e,t){_e=e;e.options=n(t,railsAutocompleteConfig.DEFAULT_OPTIONS);this.init(_e)};e.railsAutocomplete.fn=e.railsAutocomplete.prototype={railsAutocomplete:"0.0.1"};e.railsAutocomplete.configuration={createWithDefaults:true};e.railsAutocomplete.fn.extend=e.railsAutocomplete.extend=e.extend;e.railsAutocomplete.fn.extend({init:function(t){t.options.delimiter=t.options.delimiter||e(t).attr("data-delimiter")||null;t.options.url=t.options.url||e(t).attr("data-autocomplete");railsAutocompleteTag.init(t);if(t.options.withText){if(!t.options.delimiter){throw"When working with joined text, autocomplete rails needs a delimiter to be defined."}railsAutocompleteTag.autocomplete(t.options.delimiter)}else{railsAutocompleteTag.autocomplete()}}});e(document).ready(function(){if(e.railsAutocomplete.configuration.createWithDefaults){e("input[data-autocomplete]").railsAutocomplete({delimiter:",",insertDelimiter:true,focusOnNext:true});e("textarea[data-autocomplete]").railsAutocomplete({delimiter:",",insertDelimiter:true})}})})(jQuery);var railsAutocompleteTag=function(){function n(n){t=n;e=railsAutocompleteConfig.autocompleteCallbacks(t)}function r(n){if(n){i(n)}else{jQuery(t).autocomplete(e)}}function i(n){var r=n.charCodeAt(0),i=null;jQuery(t).on("keypress",function(n){if(n.which===r){if(null===i){i=jQuery(t).autocomplete(e)}else{jQuery(t).autocomplete("enable")}}else if(n.which===",".charCodeAt(0)){jQuery(t).autocomplete("disable")}})}var e,t;return{init:n,autocomplete:r}}();var railsAutocompleteConfig=function(){function e(e){e.helpers=new _railsAutocompleteHelpers(e);return{source:function(t,n){jQuery.getJSON(e.options.url,{term:e.helpers.extractCurrent(t.term)},function(t){if(arguments[0].length==0){arguments[0]=[];arguments[0][0]={id:"",label:"no existing match"}}jQuery(arguments[0]).each(function(t,n){var r={};r[n.id]=n;jQuery(e).data(r)});n.apply(null,arguments)})},change:function(e,t){if(jQuery(jQuery(this).attr("data-id-element")).val()==""){return}jQuery(jQuery(this).attr("data-id-element")).val(t.item?t.item.id:"");var n=jQuery.parseJSON(jQuery(this).attr("data-update-elements")),r=t.item?jQuery(this).data(t.item.id.toString()):{};if(n&&jQuery(n["id"]).val()==""){return}for(var i in n){jQuery(n[i]).val(t.item?r[i]:"")}},search:function(){var t=e.helpers.extractCurrent(this.value);if(t.length<2){return false}},focus:function(){return false},close:function(){if(e.options.withText){jQuery(this).autocomplete("disable");jQuery(this).focus().trigger(jQuery.Event("keypress",{which:13}))}},select:function(t,n){var r=e.helpers.split(this.value);r[e.helpers.currentTermIndex(r)]=n.item.value;if(this.options.delimiter!=null){r=jQuery.map(r,function(e,t){if(!(e==""||e==" ")){return jQuery.trim(e)}});if(this.options.insertDelimiter==true){if(this.options.delimiterPosition==="after"){r.push("")}}this.value=e.helpers.getValue(r)}else{this.value=r.join("");if(jQuery(this).attr("data-id-element")){jQuery(jQuery(this).attr("data-id-element")).val(n.item.id)}if(jQuery(this).attr("data-update-elements")){var i=jQuery(this).data(n.item.id.toString());var s=jQuery.parseJSON(jQuery(this).attr("data-update-elements"));for(var o in s){jQuery(s[o]).val(i[o])}}}var u=this.value;jQuery(this).bind("keyup.clearId",function(){if(jQuery(this).val().trim()!=u.trim()){jQuery(jQuery(this).attr("data-id-element")).val("");jQuery(this).unbind("keyup.clearId")}});jQuery(e).trigger("railsAutocomplete.select",n);if(t.keyCode===9&&!e.options.focusOnNext){t.preventDefault();jQuery(e).focus()}return false}}}function t(){return{delimiter:null,delimiterPosition:"after",insertDelimiter:false,focusOnNext:false,url:null,withText:false,filter:null}}return{autocompleteCallbacks:e,DEFAULT_OPTIONS:t()}}();_railsAutocompleteHelpers=function(e){this.options=e.options;this.element=e};_railsAutocompleteHelpers.prototype={split:function(e){return e.split(this.options.delimiter)},leftJoin:function(e,t){var n="";for(var r=0;ri){n=i}else if(n<0){n=0}for(var s=0;s Date: Sun, 18 Nov 2012 17:29:23 +0100 Subject: [PATCH 6/8] Updated readme for style --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b439c076..128a79b4 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,16 @@ and [SimpleForm](https://github.com/plataformatec/simple_form) ## Logs * *1.1.0* : - * Code refactoring; - * Now handle textareas; - * Can now be set up via Javascript : `$('#my_element').railsAutocomplete({ /* options */ });`; - * With a new set of options; - * Term autocompletion now works on previous terms, and the autocompletion will no longer happen at the end of the input/textarea, but were the cursor stands; - * Can now handle writing terms among normal non completed text through the use of a delimiter. The autocompletion is disabled while writing normal text and is triggered when one presses the delimiter key (like '#' or ',', ...). There are still a few glitches; - * Terms are now using a comma delimiter by default, enabling multiple terms autocompletion in a same input; - * Delimiters can be at the beginning or the end of the term string. Allowing for "#term1 #term2" or "term1, term2"; - * is now used for autocompletion and an option allows to keep the focus in the current element; - * The current autocompleted term can now be filtered before processing. + * Code refactoring; + * Now handle textareas; + * Can now be set up via Javascript : `$('#my_element').railsAutocomplete({ /* options */ });`; + * With a new set of options; + * Term autocompletion now works on previous terms, and the autocompletion will no longer happen at the end of the input/textarea, but were the cursor stands; + * Can now handle writing terms among normal non completed text through the use of a delimiter. The autocompletion is disabled while writing normal text and is triggered when one presses the delimiter key (like '#' or ',', ...). There are still a few glitches; + * Terms are now using a comma delimiter by default, enabling multiple terms autocompletion in a same input; + * Delimiters can be at the beginning or the end of the term string. Allowing for "#term1 #term2" or "term1, term2"; + * is now used for autocompletion and an option allows to keep the focus in the current element; + * The current autocompleted term can now be filtered before processing. ## ActiveRecord From b9f09dc76f11adca4f433c81787d9d202c522dd4 Mon Sep 17 00:00:00 2001 From: gabriel-dehan Date: Sun, 18 Nov 2012 17:32:23 +0100 Subject: [PATCH 7/8] Updated readme for style --- README.md | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 128a79b4..49e1b2c4 100644 --- a/README.md +++ b/README.md @@ -221,29 +221,24 @@ Now your autocomplete code is unobtrusive, Rails 3 style. By default, a rather basic autocompletion for all fields and text-area with the attribute data-autocomplete takes place. The default text fields and text area are activated with the following options : - ```javascript - jQuery('input[data-autocomplete]').railsAutocomplete({ - delimiter : ',', - insertDelimiter : true, - focusOnNext : true - }); - jQuery('textarea[data-autocomplete]').railsAutocomplete({ - delimiter : ',', - insertDelimiter : true - }); - ``` + jQuery('input[data-autocomplete]').railsAutocomplete({ + delimiter : ',', + insertDelimiter : true, + focusOnNext : true + }); + jQuery('textarea[data-autocomplete]').railsAutocomplete({ + delimiter : ',', + insertDelimiter : true + }); To deactivate the default autocompletion use : - ```javascript jQuery.railsAutocomplete.configuration.createWithDefaults = false; - ``` and call `jQuery(e).railsAutoComplete();` with the options you need. Available options are the followings : - ```javascript // Default options { delimiter: null, @@ -252,8 +247,7 @@ Available options are the followings : url: null, withText: false, filter: null - } - ``` + } For more informations please refer to the documented code in the file autocomplete-rails-uncompressed.js, around line 257 (function _defaultOptions()); From 82df741c80c1cc048c0aeb5b3e37b54d4d810d0e Mon Sep 17 00:00:00 2001 From: Gabriel Dehan Date: Mon, 21 Jan 2013 18:40:35 +0100 Subject: [PATCH 8/8] Compatibility with jquery 1.9 --- README.md | 2 +- lib/assets/javascripts/autocomplete-rails-uncompressed.js | 2 +- lib/assets/javascripts/autocomplete-rails.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 49e1b2c4..608c59cb 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ The default text fields and text area are activated with the following options : insertDelimiter : true }); -To deactivate the default autocompletion use : +To deactivate the default autocompletion use the following BEFORE the the page loaded event ($(document, function() {}), $(function(){}), $.onload, ...) : jQuery.railsAutocomplete.configuration.createWithDefaults = false; diff --git a/lib/assets/javascripts/autocomplete-rails-uncompressed.js b/lib/assets/javascripts/autocomplete-rails-uncompressed.js index b4e52664..2a7d90bd 100644 --- a/lib/assets/javascripts/autocomplete-rails-uncompressed.js +++ b/lib/assets/javascripts/autocomplete-rails-uncompressed.js @@ -40,7 +40,7 @@ var self = null; jQuery.fn.railsAutocomplete = function(opts) { - return this.live('focus',function() { + return this.on('focus',function() { if (!this.railsAutoCompleter) { this.railsAutoCompleter = new jQuery.railsAutocomplete(this, opts); } diff --git a/lib/assets/javascripts/autocomplete-rails.js b/lib/assets/javascripts/autocomplete-rails.js index 74cc2410..ea51f9a0 100644 --- a/lib/assets/javascripts/autocomplete-rails.js +++ b/lib/assets/javascripts/autocomplete-rails.js @@ -13,4 +13,4 @@ * Example: * */ -(function(e){function n(e,t){for(var n in t){if(e[n]===undefined)e[n]=t[n]}return e}e.fn.caretPosition=function(){el=this[0];if(el.selectionStart){return el.selectionStart}else if(document.selection){el.focus();var e=document.selection.createRange();if(e==null){return 0}var t=el.createTextRange(),n=t.duplicate();t.moveToBookmark(e.getBookmark());n.setEndPoint("EndToStart",t);return n.text.length}return 0};var t=null;e.fn.railsAutocomplete=function(t){return this.live("focus",function(){if(!this.railsAutoCompleter){this.railsAutoCompleter=new e.railsAutocomplete(this,t)}})};e.railsAutocomplete=function(e,t){_e=e;e.options=n(t,railsAutocompleteConfig.DEFAULT_OPTIONS);this.init(_e)};e.railsAutocomplete.fn=e.railsAutocomplete.prototype={railsAutocomplete:"0.0.1"};e.railsAutocomplete.configuration={createWithDefaults:true};e.railsAutocomplete.fn.extend=e.railsAutocomplete.extend=e.extend;e.railsAutocomplete.fn.extend({init:function(t){t.options.delimiter=t.options.delimiter||e(t).attr("data-delimiter")||null;t.options.url=t.options.url||e(t).attr("data-autocomplete");railsAutocompleteTag.init(t);if(t.options.withText){if(!t.options.delimiter){throw"When working with joined text, autocomplete rails needs a delimiter to be defined."}railsAutocompleteTag.autocomplete(t.options.delimiter)}else{railsAutocompleteTag.autocomplete()}}});e(document).ready(function(){if(e.railsAutocomplete.configuration.createWithDefaults){e("input[data-autocomplete]").railsAutocomplete({delimiter:",",insertDelimiter:true,focusOnNext:true});e("textarea[data-autocomplete]").railsAutocomplete({delimiter:",",insertDelimiter:true})}})})(jQuery);var railsAutocompleteTag=function(){function n(n){t=n;e=railsAutocompleteConfig.autocompleteCallbacks(t)}function r(n){if(n){i(n)}else{jQuery(t).autocomplete(e)}}function i(n){var r=n.charCodeAt(0),i=null;jQuery(t).on("keypress",function(n){if(n.which===r){if(null===i){i=jQuery(t).autocomplete(e)}else{jQuery(t).autocomplete("enable")}}else if(n.which===",".charCodeAt(0)){jQuery(t).autocomplete("disable")}})}var e,t;return{init:n,autocomplete:r}}();var railsAutocompleteConfig=function(){function e(e){e.helpers=new _railsAutocompleteHelpers(e);return{source:function(t,n){jQuery.getJSON(e.options.url,{term:e.helpers.extractCurrent(t.term)},function(t){if(arguments[0].length==0){arguments[0]=[];arguments[0][0]={id:"",label:"no existing match"}}jQuery(arguments[0]).each(function(t,n){var r={};r[n.id]=n;jQuery(e).data(r)});n.apply(null,arguments)})},change:function(e,t){if(jQuery(jQuery(this).attr("data-id-element")).val()==""){return}jQuery(jQuery(this).attr("data-id-element")).val(t.item?t.item.id:"");var n=jQuery.parseJSON(jQuery(this).attr("data-update-elements")),r=t.item?jQuery(this).data(t.item.id.toString()):{};if(n&&jQuery(n["id"]).val()==""){return}for(var i in n){jQuery(n[i]).val(t.item?r[i]:"")}},search:function(){var t=e.helpers.extractCurrent(this.value);if(t.length<2){return false}},focus:function(){return false},close:function(){if(e.options.withText){jQuery(this).autocomplete("disable");jQuery(this).focus().trigger(jQuery.Event("keypress",{which:13}))}},select:function(t,n){var r=e.helpers.split(this.value);r[e.helpers.currentTermIndex(r)]=n.item.value;if(this.options.delimiter!=null){r=jQuery.map(r,function(e,t){if(!(e==""||e==" ")){return jQuery.trim(e)}});if(this.options.insertDelimiter==true){if(this.options.delimiterPosition==="after"){r.push("")}}this.value=e.helpers.getValue(r)}else{this.value=r.join("");if(jQuery(this).attr("data-id-element")){jQuery(jQuery(this).attr("data-id-element")).val(n.item.id)}if(jQuery(this).attr("data-update-elements")){var i=jQuery(this).data(n.item.id.toString());var s=jQuery.parseJSON(jQuery(this).attr("data-update-elements"));for(var o in s){jQuery(s[o]).val(i[o])}}}var u=this.value;jQuery(this).bind("keyup.clearId",function(){if(jQuery(this).val().trim()!=u.trim()){jQuery(jQuery(this).attr("data-id-element")).val("");jQuery(this).unbind("keyup.clearId")}});jQuery(e).trigger("railsAutocomplete.select",n);if(t.keyCode===9&&!e.options.focusOnNext){t.preventDefault();jQuery(e).focus()}return false}}}function t(){return{delimiter:null,delimiterPosition:"after",insertDelimiter:false,focusOnNext:false,url:null,withText:false,filter:null}}return{autocompleteCallbacks:e,DEFAULT_OPTIONS:t()}}();_railsAutocompleteHelpers=function(e){this.options=e.options;this.element=e};_railsAutocompleteHelpers.prototype={split:function(e){return e.split(this.options.delimiter)},leftJoin:function(e,t){var n="";for(var r=0;ri){n=i}else if(n<0){n=0}for(var s=0;si){n=i}else if(n<0){n=0}for(var s=0;s