From 4d6ebbb803c9380c692ff88a239326541cc9baaf Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Mon, 5 Dec 2011 12:19:35 -0500 Subject: [PATCH 01/14] Added the possibility to use scopes with arguments, by passing a array or a lambda via the scopes option. --- lib/rails3-jquery-autocomplete/autocomplete.rb | 1 + lib/rails3-jquery-autocomplete/orm/active_record.rb | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/rails3-jquery-autocomplete/autocomplete.rb b/lib/rails3-jquery-autocomplete/autocomplete.rb index fefc9146..13dcbb3e 100644 --- a/lib/rails3-jquery-autocomplete/autocomplete.rb +++ b/lib/rails3-jquery-autocomplete/autocomplete.rb @@ -52,6 +52,7 @@ def autocomplete(object, method, options = {}) #allow specifying fully qualified class name for model object class_name = options[:class_name] || object items = get_autocomplete_items(:model => get_object(class_name), \ + :controller => self, \ :options => options, :term => term, :method => method) else items = {} diff --git a/lib/rails3-jquery-autocomplete/orm/active_record.rb b/lib/rails3-jquery-autocomplete/orm/active_record.rb index e9260dd0..8ab31632 100644 --- a/lib/rails3-jquery-autocomplete/orm/active_record.rb +++ b/lib/rails3-jquery-autocomplete/orm/active_record.rb @@ -20,7 +20,13 @@ def get_autocomplete_items(parameters) items = model.scoped - scopes.each { |scope| items = items.send(scope) } unless scopes.empty? + scopes.each do |scope| + if scope.is_a? Proc + items = scope.call(items, parameters) + else + items = items.send(*Array(scope)) + end + end unless scopes.empty? items = items.select(get_autocomplete_select_clause(model, method, options)) unless options[:full_model] items = items.where(get_autocomplete_where_clause(model, term, method, options)). From daafcb34121ab8712c6efad6f14968c6d07b6192 Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Mon, 5 Dec 2011 18:17:16 -0500 Subject: [PATCH 02/14] Added an option to use a specific action name. --- lib/rails3-jquery-autocomplete/autocomplete.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/rails3-jquery-autocomplete/autocomplete.rb b/lib/rails3-jquery-autocomplete/autocomplete.rb index 13dcbb3e..9f8a5ecd 100644 --- a/lib/rails3-jquery-autocomplete/autocomplete.rb +++ b/lib/rails3-jquery-autocomplete/autocomplete.rb @@ -42,7 +42,8 @@ def self.included(target) # module ClassMethods def autocomplete(object, method, options = {}) - define_method("autocomplete_#{object}_#{method}") do + define_method(options[:action_name] || + "autocomplete_#{object}_#{method}") do method = options[:column_name] if options.has_key?(:column_name) From 27656f1954d2ab5cf7541467b4616d37d7aa0b84 Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Mon, 5 Dec 2011 18:27:02 -0500 Subject: [PATCH 03/14] Improved the extra_data option to handle hashes where the keys are the columns passed to select and the values are the names of the methods to be called or lambdas. --- lib/rails3-jquery-autocomplete/autocomplete.rb | 8 ++++++-- lib/rails3-jquery-autocomplete/orm/active_record.rb | 10 +++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/rails3-jquery-autocomplete/autocomplete.rb b/lib/rails3-jquery-autocomplete/autocomplete.rb index 9f8a5ecd..e87723a2 100644 --- a/lib/rails3-jquery-autocomplete/autocomplete.rb +++ b/lib/rails3-jquery-autocomplete/autocomplete.rb @@ -86,8 +86,12 @@ def get_object(model_sym) def json_for_autocomplete(items, method, extra_data=[]) items.collect do |item| hash = {"id" => item.id.to_s, "label" => item.send(method), "value" => item.send(method)} - extra_data.each do |datum| - hash[datum] = item.send(datum) + extra_data.each do |k, v| + if v + hash[k] = v.is_a?(Proc) ? v.call(item) : item.send(v) + else + hash[k] = item.send(k) + end end if extra_data # TODO: Come back to remove this if clause when test suite is better hash diff --git a/lib/rails3-jquery-autocomplete/orm/active_record.rb b/lib/rails3-jquery-autocomplete/orm/active_record.rb index 8ab31632..540ca48f 100644 --- a/lib/rails3-jquery-autocomplete/orm/active_record.rb +++ b/lib/rails3-jquery-autocomplete/orm/active_record.rb @@ -35,7 +35,15 @@ def get_autocomplete_items(parameters) def get_autocomplete_select_clause(model, method, options) table_name = model.table_name - (["#{table_name}.#{model.primary_key}", "#{table_name}.#{method}"] + (options[:extra_data].blank? ? [] : options[:extra_data])) + extra_columns = + if options[:extra_data].nil? + [] + elsif options[:extra_data].is_a? Hash + options[:extra_data].keys + else + options[:extra_data] + end + (["#{table_name}.#{model.primary_key}", "#{table_name}.#{method}"] + extra_columns) end def get_autocomplete_where_clause(model, term, method, options) From 1f09e1fb0ee33eeaac30ada47e188445f2ac4a6d Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Tue, 6 Dec 2011 14:23:57 -0500 Subject: [PATCH 04/14] Made the arguments sent to extra_data lambdas the same as those sent to scopes lambdas. --- .../autocomplete.rb | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/rails3-jquery-autocomplete/autocomplete.rb b/lib/rails3-jquery-autocomplete/autocomplete.rb index e87723a2..96a85d9f 100644 --- a/lib/rails3-jquery-autocomplete/autocomplete.rb +++ b/lib/rails3-jquery-autocomplete/autocomplete.rb @@ -49,17 +49,23 @@ def autocomplete(object, method, options = {}) term = params[:term] + # allow specifying fully qualified class name for model object + class_name = options[:class_name] || object + parameters = { + :model => get_object(class_name), + :controller => self, + :term => term, + :method => method, + :options => options + } + if term && !term.blank? - #allow specifying fully qualified class name for model object - class_name = options[:class_name] || object - items = get_autocomplete_items(:model => get_object(class_name), \ - :controller => self, \ - :options => options, :term => term, :method => method) + items = get_autocomplete_items(parameters) else items = {} end - render :json => json_for_autocomplete(items, options[:display_value] ||= method, options[:extra_data]) + render :json => json_for_autocomplete(items, parameters) end end end @@ -83,12 +89,14 @@ def get_object(model_sym) # Can be overriden to show whatever you like # Hash also includes a key/value pair for each method in extra_data # - def json_for_autocomplete(items, method, extra_data=[]) + def json_for_autocomplete(items, parameters) + method = parameters[:options][:display_value] ||= parameters[:method] + extra_data = parameters[:options][:extra_data] items.collect do |item| hash = {"id" => item.id.to_s, "label" => item.send(method), "value" => item.send(method)} extra_data.each do |k, v| if v - hash[k] = v.is_a?(Proc) ? v.call(item) : item.send(v) + hash[k] = v.is_a?(Proc) ? v.call(item, parameters) : item.send(v) else hash[k] = item.send(k) end From 884e72c683ea46889667b6041becad49074897f0 Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Wed, 7 Dec 2011 01:09:31 -0500 Subject: [PATCH 05/14] Fixed tests arguments expectations for json_for_autocomplete and get_autocomplete_items methods. --- .../autocomplete_test.rb | 10 ++++++---- test/lib/rails3-jquery-autocomplete_test.rb | 17 +++++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb b/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb index 4630ce4f..d8b890af 100644 --- a/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb +++ b/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb @@ -32,8 +32,9 @@ class AutocompleteTest < Test::Unit::TestCase item = mock(Object) mock(item).send(:name).times(2) { 'Object Name' } mock(item).id { 1 } - items = [item] - response = self.json_for_autocomplete(items, :name).first + items = [item] + parameters = { :method => :name, :options => {} } + response = self.json_for_autocomplete(items, parameters).first assert_equal response["id"], "1" assert_equal response["value"], "Object Name" assert_equal response["label"], "Object Name" @@ -46,8 +47,9 @@ class AutocompleteTest < Test::Unit::TestCase mock(item).id { 1 } mock(item).send("extra") { 'Object Extra ' } - items = [item] - response = self.json_for_autocomplete(items, :name, ["extra"]).first + items = [item] + parameters = { :method => :name, :options => { :extra_data => ["extra"] } } + response = self.json_for_autocomplete(items, parameters).first assert_equal "1" , response["id"] assert_equal "Object Name" , response["value"] diff --git a/test/lib/rails3-jquery-autocomplete_test.rb b/test/lib/rails3-jquery-autocomplete_test.rb index 4ca11a8d..ad1034cf 100644 --- a/test/lib/rails3-jquery-autocomplete_test.rb +++ b/test/lib/rails3-jquery-autocomplete_test.rb @@ -12,6 +12,13 @@ class ::Movie ; end @controller = ActorsController.new @items = {} @options = { :display_value => :name } + @parameters = { + :term => nil, + :model => Movie, + :method => :name, + :controller => @controller, + :options => {} + } end should 'respond to the action' do @@ -19,17 +26,15 @@ class ::Movie ; end end should 'render the JSON items' do - mock(@controller).get_autocomplete_items({ - :model => Movie, :method => :name, :options => @options, :term => "query" - }) { @items } - - mock(@controller).json_for_autocomplete(@items, :name, nil) + parameters = @parameters.merge(:term => "query") + mock(@controller).get_autocomplete_items(parameters) { @items } + mock(@controller).json_for_autocomplete(@items, parameters) get :autocomplete_movie_name, :term => 'query' end context 'no term is specified' do should "render an empty hash" do - mock(@controller).json_for_autocomplete({}, :name, nil) + mock(@controller).json_for_autocomplete({}, @parameters) get :autocomplete_movie_name end end From d1a691cc4c778b296f18a65337a3b3e881507451 Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Wed, 7 Dec 2011 01:23:01 -0500 Subject: [PATCH 06/14] Refactored parts of the json_for_autocomplete unit tests and made the 'with extra data' test check that the extra data attribute is actually returned. --- .../autocomplete_test.rb | 21 +++++++++---------- test/test_helper.rb | 7 +++++++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb b/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb index d8b890af..d98e3c8a 100644 --- a/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb +++ b/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb @@ -29,31 +29,30 @@ class AutocompleteTest < Test::Unit::TestCase context '#json_for_autocomplete' do should 'parse items to JSON' do - item = mock(Object) - mock(item).send(:name).times(2) { 'Object Name' } - mock(item).id { 1 } + item = generate_mocked_model_instance + items = [item] parameters = { :method => :name, :options => {} } response = self.json_for_autocomplete(items, parameters).first + assert_equal response["id"], "1" assert_equal response["value"], "Object Name" assert_equal response["label"], "Object Name" end - context 'with extra data' do + context 'with extra data as an Array' do should 'add that extra data to result' do - item = mock(Object) - mock(item).send(:name).times(2) { 'Object Name' } - mock(item).id { 1 } - mock(item).send("extra") { 'Object Extra ' } + item = generate_mocked_model_instance + mock(item).send("extra") { 'Object Extra' } items = [item] parameters = { :method => :name, :options => { :extra_data => ["extra"] } } response = self.json_for_autocomplete(items, parameters).first - assert_equal "1" , response["id"] - assert_equal "Object Name" , response["value"] - assert_equal "Object Name" , response["label"] + assert_equal "1" , response["id"] + assert_equal "Object Name" , response["value"] + assert_equal "Object Name" , response["label"] + assert_equal "Object Extra" , response["extra"] end end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 47c50d4f..6b0ba7c6 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -27,5 +27,12 @@ class Application < ::Rails::Application class Test::Unit::TestCase include RR::Adapters::TestUnit + + def generate_mocked_model_instance + item = mock(Object) + mock(item).send(:name).times(2) { 'Object Name' } + mock(item).id { 1 } + item + end end From 46e480cfad5262e391a3f210687cd6184437b047 Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Wed, 7 Dec 2011 01:30:24 -0500 Subject: [PATCH 07/14] Added tests for json_for_autocomplete with extra data as a Hash. --- .../autocomplete_test.rb | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb b/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb index d98e3c8a..050f1211 100644 --- a/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb +++ b/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb @@ -31,8 +31,8 @@ class AutocompleteTest < Test::Unit::TestCase should 'parse items to JSON' do item = generate_mocked_model_instance - items = [item] parameters = { :method => :name, :options => {} } + items = [item] response = self.json_for_autocomplete(items, parameters).first assert_equal response["id"], "1" @@ -45,8 +45,8 @@ class AutocompleteTest < Test::Unit::TestCase item = generate_mocked_model_instance mock(item).send("extra") { 'Object Extra' } - items = [item] parameters = { :method => :name, :options => { :extra_data => ["extra"] } } + items = [item] response = self.json_for_autocomplete(items, parameters).first assert_equal "1" , response["id"] @@ -55,6 +55,52 @@ class AutocompleteTest < Test::Unit::TestCase assert_equal "Object Extra" , response["extra"] end end + + context 'with extra data as a Hash' do + context 'having lambdas as values' do + should 'add that extra data to result' do + item = generate_mocked_model_instance + + parameters = { + :method => :name, + :options => { + :extra_data => { + "extra" => lambda { |item, parameters| 'Lambda Extra' } + } + } + } + items = [item] + response = self.json_for_autocomplete(items, parameters).first + + assert_equal "1" , response["id"] + assert_equal "Object Name" , response["value"] + assert_equal "Object Name" , response["label"] + assert_equal "Lambda Extra" , response["extra"] + end + end + + context 'having non-lambdas as values' do + should 'add that extra data to result' do + item = generate_mocked_model_instance + mock(item).send("exxtra") { 'Object Exxtra' } + + parameters = { + :method => :name, + :options => { :extra_data => { + "extra" => "exxtra" + } + } + } + items = [item] + response = self.json_for_autocomplete(items, parameters).first + + assert_equal "1" , response["id"] + assert_equal "Object Name" , response["value"] + assert_equal "Object Name" , response["label"] + assert_equal 'Object Exxtra' , response["extra"] + end + end + end end end end From b29986246074616a46462193ed0931f478407f0f Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Thu, 8 Dec 2011 15:13:35 -0500 Subject: [PATCH 08/14] Modified the Formtastic 2 custom input to handle a new :id_field option that generate an id field and set the id_element html option. --- lib/rails3-jquery-autocomplete/formtastic.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/rails3-jquery-autocomplete/formtastic.rb b/lib/rails3-jquery-autocomplete/formtastic.rb index 4fea6945..889996de 100644 --- a/lib/rails3-jquery-autocomplete/formtastic.rb +++ b/lib/rails3-jquery-autocomplete/formtastic.rb @@ -19,10 +19,24 @@ class AutocompleteInput include Base include Base::Stringish + def id_field_dom_id(method) + [ builder.custom_namespace, + sanitized_object_name, + dom_index, + method.to_s.gsub(/[\?\/\-]$/, '') + ].reject { |x| x.blank? }.join('_') + end + + def input_html_options + { :id_element => '#' + id_field_dom_id(options[:id_field]) + }.merge(super) if options[:id_field] + end + def to_html input_wrapping do - label_html << - builder.autocomplete_field(method, options.delete(:url), input_html_options) + result = label_html << + builder.autocomplete_field(method, options.delete(:url), input_html_options) << + (builder.hidden_field options.delete :id_field if options[:id_field]) end end end From 7b0a195487282687e81cac7e2dc7720fa5039bb9 Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Mon, 12 Dec 2011 13:12:43 -0500 Subject: [PATCH 09/14] Fixed some bugs in formtastic helpers. --- lib/rails3-jquery-autocomplete/formtastic.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/rails3-jquery-autocomplete/formtastic.rb b/lib/rails3-jquery-autocomplete/formtastic.rb index 889996de..3c913c53 100644 --- a/lib/rails3-jquery-autocomplete/formtastic.rb +++ b/lib/rails3-jquery-autocomplete/formtastic.rb @@ -28,15 +28,19 @@ def id_field_dom_id(method) end def input_html_options - { :id_element => '#' + id_field_dom_id(options[:id_field]) - }.merge(super) if options[:id_field] + if options[:id_field] + { :id_element => '#' + id_field_dom_id(options[:id_field]) + }.merge(super) + else + super + end end def to_html input_wrapping do result = label_html << builder.autocomplete_field(method, options.delete(:url), input_html_options) << - (builder.hidden_field options.delete :id_field if options[:id_field]) + (options[:id_field] ? builder.hidden_field(options.delete :id_field) : '') end end end From 4a965612550bb36a2965c159b521da8b7be08129 Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Tue, 20 Dec 2011 13:34:16 -0500 Subject: [PATCH 10/14] Added a callback to be able to send extra data to the autocomplete action and removed the minified version of the JS file. --- .../autocomplete-rails-uncompressed.js | 113 ------------------ lib/assets/javascripts/autocomplete-rails.js | 100 +++++++++++++++- 2 files changed, 99 insertions(+), 114 deletions(-) delete mode 100644 lib/assets/javascripts/autocomplete-rails-uncompressed.js diff --git a/lib/assets/javascripts/autocomplete-rails-uncompressed.js b/lib/assets/javascripts/autocomplete-rails-uncompressed.js deleted file mode 100644 index e844c159..00000000 --- a/lib/assets/javascripts/autocomplete-rails-uncompressed.js +++ /dev/null @@ -1,113 +0,0 @@ -/* -* Unobtrusive autocomplete -* -* To use it, you just have to include the HTML attribute autocomplete -* with the autocomplete URL as the value -* -* Example: -* -* -* Optionally, you can use a jQuery selector to specify a field that can -* be updated with the element id whenever you find a matching value -* -* Example: -* -*/ - -$(document).ready(function(){ - $('input[data-autocomplete]').railsAutocomplete(); -}); - -(function(jQuery) -{ - var self = null; - jQuery.fn.railsAutocomplete = function() { - return this.live('focus',function() { - if (!this.railsAutoCompleter) { - this.railsAutoCompleter = new jQuery.railsAutocomplete(this); - } - }); - }; - - jQuery.railsAutocomplete = function (e) { - _e = e; - this.init(_e); - }; - - jQuery.railsAutocomplete.fn = jQuery.railsAutocomplete.prototype = { - railsAutocomplete: '0.0.1' - }; - - jQuery.railsAutocomplete.fn.extend = jQuery.railsAutocomplete.extend = jQuery.extend; - jQuery.railsAutocomplete.fn.extend({ - init: function(e) { - e.delimiter = $(e).attr('data-delimiter') || null; - function split( val ) { - return val.split( e.delimiter ); - } - function extractLast( term ) { - return split( term ).pop().replace(/^\s+/,""); - } - - $(e).autocomplete({ - source: function( request, response ) { - $.getJSON( $(e).attr('data-autocomplete'), { - term: extractLast( request.term ) - }, function() { - $(arguments[0]).each(function(i, el) { - var obj = {}; - obj[el.id] = el; - $(e).data(obj); - }); - response.apply(null, arguments); - }); - }, - 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 ($(this).attr('data-id-element')) { - $($(this).attr('data-id-element')).val(ui.item.id); - } - if ($(this).attr('data-update-elements')) { - var data = $(this).data(ui.item.id.toString()); - var update_elements = $.parseJSON($(this).attr("data-update-elements")); - for (var key in update_elements) { - $(update_elements[key]).val(data[key]); - } - } - } - var remember_string = this.value; - $(this).bind('keyup.clearId', function(){ - if($(this).val().trim() != remember_string.trim()){ - $($(this).attr('data-id-element')).val(""); - $(this).unbind('keyup.clearId'); - } - }); - $(this).trigger('railsAutocomplete.select', ui); - - return false; - } - }); - } - }); -})(jQuery); diff --git a/lib/assets/javascripts/autocomplete-rails.js b/lib/assets/javascripts/autocomplete-rails.js index 2e0bfdbd..c5646659 100644 --- a/lib/assets/javascripts/autocomplete-rails.js +++ b/lib/assets/javascripts/autocomplete-rails.js @@ -13,4 +13,102 @@ * Example: * */ -$(document).ready(function(){$("input[data-autocomplete]").railsAutocomplete()}),function(a){var b=null;a.fn.railsAutocomplete=function(){return this.live("focus",function(){this.railsAutoCompleter||(this.railsAutoCompleter=new a.railsAutocomplete(this))})},a.railsAutocomplete=function(a){_e=a,this.init(_e)},a.railsAutocomplete.fn=a.railsAutocomplete.prototype={railsAutocomplete:"0.0.1"},a.railsAutocomplete.fn.extend=a.railsAutocomplete.extend=a.extend,a.railsAutocomplete.fn.extend({init:function(a){function b(b){return b.split(a.delimiter)}function c(a){return b(a).pop().replace(/^\s+/,"")}a.delimiter=$(a).attr("data-delimiter")||null,$(a).autocomplete({source:function(b,d){$.getJSON($(a).attr("data-autocomplete"),{term:c(b.term)},function(){$(arguments[0]).each(function(b,c){var d={};d[c.id]=c,$(a).data(d)}),d.apply(null,arguments)})},search:function(){var a=c(this.value);if(a.length<2)return!1},focus:function(){return!1},select:function(c,d){var f=b(this.value);f.pop(),f.push(d.item.value);if(a.delimiter!=null)f.push(""),this.value=f.join(a.delimiter);else{this.value=f.join(""),$(this).attr("data-id-element")&&$($(this).attr("data-id-element")).val(d.item.id);if($(this).attr("data-update-elements")){var g=$(this).data(d.item.id.toString()),h=$.parseJSON($(this).attr("data-update-elements"));for(var i in h)$(h[i]).val(g[i])}}var j=this.value;return $(this).bind("keyup.clearId",function(){$(this).val().trim()!=j.trim()&&($($(this).attr("data-id-element")).val(""),$(this).unbind("keyup.clearId"))}),$(this).trigger("railsAutocomplete.select",d),!1}})}})}(jQuery) \ No newline at end of file + +$(document).ready(function(){ + $('input[data-autocomplete]').railsAutocomplete(); +}); + +(function(jQuery) +{ + var self = null; + jQuery.fn.railsAutocomplete = function() { + return this.live('focus',function() { + if (!this.railsAutoCompleter) { + this.railsAutoCompleter = new jQuery.railsAutocomplete(this); + } + }); + }; + + jQuery.railsAutocomplete = function (e) { + _e = e; + this.init(_e); + }; + + jQuery.railsAutocomplete.fn = jQuery.railsAutocomplete.prototype = { + railsAutocomplete: '0.0.1' + }; + + jQuery.railsAutocomplete.fn.extend = jQuery.railsAutocomplete.extend = jQuery.extend; + jQuery.railsAutocomplete.fn.extend({ + init: function(e) { + e.delimiter = $(e).attr('data-delimiter') || null; + function split( val ) { + return val.split( e.delimiter ); + } + function extractLast( term ) { + return split( term ).pop().replace(/^\s+/,""); + } + + $(e).autocomplete({ + source: function( request, response ) { + var data = { term: extractLast( request.term ) }; + var extraData = $(e).triggerHandler('railsAutocomplete.source', data); + data = extraData ? extraData : data; + $.getJSON( $(e).attr('data-autocomplete'), data, function() { + $(arguments[0]).each(function(i, el) { + var obj = {}; + obj[el.id] = el; + $(e).data(obj); + }); + response.apply(null, arguments); + }); + }, + 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 ($(this).attr('data-id-element')) { + $($(this).attr('data-id-element')).val(ui.item.id); + } + if ($(this).attr('data-update-elements')) { + var data = $(this).data(ui.item.id.toString()); + var update_elements = $.parseJSON($(this).attr("data-update-elements")); + for (var key in update_elements) { + $(update_elements[key]).val(data[key]); + } + } + } + var remember_string = this.value; + $(this).bind('keyup.clearId', function(){ + if($(this).val().trim() != remember_string.trim()){ + $($(this).attr('data-id-element')).val(""); + $(this).unbind('keyup.clearId'); + } + }); + $(this).trigger('railsAutocomplete.select', ui); + + return false; + } + }); + } + }); +})(jQuery); From 95fd7cefa6b8c5341ca19e970450c3198ecaafcd Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Tue, 20 Dec 2011 14:10:09 -0500 Subject: [PATCH 11/14] Trigger the change event when setting the associated id-element input. --- lib/assets/javascripts/autocomplete-rails.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/assets/javascripts/autocomplete-rails.js b/lib/assets/javascripts/autocomplete-rails.js index c5646659..11f79ca6 100644 --- a/lib/assets/javascripts/autocomplete-rails.js +++ b/lib/assets/javascripts/autocomplete-rails.js @@ -87,7 +87,7 @@ $(document).ready(function(){ } else { this.value = terms.join(""); if ($(this).attr('data-id-element')) { - $($(this).attr('data-id-element')).val(ui.item.id); + $($(this).attr('data-id-element')).val(ui.item.id).change(); } if ($(this).attr('data-update-elements')) { var data = $(this).data(ui.item.id.toString()); @@ -100,7 +100,7 @@ $(document).ready(function(){ var remember_string = this.value; $(this).bind('keyup.clearId', function(){ if($(this).val().trim() != remember_string.trim()){ - $($(this).attr('data-id-element')).val(""); + $($(this).attr('data-id-element')).val("").change(); $(this).unbind('keyup.clearId'); } }); From cdad96d845daa50aa819035a9efec38c52332949 Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Sat, 21 Jan 2012 19:18:02 -0500 Subject: [PATCH 12/14] Restored the autocomplete-rails-uncompressed.js file. --- .../autocomplete-rails-uncompressed.js | 114 ++++++++++++++++++ lib/assets/javascripts/autocomplete-rails.js | 100 +-------------- 2 files changed, 115 insertions(+), 99 deletions(-) create mode 100644 lib/assets/javascripts/autocomplete-rails-uncompressed.js diff --git a/lib/assets/javascripts/autocomplete-rails-uncompressed.js b/lib/assets/javascripts/autocomplete-rails-uncompressed.js new file mode 100644 index 00000000..11f79ca6 --- /dev/null +++ b/lib/assets/javascripts/autocomplete-rails-uncompressed.js @@ -0,0 +1,114 @@ +/* +* Unobtrusive autocomplete +* +* To use it, you just have to include the HTML attribute autocomplete +* with the autocomplete URL as the value +* +* Example: +* +* +* Optionally, you can use a jQuery selector to specify a field that can +* be updated with the element id whenever you find a matching value +* +* Example: +* +*/ + +$(document).ready(function(){ + $('input[data-autocomplete]').railsAutocomplete(); +}); + +(function(jQuery) +{ + var self = null; + jQuery.fn.railsAutocomplete = function() { + return this.live('focus',function() { + if (!this.railsAutoCompleter) { + this.railsAutoCompleter = new jQuery.railsAutocomplete(this); + } + }); + }; + + jQuery.railsAutocomplete = function (e) { + _e = e; + this.init(_e); + }; + + jQuery.railsAutocomplete.fn = jQuery.railsAutocomplete.prototype = { + railsAutocomplete: '0.0.1' + }; + + jQuery.railsAutocomplete.fn.extend = jQuery.railsAutocomplete.extend = jQuery.extend; + jQuery.railsAutocomplete.fn.extend({ + init: function(e) { + e.delimiter = $(e).attr('data-delimiter') || null; + function split( val ) { + return val.split( e.delimiter ); + } + function extractLast( term ) { + return split( term ).pop().replace(/^\s+/,""); + } + + $(e).autocomplete({ + source: function( request, response ) { + var data = { term: extractLast( request.term ) }; + var extraData = $(e).triggerHandler('railsAutocomplete.source', data); + data = extraData ? extraData : data; + $.getJSON( $(e).attr('data-autocomplete'), data, function() { + $(arguments[0]).each(function(i, el) { + var obj = {}; + obj[el.id] = el; + $(e).data(obj); + }); + response.apply(null, arguments); + }); + }, + 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 ($(this).attr('data-id-element')) { + $($(this).attr('data-id-element')).val(ui.item.id).change(); + } + if ($(this).attr('data-update-elements')) { + var data = $(this).data(ui.item.id.toString()); + var update_elements = $.parseJSON($(this).attr("data-update-elements")); + for (var key in update_elements) { + $(update_elements[key]).val(data[key]); + } + } + } + var remember_string = this.value; + $(this).bind('keyup.clearId', function(){ + if($(this).val().trim() != remember_string.trim()){ + $($(this).attr('data-id-element')).val("").change(); + $(this).unbind('keyup.clearId'); + } + }); + $(this).trigger('railsAutocomplete.select', ui); + + return false; + } + }); + } + }); +})(jQuery); diff --git a/lib/assets/javascripts/autocomplete-rails.js b/lib/assets/javascripts/autocomplete-rails.js index 11f79ca6..36dd168c 100644 --- a/lib/assets/javascripts/autocomplete-rails.js +++ b/lib/assets/javascripts/autocomplete-rails.js @@ -13,102 +13,4 @@ * Example: * */ - -$(document).ready(function(){ - $('input[data-autocomplete]').railsAutocomplete(); -}); - -(function(jQuery) -{ - var self = null; - jQuery.fn.railsAutocomplete = function() { - return this.live('focus',function() { - if (!this.railsAutoCompleter) { - this.railsAutoCompleter = new jQuery.railsAutocomplete(this); - } - }); - }; - - jQuery.railsAutocomplete = function (e) { - _e = e; - this.init(_e); - }; - - jQuery.railsAutocomplete.fn = jQuery.railsAutocomplete.prototype = { - railsAutocomplete: '0.0.1' - }; - - jQuery.railsAutocomplete.fn.extend = jQuery.railsAutocomplete.extend = jQuery.extend; - jQuery.railsAutocomplete.fn.extend({ - init: function(e) { - e.delimiter = $(e).attr('data-delimiter') || null; - function split( val ) { - return val.split( e.delimiter ); - } - function extractLast( term ) { - return split( term ).pop().replace(/^\s+/,""); - } - - $(e).autocomplete({ - source: function( request, response ) { - var data = { term: extractLast( request.term ) }; - var extraData = $(e).triggerHandler('railsAutocomplete.source', data); - data = extraData ? extraData : data; - $.getJSON( $(e).attr('data-autocomplete'), data, function() { - $(arguments[0]).each(function(i, el) { - var obj = {}; - obj[el.id] = el; - $(e).data(obj); - }); - response.apply(null, arguments); - }); - }, - 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 ($(this).attr('data-id-element')) { - $($(this).attr('data-id-element')).val(ui.item.id).change(); - } - if ($(this).attr('data-update-elements')) { - var data = $(this).data(ui.item.id.toString()); - var update_elements = $.parseJSON($(this).attr("data-update-elements")); - for (var key in update_elements) { - $(update_elements[key]).val(data[key]); - } - } - } - var remember_string = this.value; - $(this).bind('keyup.clearId', function(){ - if($(this).val().trim() != remember_string.trim()){ - $($(this).attr('data-id-element')).val("").change(); - $(this).unbind('keyup.clearId'); - } - }); - $(this).trigger('railsAutocomplete.select', ui); - - return false; - } - }); - } - }); -})(jQuery); +$(document).ready(function(){$("input[data-autocomplete]").railsAutocomplete()}),function(a){var b=null;a.fn.railsAutocomplete=function(){return this.live("focus",function(){this.railsAutoCompleter||(this.railsAutoCompleter=new a.railsAutocomplete(this))})},a.railsAutocomplete=function(a){_e=a,this.init(_e)},a.railsAutocomplete.fn=a.railsAutocomplete.prototype={railsAutocomplete:"0.0.1"},a.railsAutocomplete.fn.extend=a.railsAutocomplete.extend=a.extend,a.railsAutocomplete.fn.extend({init:function(a){function b(b){return b.split(a.delimiter)}function c(a){return b(a).pop().replace(/^\s+/,"")}a.delimiter=$(a).attr("data-delimiter")||null,$(a).autocomplete({source:function(b,d){var f={term:c(b.term)},g=$(a).triggerHandler("railsAutocomplete.source",f);f=g?g:f,$.getJSON($(a).attr("data-autocomplete"),f,function(){$(arguments[0]).each(function(b,c){var d={};d[c.id]=c,$(a).data(d)}),d.apply(null,arguments)})},search:function(){var a=c(this.value);if(a.length<2)return!1},focus:function(){return!1},select:function(c,d){var f=b(this.value);f.pop(),f.push(d.item.value);if(a.delimiter!=null)f.push(""),this.value=f.join(a.delimiter);else{this.value=f.join(""),$(this).attr("data-id-element")&&$($(this).attr("data-id-element")).val(d.item.id).change();if($(this).attr("data-update-elements")){var g=$(this).data(d.item.id.toString()),h=$.parseJSON($(this).attr("data-update-elements"));for(var i in h)$(h[i]).val(g[i])}}var j=this.value;return $(this).bind("keyup.clearId",function(){$(this).val().trim()!=j.trim()&&($($(this).attr("data-id-element")).val("").change(),$(this).unbind("keyup.clearId"))}),$(this).trigger("railsAutocomplete.select",d),!1}})}})}(jQuery) \ No newline at end of file From a2ebcd1b29cdba07adcc9e24d57bd738daa9f80f Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Sat, 21 Jan 2012 19:21:58 -0500 Subject: [PATCH 13/14] Added the label option to be able to specify a specific method to be used for the content of the autocompletion list. --- lib/rails3-jquery-autocomplete/autocomplete.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/rails3-jquery-autocomplete/autocomplete.rb b/lib/rails3-jquery-autocomplete/autocomplete.rb index 96a85d9f..ca5a5305 100644 --- a/lib/rails3-jquery-autocomplete/autocomplete.rb +++ b/lib/rails3-jquery-autocomplete/autocomplete.rb @@ -90,10 +90,17 @@ def get_object(model_sym) # Hash also includes a key/value pair for each method in extra_data # def json_for_autocomplete(items, parameters) - method = parameters[:options][:display_value] ||= parameters[:method] - extra_data = parameters[:options][:extra_data] + display_value = parameters[:options][:display_value] ||= parameters[:method] + label = parameters[:options][:label] ||= parameters[:method] + extra_data = parameters[:options][:extra_data] + items.collect do |item| - hash = {"id" => item.id.to_s, "label" => item.send(method), "value" => item.send(method)} + hash = { + "id" => item.id.to_s, + "label" => item.send(label), + "value" => item.send(display_value) + } + extra_data.each do |k, v| if v hash[k] = v.is_a?(Proc) ? v.call(item, parameters) : item.send(v) From c0bcf6f38721285e37890a2e077d6801207c2e43 Mon Sep 17 00:00:00 2001 From: Nicolas Buduroi Date: Tue, 24 Jan 2012 16:01:25 -0500 Subject: [PATCH 14/14] Extracted the code binding the keyup.clearId event into a function and call it when creating autocomplete field. --- .../autocomplete-rails-uncompressed.js | 20 ++++++++++++------- lib/assets/javascripts/autocomplete-rails.js | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/assets/javascripts/autocomplete-rails-uncompressed.js b/lib/assets/javascripts/autocomplete-rails-uncompressed.js index 11f79ca6..a1456008 100644 --- a/lib/assets/javascripts/autocomplete-rails-uncompressed.js +++ b/lib/assets/javascripts/autocomplete-rails-uncompressed.js @@ -48,6 +48,15 @@ $(document).ready(function(){ function extractLast( term ) { return split( term ).pop().replace(/^\s+/,""); } + function bindClearId() { + var remember_string = this.value; + $(this).bind('keyup.clearId', function(){ + if($(this).val().trim() != remember_string.trim()){ + $($(this).attr('data-id-element')).val("").change(); + $(this).unbind('keyup.clearId'); + } + }); + } $(e).autocomplete({ source: function( request, response ) { @@ -74,6 +83,9 @@ $(document).ready(function(){ // prevent value inserted on focus return false; }, + create: function() { + bindClearId.call(this); + }, select: function( event, ui ) { var terms = split( this.value ); // remove the current input @@ -97,13 +109,7 @@ $(document).ready(function(){ } } } - var remember_string = this.value; - $(this).bind('keyup.clearId', function(){ - if($(this).val().trim() != remember_string.trim()){ - $($(this).attr('data-id-element')).val("").change(); - $(this).unbind('keyup.clearId'); - } - }); + bindClearId.call(this); $(this).trigger('railsAutocomplete.select', ui); return false; diff --git a/lib/assets/javascripts/autocomplete-rails.js b/lib/assets/javascripts/autocomplete-rails.js index 36dd168c..9785f250 100644 --- a/lib/assets/javascripts/autocomplete-rails.js +++ b/lib/assets/javascripts/autocomplete-rails.js @@ -13,4 +13,4 @@ * Example: * */ -$(document).ready(function(){$("input[data-autocomplete]").railsAutocomplete()}),function(a){var b=null;a.fn.railsAutocomplete=function(){return this.live("focus",function(){this.railsAutoCompleter||(this.railsAutoCompleter=new a.railsAutocomplete(this))})},a.railsAutocomplete=function(a){_e=a,this.init(_e)},a.railsAutocomplete.fn=a.railsAutocomplete.prototype={railsAutocomplete:"0.0.1"},a.railsAutocomplete.fn.extend=a.railsAutocomplete.extend=a.extend,a.railsAutocomplete.fn.extend({init:function(a){function b(b){return b.split(a.delimiter)}function c(a){return b(a).pop().replace(/^\s+/,"")}a.delimiter=$(a).attr("data-delimiter")||null,$(a).autocomplete({source:function(b,d){var f={term:c(b.term)},g=$(a).triggerHandler("railsAutocomplete.source",f);f=g?g:f,$.getJSON($(a).attr("data-autocomplete"),f,function(){$(arguments[0]).each(function(b,c){var d={};d[c.id]=c,$(a).data(d)}),d.apply(null,arguments)})},search:function(){var a=c(this.value);if(a.length<2)return!1},focus:function(){return!1},select:function(c,d){var f=b(this.value);f.pop(),f.push(d.item.value);if(a.delimiter!=null)f.push(""),this.value=f.join(a.delimiter);else{this.value=f.join(""),$(this).attr("data-id-element")&&$($(this).attr("data-id-element")).val(d.item.id).change();if($(this).attr("data-update-elements")){var g=$(this).data(d.item.id.toString()),h=$.parseJSON($(this).attr("data-update-elements"));for(var i in h)$(h[i]).val(g[i])}}var j=this.value;return $(this).bind("keyup.clearId",function(){$(this).val().trim()!=j.trim()&&($($(this).attr("data-id-element")).val("").change(),$(this).unbind("keyup.clearId"))}),$(this).trigger("railsAutocomplete.select",d),!1}})}})}(jQuery) \ No newline at end of file +$(document).ready(function(){$("input[data-autocomplete]").railsAutocomplete()}),function(a){var b=null;a.fn.railsAutocomplete=function(){return this.live("focus",function(){this.railsAutoCompleter||(this.railsAutoCompleter=new a.railsAutocomplete(this))})},a.railsAutocomplete=function(a){_e=a,this.init(_e)},a.railsAutocomplete.fn=a.railsAutocomplete.prototype={railsAutocomplete:"0.0.1"},a.railsAutocomplete.fn.extend=a.railsAutocomplete.extend=a.extend,a.railsAutocomplete.fn.extend({init:function(a){function b(b){return b.split(a.delimiter)}function c(a){return b(a).pop().replace(/^\s+/,"")}function d(){var a=this.value;$(this).bind("keyup.clearId",function(){$(this).val().trim()!=a.trim()&&($($(this).attr("data-id-element")).val("").change(),$(this).unbind("keyup.clearId"))})}a.delimiter=$(a).attr("data-delimiter")||null,$(a).autocomplete({source:function(b,d){var f={term:c(b.term)},g=$(a).triggerHandler("railsAutocomplete.source",f);f=g?g:f,$.getJSON($(a).attr("data-autocomplete"),f,function(){$(arguments[0]).each(function(b,c){var d={};d[c.id]=c,$(a).data(d)}),d.apply(null,arguments)})},search:function(){var a=c(this.value);if(a.length<2)return!1},focus:function(){return!1},create:function(){d.call(this)},select:function(c,f){var g=b(this.value);g.pop(),g.push(f.item.value);if(a.delimiter!=null)g.push(""),this.value=g.join(a.delimiter);else{this.value=g.join(""),$(this).attr("data-id-element")&&$($(this).attr("data-id-element")).val(f.item.id).change();if($(this).attr("data-update-elements")){var h=$(this).data(f.item.id.toString()),i=$.parseJSON($(this).attr("data-update-elements"));for(var j in i)$(i[j]).val(h[j])}}return d.call(this),$(this).trigger("railsAutocomplete.select",f),!1}})}})}(jQuery) \ No newline at end of file