autocomplete-activeadmin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -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,197 @@
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(selector) {
21
+ var handler = function() {
22
+ if (!this.railsAutoCompleter) {
23
+ this.railsAutoCompleter = new jQuery.railsAutocomplete(this);
24
+ }
25
+ };
26
+ if (jQuery.fn.on !== undefined) {
27
+ if (!selector) {
28
+ return;
29
+ }
30
+ return jQuery(document).on('focus',selector,handler);
31
+ }
32
+ else {
33
+ return this.live('focus',handler);
34
+ }
35
+ };
36
+
37
+ jQuery.railsAutocomplete = function (e) {
38
+ var _e = e;
39
+ this.init(_e);
40
+ };
41
+ jQuery.railsAutocomplete.options = {
42
+ showNoMatches: true,
43
+ noMatchesLabel: 'no existing match'
44
+ }
45
+
46
+ jQuery.railsAutocomplete.fn = jQuery.railsAutocomplete.prototype = {
47
+ railsAutocomplete: '0.0.1'
48
+ };
49
+
50
+ jQuery.railsAutocomplete.fn.extend = jQuery.railsAutocomplete.extend = jQuery.extend;
51
+ jQuery.railsAutocomplete.fn.extend({
52
+ init: function(e) {
53
+ e.delimiter = jQuery(e).attr('data-delimiter') || null;
54
+ e.min_length = jQuery(e).attr('data-min-length') || jQuery(e).attr('min-length') || 2;
55
+ e.append_to = jQuery(e).attr('data-append-to') || null;
56
+ e.autoFocus = jQuery(e).attr('data-auto-focus') || false;
57
+ function split( val ) {
58
+ return val.split( e.delimiter );
59
+ }
60
+ function extractLast( term ) {
61
+ return split( term ).pop().replace(/^\s+/,"");
62
+ }
63
+
64
+ jQuery(e).autocomplete({
65
+ appendTo: e.append_to,
66
+ autoFocus: e.autoFocus,
67
+ delay: jQuery(e).attr('delay') || 0,
68
+ source: function( request, response ) {
69
+ var firedFrom = this.element[0];
70
+ var params = {term: extractLast( request.term )};
71
+ if (jQuery(e).attr('data-autocomplete-fields')) {
72
+ jQuery.each(jQuery.parseJSON(jQuery(e).attr('data-autocomplete-fields')), function(field, selector) {
73
+ params[field] = jQuery(selector).val();
74
+ });
75
+ }
76
+ jQuery.getJSON( jQuery(e).attr('data-autocomplete'), params, function() {
77
+ var options = {};
78
+ jQuery.extend(options, jQuery.railsAutocomplete.options);
79
+ jQuery.each(options, function(key, value) {
80
+ if(options.hasOwnProperty(key)) {
81
+ var attrVal = jQuery(e).attr('data-' + key);
82
+ options[key] = attrVal ? attrVal : value;
83
+ }
84
+ });
85
+ if(arguments[0].length == 0 && jQuery.inArray(options.showNoMatches, [true, 'true']) >= 0) {
86
+ arguments[0] = [];
87
+ arguments[0][0] = { id: "", label: options.noMatchesLabel };
88
+ }
89
+ jQuery(arguments[0]).each(function(i, el) {
90
+ var obj = {};
91
+ obj[el.id] = el;
92
+ jQuery(e).data(obj);
93
+ });
94
+ response.apply(null, arguments);
95
+ jQuery(firedFrom).trigger('railsAutocomplete.source', arguments);
96
+ });
97
+ },
98
+ change: function( event, ui ) {
99
+ if(!jQuery(this).is('[data-id-element]') ||
100
+ jQuery(jQuery(this).attr('data-id-element')).val() === "") {
101
+ return;
102
+ }
103
+ jQuery(jQuery(this).attr('data-id-element')).val(ui.item ? ui.item.id : "").trigger('change');
104
+
105
+ if (jQuery(this).attr('data-update-elements')) {
106
+ var update_elements = jQuery.parseJSON(jQuery(this).attr("data-update-elements"));
107
+ var data = ui.item ? jQuery(this).data(ui.item.id.toString()) : {};
108
+ if(update_elements && jQuery(update_elements['id']).val() === "") {
109
+ return;
110
+ }
111
+ for (var key in update_elements) {
112
+ var element = jQuery(update_elements[key]);
113
+ if (element.is(':checkbox')) {
114
+ if (data[key] != null) {
115
+ element.prop('checked', data[key]);
116
+ }
117
+ } else {
118
+ element.val(ui.item ? data[key] : "").trigger('change');
119
+ }
120
+ }
121
+ }
122
+ },
123
+ search: function() {
124
+ // custom minLength
125
+ var term = extractLast( this.value );
126
+ if ( term.length < e.min_length ) {
127
+ return false;
128
+ }
129
+ },
130
+ focus: function() {
131
+ // prevent value inserted on focus
132
+ return false;
133
+ },
134
+ select: function( event, ui ) {
135
+ // first ensure value is a string
136
+ ui.item.value = ui.item.value.toString();
137
+ if(ui.item.value.toLowerCase().indexOf('no match') != -1 || ui.item.value.toLowerCase().indexOf('too many results') != -1){
138
+ jQuery(this).trigger('railsAutocomplete.noMatch', ui);
139
+ return false;
140
+ }
141
+ var terms = split( this.value );
142
+ // remove the current input
143
+ terms.pop();
144
+ // add the selected item
145
+ terms.push( ui.item.value );
146
+ // add placeholder to get the comma-and-space at the end
147
+ if (e.delimiter != null) {
148
+ terms.push( "" );
149
+ this.value = terms.join( e.delimiter );
150
+ } else {
151
+ this.value = terms.join("");
152
+ if (jQuery(this).attr('data-id-element')) {
153
+ jQuery(jQuery(this).attr('data-id-element')).val(ui.item.id).trigger('change');
154
+ }
155
+ if (jQuery(this).attr('data-update-elements')) {
156
+ var data = ui.item;
157
+ var new_record = ui.item.value.indexOf('Create New') != -1 ? true : false;
158
+ var update_elements = jQuery.parseJSON(jQuery(this).attr("data-update-elements"));
159
+ for (var key in update_elements) {
160
+ if(jQuery(update_elements[key]).attr("type") === "checkbox"){
161
+ if(data[key] === true || data[key] === 1) {
162
+ jQuery(update_elements[key]).attr("checked","checked");
163
+ }
164
+ else {
165
+ jQuery(update_elements[key]).removeAttr("checked");
166
+ }
167
+ }
168
+ else{
169
+ if((new_record && data[key] && data[key].indexOf('Create New') == -1) || !new_record){
170
+ jQuery(update_elements[key]).val(data[key]).trigger('change');
171
+ }else{
172
+ jQuery(update_elements[key]).val('').trigger('change');
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ var remember_string = this.value;
179
+ jQuery(this).bind('keyup.clearId', function(){
180
+ if(jQuery.trim(jQuery(this).val()) != jQuery.trim(remember_string)){
181
+ jQuery(jQuery(this).attr('data-id-element')).val("").trigger('change');
182
+ jQuery(this).unbind('keyup.clearId');
183
+ }
184
+ });
185
+ jQuery(e).trigger('railsAutocomplete.select', ui);
186
+
187
+ return false;
188
+ }
189
+ });
190
+ jQuery(e).trigger('railsAutocomplete.init');
191
+ }
192
+ });
193
+
194
+ jQuery(document).ready(function(){
195
+ jQuery('input[data-autocomplete]').railsAutocomplete('input[data-autocomplete]');
196
+ });
197
+ })(jQuery);
@@ -0,0 +1 @@
1
+ !function(t){t.fn.railsAutocomplete=function(e){var a=function(){this.railsAutoCompleter||(this.railsAutoCompleter=new t.railsAutocomplete(this))};if(void 0!==t.fn.on){if(!e)return;return t(document).on("focus",e,a)}return this.live("focus",a)},t.railsAutocomplete=function(t){var e=t;this.init(e)},t.railsAutocomplete.options={showNoMatches:!0,noMatchesLabel:"no existing match"},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("data-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,r){var n=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(){var a={};t.extend(a,t.railsAutocomplete.options),t.each(a,function(i,r){if(a.hasOwnProperty(i)){var n=t(e).attr("data-"+i);a[i]=n?n:r}}),0==arguments[0].length&&t.inArray(a.showNoMatches,[!0,"true"])>=0&&(arguments[0]=[],arguments[0][0]={id:"",label:a.noMatchesLabel}),t(arguments[0]).each(function(a,i){var r={};r[i.id]=i,t(e).data(r)}),r.apply(null,arguments),t(n).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:"").trigger("change"),t(this).attr("data-update-elements"))){var i=t.parseJSON(t(this).attr("data-update-elements")),r=a.item?t(this).data(a.item.id.toString()):{};if(i&&""===t(i.id).val())return;for(var n in i){var o=t(i[n]);o.is(":checkbox")?null!=r[n]&&o.prop("checked",r[n]):o.val(a.item?r[n]:"").trigger("change")}}},search:function(){var t=i(this.value);return t.length<e.min_length?!1:void 0},focus:function(){return!1},select:function(i,r){if(r.item.value=r.item.value.toString(),-1!=r.item.value.toLowerCase().indexOf("no match")||-1!=r.item.value.toLowerCase().indexOf("too many results"))return t(this).trigger("railsAutocomplete.noMatch",r),!1;var n=a(this.value);if(n.pop(),n.push(r.item.value),null!=e.delimiter)n.push(""),this.value=n.join(e.delimiter);else if(this.value=n.join(""),t(this).attr("data-id-element")&&t(t(this).attr("data-id-element")).val(r.item.id).trigger("change"),t(this).attr("data-update-elements")){var o=r.item,l=-1!=r.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"):l&&o[s]&&-1==o[s].indexOf("Create New")||!l?t(u[s]).val(o[s]).trigger("change"):t(u[s]).val("").trigger("change")}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("").trigger("change"),t(this).unbind("keyup.clearId"))}),t(e).trigger("railsAutocomplete.select",r),!1}}),t(e).trigger("railsAutocomplete.init")}}),t(document).ready(function(){t("input[data-autocomplete]").railsAutocomplete("input[data-autocomplete]")})}(jQuery);
@@ -0,0 +1,23 @@
1
+ require 'autocomplete-activeadmin/form_helper'
2
+ require 'autocomplete-activeadmin/autocomplete'
3
+
4
+ module AutocompleteActiveAdmin
5
+ autoload :Orm , 'autocomplete-activeadmin/orm'
6
+ autoload :FormtasticPlugin , 'autocomplete-activeadmin/formtastic_plugin'
7
+
8
+ unless ::Rails.version < "3.1"
9
+ require 'autocomplete-activeadmin/rails/engine'
10
+ end
11
+ end
12
+
13
+ class ActionController::Base
14
+ include AutocompleteActiveAdmin::Autocomplete
15
+ end
16
+
17
+ require 'autocomplete-activeadmin/formtastic'
18
+
19
+ begin
20
+ require 'simple_form'
21
+ require 'autocomplete-activeadmin/simple_form_plugin'
22
+ rescue LoadError
23
+ end
@@ -0,0 +1,116 @@
1
+ module AutocompleteActiveAdmin
2
+ module Autocomplete
3
+ def self.included(target)
4
+ target.extend AutocompleteActiveAdmin::Autocomplete::ClassMethods
5
+
6
+ target.send :include, AutocompleteActiveAdmin::Orm::Mongoid if defined?(Mongoid::Document)
7
+ target.send :include, AutocompleteActiveAdmin::Orm::MongoMapper if defined?(MongoMapper::Document)
8
+ target.send :include, AutocompleteActiveAdmin::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 = HashWithIndifferentAccess.new({"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 AutocompleteActiveAdmin::FormtasticPlugin
38
+ end
39
+ end
40
+ rescue LoadError
41
+ end