Commit 6d3180c28e566a849e0145c2c0df1ed05758bb01
1 parent
122fa1cd
Exists in
staging
and in
42 other branches
ActionItem129: adding auto_complete plugin
git-svn-id: https://svn.colivre.coop.br/svn/noosfero/trunk@1833 3f533792-8f58-4932-b0fe-aaf55b0a4547
Showing
6 changed files
with
304 additions
and
0 deletions
Show diff stats
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +Example: | ||
2 | + | ||
3 | + # Controller | ||
4 | + class BlogController < ApplicationController | ||
5 | + auto_complete_for :post, :title | ||
6 | + end | ||
7 | + | ||
8 | + # View | ||
9 | + <%= text_field_with_auto_complete :post, title %> | ||
10 | + | ||
11 | +By default, auto_complete_for limits the results to 10 entries, | ||
12 | +and sorts by the given field. | ||
13 | + | ||
14 | +auto_complete_for takes a third parameter, an options hash to | ||
15 | +the find method used to search for the records: | ||
16 | + | ||
17 | + auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC' | ||
18 | + | ||
19 | +For more examples, see script.aculo.us: | ||
20 | +* http://script.aculo.us/demos/ajax/autocompleter | ||
21 | +* http://script.aculo.us/demos/ajax/autocompleter_customized | ||
22 | + | ||
23 | +Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license |
@@ -0,0 +1,22 @@ | @@ -0,0 +1,22 @@ | ||
1 | +require 'rake' | ||
2 | +require 'rake/testtask' | ||
3 | +require 'rake/rdoctask' | ||
4 | + | ||
5 | +desc 'Default: run unit tests.' | ||
6 | +task :default => :test | ||
7 | + | ||
8 | +desc 'Test auto_complete plugin.' | ||
9 | +Rake::TestTask.new(:test) do |t| | ||
10 | + t.libs << 'lib' | ||
11 | + t.pattern = 'test/**/*_test.rb' | ||
12 | + t.verbose = true | ||
13 | +end | ||
14 | + | ||
15 | +desc 'Generate documentation for auto_complete plugin.' | ||
16 | +Rake::RDocTask.new(:rdoc) do |rdoc| | ||
17 | + rdoc.rdoc_dir = 'rdoc' | ||
18 | + rdoc.title = 'Auto Complete' | ||
19 | + rdoc.options << '--line-numbers' << '--inline-source' | ||
20 | + rdoc.rdoc_files.include('README') | ||
21 | + rdoc.rdoc_files.include('lib/**/*.rb') | ||
22 | +end |
@@ -0,0 +1,47 @@ | @@ -0,0 +1,47 @@ | ||
1 | +module AutoComplete | ||
2 | + | ||
3 | + def self.included(base) | ||
4 | + base.extend(ClassMethods) | ||
5 | + end | ||
6 | + | ||
7 | + # | ||
8 | + # Example: | ||
9 | + # | ||
10 | + # # Controller | ||
11 | + # class BlogController < ApplicationController | ||
12 | + # auto_complete_for :post, :title | ||
13 | + # end | ||
14 | + # | ||
15 | + # # View | ||
16 | + # <%= text_field_with_auto_complete :post, title %> | ||
17 | + # | ||
18 | + # By default, auto_complete_for limits the results to 10 entries, | ||
19 | + # and sorts by the given field. | ||
20 | + # | ||
21 | + # auto_complete_for takes a third parameter, an options hash to | ||
22 | + # the find method used to search for the records: | ||
23 | + # | ||
24 | + # auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC' | ||
25 | + # | ||
26 | + # For help on defining text input fields with autocompletion, | ||
27 | + # see ActionView::Helpers::JavaScriptHelper. | ||
28 | + # | ||
29 | + # For more examples, see script.aculo.us: | ||
30 | + # * http://script.aculo.us/demos/ajax/autocompleter | ||
31 | + # * http://script.aculo.us/demos/ajax/autocompleter_customized | ||
32 | + module ClassMethods | ||
33 | + def auto_complete_for(object, method, options = {}) | ||
34 | + define_method("auto_complete_for_#{object}_#{method}") do | ||
35 | + find_options = { | ||
36 | + :conditions => [ "LOWER(#{method}) LIKE ?", '%' + params[object][method].downcase + '%' ], | ||
37 | + :order => "#{method} ASC", | ||
38 | + :limit => 10 }.merge!(options) | ||
39 | + | ||
40 | + @items = object.to_s.camelize.constantize.find(:all, find_options) | ||
41 | + | ||
42 | + render :inline => "<%= auto_complete_result @items, '#{method}' %>" | ||
43 | + end | ||
44 | + end | ||
45 | + end | ||
46 | + | ||
47 | +end | ||
0 | \ No newline at end of file | 48 | \ No newline at end of file |
vendor/plugins/auto_complete/lib/auto_complete_macros_helper.rb
0 → 100644
@@ -0,0 +1,143 @@ | @@ -0,0 +1,143 @@ | ||
1 | +module AutoCompleteMacrosHelper | ||
2 | + # Adds AJAX autocomplete functionality to the text input field with the | ||
3 | + # DOM ID specified by +field_id+. | ||
4 | + # | ||
5 | + # This function expects that the called action returns an HTML <ul> list, | ||
6 | + # or nothing if no entries should be displayed for autocompletion. | ||
7 | + # | ||
8 | + # You'll probably want to turn the browser's built-in autocompletion off, | ||
9 | + # so be sure to include an <tt>autocomplete="off"</tt> attribute with your text | ||
10 | + # input field. | ||
11 | + # | ||
12 | + # The autocompleter object is assigned to a Javascript variable named <tt>field_id</tt>_auto_completer. | ||
13 | + # This object is useful if you for example want to trigger the auto-complete suggestions through | ||
14 | + # other means than user input (for that specific case, call the <tt>activate</tt> method on that object). | ||
15 | + # | ||
16 | + # Required +options+ are: | ||
17 | + # <tt>:url</tt>:: URL to call for autocompletion results | ||
18 | + # in url_for format. | ||
19 | + # | ||
20 | + # Addtional +options+ are: | ||
21 | + # <tt>:update</tt>:: Specifies the DOM ID of the element whose | ||
22 | + # innerHTML should be updated with the autocomplete | ||
23 | + # entries returned by the AJAX request. | ||
24 | + # Defaults to <tt>field_id</tt> + '_auto_complete' | ||
25 | + # <tt>:with</tt>:: A JavaScript expression specifying the | ||
26 | + # parameters for the XMLHttpRequest. This defaults | ||
27 | + # to 'fieldname=value'. | ||
28 | + # <tt>:frequency</tt>:: Determines the time to wait after the last keystroke | ||
29 | + # for the AJAX request to be initiated. | ||
30 | + # <tt>:indicator</tt>:: Specifies the DOM ID of an element which will be | ||
31 | + # displayed while autocomplete is running. | ||
32 | + # <tt>:tokens</tt>:: A string or an array of strings containing | ||
33 | + # separator tokens for tokenized incremental | ||
34 | + # autocompletion. Example: <tt>:tokens => ','</tt> would | ||
35 | + # allow multiple autocompletion entries, separated | ||
36 | + # by commas. | ||
37 | + # <tt>:min_chars</tt>:: The minimum number of characters that should be | ||
38 | + # in the input field before an Ajax call is made | ||
39 | + # to the server. | ||
40 | + # <tt>:on_hide</tt>:: A Javascript expression that is called when the | ||
41 | + # autocompletion div is hidden. The expression | ||
42 | + # should take two variables: element and update. | ||
43 | + # Element is a DOM element for the field, update | ||
44 | + # is a DOM element for the div from which the | ||
45 | + # innerHTML is replaced. | ||
46 | + # <tt>:on_show</tt>:: Like on_hide, only now the expression is called | ||
47 | + # then the div is shown. | ||
48 | + # <tt>:after_update_element</tt>:: A Javascript expression that is called when the | ||
49 | + # user has selected one of the proposed values. | ||
50 | + # The expression should take two variables: element and value. | ||
51 | + # Element is a DOM element for the field, value | ||
52 | + # is the value selected by the user. | ||
53 | + # <tt>:select</tt>:: Pick the class of the element from which the value for | ||
54 | + # insertion should be extracted. If this is not specified, | ||
55 | + # the entire element is used. | ||
56 | + # <tt>:method</tt>:: Specifies the HTTP verb to use when the autocompletion | ||
57 | + # request is made. Defaults to POST. | ||
58 | + def auto_complete_field(field_id, options = {}) | ||
59 | + function = "var #{field_id}_auto_completer = new Ajax.Autocompleter(" | ||
60 | + function << "'#{field_id}', " | ||
61 | + function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', " | ||
62 | + function << "'#{url_for(options[:url])}'" | ||
63 | + | ||
64 | + js_options = {} | ||
65 | + js_options[:tokens] = array_or_string_for_javascript(options[:tokens]) if options[:tokens] | ||
66 | + js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with] | ||
67 | + js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator] | ||
68 | + js_options[:select] = "'#{options[:select]}'" if options[:select] | ||
69 | + js_options[:paramName] = "'#{options[:param_name]}'" if options[:param_name] | ||
70 | + js_options[:frequency] = "#{options[:frequency]}" if options[:frequency] | ||
71 | + js_options[:method] = "'#{options[:method].to_s}'" if options[:method] | ||
72 | + | ||
73 | + { :after_update_element => :afterUpdateElement, | ||
74 | + :on_show => :onShow, :on_hide => :onHide, :min_chars => :minChars }.each do |k,v| | ||
75 | + js_options[v] = options[k] if options[k] | ||
76 | + end | ||
77 | + | ||
78 | + function << (', ' + options_for_javascript(js_options) + ')') | ||
79 | + | ||
80 | + javascript_tag(function) | ||
81 | + end | ||
82 | + | ||
83 | + # Use this method in your view to generate a return for the AJAX autocomplete requests. | ||
84 | + # | ||
85 | + # Example action: | ||
86 | + # | ||
87 | + # def auto_complete_for_item_title | ||
88 | + # @items = Item.find(:all, | ||
89 | + # :conditions => [ 'LOWER(description) LIKE ?', | ||
90 | + # '%' + request.raw_post.downcase + '%' ]) | ||
91 | + # render :inline => "<%= auto_complete_result(@items, 'description') %>" | ||
92 | + # end | ||
93 | + # | ||
94 | + # The auto_complete_result can of course also be called from a view belonging to the | ||
95 | + # auto_complete action if you need to decorate it further. | ||
96 | + def auto_complete_result(entries, field, phrase = nil) | ||
97 | + return unless entries | ||
98 | + items = entries.map { |entry| content_tag("li", phrase ? highlight(entry[field], phrase) : h(entry[field])) } | ||
99 | + content_tag("ul", items.uniq) | ||
100 | + end | ||
101 | + | ||
102 | + # Wrapper for text_field with added AJAX autocompletion functionality. | ||
103 | + # | ||
104 | + # In your controller, you'll need to define an action called | ||
105 | + # auto_complete_for to respond the AJAX calls, | ||
106 | + # | ||
107 | + def text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {}) | ||
108 | + (completion_options[:skip_style] ? "" : auto_complete_stylesheet) + | ||
109 | + text_field(object, method, tag_options) + | ||
110 | + content_tag("div", "", :id => "#{object}_#{method}_auto_complete", :class => "auto_complete") + | ||
111 | + auto_complete_field("#{object}_#{method}", { :url => { :action => "auto_complete_for_#{object}_#{method}" } }.update(completion_options)) | ||
112 | + end | ||
113 | + | ||
114 | + private | ||
115 | + def auto_complete_stylesheet | ||
116 | + content_tag('style', <<-EOT, :type => Mime::CSS) | ||
117 | + div.auto_complete { | ||
118 | + width: 350px; | ||
119 | + background: #fff; | ||
120 | + } | ||
121 | + div.auto_complete ul { | ||
122 | + border:1px solid #888; | ||
123 | + margin:0; | ||
124 | + padding:0; | ||
125 | + width:100%; | ||
126 | + list-style-type:none; | ||
127 | + } | ||
128 | + div.auto_complete ul li { | ||
129 | + margin:0; | ||
130 | + padding:3px; | ||
131 | + } | ||
132 | + div.auto_complete ul li.selected { | ||
133 | + background-color: #ffb; | ||
134 | + } | ||
135 | + div.auto_complete ul strong.highlight { | ||
136 | + color: #800; | ||
137 | + margin:0; | ||
138 | + padding:0; | ||
139 | + } | ||
140 | + EOT | ||
141 | + end | ||
142 | + | ||
143 | +end |
@@ -0,0 +1,67 @@ | @@ -0,0 +1,67 @@ | ||
1 | +require File.expand_path(File.join(File.dirname(__FILE__), '../../../../test/test_helper')) | ||
2 | + | ||
3 | +class AutoCompleteTest < Test::Unit::TestCase | ||
4 | + include AutoComplete | ||
5 | + include AutoCompleteMacrosHelper | ||
6 | + | ||
7 | + include ActionView::Helpers::UrlHelper | ||
8 | + include ActionView::Helpers::TagHelper | ||
9 | + include ActionView::Helpers::TextHelper | ||
10 | + include ActionView::Helpers::FormHelper | ||
11 | + include ActionView::Helpers::CaptureHelper | ||
12 | + | ||
13 | + def setup | ||
14 | + @controller = Class.new do | ||
15 | + def url_for(options) | ||
16 | + url = "http://www.example.com/" | ||
17 | + url << options[:action].to_s if options and options[:action] | ||
18 | + url | ||
19 | + end | ||
20 | + end | ||
21 | + @controller = @controller.new | ||
22 | + end | ||
23 | + | ||
24 | + | ||
25 | + def test_auto_complete_field | ||
26 | + assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {})\n//]]>\n</script>), | ||
27 | + auto_complete_field("some_input", :url => { :action => "autocomplete" }); | ||
28 | + assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {tokens:','})\n//]]>\n</script>), | ||
29 | + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => ','); | ||
30 | + assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {tokens:[',']})\n//]]>\n</script>), | ||
31 | + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => [',']); | ||
32 | + assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {minChars:3})\n//]]>\n</script>), | ||
33 | + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :min_chars => 3); | ||
34 | + assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {onHide:function(element, update){alert('me');}})\n//]]>\n</script>), | ||
35 | + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :on_hide => "function(element, update){alert('me');}"); | ||
36 | + assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {frequency:2})\n//]]>\n</script>), | ||
37 | + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :frequency => 2); | ||
38 | + assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {afterUpdateElement:function(element,value){alert('You have chosen: '+value)}})\n//]]>\n</script>), | ||
39 | + auto_complete_field("some_input", :url => { :action => "autocomplete" }, | ||
40 | + :after_update_element => "function(element,value){alert('You have chosen: '+value)}"); | ||
41 | + assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {paramName:'huidriwusch'})\n//]]>\n</script>), | ||
42 | + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :param_name => 'huidriwusch'); | ||
43 | + assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {method:'get'})\n//]]>\n</script>), | ||
44 | + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :method => :get); | ||
45 | + end | ||
46 | + | ||
47 | + def test_auto_complete_result | ||
48 | + result = [ { :title => 'test1' }, { :title => 'test2' } ] | ||
49 | + assert_equal %(<ul><li>test1</li><li>test2</li></ul>), | ||
50 | + auto_complete_result(result, :title) | ||
51 | + assert_equal %(<ul><li>t<strong class=\"highlight\">est</strong>1</li><li>t<strong class=\"highlight\">est</strong>2</li></ul>), | ||
52 | + auto_complete_result(result, :title, "est") | ||
53 | + | ||
54 | + resultuniq = [ { :title => 'test1' }, { :title => 'test1' } ] | ||
55 | + assert_equal %(<ul><li>t<strong class=\"highlight\">est</strong>1</li></ul>), | ||
56 | + auto_complete_result(resultuniq, :title, "est") | ||
57 | + end | ||
58 | + | ||
59 | + def test_text_field_with_auto_complete | ||
60 | + assert_match %(<style type="text/css">), | ||
61 | + text_field_with_auto_complete(:message, :recipient) | ||
62 | + | ||
63 | + assert_dom_equal %(<input id=\"message_recipient\" name=\"message[recipient]\" size=\"30\" type=\"text\" /><div class=\"auto_complete\" id=\"message_recipient_auto_complete\"></div><script type=\"text/javascript\">\n//<![CDATA[\nvar message_recipient_auto_completer = new Ajax.Autocompleter('message_recipient', 'message_recipient_auto_complete', 'http://www.example.com/auto_complete_for_message_recipient', {})\n//]]>\n</script>), | ||
64 | + text_field_with_auto_complete(:message, :recipient, {}, :skip_style => true) | ||
65 | + end | ||
66 | + | ||
67 | +end |