rails-autocomplete 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +51 -0
- data/LICENSE +20 -0
- data/README.md +453 -0
- data/Rakefile +21 -0
- data/lib/assets/javascripts/autocomplete-rails-uncompressed.js +197 -0
- data/lib/assets/javascripts/autocomplete-rails.js +1 -0
- data/lib/cucumber/autocomplete.rb +6 -0
- data/lib/generators/autocomplete/install_generator.rb +14 -0
- data/lib/generators/autocomplete/uncompressed_generator.rb +14 -0
- data/lib/rails-jquery-autocomplete.rb +23 -0
- data/lib/rails-jquery-autocomplete/autocomplete.rb +112 -0
- data/lib/rails-jquery-autocomplete/form_helper.rb +49 -0
- data/lib/rails-jquery-autocomplete/formtastic.rb +41 -0
- data/lib/rails-jquery-autocomplete/formtastic_plugin.rb +44 -0
- data/lib/rails-jquery-autocomplete/orm.rb +6 -0
- data/lib/rails-jquery-autocomplete/orm/active_record.rb +83 -0
- data/lib/rails-jquery-autocomplete/rails/engine.rb +5 -0
- data/lib/rails-jquery-autocomplete/simple_form_plugin.rb +100 -0
- data/lib/rails-jquery-autocomplete/version.rb +3 -0
- data/lib/steak/autocomplete.rb +12 -0
- data/test/form_helper_test.rb +25 -0
- data/test/generators/autocomplete/install_generator_test.rb +25 -0
- data/test/generators/autocomplete/uncompressed_generator_test.rb +25 -0
- data/test/lib/rails-jquery-autocomplete/autocomplete_test.rb +93 -0
- data/test/lib/rails-jquery-autocomplete/orm/active_record_test.rb +190 -0
- data/test/lib/rails-jquery-autocomplete/simple_form_plugin_test.rb +33 -0
- data/test/lib/rails-jquery-autocomplete_test.rb +38 -0
- data/test/test_helper.rb +35 -0
- data/test/view_test_helper.rb +108 -0
- metadata +196 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
#
|
2
|
+
# Load the formtastic plugin if using Formtastic
|
3
|
+
#
|
4
|
+
begin
|
5
|
+
require 'formtastic'
|
6
|
+
begin
|
7
|
+
require "formtastic/version"
|
8
|
+
rescue LoadError
|
9
|
+
end
|
10
|
+
|
11
|
+
if defined?(Formtastic::VERSION)
|
12
|
+
#
|
13
|
+
# Formtastic 2.x
|
14
|
+
#
|
15
|
+
|
16
|
+
module Formtastic
|
17
|
+
module Inputs
|
18
|
+
class AutocompleteInput
|
19
|
+
include Base
|
20
|
+
include Base::Stringish
|
21
|
+
|
22
|
+
def to_html
|
23
|
+
input_wrapping do
|
24
|
+
label_html <<
|
25
|
+
builder.autocomplete_field(method, options.delete(:url), input_html_options)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
else
|
32
|
+
|
33
|
+
#
|
34
|
+
# Formtastic 1.x
|
35
|
+
#
|
36
|
+
class Formtastic::SemanticFormBuilder < ActionView::Helpers::FormBuilder
|
37
|
+
include RailsJQueryAutocomplete::FormtasticPlugin
|
38
|
+
end
|
39
|
+
end
|
40
|
+
rescue LoadError
|
41
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RailsJQueryAutocomplete
|
2
|
+
module FormtasticPlugin
|
3
|
+
def autocomplete_input(method, options = {})
|
4
|
+
if options.key?(:selected) || options.key?(:checked) || options.key?(:default)
|
5
|
+
::ActiveSupport::Deprecation.warn(
|
6
|
+
"The :selected, :checked (and :default) options are deprecated in Formtastic and will be removed from 1.0. " <<
|
7
|
+
"Please set default values in your models (using an after_initialize callback) or in your controller set-up. " <<
|
8
|
+
"See http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html for more information.", caller)
|
9
|
+
end
|
10
|
+
|
11
|
+
options[:required] = method_required?(method) unless options.key?(:required)
|
12
|
+
options[:as] ||= "autocompleted_string"
|
13
|
+
|
14
|
+
html_class = [ options[:as], (options[:required] ? :required : :optional) ]
|
15
|
+
html_class << 'error' if @object && @object.respond_to?(:errors) && !@object.errors[method.to_sym].blank?
|
16
|
+
|
17
|
+
wrapper_html = options.delete(:wrapper_html) || {}
|
18
|
+
wrapper_html[:id] ||= generate_html_id(method)
|
19
|
+
wrapper_html[:class] = (html_class << wrapper_html[:class]).flatten.compact.join(' ')
|
20
|
+
|
21
|
+
if options[:input_html] && options[:input_html][:id]
|
22
|
+
options[:label_html] ||= {}
|
23
|
+
options[:label_html][:for] ||= options[:input_html][:id]
|
24
|
+
end
|
25
|
+
|
26
|
+
input_parts = self.class.inline_order.dup
|
27
|
+
input_parts = input_parts - [:errors, :hints] if options[:as] == :hidden
|
28
|
+
|
29
|
+
list_item_content = input_parts.map do |type|
|
30
|
+
send(:"inline_#{type}_for", method, options)
|
31
|
+
end.compact.join("\n")
|
32
|
+
|
33
|
+
return template.content_tag(:li, Formtastic::Util.html_safe(list_item_content), wrapper_html)
|
34
|
+
end
|
35
|
+
|
36
|
+
alias_method :autocompleted_input, :autocomplete_input
|
37
|
+
|
38
|
+
|
39
|
+
protected
|
40
|
+
def autocompleted_string_input(method, options)
|
41
|
+
self.label(method, options_for_label(options)) << autocomplete_field(method, options.delete(:url), options)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module RailsJQueryAutocomplete
|
2
|
+
module Orm
|
3
|
+
module ActiveRecord
|
4
|
+
def active_record_get_autocomplete_order(method, options, model=nil)
|
5
|
+
order = options[:order]
|
6
|
+
|
7
|
+
table_prefix = model ? "#{model.table_name}." : ""
|
8
|
+
if sqlite?
|
9
|
+
order || "LOWER(#{method}) ASC"
|
10
|
+
else
|
11
|
+
order || "LOWER(#{table_prefix}#{method}) ASC"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def active_record_get_autocomplete_items(parameters)
|
16
|
+
model = parameters[:model]
|
17
|
+
term = parameters[:term]
|
18
|
+
options = parameters[:options]
|
19
|
+
method = options[:hstore] ? options[:hstore][:method] : parameters[:method]
|
20
|
+
scopes = Array(options[:scopes])
|
21
|
+
where = options[:where]
|
22
|
+
limit = get_autocomplete_limit(options)
|
23
|
+
order = active_record_get_autocomplete_order(method, options, model)
|
24
|
+
|
25
|
+
items = (::Rails::VERSION::MAJOR * 10 + ::Rails::VERSION::MINOR) >= 40 ? model.where(nil) : model.scoped
|
26
|
+
|
27
|
+
scopes.each { |scope| items = items.send(scope) } unless scopes.empty?
|
28
|
+
|
29
|
+
items = items.select(get_autocomplete_select_clause(model, method, options)) unless options[:full_model]
|
30
|
+
items = items.where(get_autocomplete_where_clause(model, term, method, options)).
|
31
|
+
limit(limit).order(order)
|
32
|
+
items = items.where(where) unless where.blank?
|
33
|
+
|
34
|
+
items
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_autocomplete_select_clause(model, method, options)
|
38
|
+
if sqlite?
|
39
|
+
table_name = model.quoted_table_name
|
40
|
+
([
|
41
|
+
"#{table_name}.#{model.connection.quote_column_name(model.primary_key)} as #{model.primary_key}",
|
42
|
+
"#{table_name}.#{model.connection.quote_column_name(method)} as #{method}"
|
43
|
+
] + (options[:extra_data].blank? ? [] : options[:extra_data]))
|
44
|
+
else
|
45
|
+
table_name = model.table_name
|
46
|
+
(["#{table_name}.#{model.primary_key}", "#{table_name}.#{method}"] + (options[:extra_data].blank? ? [] : options[:extra_data]))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_autocomplete_where_clause(model, term, method, options)
|
51
|
+
table_name = model.table_name
|
52
|
+
is_full_search = options[:full]
|
53
|
+
is_case_sensitive_search = options[:case_sensitive]
|
54
|
+
like_clause = (postgres?(model) && !is_case_sensitive_search ? 'ILIKE' : 'LIKE')
|
55
|
+
column_transform = is_case_sensitive_search ? '' : 'LOWER'
|
56
|
+
term = "#{(is_full_search ? '%' : '')}#{term.gsub(/([_%\\])/, '\\\\\1')}%"
|
57
|
+
if options[:hstore]
|
58
|
+
["#{column_transform}(#{table_name}.#{method} -> '#{options[:hstore][:key]}') LIKE #{column_transform}(?)", term]
|
59
|
+
elsif sqlite?
|
60
|
+
["#{column_transform}(#{method}) #{like_clause} #{column_transform}(?)", term]
|
61
|
+
else
|
62
|
+
["#{column_transform}(#{table_name}.#{method}) #{like_clause} #{column_transform}(?)", term]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def sqlite?
|
69
|
+
begin
|
70
|
+
return ::ActiveRecord::Base.connection.to_s.match(/SQLite/)
|
71
|
+
rescue ::ActiveRecord::ConnectionNotEstablished
|
72
|
+
return false
|
73
|
+
end
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
|
77
|
+
def postgres?(model)
|
78
|
+
# Figure out if this particular model uses the PostgreSQL adapter
|
79
|
+
model.connection.class.to_s.match(/PostgreSQLAdapter/)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module SimpleForm
|
2
|
+
module Inputs
|
3
|
+
module Autocomplete
|
4
|
+
#
|
5
|
+
# Method used to rename the autocomplete key to a more standard
|
6
|
+
# data-autocomplete key
|
7
|
+
#
|
8
|
+
def rewrite_autocomplete_option
|
9
|
+
new_options = {}
|
10
|
+
new_options["data-autocomplete-fields"] = JSON.generate(options.delete :fields) if options[:fields]
|
11
|
+
new_options["data-update-elements"] = JSON.generate(options.delete :update_elements) if options[:update_elements]
|
12
|
+
new_options["data-id-element"] = options.delete :id_element if options[:id_element]
|
13
|
+
input_html_options.merge new_options
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class AutocompleteInput < Base
|
18
|
+
include Autocomplete
|
19
|
+
|
20
|
+
protected
|
21
|
+
def limit
|
22
|
+
column && column.limit
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_placeholder?
|
26
|
+
placeholder_present?
|
27
|
+
end
|
28
|
+
|
29
|
+
def input(args = nil)
|
30
|
+
# This branching is to deal with a change beginning in simple_form 3.0.2 and above to ensure backwards compatibility
|
31
|
+
if args.nil?
|
32
|
+
@builder.autocomplete_field(
|
33
|
+
attribute_name,
|
34
|
+
options[:url],
|
35
|
+
rewrite_autocomplete_option
|
36
|
+
)
|
37
|
+
else
|
38
|
+
@builder.autocomplete_field(
|
39
|
+
attribute_name,
|
40
|
+
options[:url],
|
41
|
+
merge_wrapper_options(rewrite_autocomplete_option, args)
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class AutocompleteCollectionInput < CollectionInput
|
48
|
+
include Autocomplete
|
49
|
+
|
50
|
+
def input(opts)
|
51
|
+
# http://www.codeofficer.com/blog/entry/form_builders_in_rails_discovering_field_names_and_ids_for_javascript/
|
52
|
+
hidden_id = "#{object_name}_#{attribute_name}_hidden".gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
|
53
|
+
id_element = options[:id_element]
|
54
|
+
if id_element
|
55
|
+
id_element << ", #" << hidden_id
|
56
|
+
else
|
57
|
+
id_element = "#" + hidden_id
|
58
|
+
end
|
59
|
+
options[:id_element] = id_element
|
60
|
+
|
61
|
+
# This branching is to deal with a change beginning in simple_form 3.0.2 and above to ensure backwards compatibility
|
62
|
+
if opts.nil?
|
63
|
+
autocomplete_options = rewrite_autocomplete_option
|
64
|
+
else
|
65
|
+
merge_wrapper_options(rewrite_autocomplete_option, args)
|
66
|
+
end
|
67
|
+
|
68
|
+
label_method, value_method = detect_collection_methods
|
69
|
+
association = object.send(reflection.name)
|
70
|
+
if association && association.respond_to?(label_method)
|
71
|
+
autocomplete_options[:value] = association.send(label_method)
|
72
|
+
end
|
73
|
+
out = @builder.autocomplete_field(
|
74
|
+
attribute_name,
|
75
|
+
options[:url],
|
76
|
+
autocomplete_options
|
77
|
+
)
|
78
|
+
hidden_options = if association && association.respond_to?(value_method)
|
79
|
+
new_options = {}
|
80
|
+
new_options[:value] = association.send(value_method)
|
81
|
+
input_html_options.merge new_options
|
82
|
+
else
|
83
|
+
input_html_options
|
84
|
+
end
|
85
|
+
hidden_options[:id] = hidden_id
|
86
|
+
out << @builder.hidden_field(
|
87
|
+
attribute_name,
|
88
|
+
hidden_options
|
89
|
+
)
|
90
|
+
out.html_safe
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class FormBuilder
|
96
|
+
map_type :autocomplete, :to => SimpleForm::Inputs::AutocompleteInput
|
97
|
+
map_type :autocomplete_collection, :to => SimpleForm::Inputs::AutocompleteCollectionInput
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Steak
|
2
|
+
module Autocomplete
|
3
|
+
def choose_autocomplete_result(text, input_id="input[data-autocomplete]")
|
4
|
+
page.execute_script %Q{ $('#{input_id}').trigger("focus") }
|
5
|
+
page.execute_script %Q{ $('#{input_id}').trigger("keydown") }
|
6
|
+
sleep 1
|
7
|
+
page.execute_script %Q{ $('.ui-menu-item a:contains("#{text}")').trigger("mouseenter").trigger("click"); }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec.configuration.include Steak::Autocomplete, :type => :acceptance
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class Post
|
4
|
+
attr_accessor :author
|
5
|
+
end
|
6
|
+
|
7
|
+
class FormHelperTest < ActionView::TestCase
|
8
|
+
def test_text_field_tag
|
9
|
+
assert_match(/autocomplete=\"some\/path\"/, text_field_tag('field_name', '', :autocomplete => 'some/path'))
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_text_field
|
13
|
+
post = Post.new
|
14
|
+
assert_match(/autocomplete=\"some\/path\"/, text_field(:post, :author, :autocomplete => 'some/path'))
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_autocomplete_field_tag
|
18
|
+
assert_match(/data-autocomplete=\"some\/path\"/, autocomplete_field_tag('field_name', '', 'some/path'))
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_autocomplete_field
|
22
|
+
post= Post.new
|
23
|
+
assert_match(/data-autocomplete=\"some\/path\"/, autocomplete_field(:post, :author, 'some/path'))
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'generators/autocomplete/install_generator'
|
3
|
+
|
4
|
+
module Autocomplete
|
5
|
+
class InstallGeneratorTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@destination = File.join('tmp', 'test_app')
|
8
|
+
@source = InstallGenerator.source_root
|
9
|
+
@filename = File.join(@destination, 'public', 'javascripts', 'autocomplete-rails.js')
|
10
|
+
|
11
|
+
File.unlink(@filename) if File.exists?(@filename)
|
12
|
+
|
13
|
+
InstallGenerator.start([], :destination_root => @destination)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_install
|
17
|
+
assert File.exists?(@filename)
|
18
|
+
|
19
|
+
assert_equal(
|
20
|
+
File.read(File.join(@source, 'autocomplete-rails.js')),
|
21
|
+
File.read(@filename)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'generators/autocomplete/uncompressed_generator'
|
3
|
+
|
4
|
+
module Autocomplete
|
5
|
+
class UncompressedGeneratorTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@destination = File.join('tmp', 'test_app')
|
8
|
+
@source = UncompressedGenerator.source_root
|
9
|
+
@filename = File.join(@destination, 'public', 'javascripts', 'autocomplete-rails.js')
|
10
|
+
|
11
|
+
File.unlink(@filename) if File.exists?(@filename)
|
12
|
+
|
13
|
+
UncompressedGenerator.start([], :destination_root => @destination)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_install
|
17
|
+
assert File.exists?(@filename)
|
18
|
+
|
19
|
+
assert_equal(
|
20
|
+
File.read(File.join(@source, 'autocomplete-rails-uncompressed.js')),
|
21
|
+
File.read(@filename)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module RailsJQueryAutocomplete
|
4
|
+
class AutocompleteTest < Test::Unit::TestCase
|
5
|
+
include RailsJQueryAutocomplete::Autocomplete
|
6
|
+
|
7
|
+
context 'ClassMethods' do
|
8
|
+
context '#autocomplete' do
|
9
|
+
context '#get_prefix' do
|
10
|
+
context 'Mongoid and MongoMapper is not defined' do
|
11
|
+
setup do
|
12
|
+
ActorsController = Class.new(ActionController::Base)
|
13
|
+
ActorsController.autocomplete(:movie, :name)
|
14
|
+
@controller = ActorsController.new
|
15
|
+
|
16
|
+
@model = Class.new(ActiveRecord::Base)
|
17
|
+
|
18
|
+
Object.send(:remove_const, :Mongoid)
|
19
|
+
Object.send(:remove_const, :MongoMapper)
|
20
|
+
end
|
21
|
+
|
22
|
+
should 'not raise exception' do
|
23
|
+
@controller.get_prefix(@model)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context '#get_autocomplete_limit' do
|
31
|
+
context 'the limit option was specified' do
|
32
|
+
should "return the limit option" do
|
33
|
+
assert_equal 99, get_autocomplete_limit({:limit => 99})
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'the limit option is not specified' do
|
38
|
+
should 'return 10' do
|
39
|
+
assert_equal 10, get_autocomplete_limit({})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context '#get_object' do
|
45
|
+
should 'return the specified sym as a class name' do
|
46
|
+
symbol = Object.new
|
47
|
+
class_object = Class.new
|
48
|
+
mock(symbol).to_s.mock!.camelize.mock!.constantize { class_object }
|
49
|
+
assert_equal class_object, get_object(symbol)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context '#json_for_autocomplete' do
|
54
|
+
should 'parse items to JSON' do
|
55
|
+
item = mock(Object)
|
56
|
+
mock(item).send(:name).times(2) { 'Object Name' }
|
57
|
+
mock(item).id { 1 }
|
58
|
+
items = [item]
|
59
|
+
response = self.json_for_autocomplete(items, :name).first
|
60
|
+
assert_equal response["id"], "1"
|
61
|
+
assert_equal response["value"], "Object Name"
|
62
|
+
assert_equal response["label"], "Object Name"
|
63
|
+
end
|
64
|
+
|
65
|
+
should 'return an instance of HashWithIndifferentAccess' do
|
66
|
+
item = mock(Object)
|
67
|
+
mock(item).send(:name).times(2) { 'Object Name' }
|
68
|
+
mock(item).id { 1 }
|
69
|
+
items = [item]
|
70
|
+
response = self.json_for_autocomplete(items, :name).first
|
71
|
+
assert_equal response.is_a?(HashWithIndifferentAccess), true
|
72
|
+
assert_equal response["id"], "1"
|
73
|
+
assert_equal response[:id], "1"
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'with extra data' do
|
77
|
+
should 'add that extra data to result' do
|
78
|
+
item = mock(Object)
|
79
|
+
mock(item).send(:name).times(2) { 'Object Name' }
|
80
|
+
mock(item).id { 1 }
|
81
|
+
mock(item).send("extra") { 'Object Extra ' }
|
82
|
+
|
83
|
+
items = [item]
|
84
|
+
response = self.json_for_autocomplete(items, :name, ["extra"]).first
|
85
|
+
|
86
|
+
assert_equal "1" , response["id"]
|
87
|
+
assert_equal "Object Name" , response["value"]
|
88
|
+
assert_equal "Object Name" , response["label"]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|