recordselect 3.2.1 → 3.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ en:
2
+ record_select:
3
+ next: "Next"
4
+ next_items: "Next %{count}"
5
+ previous: "Previous"
6
+ previous_items: "Previous %{count}"
7
+ records_found:
8
+ one: "1 %{model} found"
9
+ other: "%{count} %{model} found"
@@ -0,0 +1,13 @@
1
+ es:
2
+ record_select:
3
+ next: "Siguiente"
4
+ next_items:
5
+ one: "Siguente"
6
+ other: "%{count} siguentes"
7
+ previous: "Anterior"
8
+ previous_items:
9
+ one: "Anterior"
10
+ other: "%{count} anteriores"
11
+ records_found:
12
+ one: "1 %{model} encontrado"
13
+ other: "%{count} %{model} encontrados"
@@ -2,7 +2,7 @@ module RecordSelect
2
2
  module Version
3
3
  MAJOR = 3
4
4
  MINOR = 2
5
- PATCH = 1
5
+ PATCH = 2
6
6
 
7
7
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
8
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recordselect
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 3
8
8
  - 2
9
- - 1
10
- version: 3.2.1
9
+ - 2
10
+ version: 3.2.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Sergio Cambra
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2012-02-24 00:00:00 Z
20
+ date: 2012-02-29 00:00:00 Z
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
23
23
  version_requirements: &id001 !ruby/object:Gem::Requirement
@@ -88,34 +88,31 @@ extensions: []
88
88
  extra_rdoc_files:
89
89
  - README
90
90
  files:
91
- - app/views/record_select/_list.html.erb
92
- - app/views/record_select/_search.html.erb
93
- - app/views/record_select/browse.js.erb
94
- - app/views/record_select/_browse.html.erb
95
- - app/views/record_select/_list.html.erb~
96
91
  - app/assets/images/record_select/cross.gif
97
92
  - app/assets/images/record_select/next.gif
98
93
  - app/assets/images/record_select/previous.gif
99
94
  - app/assets/javascripts/jquery/record_select.js
100
- - app/assets/javascripts/jquery/record_select.js~
101
95
  - app/assets/javascripts/prototype/record_select.js
102
- - app/assets/javascripts/prototype/record_select.js~
103
96
  - app/assets/javascripts/record_select.js.erb
104
97
  - app/assets/stylesheets/record_select.css.erb
98
+ - app/views/record_select/_browse.html.erb
99
+ - app/views/record_select/_list.html.erb
100
+ - app/views/record_select/_search.html.erb
101
+ - app/views/record_select/browse.js.erb
102
+ - config/locales/en.yml
103
+ - config/locales/es.yml
105
104
  - lib/record_select.rb
106
- - lib/recordselect.rb
107
105
  - lib/record_select/actions.rb
108
106
  - lib/record_select/conditions.rb
109
107
  - lib/record_select/config.rb
110
- - lib/record_select/form_builder.rb
108
+ - lib/record_select/engine.rb
111
109
  - lib/record_select/extensions/active_record.rb
112
110
  - lib/record_select/extensions/localization.rb
113
111
  - lib/record_select/extensions/routing_mapper.rb
114
- - lib/record_select/extensions/localization.rb~
112
+ - lib/record_select/form_builder.rb
115
113
  - lib/record_select/helpers/record_select_helper.rb
116
- - lib/record_select/helpers/record_select_helper.rb~
117
114
  - lib/record_select/version.rb
118
- - lib/record_select/engine.rb
115
+ - lib/recordselect.rb
119
116
  - MIT-LICENSE
120
117
  - CHANGELOG
121
118
  - README
