Skip to content

Commit

Permalink
Changed query to select only the used columns, added an :extra_data o…
Browse files Browse the repository at this point in the history
…ption to retrieve extra columns and return them in the JSON response, added :update_elements option to the form_helpers to be able to update any HTML element with any of the extra column data returned, fixed form_helpers to maintain the original functionality of text_field and text_field_tag
  • Loading branch information
jakemack committed Feb 5, 2011
1 parent b3c18ac commit 1067e4b
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 40 deletions.
30 changes: 28 additions & 2 deletions README.markdown
Expand Up @@ -42,7 +42,7 @@ Install it
Run the generator

rails generate autocomplete

And include autocomplete-rails.js on your layouts

javascript_include_tag "autocomplete-rails.js"
Expand Down Expand Up @@ -105,6 +105,16 @@ Only the following terms mould match the query 'un':

* Unacceptable

#### :extra_data

By default, your search will only return the required columns from the database needed to populate your form, namely id and the column you are searching (name, in the above example).

Passing an array of attributes/column names to this option will fetch and return the specified data.

class ProductsController < Admin::BaseController
autocomplete :brand, :name, :extra_data => [:slogan]
end

#### :display_value

If you want to display a different version of what you're looking for, you can use the :display_value option.
Expand All @@ -126,6 +136,8 @@ In the example above, you will search by _name_, but the autocomplete list will

This wouldn't really make much sense unless you use it with the :id_element HTML tag. (See below)

Only the object's id and the column you are searching on will be returned in JSON, so if your display_value method requires another parameter, make sure to fetch it with the :extra_data option

### View

On your view, all you have to do is include the attribute autocomplete on the text field
Expand Down Expand Up @@ -156,6 +168,20 @@ If you need to use the id of the selected object, you can use the *:id_element*

This will update the field with id *#some_element with the id of the selected object. The value for this option can be any jQuery selector.

### Getting extra object data

If you need to extra data about the selected object, you can use the *:update_elements* HTML attribute.

The :update_elements attribute accepts a hash where the keys represent the object attribute/column data to use to update and the values are jQuery selectors to retrieve the HTML element to update:

f.autocomplete_field :brand_name, autocomplete_brand_name_products_path, :update_elements => {:id => '#id_element', :slogan => '#some_other_element'}

class ProductsController < Admin::BaseController
autocomplete :brand, :name, :extra_data => [:slogan]
end

The previous example would fetch the extra attribute slogan and update jQuery('#some_other_element') with the slogan value.

## Formtastic

If you are using [Formtastic](http://github.com/justinfrench/formtastic), you automatically get the *autocompleted_input* helper on *semantic_form_for*:
Expand Down Expand Up @@ -232,6 +258,6 @@ integration folder:

# About the Author

[Crowd Interactive](http://www.crowdint.com) is an American web design and development company that happens to work in Colima, Mexico.
[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)!
30 changes: 22 additions & 8 deletions lib/generators/templates/autocomplete-rails.js
Expand Up @@ -6,7 +6,7 @@
*
* Example:
* <input type="text" data-autocomplete="/url/to/autocomplete">
*
*
* 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
*
Expand All @@ -28,16 +28,16 @@ $(document).ready(function(){
}
});
};

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) {
Expand All @@ -48,12 +48,19 @@ $(document).ready(function(){
function extractLast( term ) {
return split( term ).pop().replace(/^\s+/,"");
}

$(e).autocomplete({
source: function( request, response ) {
$.getJSON( $(e).attr('data-autocomplete'), {
term: extractLast( request.term )
}, response );
}, function() {
$(arguments[0]).each(function(i, el) {
var obj = {};
obj[el.id] = el;
$(e).data(obj);
});
response.apply(null, arguments);
});
},
search: function() {
// custom minLength
Expand Down Expand Up @@ -81,8 +88,15 @@ $(document).ready(function(){
if ($(this).attr('id_element')) {
$($(this).attr('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]);
}
}
}

return false;
}
});
Expand Down
14 changes: 7 additions & 7 deletions lib/rails3-jquery-autocomplete/autocomplete.rb
@@ -1,22 +1,22 @@
module Rails3JQueryAutocomplete

# Inspired on DHH's autocomplete plugin
#
#
# Usage:
#
#
# class ProductsController < Admin::BaseController
# autocomplete :brand, :name
# end
#
# This will magically generate an action autocomplete_brand_name, so,
# This will magically generate an action autocomplete_brand_name, so,
# don't forget to add it on your routes file
#
#
# resources :products do
# get :autocomplete_brand_name, :on => :collection
# end
#
# Now, on your view, all you have to do is have a text field like:
#
#
# f.text_field :brand_name, :autocomplete => autocomplete_brand_name_products_path
#
#
Expand All @@ -31,12 +31,12 @@ 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), \
:options => options, :term => term, :method => method)
:options => options, :term => term, :method => method)
else
items = {}
end

