rails-autocomplete 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +51 -0
  3. data/LICENSE +20 -0
  4. data/README.md +453 -0
  5. data/Rakefile +21 -0
  6. data/lib/assets/javascripts/autocomplete-rails-uncompressed.js +197 -0
  7. data/lib/assets/javascripts/autocomplete-rails.js +1 -0
  8. data/lib/cucumber/autocomplete.rb +6 -0
  9. data/lib/generators/autocomplete/install_generator.rb +14 -0
  10. data/lib/generators/autocomplete/uncompressed_generator.rb +14 -0
  11. data/lib/rails-jquery-autocomplete.rb +23 -0
  12. data/lib/rails-jquery-autocomplete/autocomplete.rb +112 -0
  13. data/lib/rails-jquery-autocomplete/form_helper.rb +49 -0
  14. data/lib/rails-jquery-autocomplete/formtastic.rb +41 -0
  15. data/lib/rails-jquery-autocomplete/formtastic_plugin.rb +44 -0
  16. data/lib/rails-jquery-autocomplete/orm.rb +6 -0
  17. data/lib/rails-jquery-autocomplete/orm/active_record.rb +83 -0
  18. data/lib/rails-jquery-autocomplete/rails/engine.rb +5 -0
  19. data/lib/rails-jquery-autocomplete/simple_form_plugin.rb +100 -0
  20. data/lib/rails-jquery-autocomplete/version.rb +3 -0
  21. data/lib/steak/autocomplete.rb +12 -0
  22. data/test/form_helper_test.rb +25 -0
  23. data/test/generators/autocomplete/install_generator_test.rb +25 -0
  24. data/test/generators/autocomplete/uncompressed_generator_test.rb +25 -0
  25. data/test/lib/rails-jquery-autocomplete/autocomplete_test.rb +93 -0
  26. data/test/lib/rails-jquery-autocomplete/orm/active_record_test.rb +190 -0
  27. data/test/lib/rails-jquery-autocomplete/simple_form_plugin_test.rb +33 -0
  28. data/test/lib/rails-jquery-autocomplete_test.rb +38 -0
  29. data/test/test_helper.rb +35 -0
  30. data/test/view_test_helper.rb +108 -0
  31. 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,6 @@
1
+ module RailsJQueryAutocomplete
2
+ module Orm
3
+ autoload :ActiveRecord , 'rails-jquery-autocomplete/orm/active_record'
4
+ end
5
+ end
6
+
@@ -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,5 @@
1
+ module RailsJQueryAutocomplete
2
+ module Rails
3
+ class Engine < ::Rails::Engine ; end
4
+ end
5
+ 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,3 @@
1
+ module RailsJQueryAutocomplete
2
+ VERSION = '1.1.0'
3
+ 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