diff --git a/lib/assets/javascripts/autocomplete-rails-uncompressed.js b/lib/assets/javascripts/autocomplete-rails-uncompressed.js index e844c159..a1456008 100644 --- a/lib/assets/javascripts/autocomplete-rails-uncompressed.js +++ b/lib/assets/javascripts/autocomplete-rails-uncompressed.js @@ -48,12 +48,22 @@ $(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 ) { - $.getJSON( $(e).attr('data-autocomplete'), { - term: extractLast( request.term ) - }, function() { + 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; @@ -73,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 @@ -86,7 +99,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()); @@ -96,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(""); - $(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 2e0bfdbd..9785f250 100644 --- a/lib/assets/javascripts/autocomplete-rails.js +++ b/lib/assets/javascripts/autocomplete-rails.js @@ -13,4 +13,4 @@ * Example: * <input type="text" data-autocomplete="/url/to/autocomplete" data-id-element="#id_field"> */ -$(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(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 diff --git a/lib/rails3-jquery-autocomplete/autocomplete.rb b/lib/rails3-jquery-autocomplete/autocomplete.rb index fefc9146..ca5a5305 100644 --- a/lib/rails3-jquery-autocomplete/autocomplete.rb +++ b/lib/rails3-jquery-autocomplete/autocomplete.rb @@ -42,22 +42,30 @@ 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) 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), \ - :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 @@ -81,11 +89,24 @@ 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) + 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)} - extra_data.each do |datum| - hash[datum] = item.send(datum) + 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) + 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/formtastic.rb b/lib/rails3-jquery-autocomplete/formtastic.rb index 4fea6945..3c913c53 100644 --- a/lib/rails3-jquery-autocomplete/formtastic.rb +++ b/lib/rails3-jquery-autocomplete/formtastic.rb @@ -19,10 +19,28 @@ 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 + 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 - 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) << + (options[:id_field] ? builder.hidden_field(options.delete :id_field) : '') end end end diff --git a/lib/rails3-jquery-autocomplete/orm/active_record.rb b/lib/rails3-jquery-autocomplete/orm/active_record.rb index e9260dd0..540ca48f 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)). @@ -29,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) diff --git a/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb b/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb index 4630ce4f..050f1211 100644 --- a/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb +++ b/test/lib/rails3-jquery-autocomplete/autocomplete_test.rb @@ -29,29 +29,76 @@ 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 } - items = [item] - response = self.json_for_autocomplete(items, :name).first + item = generate_mocked_model_instance + + parameters = { :method => :name, :options => {} } + items = [item] + 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' } + + parameters = { :method => :name, :options => { :extra_data => ["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 "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' } - items = [item] - response = self.json_for_autocomplete(items, :name, ["extra"]).first + 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 "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 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 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