render :json => json_for_autocomplete(items, options[:display_value] ||= method)
render :json => json_for_autocomplete(items, options[:display_value] ||= method, options[:extra_data])
end
end
end
Expand Down
16 changes: 8 additions & 8 deletions lib/rails3-jquery-autocomplete/form_helper.rb
Expand Up @@ -3,7 +3,7 @@ module Helpers
module FormHelper
alias_method :original_text_field, :text_field
def text_field(object_name, method, options = {})
original_text_field(object_name, method, rename_autocomplete_option(options))
original_text_field(object_name, method, options)
end

# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) and
Expand All @@ -14,15 +14,15 @@ def text_field(object_name, method, options = {})
# # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" data-autocomplete="author/autocomplete"/>
#
def autocomplete_field(object_name, method, source, options ={})
options[:autocomplete] = source
text_field(object_name, method, options)
options["data-autocomplete"] = source
text_field(object_name, method, rename_autocomplete_option(options))
end
end

module FormTagHelper
alias_method :original_text_field_tag, :text_field_tag
def text_field_tag(name, value = nil, options = {})
original_text_field_tag(name, value, rename_autocomplete_option(options))
original_text_field_tag(name, value, options)
end

# Creates a standard text field that can be populated with jQuery's autocomplete plugin
Expand All @@ -32,8 +32,8 @@ def text_field_tag(name, value = nil, options = {})
# # => <input id="address" name="address" size="75" type="text" value="" data-autocomplete="address/autocomplete"/>
#
def autocomplete_field_tag(name, value, source, options ={})
options[:autocomplete] = source
text_field_tag(name, value, options)
options["data-autocomplete"] = source
text_field_tag(name, value, rename_autocomplete_option(options))
end
end

Expand All @@ -42,8 +42,8 @@ def autocomplete_field_tag(name, value, source, options ={})
# data-autocomplete key
#
private
def rename_autocomplete_option(options)
options["data-autocomplete"] = options.delete(:autocomplete)
def rewrite_autocomplete_option(options)
options["data-update-elements"] = JSON.generate(options.delete :update_elements) if options[:update_elements]
options
end
end
Expand Down
28 changes: 18 additions & 10 deletions lib/rails3-jquery-autocomplete/helpers.rb
Expand Up @@ -4,11 +4,18 @@ module Rails3JQueryAutocomplete
module Helpers

#
# Returns a three keys hash actually used by the Autocomplete jQuery-ui
# Returns a hash with three keys actually used by the Autocomplete jQuery-ui
# 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)
items.collect {|item| {"id" => item.id, "label" => item.send(method), "value" => item.send(method)}}
def json_for_autocomplete(items, method, extra_data)
items.collect do |item|
hash = {"id" => item.id, "label" => item.send(method), "value" => item.send(method)}
extra_data.each do |datum|
hash[datum] = item.send(datum)
end if extra_data
hash
end
end

# Returns parameter model_sym as a constant
Expand All @@ -22,7 +29,7 @@ def get_object(model_sym)

