Commit 6d3180c28e566a849e0145c2c0df1ed05758bb01
1 parent
122fa1cd
Exists in
master
and in
22 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 @@ |
| 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 @@ |
| 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 @@ |
| 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 | 48 | \ No newline at end of file | ... | ... |
vendor/plugins/auto_complete/lib/auto_complete_macros_helper.rb
0 → 100644
| ... | ... | @@ -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 @@ |
| 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 | ... | ... |