@@ -1,543 +0,0 @@
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) clearTimeout(observed.timer);
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
- var label = $.trim(item.find('label').first().text());
165
- if (!label) label = item.text();
166
- onselect(item.parent().attr('id').substr(2), label, e);
167
- } catch(e) {
168
- alert(e);
169
- }
170
- return false;
171
- }
172
- else return true;
173
- }
174
-
175
- RecordSelect.render_page = function(record_select_id, page) {
176
- $('#' + record_select_id + ' ol').first().replaceWith(page);
177
- };
178
-
179
- RecordSelect.Abstract = Class.extend({
180
- /**
181
- * obj - the id or element that will anchor the recordselect to the page
182
- * url - the url to run the recordselect
183
- * options - ??? (check concrete classes)
184
- */
185
- init: function(obj, url, options) {
186
- if (typeof(obj) == 'string') obj = '#' + obj;
187
- this.obj = $(obj);
188
- this.url = url;
189
- this.options = options;
190
- this.container;
191
- if (this.options.onchange && typeof this.options.onchange != 'function') {
192
- this.options.onchange = eval(this.options.onchange);
193
- }
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(), top = this.obj.height() + offset.top;
240
- this.container.show();
241
- this.container.css('left', offset.left);
242
- if (top + this.container.height() > $(window).height())
243
- this.container.css('bottom', $(window).height() - offset.top);
244
- else this.container.css('top', top);
245
-
246
- if (this._use_iframe_mask()) {
247
- this.container.after('<iframe src="javascript:false;" class="record-select-mask" />');
248
- var mask = this.container.next('iframe');
249
- mask.css('left', this.container.css('left'))
250
- .css('top', this.container.css('top'));
251
- }
252
-
253
- if (this._use_iframe_mask()) {
254
- var dimensions = this.container.children().first();
255
- mask.css('width', dimensions.css('width'))
256
- .css('height', dimensions.css('height'));
257
- }
258
- },
259
-
260
- /**
261
- * closes the recordselect by emptying the container
262
- */
263
- close: function() {
264
- if (this._use_iframe_mask()) {
265
- this.container.next('iframe').remove();
266
- }
267
-
268
- this.container.hide();
269
- // hopefully by using remove() instead of innerHTML we won't leak memory
270
- this.container.children().remove();
271
- },
272
-
273
- /**
274
- * returns true/false for whether the recordselect is open
275
- */
276
- is_open: function() {
277
- return (!($.trim(this.container.html()).length == 0))
278
- },
279
-
280
- /**
281
- * when the user clicks outside the dropdown
282
- */
283
- onbodyclick: function(event) {
284
- if (!this.is_open()) return;
285
- if (this.container.has($(event.target)).length > 0) {
286
- return;
287
- } else if (!this.obj.is(event.target)) {
288
- this.close();
289
- }
290
- },
291
-
292
- /**
293
- * creates and initializes (and returns) the recordselect container
294
- */
295
- create_container: function() {
296
- var e = $("<div />", {'class': "record-select-container record-select-handler"});
297
- e.css('display', 'none')
298
- $(document.body).append(e);
299
- e.get(0).onselect = $.proxy(this, "onselect")
300
- return e;
301
- },
302
-
303
- onkeyup: function(event) {
304
- if (!this.is_open()) return;
305
- this.container.find('.text-input').val(this.obj.val()).trigger(event);
306
- },
307
-
308
- /**
309
- * all the behavior to respond to a text field as a search box
310
- */
311
- _respond_to_text_field: function(text_field) {
312
- // attach the events to start this party
313
- text_field.focus($.proxy(this, 'open'));
314
-
315
- // the autosearch event - needs to happen slightly late (keyup is later than keypress)
316
- text_field.keyup($.proxy(this, 'onkeyup'));
317
-
318
- // keyboard navigation, if available
319
- if (this.onkeydown) {
320
- text_field.keydown($.proxy(this, "onkeydown"));
321
- }
322
- },
323
-
324
- _use_iframe_mask: function() {
325
- return this.container.insertAdjacentHTML ? true : false;
326
- }
327
- });
328
-
329
-
330
-
331
- /**
332
- * Adds keyboard navigation to RecordSelect objects
333
- */
334
- $.extend(RecordSelect.Abstract.prototype, {
335
- current: null,
336
-
337
- /**
338
- * keyboard navigation - where to intercept the keys is up to the concrete class
339
- */
340
- onkeydown: function(ev) {
341
- var elem;
342
- switch (ev.keyCode) {
343
- case 38: //Event.KEY_UP
344
- if (this.current && this.current.closest('.record-select').length && this.current.closest('html').length) elem = this.current.prev();
345
- if (!elem) elem = this.container.find('ol li.record').last();
346
- this.highlight(elem);
347
- break;
348
- case 40: //Event.KEY_DOWN
349
- if (this.current && this.current.closest('.record-select').length && this.current.closest('html').length) elem = this.current.next();
350
- if (!elem) elem = this.container.find('ol li.record').first();
351
- this.highlight(elem);
352
- break;
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
- * and field_name: key, where value will be set as name of the input field.
409
- */
410
- RecordSelect.Single = RecordSelect.Abstract.extend({
411
- onload: function() {
412
- // initialize the container
413
- this.container = this.create_container();
414
- this.container.addClass('record-select-autocomplete');
415
-
416
- // create the hidden input
417
- this.obj.after('<input type="hidden" name="" value="" />');
418
- this.hidden_input = this.obj.next();
419
-
420
- // transfer the input name from the text input to the hidden input
421
- this.hidden_input.attr('name', this.obj.attr('name'));
422
- this.obj.attr('name', this.options.field_name || '');
423
-
424
- // initialize the values
425
- this.set(this.options.id, this.options.label);
426
-
427
- this._respond_to_text_field(this.obj);
428
- if (this.obj.focused) this.open(); // if it was focused before we could attach observers
429
- },
430
-
431
- close: function() {
432
- // if they close the dialog with the text field empty, then delete the id value
433
- if (this.obj.val() == '') this.set('', '');
434
-
435
- RecordSelect.Abstract.prototype.close.call(this);
436
- },
437
-
438
- onselect: function(id, value) {
439
- this.set(id, value);
440
- if (this.options.onchange) this.options.onchange.call(this, id, value);
441
- this.obj.trigger("recordselect:change", [id, value]);
442
- this.close();
443
- },
444
-
445
- /**
446
- * sets the id/label
447
- */
448
- set: function(id, label) {
449
- // unescaped html missing for label
450
- this.obj.val(label);
451
- this.hidden_input.val(id);
452
- }
453
- });
454
-
455
- /**
456
- * Used by record_select_autocomplete helper
457
- * The options hash may contain label: key, designating the current value
458
- * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine.
459
- */
460
- RecordSelect.Autocomplete = RecordSelect.Abstract.extend({
461
- onload: function() {
462
- // initialize the container
463
- this.container = this.create_container();
464
- this.container.addClass('record-select-autocomplete');
465
-
466
- // initialize the values
467
- this.set(this.options.label);
468
-
469
- this._respond_to_text_field(this.obj);
470
- if (this.obj.focused) this.open(); // if it was focused before we could attach observers
471
- },
472
-
473
- close: function() {
474
- // if they close the dialog with the text field empty, then delete the id value
475
- if (this.obj.val() == '') this.set('');
476
-
477
- RecordSelect.Abstract.prototype.close.call(this);
478
- },
479
-
480
- onselect: function(id, value) {
481
- this.set(value);
482
- if (this.options.onchange) this.options.onchange.call(this, id, value);
483
- this.obj.trigger("recordselect:change", [id, value]);
484
- this.close();
485
- },
486
-
487
- /**
488
- * sets the id/label
489
- */
490
- set: function(label) {
491
- // unescaped html missing for label
492
- this.obj.val(label);
493
- }
494
- });
495
-
496
- /**
497
- * Used by record_multi_select_field helper.
498
- * Options:
499
- * list - the id (or object) of the <ul> to contain the <li>s of selected entries
500
- * current - an array of id:/label: keys designating the currently selected entries
501
- */
502
- RecordSelect.Multiple = RecordSelect.Abstract.extend({
503
- onload: function() {
504
- // initialize the container
505
- this.container = this.create_container();
506
- this.container.addClass('record-select-autocomplete');
507
-
508
- // decide where the <li> entries should be placed
509
- if (this.options.list) this.list_container = $(this.options.list);
510
- else this.list_container = this.obj.next('ul');
511
-
512
- // take the input name from the text input, and store it for this.add()
513
- this.input_name = this.obj.attr('name');
514
- this.obj.attr('name', '');
515
-
516
- // initialize the list
517
- for(var i = 0, length = this.options.current.length; i < length; i++) {
518
- this.add(this.options.current[i].id, this.options.current[i].label);
519
- }
520
-
521
- this._respond_to_text_field(this.obj);
522
- if (this.obj.focused) this.open(); // if it was focused before we could attach observers
523
- },
524
-
525
- onselect: function(id, value) {
526
- this.add(id, value);
527
- },
528
-
529
- /**
530
- * Adds a record to the selected list
531
- */
532
- add: function(id, label) {
533
- // return silently if this value has already been selected
534
- if (this.list_container.has('input[value=' + id + ']').length > 0) return;
535
-
536
- var entry = '<li>'
537
- + '<a href="#" onclick="$(this).parent().remove(); return false;" class="remove">remove</a>'
538
- + '<input type="hidden" name="' + this.input_name + '" value="' + id + '" />'
539
- + '<label>' + label + '</label>'
540
- + '</li>';
541
- this.list_container.prepend(entry)
542
- }
543
- });
@@ -1,419 +0,0 @@
1
- document.observe("dom:loaded", function() {
2
- RecordSelect.document_loaded = true;
3
- document.on('ajax:before', 'div.record-select * li.record a', function(event) {
4
- var link = event.findElement();
5
- if (link) {
6
- if (RecordSelect.notify(link) == false) {
7
- event.stop();
8
- } else {
9
- link.toggleClassName("selected");
10
- }
11
- }
12
- return true;
13
- });
14
- });
15
-
16
- Form.Element.AfterActivity = function(element, callback, delay) {
17
- element = $(element);
18
- if (!delay) delay = 0.25;
19
- new Form.Element.Observer(element, delay, function(element, value) {
20
- // TODO: display loading indicator
21
- if (element.activity_timer) clearTimeout(element.activity_timer);
22
- element.activity_timer = setTimeout(function() {
23
- callback(element.value);
24
- }, delay * 1000 + 50);
25
- });
26
- }
27
-
28
- var RecordSelect = new Object();
29
- RecordSelect.document_loaded = false;
30
-
31
- RecordSelect.notify = function(item) {
32
- var e = Element.up(item, '.record-select-handler');
33
- var onselect = e.onselect || e.getAttribute('onselect');
34
- if (typeof onselect != 'function') onselect = eval(onselect);
35
- if (onselect)
36
- {
37
- try {
38
- onselect(item.parentNode.id.substr(2), (item.down('label') || item).innerHTML.unescapeHTML(), e);
39
- } catch(e) {
40
- alert(e);
41
- }
42
- return false;
43
- }
44
- else return true;
45
- };
46
-
47
- RecordSelect.render_page = function(record_select_id, page) {
48
- var page_element = $$('#' + record_select_id + ' ol')[0];
49
- if (page_element) Element.replace(page_element, page);
50
- };
51
-
52
- RecordSelect.Abstract = Class.create();
53
- Object.extend(RecordSelect.Abstract.prototype, {
54
- /**
55
- * obj - the id or element that will anchor the recordselect to the page
56
- * url - the url to run the recordselect
57
- * options - ??? (check concrete classes)
58
- */
59
- initialize: function(obj, url, options) {
60
- this.obj = $(obj);
61
- this.url = url;
62
- this.options = options;
63
- this.container;
64
- if (this.options.onchange && typeof this.options.onchange != 'function') {
65
- this.options.onchange = eval(this.options.onchange);
66
- }
67
-
68
- if (RecordSelect.document_loaded) this.onload();
69
- else Event.observe(window, 'load', this.onload.bind(this));
70
- },
71
-
72
- /**
73
- * Finish the setup - IE doesn't like doing certain things before the page loads
74
- * --override--
75
- */
76
- onload: function() {},
77
-
78
- /**
79
- * the onselect event handler - when someone clicks on a record
80
- * --override--
81
- */
82
- onselect: function(id, value) {
83
- alert(id + ': ' + value);
84
- },
85
-
86
- /**
87
- * opens the recordselect
88
- */
89
- open: function() {
90
- if (this.is_open()) return;
91
-
92
- new Ajax.Updater(this.container, this.url, {
93
- method: 'get',
94
- evalScripts: true,
95
- asynchronous: true,
96
- onComplete: function() {
97
- this.show();
98
- // needs to be mousedown so the event doesn't get canceled by other code (see issue #26)
99
- Element.observe(document.body, 'mousedown', this.onbodyclick.bindAsEventListener(this));
100
- }.bind(this)
101
- });
102
- },
103
-
104
- /**
105
- * positions and reveals the recordselect
106
- */
107
- show: function() {
108
- var offset = Position.cumulativeOffset(this.obj),
109
- top = Element.getHeight(this.obj) + offset[1],
110
- window_height = document.viewport.getHeight();
111
- this.container.style.left = offset[0] + 'px';
112
- if (top + Element.getHeight(this.container) > window_height)
113
- this.container.style.bottom = (window_height - offset[1]) + 'px';
114
- else this.container.style.top = top + 'px';
115
-
116
- if (this._use_iframe_mask()) {
117
- this.container.insertAdjacentHTML('afterEnd', '<iframe src="javascript:false;" class="record-select-mask" />');
118
- var mask = this.container.next('iframe');
119
- mask.style.left = this.container.style.left;
120
- mask.style.top = this.container.style.top;
121
- }
122
-
123
- this.container.show();
124
-
125
- if (this._use_iframe_mask()) {
126
- var dimensions = this.container.immediateDescendants().first().getDimensions();
127
- mask.style.width = dimensions.width + 'px';
128
- mask.style.height = dimensions.height + 'px';
129
- }
130
- },
131
-
132
- /**
133
- * closes the recordselect by emptying the container
134
- */
135
- close: function() {
136
- if (this._use_iframe_mask()) {
137
- this.container.next('iframe').remove();
138
- }
139
-
140
- this.container.hide();
141
- // hopefully by using remove() instead of innerHTML we won't leak memory
142
- this.container.immediateDescendants().invoke('remove');
143
- },
144
-
145
- /**
146
- * returns true/false for whether the recordselect is open
147
- */
148
- is_open: function() {
149
- return (!this.container.innerHTML.blank())
150
- },
151
-
152
- /**
153
- * when the user clicks outside the dropdown
154
- */
155
- onbodyclick: function(ev) {
156
- if (!this.is_open()) return;
157
- var elem = $(Event.element(ev));
158
- var ancestors = elem.ancestors();
159
- ancestors.push(elem);
160
- if (ancestors.include(this.container) || ancestors.include(this.obj)) return;
161
- this.close();
162
- },
163
-
164
- /**
165
- * creates and initializes (and returns) the recordselect container
166
- */
167
- create_container: function() {
168
- new Insertion.Bottom(document.body, '<div class="record-select-container record-select-handler"></div>');
169
- e = document.body.childNodes[document.body.childNodes.length - 1];
170
- e.onselect = this.onselect.bind(this);
171
- e.style.display = 'none';
172
-
173
- return $(e);
174
- },
175
-
176
- /**
177
- * all the behavior to respond to a text field as a search box
178
- */
179
- _respond_to_text_field: function(text_field) {
180
- // attach the events to start this party
181
- text_field.observe('focus', this.open.bind(this));
182
-
183
- // the autosearch event - needs to happen slightly late (keyup is later than keypress)
184
- text_field.observe('keyup', function() {
185
- if (!this.is_open()) return;
186
- this.container.down('.text-input').value = text_field.value;
187
- }.bind(this));
188
-
189
- // keyboard navigation, if available
190
- if (this.onkeypress) {
191
- text_field.observe('keypress', this.onkeypress.bind(this));
192
- }
193
- },
194
-
195
- _use_iframe_mask: function() {
196
- return this.container.insertAdjacentHTML ? true : false;
197
- }
198
- });
199
-
200
- /**
201
- * Adds keyboard navigation to RecordSelect objects
202
- */
203
- Object.extend(RecordSelect.Abstract.prototype, {
204
- current: null,
205
-
206
- /**
207
- * keyboard navigation - where to intercept the keys is up to the concrete class
208
- */
209
- onkeypress: function(ev) {
210
- var elem;
211
- switch (ev.keyCode) {
212
- case Event.KEY_UP:
213
- if (this.current && this.current.up('.record-select')) elem = this.current.previous();
214
- if (!elem) elem = this.container.getElementsBySelector('ol li.record').last();
215
- this.highlight(elem);
216
- break;
217
- case Event.KEY_DOWN:
218
- if (this.current && this.current.up('.record-select')) elem = this.current.next();
219
- if (!elem) elem = this.container.getElementsBySelector('ol li.record').first();
220
- this.highlight(elem);
221
- break;
222
- case Event.KEY_SPACE:
223
- case Event.KEY_RETURN:
224
- if (this.current) this.current.down('a').onclick();
225
- break;
226
- case Event.KEY_RIGHT:
227
- elem = this.container.down('li.pagination.next');
228
- if (elem) elem.down('a').onclick();
229
- break;
230
- case Event.KEY_LEFT:
231
- elem = this.container.down('li.pagination.previous');
232
- if (elem) elem.down('a').onclick();
233
- break;
234
- case Event.KEY_ESC:
235
- this.close();
236
- break;
237
- default:
238
- return;
239
- }
240
- Event.stop(ev); // so "enter" doesn't submit the form, among other things(?)
241
- },
242
-
243
- /**
244
- * moves the highlight to a new object
245
- */
246
- highlight: function(obj) {
247
- if (this.current) this.current.removeClassName('current');
248
- this.current = $(obj);
249
- obj.addClassName('current');
250
- }
251
- });
252
-
253
- /**
254
- * Used by link_to_record_select
255
- * The options hash should contain a onselect: key, with a javascript function as value
256
- */
257
- RecordSelect.Dialog = Class.create();
258
- RecordSelect.Dialog.prototype = Object.extend(new RecordSelect.Abstract(), {
259
- onload: function() {
260
- this.container = this.create_container();
261
- this.obj.observe('click', this.toggle.bind(this));
262
-
263
- if (this.onkeypress) this.obj.observe('keypress', this.onkeypress.bind(this));
264
- },
265
-
266
- onselect: function(id, value) {
267
- if (this.options.onselect(id, value) != false) this.close();
268
- },
269
-
270
- toggle: function() {
271
- if (this.is_open()) this.close();
272
- else this.open();
273
- }
274
- });
275
-
276
- /**
277
- * Used by record_select_field helper
278
- * The options hash may contain id: and label: keys, designating the current value
279
- * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine
280
- * and field_name: key, where value will be set as name of the input field.
281
- */
282
- RecordSelect.Single = Class.create();
283
- RecordSelect.Single.prototype = Object.extend(new RecordSelect.Abstract(), {
284
- onload: function() {
285
- // initialize the container
286
- this.container = this.create_container();
287
- this.container.addClassName('record-select-autocomplete');
288
-
289
- // create the hidden input
290
- new Insertion.After(this.obj, '<input type="hidden" name="" value="" />')
291
- this.hidden_input = this.obj.next();
292
-
293
- // transfer the input name from the text input to the hidden input
294
- this.hidden_input.name = this.obj.name;
295
- this.obj.name = this.options.field_name || '';
296
-
297
- // initialize the values
298
- this.set(this.options.id, this.options.label);
299
-
300
- this._respond_to_text_field(this.obj);
301
- if (this.obj.focused) this.open(); // if it was focused before we could attach observers
302
- },
303
-
304
- close: function() {
305
- // if they close the dialog with the text field empty, then delete the id value
306
- if (this.obj.value == '') this.set('', '');
307
-
308
- RecordSelect.Abstract.prototype.close.call(this);
309
- },
310
-
311
- onselect: function(id, value) {
312
- this.set(id, value);
313
- if (this.options.onchange) this.options.onchange.call(this, id, value);
314
- this.obj.fire('recordselect:change', {"id": id, "label": value});
315
- this.close();
316
- },
317
-
318
- /**
319
- * sets the id/label
320
- */
321
- set: function(id, label) {
322
- this.obj.value = label.unescapeHTML();
323
- this.hidden_input.value = id;
324
- }
325
- });
326
-
327
- /**
328
- * Used by record_select_autocomplete helper
329
- * The options hash may contain label: key, designating the current value
330
- * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine.
331
- */
332
- RecordSelect.Autocomplete = Class.create();
333
- RecordSelect.Autocomplete.prototype = Object.extend(new RecordSelect.Abstract(), {
334
- onload: function() {
335
- // initialize the container
336
- this.container = this.create_container();
337
- this.container.addClassName('record-select-autocomplete');
338
-
339
- // initialize the values
340
- this.set(this.options.label);
341
-
342
- this._respond_to_text_field(this.obj);
343
- if (this.obj.focused) this.open(); // if it was focused before we could attach observers
344
- },
345
-
346
- close: function() {
347
- // if they close the dialog with the text field empty, then delete the id value
348
- if (this.obj.value == '') this.set('', '');
349
-
350
- RecordSelect.Abstract.prototype.close.call(this);
351
- },
352
-
353
- onselect: function(id, value) {
354
- this.set(value);
355
- if (this.options.onchange) this.options.onchange.call(this, id, value);
356
- this.obj.fire('recordselect:change', {"id": id, "label": value});
357
- this.close();
358
- },
359
-
360
- /**
361
- * sets the id/label
362
- */
363
- set: function(label) {
364
- this.obj.value = label.unescapeHTML();
365
- }
366
- });
367
-
368
- /**
369
- * Used by record_multi_select_field helper.
370
- * Options:
371
- * list - the id (or object) of the <ul> to contain the <li>s of selected entries
372
- * current - an array of id:/label: keys designating the currently selected entries
373
- */
374
- RecordSelect.Multiple = Class.create();
375
- RecordSelect.Multiple.prototype = Object.extend(new RecordSelect.Abstract(), {
376
- onload: function() {
377
- // initialize the container
378
- this.container = this.create_container();
379
- this.container.addClassName('record-select-autocomplete');
380
-
381
- // decide where the <li> entries should be placed
382
- if (this.options.list) this.list_container = $(this.options.list);
383
- else this.list_container = this.obj.next('ul');
384
-
385
- // take the input name from the text input, and store it for this.add()
386
- this.input_name = this.obj.name;
387
- this.obj.name = '';
388
-
389
- // initialize the list
390
- $A(this.options.current).each(function(c) {
391
- this.add(c.id, c.label);
392
- }.bind(this));
393
-
394
- this._respond_to_text_field(this.obj);
395
- if (this.obj.focused) this.open(); // if it was focused before we could attach observers
396
- },
397
-
398
- onselect: function(id, value) {
399
- this.add(id, value);
400
- },
401
-
402
- /**
403
- * Adds a record to the selected list
404
- */
405
- add: function(id, label) {
406
- // return silently if this value has already been selected
407
- var already_selected = this.list_container.getElementsBySelector('input').any(function(i) {
408
- return i.value == id
409
- });
410
- if (already_selected) return;
411
-
412
- var entry = '<li>'
413
- + '<a href="#" onclick="$(this.parentNode).remove(); return false;" class="remove">remove</a>'
414
- + '<input type="hidden" name="' + this.input_name + '" value="' + id + '" />'
415
- + '<label>' + label + '</label>'
416
- + '</li>';
417
- new Insertion.Top(this.list_container, entry);
418
- }
419
- });
@@ -1,31 +0,0 @@
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_(:records_found, :count => page.pager.count,
10
- :model => record_select_config.model.model_name.human.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_items,
14
- :count => 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_items, :count => 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>
@@ -1,14 +0,0 @@
1
- # Provides a simple pass-through localizer for RecordSelect. If you want
2
- # to localize RS, you need to override this method and route it to your
3
- # own system.
4
- class Object
5
- def rs_(string_to_localize, *args)
6
- args.empty? ? string_to_localize : (sprintf string_to_localize, *args)
7
- unless key.blank?
8
- text = I18n.translate "#{key}", {:scope => [:record_select], :default => key.is_a?(String) ? key : key.to_s.titleize}.merge(options)
9
- # text = nil if text.include?('translation missing:')
10
- end
11
- text ||= key
12
- text
13
- end
14
- end
@@ -1,209 +0,0 @@
1
- module RecordSelectHelper
2
- # Adds a link on the page that toggles a RecordSelect widget from the given controller.
3
- #
4
- # *Options*
5
- # +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.
6
- # +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.
7
- def link_to_record_select(name, controller, options = {})
8
- options[:params] ||= {}
9
- options[:params].merge!(:controller => controller, :action => :browse)
10
- options[:onselect] = "function(id, label) {#{options[:onselect]}}" if options[:onselect]
11
- options[:html] ||= {}
12
- options[:html][:id] ||= "rs_#{rand(9999)}"
13
-
14
- assert_controller_responds(options[:params][:controller])
15
-
16
- html = link_to_function(name, '', options[:html])
17
- html << javascript_tag("new RecordSelect.Dialog(#{options[:html][:id].to_json}, #{url_for(options[:params].merge(:escape => false)).to_json}, {onselect: #{options[:onselect] || ''}})")
18
-
19
- return html
20
- end
21
-
22
- # Adds a RecordSelect-based form field. The field submits the record's id using a hidden input.
23
- #
24
- # *Arguments*
25
- # +name+:: the input name that will be used to submit the selected record's id.
26
- # +current+:: the currently selected object. provide a new record if there're none currently selected and you have not passed the optional :controller argument.
27
- #
28
- # *Options*
29
- # +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)
30
- # +params+:: A hash of extra URL parameters
31
- # +id+:: The id to use for the input. Defaults based on the input's name.
32
- # +field_name+:: The name to use for the text input. Defaults to '', so field is not submitted.
33
- # +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!).
34
- def record_select_field(name, current, options = {})
35
- options[:controller] ||= current.class.to_s.pluralize.underscore
36
- options[:params] ||= {}
37
- options[:id] ||= name.gsub(/[\[\]]/, '_')
38
-
39
- id = options.delete(:id)
40
- html = text_field_tag(name, nil, :autocomplete => 'off', :id => id, :class => options.delete(:class), :onfocus => "this.focused=true", :onblur => "this.focused=false")
41
-
42
- controller = assert_controller_responds(options[:controller])
43
-
44
- options[:id] = options[:label] = ''
45
- if current and not current.new_record?
46
- options[:id] = current.id
47
- options[:label] = label_for_field(current, controller)
48
- end
49
-
50
- url = url_for({:action => :browse, :controller => options.delete(:controller), :escape => false}.merge(options.delete(:params)))
51
- html << javascript_tag("new RecordSelect.Single(#{id.to_json}, #{url.to_json}, #{options.to_json});")
52
-
53
- return html
54
- end
55
-
56
- # Adds a RecordSelect-based form field. The field is autocompleted.
57
- #
58
- # *Arguments*
59
- # +name+:: the input name that will be used to submit the selected value.
60
- # +current+:: the current object. provide a new record if there're none currently selected and you have not passed the optional :controller argument.
61
- #
62
- # *Options*
63
- # +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)
64
- # +params+:: A hash of extra URL parameters
65
- # +id+:: The id to use for the input. Defaults based on the input's name.
66
- # +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!).
67
- def record_select_autocomplete(name, current, options = {})
68
- options[:controller] ||= current.class.to_s.pluralize.underscore
69
- options[:params] ||= {}
70
- options[:id] ||= name.gsub(/[\[\]]/, '_')
71
-
72
- html = text_field_tag(name, nil, :autocomplete => 'off', :id => options.delete(:id), :class => options.delete(:class), :onfocus => "this.focused=true", :onblur => "this.focused=false")
73
-
74
- controller = assert_controller_responds(options[:controller])
75
-
76
- options[:label] = ''
77
- if current and not current.new_record?
78
- options[:label] = label_for_field(current, controller)
79
- end
80
-
81
- url = url_for({:action => :browse, :controller => options.delete(:controller), :escape => false}.merge(options.delete(:params)))
82
- html << javascript_tag("new RecordSelect.Autocomplete(#{options[:id].to_json}, #{url.to_json}, #{options.to_json});")
83
-
84
- return html
85
- end
86
-
87
- # Assists with the creation of an observer for the :onchange option of the record_select_field method.
88
- # Currently only supports building an Ajax.Request based on the id of the selected record.
89
- #
90
- # options[:url] should be a hash with all the necessary options *except* :id. that parameter
91
- # will be provided based on the selected record.
92
- #
93
- # Question: if selecting users, what's more likely?
94
- # /users/5/categories
95
- # /categories?user_id=5
96
- def record_select_observer(options = {})
97
- fn = ""
98
- fn << "function(id, value) {"
99
- fn << "var url = #{url_for(options[:url].merge(:id => ":id:")).to_json}.replace(/:id:/, id);"
100
- fn << "new Ajax.Request(url);"
101
- fn << "}"
102
- end
103
-
104
- # Adds a RecordSelect-based form field for multiple selections. The values submit using a list of hidden inputs.
105
- #
106
- # *Arguments*
107
- # +name+:: the input name that will be used to submit the selected records' ids. empty brackets will be appended to the name.
108
- # +current+:: pass a collection of existing associated records
109
- #
110
- # *Options*
111
- # +controller+:: The controller configured to provide the result set.
112
- # +params+:: A hash of extra URL parameters
113
- # +id+:: The id to use for the input. Defaults based on the input's name.
114
- def record_multi_select_field(name, current, options = {})
115
- options[:controller] ||= current.first.class.to_s.pluralize.underscore
116
- options[:params] ||= {}
117
- options[:id] ||= name.gsub(/[\[\]]/, '_')
118
-
119
- controller = assert_controller_responds(options[:controller])
120
-
121
- # js identifier so we can talk to it.
122
- widget = "rs_%s" % name.gsub(/[\[\]]/, '_').chomp('_')
123
-
124
- current = current.inject([]) { |memo, record| memo.push({:id => record.id, :label => label_for_field(record, controller)}) }
125
-
126
- url = url_for({:action => :browse, :controller => options[:controller], :escape => false}.merge(options[:params]))
127
-
128
- html = text_field_tag("#{name}[]", nil, :autocomplete => 'off', :id => options[:id], :class => options[:class], :onfocus => "this.focused=true", :onblur => "this.focused=false")
129
- html << content_tag('ul', '', :class => 'record-select-list');
130
- html << javascript_tag("#{widget} = new RecordSelect.Multiple(#{options[:id].to_json}, #{url.to_json}, {current: #{current.to_json}});")
131
-
132
- return html
133
- end
134
-
135
- # A helper to render RecordSelect partials
136
- def render_record_select(options = {}) #:nodoc:
137
- controller.send(:render_record_select, options) do |options|
138
- render options
139
- end
140
- end
141
-
142
- # Provides view access to the RecordSelect configuration
143
- def record_select_config #:nodoc:
144
- controller.send :record_select_config
145
- end
146
-
147
- # The id of the RecordSelect widget for the given controller.
148
- def record_select_id(controller = nil) #:nodoc:
149
- controller ||= params[:controller]
150
- "record-select-#{controller.gsub('/', '_')}"
151
- end
152
-
153
- def record_select_search_id(controller = nil) #:nodoc:
154
- "#{record_select_id(controller)}-search"
155
- end
156
-
157
- private
158
- # render the record using the renderer and add a link to select the record
159
- def render_record_in_list(record, controller_path)
160
- text = render_record_from_config(record)
161
- if record_select_config.link?
162
- url_options = {:controller => controller_path, :action => :select, :id => record.id, :escape => false}
163
- link_to text, url_options, :method => :post, :remote => true, :class => ''
164
- else
165
- text
166
- end
167
- end
168
-
169
-
170
- # uses renderer (defaults to record_select_config.label) to determine how the given record renders.
171
- def render_record_from_config(record, renderer = record_select_config.label)
172
- case renderer
173
- when Symbol, String
174
- # return full-html from the named partial
175
- render :partial => renderer.to_s, :locals => {:record => record}
176
-
177
- when Proc
178
- # return an html-cleaned descriptive string
179
- h renderer.call(record)
180
- end
181
- end
182
-
183
- # uses the result of render_record_from_config to snag an appropriate record label
184
- # to display in a field.
185
- #
186
- # if given a controller, searches for a partial in its views path
187
- def label_for_field(record, controller = self.controller)
188
- renderer = controller.record_select_config.label
189
- case renderer
190
- when Symbol, String
191
- # find the <label> element and grab its innerHTML
192
- description = render_record_from_config(record, File.join(controller.controller_path, renderer.to_s))
193
- description.match(/<label[^>]*>(.*)<\/label>/)[1]
194
-
195
- when Proc
196
- # just return the string
197
- render_record_from_config(record, renderer)
198
- end
199
- end
200
-
201
- def assert_controller_responds(controller_name)
202
- controller_name = "#{controller_name.camelize}Controller"
203
- controller = controller_name.constantize
204
- unless controller.uses_record_select?
205
- raise "#{controller_name} has not been configured to use RecordSelect."
206
- end
207
- controller
208
- end
209
- end