recordselect_vho 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ MIT-LICENSE
data/CHANGELOG ADDED
@@ -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,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.2"
12
+ gem "rcov", ">= 0"
13
+ end
data/MIT-LICENSE ADDED
@@ -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,15 @@
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.
12
+
13
+
14
+ If you would like to use jquery:
15
+ uncomment line #RecordSelect::Config.js_framework = :jquery in init.rb
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ require './lib/record_select/version.rb'
14
+ Jeweler::Tasks.new do |gem|
15
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
16
+ gem.name = "recordselect_vho"
17
+ gem.version = RecordSelect::Version::STRING
18
+ gem.homepage = "http://github.com/vhochstein/recordselect"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{RecordSelect widget as a replacement for massive drop down lists}
21
+ gem.description = %Q{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}
22
+ gem.email = "activescaffold@googlegroups.com"
23
+ gem.authors = ["Volker Hochstein", "Lance Ivy"]
24
+ gem.add_runtime_dependency 'rails', '~> 3.0.0'
25
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
26
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
27
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
28
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
29
+ end
30
+ Jeweler::RubygemsDotOrgTasks.new
31
+
32
+ require 'rake/testtask'
33
+ Rake::TestTask.new(:test) do |test|
34
+ test.libs << 'lib' << 'test'
35
+ test.pattern = 'test/**/test_*.rb'
36
+ test.verbose = true
37
+ end
38
+
39
+ require 'rake/rdoctask'
40
+ desc 'Generate documentation for the recordselect plugin.'
41
+ Rake::RDocTask.new(:rdoc) do |rdoc|
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "Recordselect #{RecordSelect::Version::STRING}"
44
+ rdoc.options << '--line-numbers' << '--inline-source'
45
+ rdoc.rdoc_files.include('README')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ rdoc.rdoc_files.include('assets/**/*.js')
48
+ end
49
+
50
+ task :default => :test
@@ -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,31 @@
1
+ <%
2
+ controller ||= params[:controller]
3
+
4
+ pagination_url_params = params.merge(:controller => controller, :action => :browse, :search => params[:search], :update => 1)
5
+ prev_url = url_for(pagination_url_params.merge(:page => page.prev.number, :escape => false)) if page.prev?
6
+ next_url = url_for(pagination_url_params.merge(:page => page.next.number, :escape => false)) if page.next?
7
+ -%>
8
+ <ol>
9
+ <li class="found"><%= rs_("%d %s found", page.pager.count,
10
+ record_select_config.model.to_s.pluralize.titleize.downcase) %></li>
11
+ <% if page.prev? -%>
12
+ <li class="pagination previous">
13
+ <%= link_to image_tag('record_select/previous.gif', :alt => rs_('Previous')) + " " + rs_("Previous %d",
14
+ page.pager.per_page),
15
+ {:url => prev_url},
16
+ {:href => prev_url, :method => :get, :remote => true} %>
17
+ </li>
18
+ <% end -%>
19
+ <% page.items.each do |record| -%>
20
+ <li class="record <%= cycle 'odd', 'even' %>" id="rs<%= record.id -%>">
21
+ <%= render_record_in_list(record, controller) %>
22
+ </li>
23
+ <% end -%>
24
+ <% if page.next? -%>
25
+ <li class="pagination next">
26
+ <%= link_to (rs_("Next %d", page.pager.per_page) + " " + image_tag('record_select/next.gif', :alt => rs_('Next'))).html_safe,
27
+ {:url => next_url},
28
+ {:href => next_url, :method => :get, :remote => true} %>
29
+ </li>
30
+ <% end -%>
31
+ </ol>
@@ -0,0 +1,20 @@
1
+ <% url_options = params.merge(:controller => controller, :action => :browse, :page => 1, :update => 1, :escape => false) -%>
2
+ <%= form_tag url_options, {:method => :get, :remote => true, :id => record_select_search_id} -%>
3
+ <%= text_field_tag 'search', params[:search], :autocomplete => 'off', :class => 'text-input' %>
4
+ <%= submit_tag 'search', :class => "search_submit" %>
5
+ </form>
6
+
7
+ <script type="text/javascript">
8
+ //<![CDATA[
9
+ <% if RecordSelect::Config.js_framework == :prototype %>
10
+ var i = $(<%= record_select_search_id.to_json.html_safe %>).down('input.text-input');
11
+ Form.Element.AfterActivity(i, function() {
12
+ $(<%= record_select_search_id.to_json.html_safe -%>).down('input.search_submit').click();
13
+ }, 0.35);
14
+ <% elsif RecordSelect::Config.js_framework == :jquery %>
15
+ $(<%= "##{record_select_search_id}".to_json.html_safe %>).find('input.text-input').delayedObserver(0.35, function() {
16
+ $(<%= "##{record_select_search_id}".to_json.html_safe %>).trigger("submit");});
17
+ <% end %>
18
+ //]]>
19
+ </script>
20
+
@@ -0,0 +1 @@
1
+ page.call 'RecordSelect.render_page', record_select_id, render_record_select(:partial => 'list', :locals => {:page => @page})
Binary file
Binary file
Binary file
@@ -0,0 +1,500 @@
1
+ if (typeof(Class) === 'undefined') {
2
+ /* Simple Inheritance
3
+ http://ejohn.org/blog/simple-javascript-inheritance/
4
+ */
5
+ (function(){
6
+ var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
7
+
8
+ // The base Class implementation (does nothing)
9
+ this.Class = function(){};
10
+
11
+ // Create a new Class that inherits from this class
12
+ Class.extend = function(prop) {
13
+ var _super = this.prototype;
14
+
15
+ // Instantiate a base class (but only create the instance,
16
+ // don't run the init constructor)
17
+ initializing = true;
18
+ var prototype = new this();
19
+ initializing = false;
20
+
21
+ // Copy the properties over onto the new prototype
22
+ for (var name in prop) {
23
+ // Check if we're overwriting an existing function
24
+ prototype[name] = typeof prop[name] == "function" &&
25
+ typeof _super[name] == "function" && fnTest.test(prop[name]) ?
26
+ (function(name, fn){
27
+ return function() {
28
+ var tmp = this._super;
29
+
30
+ // Add a new ._super() method that is the same method
31
+ // but on the super-class
32
+ this._super = _super[name];
33
+
34
+ // The method only need to be bound temporarily, so we
35
+ // remove it when we're done executing
36
+ var ret = fn.apply(this, arguments);
37
+ this._super = tmp;
38
+
39
+ return ret;
40
+ };
41
+ })(name, prop[name]) :
42
+ prop[name];
43
+ }
44
+
45
+ // The dummy class constructor
46
+ function Class() {
47
+ // All construction is actually done in the init method
48
+ if ( !initializing && this.init )
49
+ this.init.apply(this, arguments);
50
+ }
51
+
52
+ // Populate our constructed prototype object
53
+ Class.prototype = prototype;
54
+
55
+ // Enforce the constructor to be what we expect
56
+ Class.constructor = Class;
57
+
58
+ // And make this class extendable
59
+ Class.extend = arguments.callee;
60
+
61
+ return Class;
62
+ };
63
+ })();
64
+ };
65
+
66
+ /*
67
+ jQuery delayed observer
68
+ (c) 2007 - Maxime Haineault (max@centdessin.com)
69
+
70
+ Special thanks to Stephen Goguen & Tane Piper.
71
+
72
+ Slight modifications by Elliot Winkler
73
+ */
74
+
75
+ if (typeof(jQuery.fn.delayedObserver) === 'undefined') {
76
+ (function() {
77
+ var delayedObserverStack = [];
78
+ var observed;
79
+
80
+ function delayedObserverCallback(stackPos) {
81
+ observed = delayedObserverStack[stackPos];
82
+ if (observed.timer) return;
83
+
84
+ observed.timer = setTimeout(function(){
85
+ observed.timer = null;
86
+ observed.callback(observed.obj.val(), observed.obj);
87
+ }, observed.delay * 1000);
88
+
89
+ observed.oldVal = observed.obj.val();
90
+ }
91
+
92
+ // going by
93
+ // <http://www.cambiaresearch.com/c4/702b8cd1-e5b0-42e6-83ac-25f0306e3e25/Javascript-Char-Codes-Key-Codes.aspx>
94
+ // I think these codes only work when using keyup or keydown
95
+ function isNonPrintableKey(event) {
96
+ var code = event.keyCode;
97
+ return (
98
+ event.metaKey ||
99
+ (code >= 9 || code <= 16) || (code >= 27 && code <= 40) || (code >= 91 && code <= 93) || (code >= 112 && code <= 145)
100
+ );
101
+ }
102
+
103
+ jQuery.fn.extend({
104
+ delayedObserver:function(delay, callback){
105
+ $this = $(this);
106
+
107
+ delayedObserverStack.push({
108
+ obj: $this, timer: null, delay: delay,
109
+ oldVal: $this.val(), callback: callback
110
+ });
111
+
112
+ stackPos = delayedObserverStack.length-1;
113
+
114
+ $this.keyup(function(event) {
115
+ if (isNonPrintableKey(event)) return;
116
+ observed = delayedObserverStack[stackPos];
117
+ if (observed.obj.val() == observed.obj.oldVal) return;
118
+ else delayedObserverCallback(stackPos);
119
+ });
120
+ }
121
+ });
122
+ })();
123
+ };
124
+
125
+ $(document).ready(function() {
126
+ RecordSelect.document_loaded = true;
127
+ $('div.record-select * li.record a').live('ajax:before', function(event) {
128
+ var link = $(this);
129
+ if (link) {
130
+ if (RecordSelect.notify(link) == false) {
131
+ return false;
132
+ } else {
133
+ link.toggleClass("selected");
134
+ }
135
+ }
136
+ return true;
137
+ });
138
+ });
139
+
140
+ /**
141
+ Form.Element.AfterActivity = function(element, callback, delay) {
142
+ element = $(element);
143
+ if (!delay) delay = 0.25;
144
+ new Form.Element.Observer(element, delay, function(element, value) {
145
+ // TODO: display loading indicator
146
+ if (element.activity_timer) clearTimeout(element.activity_timer);
147
+ element.activity_timer = setTimeout(function() {
148
+ callback(element.value);
149
+ }, delay * 1000 + 50);
150
+ });
151
+ }
152
+ */
153
+
154
+ var RecordSelect = new Object();
155
+ RecordSelect.document_loaded = false;
156
+
157
+ RecordSelect.notify = function(item) {
158
+ var e = item.closest('.record-select-handler');
159
+ var onselect = e.get(0).onselect || e.attr('onselect');
160
+ if (typeof onselect != 'function') onselect = eval(onselect);
161
+ if (onselect)
162
+ {
163
+ try {
164
+ // .unescapeHTML() not implemented so far
165
+ var label = $.trim(item.find('label').first().html());
166
+ if (label.length == 0) {
167
+ label = item.html();
168
+ }
169
+ onselect(item.parent().attr('id').substr(2), label, e);
170
+ } catch(e) {
171
+ alert(e);
172
+ }
173
+ return false;
174
+ }
175
+ else return true;
176
+ }
177
+
178
+ RecordSelect.render_page = function(record_select_id, page) {
179
+ $('#' + record_select_id + ' ol').first().replaceWith(page);
180
+ };
181
+
182
+ RecordSelect.Abstract = Class.extend({
183
+ /**
184
+ * obj - the id or element that will anchor the recordselect to the page
185
+ * url - the url to run the recordselect
186
+ * options - ??? (check concrete classes)
187
+ */
188
+ init: function(obj, url, options) {
189
+ if (typeof(obj) == 'string') obj = '#' + obj;
190
+ this.obj = $(obj);
191
+ this.url = url;
192
+ this.options = options;
193
+ this.container;
194
+
195
+ if (RecordSelect.document_loaded) {
196
+ this.onload();
197
+ } else {
198
+ var _this = this; $(document).ready(function() { _this.onload(); })
199
+ }
200
+ },
201
+
202
+ /**
203
+ * Finish the setup - IE doesn't like doing certain things before the page loads
204
+ * --override--
205
+ */
206
+ onload: function() {},
207
+
208
+ /**
209
+ * the onselect event handler - when someone clicks on a record
210
+ * --override--
211
+ */
212
+ onselect: function(id, value) {
213
+ alert(id + ': ' + value);
214
+ },
215
+
216
+ /**
217
+ * opens the recordselect
218
+ */
219
+ open: function() {
220
+ if (this.is_open()) return;
221
+ var _this = this;
222
+ $.ajax({
223
+ url: this.url,
224
+ //type: "POST",
225
+ //data: options['params'],
226
+ //dataType: options.ajax_data_type,
227
+ success: function(data){
228
+ _this.container.html(data);
229
+ _this.show();
230
+ $(document.body).mousedown(jQuery.proxy(_this, "onbodyclick"));
231
+ }
232
+ });
233
+ },
234
+
235
+ /**
236
+ * positions and reveals the recordselect
237
+ */
238
+ show: function() {
239
+ var offset = this.obj.offset();
240
+ this.container.css('left', offset.left)
241
+ .css('top', (this.obj.height() + offset.top));
242
+
243
+ if (this._use_iframe_mask()) {
244
+ this.container.after('<iframe src="javascript:false;" class="record-select-mask" />');
245
+ var mask = this.container.next('iframe');
246
+ mask.css('left', this.container.css('left'))
247
+ .css('top', this.container.css('top'));
248
+ }
249
+
250
+ this.container.show();
251
+
252
+ if (this._use_iframe_mask()) {
253
+ var dimensions = this.container.children().first();
254
+ mask.css('width', dimensions.css('width'))
255
+ .css('height', dimensions.css('height'));
256
+ }
257
+ },
258
+
259
+ /**
260
+ * closes the recordselect by emptying the container
261
+ */
262
+ close: function() {
263
+ if (this._use_iframe_mask()) {
264
+ this.container.next('iframe').remove();
265
+ }
266
+
267
+ this.container.hide();
268
+ // hopefully by using remove() instead of innerHTML we won't leak memory
269
+ this.container.children().remove();
270
+ },
271
+
272
+ /**
273
+ * returns true/false for whether the recordselect is open
274
+ */
275
+ is_open: function() {
276
+ return (!($.trim(this.container.html()).length == 0))
277
+ },
278
+
279
+ /**
280
+ * when the user clicks outside the dropdown
281
+ */
282
+ onbodyclick: function(event) {
283
+ if (!this.is_open()) return;
284
+ if (this.container.has($(event.target)).length > 0) {
285
+ return;
286
+ } else {
287
+ this.close();
288
+ }
289
+ },
290
+
291
+ /**
292
+ * creates and initializes (and returns) the recordselect container
293
+ */
294
+ create_container: function() {
295
+ var e = $("<div />", {class: "record-select-container record-select-handler"});
296
+ e.css('display', 'none')
297
+ $(document.body).append(e);
298
+ e.get(0).onselect = $.proxy(this, "onselect")
299
+ return e;
300
+ },
301
+
302
+ onkeyup: function(event) {
303
+ if (!this.is_open()) return;
304
+ this.container.find('.text-input').val(this.obj.val()).trigger(event);
305
+ },
306
+
307
+ /**
308
+ * all the behavior to respond to a text field as a search box
309
+ */
310
+ _respond_to_text_field: function(text_field) {
311
+ // attach the events to start this party
312
+ text_field.focus($.proxy(this, 'open'));
313
+
314
+ // the autosearch event - needs to happen slightly late (keyup is later than keypress)
315
+ text_field.keyup($.proxy(this, 'onkeyup'));
316
+
317
+ // keyboard navigation, if available
318
+ if (this.onkeypress) {
319
+ text_field.keypress($.proxy(this, "onkeypress"));
320
+ }
321
+ },
322
+
323
+ _use_iframe_mask: function() {
324
+ return this.container.insertAdjacentHTML ? true : false;
325
+ }
326
+ });
327
+
328
+
329
+
330
+ /**
331
+ * Adds keyboard navigation to RecordSelect objects
332
+ */
333
+ $.extend(RecordSelect.Abstract.prototype, {
334
+ current: null,
335
+
336
+ /**
337
+ * keyboard navigation - where to intercept the keys is up to the concrete class
338
+ */
339
+ onkeypress: function(ev) {
340
+ var elem;
341
+ switch (ev.keyCode) {
342
+ case 38: //Event.KEY_UP
343
+ if (this.current && this.current.closest('.record-select')) elem = this.current.prev();
344
+ if (!elem) elem = this.container.find('ol li.record').last();
345
+ this.highlight(elem);
346
+ break;
347
+ case 40: //Event.KEY_DOWN
348
+ if (this.current && this.current.closest('.record-select')) elem = this.current.next();
349
+ if (!elem) elem = this.container.find('ol li.record').first();
350
+ this.highlight(elem);
351
+ break;
352
+ case 32: // Event.KEY_SPACE
353
+ case 13: // Event.KEY_RETURN
354
+ if (this.current) this.current.find('a').click();
355
+ break;
356
+ case 39: // Event.KEY_RIGHT
357
+ elem = this.container.find('li.pagination.next');
358
+ if (elem) elem.find('a').click();
359
+ break;
360
+ case 37: // Event.KEY_LEFT
361
+ elem = this.container.find('li.pagination.previous');
362
+ if (elem) elem.find('a').click();
363
+ break;
364
+ case 27: // Event.KEY_ESC
365
+ this.close();
366
+ break;
367
+ default:
368
+ return true;
369
+ }
370
+ ev.preventDefault(); // so "enter" doesn't submit the form, among other things(?)
371
+ },
372
+
373
+ /**
374
+ * moves the highlight to a new object
375
+ */
376
+ highlight: function(obj) {
377
+ if (this.current) this.current.removeClass('current');
378
+ this.current = $(obj);
379
+ obj.addClass('current');
380
+ }
381
+ });
382
+
383
+ /**
384
+ * Used by link_to_record_select
385
+ * The options hash should contain a onselect: key, with a javascript function as value
386
+ */
387
+ RecordSelect.Dialog = RecordSelect.Abstract.extend({
388
+ onload: function() {
389
+ this.container = this.create_container();
390
+ this.obj.click($.proxy(this, "toggle"));
391
+ if (this.onkeypress) this.obj.keypress($.proxy(this, 'onkeypress'));
392
+ },
393
+
394
+ onselect: function(id, value) {
395
+ if (this.options.onselect(id, value) != false) this.close();
396
+ },
397
+
398
+ toggle: function() {
399
+ if (this.is_open()) this.close();
400
+ else this.open();
401
+ }
402
+ });
403
+
404
+ /**
405
+ * Used by record_select_field helper
406
+ * The options hash may contain id: and label: keys, designating the current value
407
+ * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine.
408
+ */
409
+ RecordSelect.Single = RecordSelect.Abstract.extend({
410
+ onload: function() {
411
+ // initialize the container
412
+ this.container = this.create_container();
413
+ this.container.addClass('record-select-autocomplete');
414
+
415
+ // create the hidden input
416
+ this.obj.after('<input type="hidden" name="" value="" />');
417
+ this.hidden_input = this.obj.next();
418
+
419
+ // transfer the input name from the text input to the hidden input
420
+ this.hidden_input.attr('name', this.obj.attr('name'));
421
+ this.obj.attr('name', '');
422
+
423
+ // initialize the values
424
+ this.set(this.options.id, this.options.label);
425
+
426
+ this._respond_to_text_field(this.obj);
427
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
428
+ },
429
+
430
+ close: function() {
431
+ // if they close the dialog with the text field empty, then delete the id value
432
+ if (this.obj.val() == '') this.set('', '');
433
+
434
+ RecordSelect.Abstract.prototype.close.call(this);
435
+ },
436
+
437
+ onselect: function(id, value) {
438
+ if (this.options.onchange) this.options.onchange(id, value);
439
+ this.set(id, value);
440
+ this.close();
441
+ },
442
+
443
+ /**
444
+ * sets the id/label
445
+ */
446
+ set: function(id, label) {
447
+ // unescaped html missing for label
448
+ this.obj.val(label);
449
+ this.hidden_input.val(id);
450
+ }
451
+ });
452
+
453
+ /**
454
+ * Used by record_multi_select_field helper.
455
+ * Options:
456
+ * list - the id (or object) of the <ul> to contain the <li>s of selected entries
457
+ * current - an array of id:/label: keys designating the currently selected entries
458
+ */
459
+ RecordSelect.Multiple = RecordSelect.Abstract.extend({
460
+ onload: function() {
461
+ // initialize the container
462
+ this.container = this.create_container();
463
+ this.container.addClass('record-select-autocomplete');
464
+
465
+ // decide where the <li> entries should be placed
466
+ if (this.options.list) this.list_container = $(this.options.list);
467
+ else this.list_container = this.obj.next('ul');
468
+
469
+ // take the input name from the text input, and store it for this.add()
470
+ this.input_name = this.obj.attr('name');
471
+ this.obj.attr('name', '');
472
+
473
+ // initialize the list
474
+ for(var i = 0, length = this.options.current.length; i < length; i++) {
475
+ this.add(this.options.current[i].id, this.options.current[i].label);
476
+ }
477
+
478
+ this._respond_to_text_field(this.obj);
479
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
480
+ },
481
+
482
+ onselect: function(id, value) {
483
+ this.add(id, value);
484
+ },
485
+
486
+ /**
487
+ * Adds a record to the selected list
488
+ */
489
+ add: function(id, label) {
490
+ // return silently if this value has already been selected
491
+ if (this.list_container.has('input[value=' + id + ']').length > 0) return;
492
+
493
+ var entry = '<li>'
494
+ + '<a href="#" onclick="$(this).parent().remove(); return false;" class="remove">remove</a>'
495
+ + '<input type="hidden" name="' + this.input_name + '" value="' + id + '" />'
496
+ + '<label>' + label + '</label>'
497
+ + '</li>';
498
+ this.list_container.prepend(entry)
499
+ }
500
+ });