recordselect-custom 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,25 @@
1
+ 1.0
2
+ * record_select_field always searches on the latest string
3
+ * R.S. dialog closes on mousedown instead of click (because sometimes clicks are intercepted)
4
+ * R.S. layers over <select> boxes in IE 6
5
+ * record_select_field now opens R.S. even when the field loads with focus
6
+ * new :onchange option for record_select_field, to observe selection events
7
+
8
+ 1.0rc1
9
+ * helpers complain if the controller they're configured for doesn't use recordselect
10
+ * when using keyboard navigation, hitting "enter" to select a record no longer submits the form
11
+ * if text field is empty when dialog is closed, then recordselect will empty the hidden field as well (lets you deselect)
12
+ * support for multiple selections (record_multi_select_helper)
13
+ * param-based search conditions are smarter - can search numeric fields as well
14
+ * using record_select_conditions_from_controller instead of conditions_for_collection. also added a new record_select_includes override method.
15
+ * helpers now accept a :class option
16
+ * fixed bug with url escaping when helpers were configured with multiple parameters
17
+ * new :label configuration option. it's a proc - the default one calls record.to_label (for backwards compatibility).
18
+ * cleaned up merge_conditions to use activerecord's sanitize_sql.
19
+ * div.record-select-container now has a high z-index so it will by default be visible above other absolutely positioned elements.
20
+
21
+ 0.9
22
+ stuff
23
+
24
+ 0.1
25
+ stuff
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in recordselect-custom.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 lion
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Lance Ivy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,11 @@
1
+ RecordSelect
2
+ ============
3
+
4
+ (c) 2007 - Lance Ivy
5
+
6
+ RecordSelect is a Rails widget to help you pick one record out of many. I designed it as a more usable and performant alternative to generating a massive dropdown list. It relies on AJAX for the cooler uses (all the provided view helpers rely on JavaScript?) but can also function in a pure-http fashion (although multi-select support is provided in a pure-JavaScript implementation).
7
+
8
+ Please see the ActionView::Helpers::RecordSelectHelpers for the most common API. More documentation (and HOWTOs) can be found online at http://code.google.com/p/recordselect, and a demo application is available at http://recordselect.googlecode.com/svn/demo.
9
+
10
+ = DEPENDENCIES
11
+ This depends on the excellent Paginator gem by Bruce Williams. This simple gem is available at paginator.rubyforge.org.
File without changes
@@ -0,0 +1,23 @@
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 the recordselect 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 the recordselect plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Recordselect'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ rdoc.rdoc_files.include('assets/**/*.js')
23
+ end
@@ -0,0 +1,187 @@
1
+ module RecordSelectHelper
2
+ # Print this from your layout to include everything necessary for RecordSelect to work.
3
+ # Well, not everything. You need Prototype too.
4
+ def record_select_includes
5
+ includes = ''
6
+ includes << stylesheet_link_tag('record_select/record_select')
7
+ includes << javascript_include_tag('record_select/record_select')
8
+ includes
9
+ end
10
+
11
+ # Adds a link on the page that toggles a RecordSelect widget from the given controller.
12
+ #
13
+ # *Options*
14
+ # +onselect+:: JavaScript code to handle selections client-side. This code has access to two variables: id, label. If the code returns false, the dialog will *not* close automatically.
15
+ # +params+:: Extra URL parameters. If any parameter is a column name, the parameter will be used as a search term to filter the result set.
16
+ def link_to_record_select(name, controller, options = {})
17
+ options[:params] ||= {}
18
+ options[:params].merge!(:controller => controller, :action => :browse)
19
+ options[:onselect] = "function(id, label) {#{options[:onselect]}}" if options[:onselect]
20
+ options[:html] ||= {}
21
+ options[:html][:id] ||= "rs_#{rand(9999)}"
22
+
23
+ assert_controller_responds(options[:params][:controller])
24
+
25
+ html = link_to_function(name, '', options[:html])
26
+ html << javascript_tag("new RecordSelect.Dialog(#{options[:html][:id].to_json}, #{url_for(options[:params].merge(:escape => false)).to_json}, {onselect: #{options[:onselect] || ''}})")
27
+
28
+ return html
29
+ end
30
+
31
+ # Adds a RecordSelect-based form field. The field submits the record's id using a hidden input.
32
+ #
33
+ # *Arguments*
34
+ # +name+:: the input name that will be used to submit the selected record's id.
35
+ # +current+:: the currently selected object. provide a new record if there're none currently selected and you have not passed the optional :controller argument.
36
+ #
37
+ # *Options*
38
+ # +controller+:: The controller configured to provide the result set. Optional if you have standard resource controllers (e.g. UsersController for the User model), in which case the controller will be inferred from the class of +current+ (the second argument)
39
+ # +params+:: A hash of extra URL parameters
40
+ # +id+:: The id to use for the input. Defaults based on the input's name.
41
+ # +onchange+:: A JavaScript function that will be called whenever something new is selected. It should accept the new id as the first argument, and the new label as the second argument. For example, you could set onchange to be "function(id, label) {alert(id);}", or you could create a JavaScript function somewhere else and set onchange to be "my_function" (without the parantheses!).
42
+ def record_select_field(name, current, options = {})
43
+ options[:controller] ||= current.class.to_s.pluralize.underscore
44
+ options[:params] ||= {}
45
+ options[:id] ||= name.gsub(/[\[\]]/, '_')
46
+
47
+ controller = assert_controller_responds(options[:controller])
48
+
49
+ id = label = ''
50
+ if current and not current.new_record?
51
+ id = current.id
52
+ label = label_for_field(current, controller)
53
+ end
54
+
55
+ url = url_for({:action => :browse, :controller => options[:controller], :escape => false}.merge(options[:params]))
56
+
57
+ html = text_field_tag(name, nil, :autocomplete => 'off', :id => options[:id], :class => options[:class], :onfocus => "this.focused=true", :onblur => "this.focused=false")
58
+ html << javascript_tag("$(#{options[:id].to_json}).singleRecordSelect(#{url.to_json}, {id: #{id.to_json}, label: #{label.to_json}, onchange: #{options[:onchange] || ''.to_json}});")
59
+
60
+ return html
61
+ end
62
+
63
+ # Assists with the creation of an observer for the :onchange option of the record_select_field method.
64
+ # Currently only supports building an Ajax.Request based on the id of the selected record.
65
+ #
66
+ # options[:url] should be a hash with all the necessary options *except* :id. that parameter
67
+ # will be provided based on the selected record.
68
+ #
69
+ # Question: if selecting users, what's more likely?
70
+ # /users/5/categories
71
+ # /categories?user_id=5
72
+ def record_select_observer(options = {})
73
+ fn = ""
74
+ fn << "function(id, value) {"
75
+ fn << "var url = #{url_for(options[:url].merge(:id => ":id:")).to_json}.replace(/:id:/, id);"
76
+ fn << "jQuery.get(url);"
77
+ fn << "}"
78
+ end
79
+
80
+ # Adds a RecordSelect-based form field for multiple selections. The values submit using a list of hidden inputs.
81
+ #
82
+ # *Arguments*
83
+ # +name+:: the input name that will be used to submit the selected records' ids. empty brackets will be appended to the name.
84
+ # +current+:: pass a collection of existing associated records
85
+ #
86
+ # *Options*
87
+ # +controller+:: The controller configured to provide the result set.
88
+ # +params+:: A hash of extra URL parameters
89
+ # +id+:: The id to use for the input. Defaults based on the input's name.
90
+ def record_multi_select_field(name, current, options = {})
91
+ options[:controller] ||= current.first.class.to_s.pluralize.underscore
92
+ options[:params] ||= {}
93
+ options[:id] ||= name.gsub(/[\[\]]/, '_')
94
+
95
+ controller = assert_controller_responds(options[:controller])
96
+
97
+ current = current.inject([]) { |memo, record| memo.push({:id => record.id, :label => label_for_field(record, controller)}) }
98
+
99
+ url = url_for({:action => :browse, :controller => options[:controller], :escape => false}.merge(options[:params]))
100
+
101
+ html = text_field_tag("#{name}[]", nil, :autocomplete => 'off', :id => options[:id], :class => options[:class], :onfocus => "this.focused=true", :onblur => "this.focused=false")
102
+ html << content_tag('ul', '', :class => 'record-select-list');
103
+ html << javascript_tag("new RecordSelect.Multiple(#{options[:id].to_json}, #{url.to_json}, {current: #{current.to_json}});")
104
+
105
+ return html
106
+ end
107
+
108
+ # A helper to render RecordSelect partials
109
+ def render_record_select(options = {}) #:nodoc:
110
+ controller.send(:render_record_select, options) do |options|
111
+ render options
112
+ end
113
+ end
114
+
115
+ # Provides view access to the RecordSelect configuration
116
+ def record_select_config #:nodoc:
117
+ controller.send :record_select_config
118
+ end
119
+
120
+ # The id of the RecordSelect widget for the given controller.
121
+ def record_select_id(controller = nil) #:nodoc:
122
+ controller ||= params[:controller]
123
+ "record-select-#{controller.gsub('/', '_')}"
124
+ end
125
+
126
+ def record_select_search_id(controller = nil) #:nodoc:
127
+ "#{record_select_id(controller)}-search"
128
+ end
129
+
130
+ private
131
+ # render the record using the renderer and add a link to select the record
132
+ def render_record_in_list(record, controller_path)
133
+ text = render_record_from_config(record)
134
+ if record_select_config.link?
135
+ url_params = {:controller => controller_path, :action => :select, :id => record.id, :escape => false}
136
+ link_to_remote text, {
137
+ :url => url_params,
138
+ :method => :post,
139
+ :before => 'jQuery(this).toggleClass("selected")',
140
+ :condition => "jQuery(this).recordSelect_notify()"
141
+ }, :href => url_params
142
+ else
143
+ text
144
+ end
145
+ end
146
+
147
+
148
+ # uses renderer (defaults to record_select_config.label) to determine how the given record renders.
149
+ def render_record_from_config(record, renderer = record_select_config.label)
150
+ case renderer
151
+ when Symbol, String
152
+ # return full-html from the named partial
153
+ render :partial => renderer.to_s, :locals => {:record => record}
154
+
155
+ when Proc
156
+ # return an html-cleaned descriptive string
157
+ h renderer.call(record)
158
+ end
159
+ end
160
+
161
+ # uses the result of render_record_from_config to snag an appropriate record label
162
+ # to display in a field.
163
+ #
164
+ # if given a controller, searches for a partial in its views path
165
+ def label_for_field(record, controller = self.controller)
166
+ renderer = controller.record_select_config.label
167
+ case renderer
168
+ when Symbol, String
169
+ # find the <label> element and grab its innerHTML
170
+ description = render_record_from_config(record, File.join(controller.controller_path, renderer.to_s))
171
+ description.match(/<label[^>]*>(.*)<\/label>/)[1]
172
+
173
+ when Proc
174
+ # just return the string
175
+ render_record_from_config(record, renderer)
176
+ end
177
+ end
178
+
179
+ def assert_controller_responds(controller_name)
180
+ controller_name = "#{controller_name.camelize}Controller"
181
+ controller = controller_name.constantize
182
+ unless controller.uses_record_select?
183
+ raise "#{controller_name} has not been configured to use RecordSelect."
184
+ end
185
+ controller
186
+ end
187
+ end
@@ -0,0 +1,8 @@
1
+ <%
2
+ controller ||= params[:controller]
3
+ record_select_id = record_select_id(controller)
4
+ -%>
5
+ <div class="record-select" id="<%= record_select_id -%>">
6
+ <%= render_record_select :partial => 'search', :locals => {:controller => controller, :record_select_id => record_select_id} %>
7
+ <%= render_record_select :partial => 'list', :locals => {:controller => controller, :page => @page, :record_select_id => record_select_id} %>
8
+ </div>
@@ -0,0 +1,29 @@
1
+ <%- controller ||= params[:controller] -%>
2
+ <%- pagination_url_params = params.merge(:controller => controller, :action => :browse, :search => params[:search], :update => 1) if page.prev? || page.next? -%>
3
+
4
+ <ol>
5
+ <li class="found">
6
+ <%= rs_("%d %s found", page.pager.count, record_select_config.model.to_s.pluralize.titleize.downcase) %>
7
+ </li>
8
+ <% if page.prev? -%>
9
+ <%- prev_url = url_for(pagination_url_params.merge(:page => page.prev.number, :escape => false)) %>
10
+ <li class="pagination previous">
11
+ <%- previous_text = image_tag('record_select/previous.gif', :alt => rs_('Previous')) + " " + rs_("Previous %d", page.pager.per_page) %>
12
+ <%- previous_text = "<div>#{previous_text}</div>" %>
13
+ <%= link_to_remote previous_text, {:url => prev_url, :method => :get}, {:href => prev_url} %>
14
+ </li>
15
+ <% end -%>
16
+ <% page.items.each do |record| -%>
17
+ <li class="record <%= cycle 'odd', 'even' %>" id="rs<%= record.id -%>">
18
+ <%= render_record_in_list(record, controller) %>
19
+ </li>
20
+ <% end -%>
21
+ <% if page.next? -%>
22
+ <%- next_url = url_for(pagination_url_params.merge(:page => page.next.number, :escape => false)) %>
23
+ <li class="pagination next">
24
+ <%- next_text = rs_("Next %d", page.pager.per_page) + " " + image_tag('record_select/next.gif', :alt => rs_('Next')) %>
25
+ <%- next_text = "<div>#{next_text}</div>" %>
26
+ <%= link_to_remote next_text, {:url => next_url, :method => :get}, {:href => next_url} %>
27
+ </li>
28
+ <% end -%>
29
+ </ol>
@@ -0,0 +1,14 @@
1
+ <% search_url = url_for(params.merge(:controller => controller, :action => :browse, :page => 1, :update => 1, :escape => false)) -%>
2
+ <% form_remote_tag :url => search_url, :method => :get,
3
+ :html => { :action => search_url, :id => record_select_search_id } do %>
4
+ <%= text_field_tag 'search', params[:search], :autocomplete => 'off', :class => 'text-input' %>
5
+ <% end %>
6
+
7
+ <script type="text/javascript">
8
+ //<![CDATA[
9
+ var i = $(<%= record_select_search_id.to_json %>).down('input.text-input');
10
+ Form.Element.AfterActivity(i, function() {
11
+ $(<%= record_select_search_id.to_json %>).onsubmit();
12
+ }, 0.35);
13
+ //]]>
14
+ </script>
@@ -0,0 +1,4 @@
1
+ page.assign "data", render_record_select(:partial => 'list', :locals => {:page => @page})
2
+ page.assign "elem", page.literal("$$('##{record_select_id} ol')[0]")
3
+
4
+ page << %|if (elem) {Element.replace(elem, data)}|
Binary file
Binary file
@@ -0,0 +1,327 @@
1
+ jQuery(window).bind("load", function() { RecordSelect.document_loaded = true });
2
+
3
+ jQuery.fn.afterActivity = function(callback, delay, _this) {
4
+ var element = jQuery(this);
5
+ if (!delay) delay = 0.35;
6
+
7
+ var elem;
8
+ jQuery(this).bind(("keyup"), function(event) {
9
+
10
+ // TODO 用中文输入法输英文时(输入asdf,回车),有问题
11
+ var keyCode = event.keyCode;
12
+
13
+ switch( event.keyCode ) {
14
+ case 0://Event.KEY_SPACE:
15
+ case 13://keyCode.ENTER
16
+ _this.clearActivityTimer();
17
+ _this.abortRequestXhr("ajaxGetSearh");
18
+ _this.abortRequestXhr("ajaxGetOpen");
19
+ case 16://keyCode.SHIFT:
20
+ case 17://keyCode.CTRL:
21
+ case 18://keyCode.ALT:
22
+ case 20://keyCode.CAPSLOCK:
23
+ case 27://Event.KEY_ESC:
24
+ case 33://keyCode.PAGEUP:
25
+ case 34://keyCode.PAGEDOWN:
26
+ case 35://keyCode.END:
27
+ case 36://keyCode.HOME:
28
+ case 37://Event.KEY_LEFT:
29
+ case 38://keyCode.UP:
30
+ case 39://Event.KEY_RIGHT:
31
+ case 40://keyCode.DOWN:
32
+ return;
33
+ }
34
+
35
+ if(event.isDefaultPrevented()) return;
36
+
37
+ _this.clearActivityTimer();
38
+ _this.abortRequestXhr("ajaxGetSearh");
39
+ _this.abortRequestXhr("ajaxGetOpen");
40
+ _this.activity_timer = setTimeout(function() {
41
+ if(!_this.is_open()) _this.open();
42
+ form = element.nextAll('div.record-select-container').find('form');
43
+ callback = function(){form[0] && form[0].onsubmit(_this)};
44
+ // TODO is close auto open
45
+ callback(element.value);
46
+ }, delay * 1000 + 50);
47
+ });
48
+ jQuery(this).bind(("keydown"), function(event) {
49
+
50
+ var keyCode = event.keyCode;
51
+
52
+ // hot keys
53
+ if(event.shiftKey && event.altKey && keyCode == 78){
54
+ _this.clearActivityTimer();
55
+ _this.abortRequestXhr("ajaxGetSearh");
56
+ _this.abortRequestXhr("ajaxGetOpen");
57
+ _this.close();
58
+ return;
59
+ }
60
+ switch( event.keyCode ) {
61
+ case 38://keyCode.UP:
62
+ elem = _this.has_current() && _this.current.parent('.record-select') && _this.current.prev(":not(.found)");
63
+ if (elem && elem.length == 0) elem = _this.container.find("ol li:not(.found):last");
64
+ _this.highlight(elem);
65
+ event.preventDefault();
66
+ return;
67
+ case 40://keyCode.DOWN:
68
+ if(!_this.is_open()) _this.open();
69
+ if (_this.has_current() && _this.current.parent('.record-select')) elem = _this.current.next(":not(.found)");
70
+ if (elem && elem.length == 0) elem = _this.container.find("ol li:not(.found):first");
71
+ // prevent moving cursor to end of text field in some browsers
72
+ _this.highlight(elem);
73
+ event.preventDefault();
74
+ return;
75
+ case 0://Event.KEY_SPACE:
76
+ case 13://keyCode.ENTER:
77
+ if (_this.has_current()){
78
+ elem = _this.current.find('a')[0];
79
+ switch(elem){
80
+ case _this.getPrevPageBtn()[0]: _this.doPrevPage(); break;
81
+ case _this.getNextPageBtn()[0]: _this.doNextPage(); break;
82
+ default : elem.onclick();
83
+ }
84
+ }
85
+ event.preventDefault();
86
+ return;
87
+ case 39://Event.KEY_RIGHT:
88
+ if(_this.is_open()){
89
+ _this.doNextPage();
90
+ event.preventDefault();
91
+ }
92
+ return;
93
+ case 37://Event.KEY_LEFT:
94
+ if(_this.is_open()){
95
+ _this.doPrevPage();
96
+ event.preventDefault();
97
+ }
98
+ return;
99
+ case 9://keyCode.TAB:
100
+ case 27://Event.KEY_ESC:
101
+ _this.close();
102
+ return;
103
+ case 16://keyCode.SHIFT:
104
+ case 17://keyCode.CTRL:
105
+ case 18://keyCode.ALT:
106
+ case 20://keyCode.CAPSLOCK:
107
+ case 33://keyCode.PAGEUP:
108
+ case 34://keyCode.PAGEDOWN:
109
+ case 35://keyCode.END:
110
+ case 36://keyCode.HOME:
111
+ return;
112
+ }
113
+ });
114
+ }
115
+
116
+ var RecordSelect = new Object();
117
+ RecordSelect.document_loaded = false;
118
+
119
+ $.fn.recordSelect_notify = function() {
120
+ var item = $(this);
121
+ var e = item.parents(".record-select-handler");
122
+ var my_onselect = e.my_onselect || e.attr('my_onselect') || e.prop('my_onselect');
123
+ if (typeof my_onselect != 'function') my_onselect = eval(my_onselect);
124
+ if (my_onselect) {
125
+ try {
126
+ par = item.parent();
127
+ my_onselect(par.attr("id").substring(2), item.find('label').text() || item.text(), e);
128
+ } catch(e) {
129
+ alert(e);
130
+ }
131
+ return false;
132
+ }
133
+ else return true;
134
+ return false;
135
+ }
136
+
137
+ jQuery.fn.recordSelectSingle = function(url, options) {
138
+ var obj = $(this);
139
+ var _this = {};
140
+ if(!options.openEvents) options.openEvents = "focus click";
141
+
142
+ var initialize = function(obj, url, options) {
143
+ _this.obj = obj;
144
+ _this.url = url;
145
+ _this.options = options;
146
+ _this.js_params = _this.options && _this.options.js_params;
147
+ _this.container;
148
+
149
+ if (RecordSelect.document_loaded) {
150
+ _this.my_onload();
151
+ } else {
152
+ jQuery(window).bind('load', _this.my_onload);
153
+ }
154
+
155
+ jQuery(document.body).bind('mousedown', _this.onbodyclick);
156
+ };
157
+
158
+ _this.my_onload = function() {
159
+ // initialize the container
160
+ _this.container = _this.create_container();
161
+ _this.container.addClass('record-select-autocomplete');
162
+
163
+ // create the hidden input
164
+ _this.obj.after('<input type="hidden" name="" value="" class="value"/>');
165
+ _this.hidden_input = _this.obj.next("input.value:last");
166
+
167
+ // transfer the input name from the text input to the hidden input
168
+ _this.hidden_input.attr("name", _this.obj.attr("name"));
169
+ _this.obj.attr("name", '');
170
+
171
+ // initialize the values
172
+ _this.set(_this.options.id, _this.options.label);
173
+ _this._respond_to_text_field(_this.obj);
174
+ if (_this.obj.focused) _this.open(); // if it was focused before we could attach observers
175
+ };
176
+
177
+ _this.close = function() {
178
+ _this.clearActivityTimer();
179
+ _this.abortRequestXhr("ajaxGetSearh");
180
+ _this.abortRequestXhr("ajaxGetOpen");
181
+ // if they close the dialog with the text field empty, then delete the id value
182
+ if (_this.obj.attr("value") == '') _this.set('', '');
183
+
184
+ if (_this._use_iframe_mask()) { _this.container.next('iframe').remove(); }
185
+
186
+ _this.container.hide();
187
+ // hopefully by using remove() instead of innerHTML we won't leak memory
188
+ _this.container.children().remove();
189
+ };
190
+
191
+ _this.my_onselect = function(id, value, e) {
192
+ if (_this.options.onchange) _this.options.onchange(_this.obj, id, value);
193
+ _this.set(id, value);
194
+ _this.obj.focus();
195
+ _this.close();
196
+ };
197
+
198
+ _this.open = function(ev) {
199
+ var elem;
200
+ if (_this.is_open()) return;
201
+ if (_this.ajaxGetOpen) return;
202
+ var _params = {search: _this.obj.val()};
203
+ if (_this.js_params) _params = $.extend(_params, _this.js_params());
204
+
205
+ _this.ajaxGetOpen = jQuery.get(_this.url, _params, function(data, b) {
206
+ _this.ajaxGetOpen = null;
207
+ _this.container.html(data);
208
+ _this.show();
209
+ _this.highlightFirst();
210
+ // needs to be mousedown so the event doesn't get canceled by other code (see issue #26)
211
+ });
212
+ };
213
+
214
+ _this.highlightFirst = function() {
215
+ var elem = _this.container.find('ol li.record:first');
216
+ // prevent moving cursor to end of text field in some browsers
217
+ _this.highlight(elem);
218
+ };
219
+
220
+ _this.abortRequestXhr = function(reqXhrName) {
221
+ if (_this[reqXhrName]) {
222
+ _this[reqXhrName].abort();
223
+ _this[reqXhrName] = null;
224
+ }
225
+ };
226
+
227
+ _this.clearActivityTimer = function() {
228
+ if (_this.activity_timer) clearTimeout(_this.activity_timer);
229
+ };
230
+
231
+ _this.show = function() {
232
+ var offset = _this.obj.position();
233
+ _this.container.css("left", offset.left + 'px');
234
+ _this.container.css("top", (_this.obj.outerHeight() + offset.top) + 'px');
235
+
236
+ if (_this._use_iframe_mask()) {
237
+ _this.container.append('<iframe src="javascript:false;" class="record-select-mask" />');
238
+ var mask = _this.container.next('iframe');
239
+ mask.css("left", _this.container.position().left);
240
+ mask.css("top", _this.container.position().top);
241
+ }
242
+
243
+ _this.container.show();
244
+ if (_this._use_iframe_mask()) {
245
+ var dimensions = _this.container.children();
246
+ mask.css("width", dimensions.outerWidth() + 'px');
247
+ mask.css("height", dimensions.outerHeight() + 'px');
248
+ }
249
+ };
250
+
251
+ _this.is_open = function() { return (!!_this.container.html()); };
252
+
253
+ _this.set = function(id, label) {
254
+ var text = jQuery("<div>" + label + "</div>").text();
255
+ _this.obj.val(text);
256
+ _this.hidden_input.val(_this.options.ass_value ? id : text);
257
+ };
258
+
259
+ _this.create_container = function() {
260
+ box = obj.parent();
261
+ box.append('<div class="record-select-container record-select-handler"></div>');
262
+ e = box.children()[box.children().length - 1];
263
+ e.my_onselect = _this.my_onselect;
264
+ e = jQuery(e);
265
+ e.hide();
266
+
267
+ return jQuery(e);
268
+ };
269
+
270
+ _this.onbodyclick = function(ev) {
271
+ var elem = jQuery(ev.target);
272
+ if (elem.parents("li.pagination.previous").children("a")[0] == _this.getPrevPageBtn()[0]) _this.doPrevPage();
273
+ if (elem.parents("li.pagination.next").children("a")[0] == _this.getNextPageBtn()[0]) _this.doNextPage();
274
+ if (elem.parents("div.record-select-container").length > 0 || elem[0] == _this.obj[0]) return;
275
+ _this.clearActivityTimer();
276
+ _this.abortRequestXhr("ajaxGetSearh");
277
+ _this.abortRequestXhr("ajaxGetOpen");
278
+ _this.close();
279
+ };
280
+
281
+ _this._respond_to_text_field = function(text_field) {
282
+ // attach the events to start _this party
283
+ text_field.bind(options.openEvents, _this.open);
284
+
285
+ // the autosearch event - needs to happen slightly late (keyup is later than keypress)
286
+ text_field.bind('keyup', function() {
287
+ if (!_this.is_open()) return;
288
+ _this.container.find('.text-input').attr("value", text_field.attr("value"));
289
+ });
290
+
291
+ // keyboard navigation, if available
292
+ if (_this.onkeypress) { text_field.bind((jQuery.browser.opera? "keypress" : "keydown"), _this.onkeypress); }
293
+ };
294
+ _this.current = null;
295
+
296
+ _this.onkeypress = function(event) { };
297
+
298
+ _this.highlight = function(obj) {
299
+ if (_this.has_current()) _this.current.removeClass('current');
300
+ if (!obj) return;
301
+ _this.current = jQuery(obj);
302
+ obj.addClass('current');
303
+ };
304
+
305
+ _this.getPrevPageBtn = function() { return _this.container.find("li.pagination.previous a"); };
306
+ _this.getNextPageBtn = function() { return _this.container.find("li.pagination.next a"); };
307
+
308
+ _this.doPrevPage = function(){ if(_this.getPrevPageBtn().length > 0) _this.doPage(_this.getPrevPageBtn().attr("href")); }
309
+ _this.doNextPage = function(){ if(_this.getNextPageBtn().length > 0) _this.doPage(_this.getNextPageBtn().attr("href")); }
310
+
311
+ _this.doPage = function(url) {
312
+ jQuery.ajax({
313
+ url: url,
314
+ data: _this.js_params && _this.js_params(),
315
+ success: function(data) {
316
+ eval(data);
317
+ _this.highlightFirst();
318
+ }
319
+ });
320
+ };
321
+
322
+ _this._use_iframe_mask = function() { return false; };
323
+ _this.has_current = function() { return _this.current && _this.current.length > 0; };
324
+
325
+ obj.afterActivity(null, 0.35, _this);
326
+ initialize(obj, url, options);
327
+ };