# Returns a symbol representing what implementation should be used to query
# the database and raises *NotImplementedError* if ORM implementor can not be found
def get_implementation(object)
def get_implementation(object)
ancestors_ary = object.ancestors.collect(&:to_s)
if ancestors_ary.include?('ActiveRecord::Base')
:activerecord
Expand All @@ -45,15 +52,15 @@ def get_autocomplete_order(implementation, method, options)

case implementation
when :mongoid then
if order
if order
order.split(',').collect do |fields|
sfields = fields.split
[sfields[0].downcase.to_sym, sfields[1].downcase.to_sym]
end
else
[[method.to_sym, :asc]]
end
when :activerecord then
when :activerecord then
order || "#{method} ASC"
end
end
Expand All @@ -79,10 +86,10 @@ def get_items(parameters)
# Can be overriden to return or filter however you like
# the objects to be shown by autocomplete
#
# items = get_autocomplete_items(:model => get_object(object), :options => options, :term => term, :method => method)
# items = get_autocomplete_items(:model => get_object(object), :options => options, :term => term, :method => method)
#
def get_autocomplete_items(parameters)
model = parameters[:model]
model = relation = parameters[:model]
method = parameters[:method]
options = parameters[:options]
term = parameters[:term]
Expand All @@ -91,13 +98,14 @@ def get_autocomplete_items(parameters)
limit = get_autocomplete_limit(options)
implementation = get_implementation(model)
order = get_autocomplete_order(implementation, method, options)
relation = model.select([:id, method] + (options[:extra_data].blank? ? [] : options[:extra_data])) unless options[:full_model]

case implementation
when :mongoid
search = (is_full_search ? '.*' : '^') + term + '.*'
items = model.where(method.to_sym => /#{search}/i).limit(limit).order_by(order)
items = relation.where(method.to_sym => /#{search}/i).limit(limit).order_by(order)
when :activerecord
items = model.where(["LOWER(#{method}) LIKE ?", "#{(is_full_search ? '%' : '')}#{term.downcase}%"]) \
items = relation.where(["LOWER(#{method}) LIKE ?", "#{(is_full_search ? '%' : '')}#{term.downcase}%"]) \
.limit(limit).order(order)
end
end
Expand Down
4 changes: 2 additions & 2 deletions test/form_helper_test.rb
Expand Up @@ -6,12 +6,12 @@ class Post

class FormHelperTest < ActionView::TestCase
def test_text_field_tag
assert_match(/data-autocomplete=\"some\/path\"/, text_field_tag('field_name', '', :autocomplete => 'some/path'))
assert_match(/autocomplete=\"some\/path\"/, text_field_tag('field_name', '', :autocomplete => 'some/path'))
end

def test_text_field
post = Post.new
assert_match(/data-autocomplete=\"some\/path\"/, text_field(:post, :author, :autocomplete => 'some/path'))
assert_match(/autocomplete=\"some\/path\"/, text_field(:post, :author, :autocomplete => 'some/path'))
end

def test_autocomplete_field_tag
Expand Down
6 changes: 3 additions & 3 deletions test/helpers.rb
Expand Up @@ -11,7 +11,7 @@ class HelpersTest < ::Test::Unit::TestCase
end

items_exist_assert = lambda do |model|
items = Helpers.get_items(:model => model), :term => 'A', :method => 'name')
items = Helpers.get_items(:model => model), :term => 'A', :method => 'name')
assert(items)
assert_equal(3, items.size)
end
Expand All @@ -30,7 +30,7 @@ class HelpersTest < ::Test::Unit::TestCase
response.each(&assert_structure)
end
end

context 'looking for items' do

include Rails3JQueryAutocomplete::Mongoid::Test
Expand All @@ -48,7 +48,7 @@ class HelpersTest < ::Test::Unit::TestCase
context 'passing an ActiveRecord query result' do

include Rails3JQueryAutocomplete::ActiveRecord::Test

should 'parse items to JSON' do
response = Helpers.json_for_autocomplete(Game.all, :name)
assert_not_nil(response)
Expand Down

0 comments on commit 1067e4b

Please sign in to comment.