rails-jquery-autocomplete 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+
6
+ task :default => [:uglify, :test]
7
+
8
+ Rake::TestTask.new(:test) do |test|
9
+ test.libs << 'lib' << 'test'
10
+ test.pattern = 'test/**/*_test.rb'
11
+ test.verbose = true
12
+ end
13
+
14
+ task :uglify do
15
+ require 'uglifier'
16
+ file_folder = "lib/assets/javascripts"
17
+ File.open("#{file_folder}/autocomplete-rails.js", "w") do |f|
18
+ f << Uglifier.compile(File.read("#{file_folder}/autocomplete-rails-uncompressed.js"))
19
+ end
20
+ end
21
+
@@ -0,0 +1,179 @@
1
+ /*
2
+ * Unobtrusive autocomplete
3
+ *
4
+ * To use it, you just have to include the HTML attribute autocomplete
5
+ * with the autocomplete URL as the value
6
+ *
7
+ * Example:
8
+ * <input type="text" data-autocomplete="/url/to/autocomplete">
9
+ *
10
+ * Optionally, you can use a jQuery selector to specify a field that can
11
+ * be updated with the element id whenever you find a matching value
12
+ *
13
+ * Example:
14
+ * <input type="text" data-autocomplete="/url/to/autocomplete" data-id-element="#id_field">
15
+ */
16
+
17
+ (function(jQuery)
18
+ {
19
+ var self = null;
20
+ jQuery.fn.railsAutocomplete = function() {
21
+ var handler = function() {
22
+ if (!this.railsAutoCompleter) {
23
+ this.railsAutoCompleter = new jQuery.railsAutocomplete(this);
24
+ }
25
+ };
26
+ if (jQuery.fn.on !== undefined) {
27
+ return jQuery(document).on('focus',this.selector,handler);
28
+ }
29
+ else {
30
+ return this.live('focus',handler);
31
+ }
32
+ };
33
+
34
+ jQuery.railsAutocomplete = function (e) {
35
+ var _e = e;
36
+ this.init(_e);
37
+ };
38
+
39
+ jQuery.railsAutocomplete.fn = jQuery.railsAutocomplete.prototype = {
40
+ railsAutocomplete: '0.0.1'
41
+ };
42
+
43
+ jQuery.railsAutocomplete.fn.extend = jQuery.railsAutocomplete.extend = jQuery.extend;
44
+ jQuery.railsAutocomplete.fn.extend({
45
+ init: function(e) {
46
+ e.delimiter = jQuery(e).attr('data-delimiter') || null;
47
+ e.min_length = jQuery(e).attr('min-length') || 2;
48
+ e.append_to = jQuery(e).attr('data-append-to') || null;
49
+ e.autoFocus = jQuery(e).attr('data-auto-focus') || false;
50
+ function split( val ) {
51
+ return val.split( e.delimiter );
52
+ }
53
+ function extractLast( term ) {
54
+ return split( term ).pop().replace(/^\s+/,"");
55
+ }
56
+
57
+ jQuery(e).autocomplete({
58
+ appendTo: e.append_to,
59
+ autoFocus: e.autoFocus,
60
+ delay: jQuery(e).attr('delay') || 0,
61
+ source: function( request, response ) {
62
+ var firedFrom = this.element[0];
63
+ var params = {term: extractLast( request.term )};
64
+ if (jQuery(e).attr('data-autocomplete-fields')) {
65
+ jQuery.each(jQuery.parseJSON(jQuery(e).attr('data-autocomplete-fields')), function(field, selector) {
66
+ params[field] = jQuery(selector).val();
67
+ });
68
+ }
69
+ jQuery.getJSON( jQuery(e).attr('data-autocomplete'), params, function() {
70
+ if(arguments[0].length === 0) {
71
+ arguments[0] = [];
72
+ arguments[0][0] = { id: "", label: "no existing match" };
73
+ }
74
+ jQuery(arguments[0]).each(function(i, el) {
75
+ var obj = {};
76
+ obj[el.id] = el;
77
+ jQuery(e).data(obj);
78
+ });
79
+ response.apply(null, arguments);
80
+ jQuery(firedFrom).trigger('railsAutocomplete.source', arguments);
81
+ });
82
+ },
83
+ change: function( event, ui ) {
84
+ if(!jQuery(this).is('[data-id-element]') ||
85
+ jQuery(jQuery(this).attr('data-id-element')).val() === "") {
86
+ return;
87
+ }
88
+ jQuery(jQuery(this).attr('data-id-element')).val(ui.item ? ui.item.id : "");
89
+
90
+ if (jQuery(this).attr('data-update-elements')) {
91
+ var update_elements = jQuery.parseJSON(jQuery(this).attr("data-update-elements"));
92
+ var data = ui.item ? jQuery(this).data(ui.item.id.toString()) : {};
93
+ if(update_elements && jQuery(update_elements['id']).val() === "") {
94
+ return;
95
+ }
96
+ for (var key in update_elements) {
97
+ var element = jQuery(update_elements[key]);
98
+ if (element.is(':checkbox')) {
99
+ if (data[key] != null) {
100
+ element.prop('checked', data[key]);
101
+ }
102
+ } else {
103
+ element.val(ui.item ? data[key] : "");
104
+ }
105
+ }
106
+ }
107
+ },
108
+ search: function() {
109
+ // custom minLength
110
+ var term = extractLast( this.value );
111
+ if ( term.length < e.min_length ) {
112
+ return false;
113
+ }
114
+ },
115
+ focus: function() {
116
+ // prevent value inserted on focus
117
+ return false;
118
+ },
119
+ select: function( event, ui ) {
120
+ if(ui.item.value.toLowerCase().indexOf('no match') != -1 || ui.item.value.toLowerCase().indexOf('too many results') != -1){
121
+ jQuery(this).trigger('railsAutocomplete.noMatch', ui);
122
+ return false;
123
+ }
124
+ var terms = split( this.value );
125
+ // remove the current input
126
+ terms.pop();
127
+ // add the selected item
128
+ terms.push( ui.item.value );
129
+ // add placeholder to get the comma-and-space at the end
130
+ if (e.delimiter != null) {
131
+ terms.push( "" );
132
+ this.value = terms.join( e.delimiter );
133
+ } else {
134
+ this.value = terms.join("");
135
+ if (jQuery(this).attr('data-id-element')) {
136
+ jQuery(jQuery(this).attr('data-id-element')).val(ui.item.id);
137
+ }
138
+ if (jQuery(this).attr('data-update-elements')) {
139
+ var data = ui.item;
140
+ var new_record = ui.item.value.indexOf('Create New') != -1 ? true : false;
141
+ var update_elements = jQuery.parseJSON(jQuery(this).attr("data-update-elements"));
142
+ for (var key in update_elements) {
143
+ if(jQuery(update_elements[key]).attr("type") === "checkbox"){
144
+ if(data[key] === true || data[key] === 1) {
145
+ jQuery(update_elements[key]).attr("checked","checked");
146
+ }
147
+ else {
148
+ jQuery(update_elements[key]).removeAttr("checked");
149
+ }
150
+ }
151
+ else{
152
+ if((new_record && data[key] && data[key].indexOf('Create New') == -1) || !new_record){
153
+ jQuery(update_elements[key]).val(data[key]);
154
+ }else{
155
+ jQuery(update_elements[key]).val('');
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+ var remember_string = this.value;
162
+ jQuery(this).bind('keyup.clearId', function(){
163
+ if(jQuery.trim(jQuery(this).val()) != jQuery.trim(remember_string)){
164
+ jQuery(jQuery(this).attr('data-id-element')).val("");
165
+ jQuery(this).unbind('keyup.clearId');
166
+ }
167
+ });
168
+ jQuery(e).trigger('railsAutocomplete.select', ui);
169
+
170
+ return false;
171
+ }
172
+ });
173
+ }
174
+ });
175
+
176
+ jQuery(document).ready(function(){
177
+ jQuery('input[data-autocomplete]').railsAutocomplete();
178
+ });
179
+ })(jQuery);
@@ -0,0 +1 @@
1
+ !function(t){t.fn.railsAutocomplete=function(){var e=function(){this.railsAutoCompleter||(this.railsAutoCompleter=new t.railsAutocomplete(this))};return void 0!==t.fn.on?t(document).on("focus",this.selector,e):this.live("focus",e)},t.railsAutocomplete=function(t){var e=t;this.init(e)},t.railsAutocomplete.fn=t.railsAutocomplete.prototype={railsAutocomplete:"0.0.1"},t.railsAutocomplete.fn.extend=t.railsAutocomplete.extend=t.extend,t.railsAutocomplete.fn.extend({init:function(e){function a(t){return t.split(e.delimiter)}function i(t){return a(t).pop().replace(/^\s+/,"")}e.delimiter=t(e).attr("data-delimiter")||null,e.min_length=t(e).attr("min-length")||2,e.append_to=t(e).attr("data-append-to")||null,e.autoFocus=t(e).attr("data-auto-focus")||!1,t(e).autocomplete({appendTo:e.append_to,autoFocus:e.autoFocus,delay:t(e).attr("delay")||0,source:function(a,n){var r=this.element[0],o={term:i(a.term)};t(e).attr("data-autocomplete-fields")&&t.each(t.parseJSON(t(e).attr("data-autocomplete-fields")),function(e,a){o[e]=t(a).val()}),t.getJSON(t(e).attr("data-autocomplete"),o,function(){0===arguments[0].length&&(arguments[0]=[],arguments[0][0]={id:"",label:"no existing match"}),t(arguments[0]).each(function(a,i){var n={};n[i.id]=i,t(e).data(n)}),n.apply(null,arguments),t(r).trigger("railsAutocomplete.source",arguments)})},change:function(e,a){if(t(this).is("[data-id-element]")&&""!==t(t(this).attr("data-id-element")).val()&&(t(t(this).attr("data-id-element")).val(a.item?a.item.id:""),t(this).attr("data-update-elements"))){var i=t.parseJSON(t(this).attr("data-update-elements")),n=a.item?t(this).data(a.item.id.toString()):{};if(i&&""===t(i.id).val())return;for(var r in i){var o=t(i[r]);o.is(":checkbox")?null!=n[r]&&o.prop("checked",n[r]):o.val(a.item?n[r]:"")}}},search:function(){var t=i(this.value);return t.length<e.min_length?!1:void 0},focus:function(){return!1},select:function(i,n){if(-1!=n.item.value.toLowerCase().indexOf("no match")||-1!=n.item.value.toLowerCase().indexOf("too many results"))return t(this).trigger("railsAutocomplete.noMatch",n),!1;var r=a(this.value);if(r.pop(),r.push(n.item.value),null!=e.delimiter)r.push(""),this.value=r.join(e.delimiter);else if(this.value=r.join(""),t(this).attr("data-id-element")&&t(t(this).attr("data-id-element")).val(n.item.id),t(this).attr("data-update-elements")){var o=n.item,l=-1!=n.item.value.indexOf("Create New")?!0:!1,u=t.parseJSON(t(this).attr("data-update-elements"));for(var s in u)"checkbox"===t(u[s]).attr("type")?o[s]===!0||1===o[s]?t(u[s]).attr("checked","checked"):t(u[s]).removeAttr("checked"):t(u[s]).val(l&&o[s]&&-1==o[s].indexOf("Create New")||!l?o[s]:"")}var c=this.value;return t(this).bind("keyup.clearId",function(){t.trim(t(this).val())!=t.trim(c)&&(t(t(this).attr("data-id-element")).val(""),t(this).unbind("keyup.clearId"))}),t(e).trigger("railsAutocomplete.select",n),!1}})}}),t(document).ready(function(){t("input[data-autocomplete]").railsAutocomplete()})}(jQuery);
@@ -0,0 +1,6 @@
1
+ Given /^I choose "([^"]*)" in the autocomplete list$/ do |text|
2
+ page.execute_script %Q{ $('input[data-autocomplete]').trigger("focus") }
3
+ page.execute_script %Q{ $('input[data-autocomplete]').trigger("keydown") }
4
+ sleep 1
5
+ page.execute_script %Q{ $('.ui-menu-item a:contains("#{text}")').trigger("mouseenter").trigger("click"); }
6
+ end
@@ -0,0 +1,14 @@
1
+ require 'rails/generators'
2
+
3
+ module Autocomplete
4
+ class InstallGenerator < Rails::Generators::Base
5
+ def install
6
+ # Copy the unobtrusive JS file
7
+ copy_file('autocomplete-rails.js', 'public/javascripts/autocomplete-rails.js')
8
+ end
9
+
10
+ def self.source_root
11
+ File.join(File.dirname(__FILE__), '..', '..', 'assets', 'javascripts')
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'rails/generators'
2
+
3
+ module Autocomplete
4
+ class UncompressedGenerator < Rails::Generators::Base
5
+ def install
6
+ # Copy the unobtrusive JS file
7
+ copy_file('autocomplete-rails-uncompressed.js', 'public/javascripts/autocomplete-rails.js')
8
+ end
9
+
10
+ def self.source_root
11
+ File.join(File.dirname(__FILE__), '..', '..', 'assets', 'javascripts')
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ require 'rails-jquery-autocomplete/form_helper'
2
+ require 'rails-jquery-autocomplete/autocomplete'
3
+
4
+ module RailsJQueryAutocomplete
5
+ autoload :Orm , 'rails-jquery-autocomplete/orm'
6
+ autoload :FormtasticPlugin , 'rails-jquery-autocomplete/formtastic_plugin'
7
+
8
+ unless ::Rails.version < "3.1"
9
+ require 'rails-jquery-autocomplete/rails/engine'
10
+ end
11
+ end
12
+
13
+ class ActionController::Base
14
+ include RailsJQueryAutocomplete::Autocomplete
15
+ end
16
+
17
+ require 'rails-jquery-autocomplete/formtastic'
18
+
19
+ begin
20
+ require 'simple_form'
21
+ require 'rails-jquery-autocomplete/simple_form_plugin'
22
+ rescue LoadError
23
+ end
@@ -0,0 +1,116 @@
1
+ module RailsJQueryAutocomplete
2
+ module Autocomplete
3
+ def self.included(target)
4
+ target.extend RailsJQueryAutocomplete::Autocomplete::ClassMethods
5
+
6
+ target.send :include, RailsJQueryAutocomplete::Orm::Mongoid if defined?(Mongoid::Document)
7
+ target.send :include, RailsJQueryAutocomplete::Orm::MongoMapper if defined?(MongoMapper::Document)
8
+ target.send :include, RailsJQueryAutocomplete::Orm::ActiveRecord
9
+
10
+ end
11
+
12
+ #
13
+ # Usage:
14
+ #
15
+ # class ProductsController < Admin::BaseController
16
+ # autocomplete :brand, :name
17
+ # end
18
+ #
19
+ # This will magically generate an action autocomplete_brand_name, so,
20
+ # don't forget to add it on your routes file
21
+ #
22
+ # resources :products do
23
+ # get :autocomplete_brand_name, :on => :collection
24
+ # end
25
+ #
26
+ # Now, on your view, all you have to do is have a text field like:
27
+ #
28
+ # f.text_field :brand_name, :autocomplete => autocomplete_brand_name_products_path
29
+ #
30
+ #
31
+ # Yajl is used by default to encode results, if you want to use a different encoder
32
+ # you can specify your custom encoder via block
33
+ #
34
+ # class ProductsController < Admin::BaseController
35
+ # autocomplete :brand, :name do |items|
36
+ # CustomJSONEncoder.encode(items)
37
+ # end
38
+ # end
39
+ #
40
+ module ClassMethods
41
+ def autocomplete(object, method, options = {}, &block)
42
+
43
+ define_method("get_prefix") do |model|
44
+ if defined?(Mongoid::Document) && model.include?(Mongoid::Document)
45
+ 'mongoid'
46
+ elsif defined?(MongoMapper::Document) && model.include?(MongoMapper::Document)
47
+ 'mongo_mapper'
48
+ else
49
+ 'active_record'
50
+ end
51
+ end
52
+ define_method("get_autocomplete_order") do |method, options, model=nil|
53
+ method("#{get_prefix(get_object(options[:class_name] || object))}_get_autocomplete_order").call(method, options, model)
54
+ end
55
+
56
+ define_method("get_autocomplete_items") do |parameters|
57
+ method("#{get_prefix(get_object(options[:class_name] || object))}_get_autocomplete_items").call(parameters)
58
+ end
59
+
60
+ define_method("autocomplete_#{object}_#{method}") do
61
+
62
+ method = options[:column_name] if options.has_key?(:column_name)
63
+
64
+ term = params[:term]
65
+
66
+ if term && !term.blank?
67
+ #allow specifying fully qualified class name for model object
68
+ class_name = options[:class_name] || object
69
+ items = get_autocomplete_items(:model => get_object(class_name), \
70
+ :options => options, :term => term, :method => method)
71
+ else
72
+ items = {}
73
+ end
74
+
75
+ render :json => json_for_autocomplete(items, options[:display_value] ||= method, options[:extra_data], &block), root: false
76
+ end
77
+ end
78
+ end
79
+
80
+ # Returns a limit that will be used on the query
81
+ def get_autocomplete_limit(options)
82
+ options[:limit] ||= 10
83
+ end
84
+
85
+ # Returns parameter model_sym as a constant
86
+ #
87
+ # get_object(:actor)
88
+ # # returns a Actor constant supposing it is already defined
89
+ #
90
+ def get_object(model_sym)
91
+ object = model_sym.to_s.camelize.constantize
92
+ end
93
+
94
+ #
95
+ # Returns a hash with three keys actually used by the Autocomplete jQuery-ui
96
+ # Can be overriden to show whatever you like
97
+ # Hash also includes a key/value pair for each method in extra_data
98
+ #
99
+ def json_for_autocomplete(items, method, extra_data=[])
100
+ items = items.collect do |item|
101
+ hash = {"id" => item.id.to_s, "label" => item.send(method), "value" => item.send(method)}
102
+ extra_data.each do |datum|
103
+ hash[datum] = item.send(datum)
104
+ end if extra_data
105
+ # TODO: Come back to remove this if clause when test suite is better
106
+ hash
107
+ end
108
+ if block_given?
109
+ yield(items)
110
+ else
111
+ items
112
+ end
113
+ end
114
+ end
115
+ end
116
+
@@ -0,0 +1,49 @@
1
+ module ActionView
2
+ module Helpers
3
+ module FormHelper
4
+ # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) and
5
+ # that is populated with jQuery's autocomplete plugin.
6
+ #
7
+ # ==== Examples
8
+ # autocomplete_field(:post, :title, author_autocomplete_path, :size => 20)
9
+ # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" data-autocomplete="author/autocomplete"/>
10
+ #
11
+ def autocomplete_field(object_name, method, source, options ={})
12
+ options["data-autocomplete"] = source
13
+ text_field(object_name, method, rewrite_autocomplete_option(options))
14
+ end
15
+ end
16
+
17
+ module FormTagHelper
18
+ # Creates a standard text field that can be populated with jQuery's autocomplete plugin
19
+ #
20
+ # ==== Examples
21
+ # autocomplete_field_tag 'address', '', address_autocomplete_path, :size => 75
22
+ # # => <input id="address" name="address" size="75" type="text" value="" data-autocomplete="address/autocomplete"/>
23
+ #
24
+ def autocomplete_field_tag(name, value, source, options ={})
25
+ options["data-autocomplete"] = source
26
+ text_field_tag(name, value, rewrite_autocomplete_option(options))
27
+ end
28
+ end
29
+
30
+ #
31
+ # Method used to rename the autocomplete key to a more standard
32
+ # data-autocomplete key
33
+ #
34
+ private
35
+ def rewrite_autocomplete_option(options)
36
+ options["data-autocomplete-fields"] = JSON.generate(options.delete :fields) if options[:fields]
37
+ options["data-update-elements"] = JSON.generate(options.delete :update_elements) if options[:update_elements]
38
+ options["data-id-element"] = options.delete :id_element if options[:id_element]
39
+ options["data-append-to"] = options.delete :append_to if options[:append_to]
40
+ options
41
+ end
42
+ end
43
+ end
44
+
45
+ class ActionView::Helpers::FormBuilder #:nodoc:
46
+ def autocomplete_field(method, source, options = {})
47
+ @template.autocomplete_field(@object_name, method, source, objectify_options(options))
48
+ end
49
+ end
@@ -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