recordselect_vho 3.0.205 → 3.0.206

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.
Binary file
Binary file
Binary file
@@ -0,0 +1,499 @@
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
+ $(document).on('ajax:before', 'div.record-select * li.record a', 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 13: // Event.KEY_RETURN
353
+ if (this.current) this.current.find('a').click();
354
+ break;
355
+ case 39: // Event.KEY_RIGHT
356
+ elem = this.container.find('li.pagination.next');
357
+ if (elem) elem.find('a').click();
358
+ break;
359
+ case 37: // Event.KEY_LEFT
360
+ elem = this.container.find('li.pagination.previous');
361
+ if (elem) elem.find('a').click();
362
+ break;
363
+ case 27: // Event.KEY_ESC
364
+ this.close();
365
+ break;
366
+ default:
367
+ return true;
368
+ }
369
+ ev.preventDefault(); // so "enter" doesn't submit the form, among other things(?)
370
+ },
371
+
372
+ /**
373
+ * moves the highlight to a new object
374
+ */
375
+ highlight: function(obj) {
376
+ if (this.current) this.current.removeClass('current');
377
+ this.current = $(obj);
378
+ obj.addClass('current');
379
+ }
380
+ });
381
+
382
+ /**
383
+ * Used by link_to_record_select
384
+ * The options hash should contain a onselect: key, with a javascript function as value
385
+ */
386
+ RecordSelect.Dialog = RecordSelect.Abstract.extend({
387
+ onload: function() {
388
+ this.container = this.create_container();
389
+ this.obj.click($.proxy(this, "toggle"));
390
+ if (this.onkeypress) this.obj.keypress($.proxy(this, 'onkeypress'));
391
+ },
392
+
393
+ onselect: function(id, value) {
394
+ if (this.options.onselect(id, value) != false) this.close();
395
+ },
396
+
397
+ toggle: function() {
398
+ if (this.is_open()) this.close();
399
+ else this.open();
400
+ }
401
+ });
402
+
403
+ /**
404
+ * Used by record_select_field helper
405
+ * The options hash may contain id: and label: keys, designating the current value
406
+ * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine.
407
+ */
408
+ RecordSelect.Single = RecordSelect.Abstract.extend({
409
+ onload: function() {
410
+ // initialize the container
411
+ this.container = this.create_container();
412
+ this.container.addClass('record-select-autocomplete');
413
+
414
+ // create the hidden input
415
+ this.obj.after('<input type="hidden" name="" value="" />');
416
+ this.hidden_input = this.obj.next();
417
+
418
+ // transfer the input name from the text input to the hidden input
419
+ this.hidden_input.attr('name', this.obj.attr('name'));
420
+ this.obj.attr('name', '');
421
+
422
+ // initialize the values
423
+ this.set(this.options.id, this.options.label);
424
+
425
+ this._respond_to_text_field(this.obj);
426
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
427
+ },
428
+
429
+ close: function() {
430
+ // if they close the dialog with the text field empty, then delete the id value
431
+ if (this.obj.val() == '') this.set('', '');
432
+
433
+ RecordSelect.Abstract.prototype.close.call(this);
434
+ },
435
+
436
+ onselect: function(id, value) {
437
+ if (this.options.onchange) this.options.onchange(id, value);
438
+ this.set(id, value);
439
+ this.close();
440
+ },
441
+
442
+ /**
443
+ * sets the id/label
444
+ */
445
+ set: function(id, label) {
446
+ // unescaped html missing for label
447
+ this.obj.val(label);
448
+ this.hidden_input.val(id);
449
+ }
450
+ });
451
+
452
+ /**
453
+ * Used by record_multi_select_field helper.
454
+ * Options:
455
+ * list - the id (or object) of the <ul> to contain the <li>s of selected entries
456
+ * current - an array of id:/label: keys designating the currently selected entries
457
+ */
458
+ RecordSelect.Multiple = RecordSelect.Abstract.extend({
459
+ onload: function() {
460
+ // initialize the container
461
+ this.container = this.create_container();
462
+ this.container.addClass('record-select-autocomplete');
463
+
464
+ // decide where the <li> entries should be placed
465
+ if (this.options.list) this.list_container = $(this.options.list);
466
+ else this.list_container = this.obj.next('ul');
467
+
468
+ // take the input name from the text input, and store it for this.add()
469
+ this.input_name = this.obj.attr('name');
470
+ this.obj.attr('name', '');
471
+
472
+ // initialize the list
473
+ for(var i = 0, length = this.options.current.length; i < length; i++) {
474
+ this.add(this.options.current[i].id, this.options.current[i].label);
475
+ }
476
+
477
+ this._respond_to_text_field(this.obj);
478
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
479
+ },
480
+
481
+ onselect: function(id, value) {
482
+ this.add(id, value);
483
+ },
484
+
485
+ /**
486
+ * Adds a record to the selected list
487
+ */
488
+ add: function(id, label) {
489
+ // return silently if this value has already been selected
490
+ if (this.list_container.has('input[value=' + id + ']').length > 0) return;
491
+
492
+ var entry = '<li>'
493
+ + '<a href="#" onclick="$(this).parent().remove(); return false;" class="remove">remove</a>'
494
+ + '<input type="hidden" name="' + this.input_name + '" value="' + id + '" />'
495
+ + '<label>' + label + '</label>'
496
+ + '</li>';
497
+ this.list_container.prepend(entry)
498
+ }
499
+ });
@@ -0,0 +1,369 @@
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
+
65
+ if (RecordSelect.document_loaded) this.onload();
66
+ else Event.observe(window, 'load', this.onload.bind(this));
67
+ },
68
+
69
+ /**
70
+ * Finish the setup - IE doesn't like doing certain things before the page loads
71
+ * --override--
72
+ */
73
+ onload: function() {},
74
+
75
+ /**
76
+ * the onselect event handler - when someone clicks on a record
77
+ * --override--
78
+ */
79
+ onselect: function(id, value) {
80
+ alert(id + ': ' + value);
81
+ },
82
+
83
+ /**
84
+ * opens the recordselect
85
+ */
86
+ open: function() {
87
+ if (this.is_open()) return;
88
+
89
+ new Ajax.Updater(this.container, this.url, {
90
+ method: 'get',
91
+ evalScripts: true,
92
+ asynchronous: true,
93
+ onComplete: function() {
94
+ this.show();
95
+ // needs to be mousedown so the event doesn't get canceled by other code (see issue #26)
96
+ Element.observe(document.body, 'mousedown', this.onbodyclick.bindAsEventListener(this));
97
+ }.bind(this)
98
+ });
99
+ },
100
+
101
+ /**
102
+ * positions and reveals the recordselect
103
+ */
104
+ show: function() {
105
+ var offset = Position.cumulativeOffset(this.obj);
106
+ this.container.style.left = offset[0] + 'px';
107
+ this.container.style.top = (Element.getHeight(this.obj) + offset[1]) + 'px';
108
+
109
+ if (this._use_iframe_mask()) {
110
+ this.container.insertAdjacentHTML('afterEnd', '<iframe src="javascript:false;" class="record-select-mask" />');
111
+ var mask = this.container.next('iframe');
112
+ mask.style.left = this.container.style.left;
113
+ mask.style.top = this.container.style.top;
114
+ }
115
+
116
+ this.container.show();
117
+
118
+ if (this._use_iframe_mask()) {
119
+ var dimensions = this.container.immediateDescendants().first().getDimensions();
120
+ mask.style.width = dimensions.width + 'px';
121
+ mask.style.height = dimensions.height + 'px';
122
+ }
123
+ },
124
+
125
+ /**
126
+ * closes the recordselect by emptying the container
127
+ */
128
+ close: function() {
129
+ if (this._use_iframe_mask()) {
130
+ this.container.next('iframe').remove();
131
+ }
132
+
133
+ this.container.hide();
134
+ // hopefully by using remove() instead of innerHTML we won't leak memory
135
+ this.container.immediateDescendants().invoke('remove');
136
+ },
137
+
138
+ /**
139
+ * returns true/false for whether the recordselect is open
140
+ */
141
+ is_open: function() {
142
+ return (!this.container.innerHTML.blank())
143
+ },
144
+
145
+ /**
146
+ * when the user clicks outside the dropdown
147
+ */
148
+ onbodyclick: function(ev) {
149
+ if (!this.is_open()) return;
150
+ var elem = $(Event.element(ev));
151
+ var ancestors = elem.ancestors();
152
+ ancestors.push(elem);
153
+ if (ancestors.include(this.container) || ancestors.include(this.obj)) return;
154
+ this.close();
155
+ },
156
+
157
+ /**
158
+ * creates and initializes (and returns) the recordselect container
159
+ */
160
+ create_container: function() {
161
+ new Insertion.Bottom(document.body, '<div class="record-select-container record-select-handler"></div>');
162
+ e = document.body.childNodes[document.body.childNodes.length - 1];
163
+ e.onselect = this.onselect.bind(this);
164
+ e.style.display = 'none';
165
+
166
+ return $(e);
167
+ },
168
+
169
+ /**
170
+ * all the behavior to respond to a text field as a search box
171
+ */
172
+ _respond_to_text_field: function(text_field) {
173
+ // attach the events to start this party
174
+ text_field.observe('focus', this.open.bind(this));
175
+
176
+ // the autosearch event - needs to happen slightly late (keyup is later than keypress)
177
+ text_field.observe('keyup', function() {
178
+ if (!this.is_open()) return;
179
+ this.container.down('.text-input').value = text_field.value;
180
+ }.bind(this));
181
+
182
+ // keyboard navigation, if available
183
+ if (this.onkeypress) {
184
+ text_field.observe('keypress', this.onkeypress.bind(this));
185
+ }
186
+ },
187
+
188
+ _use_iframe_mask: function() {
189
+ return this.container.insertAdjacentHTML ? true : false;
190
+ }
191
+ });
192
+
193
+ /**
194
+ * Adds keyboard navigation to RecordSelect objects
195
+ */
196
+ Object.extend(RecordSelect.Abstract.prototype, {
197
+ current: null,
198
+
199
+ /**
200
+ * keyboard navigation - where to intercept the keys is up to the concrete class
201
+ */
202
+ onkeypress: function(ev) {
203
+ var elem;
204
+ switch (ev.keyCode) {
205
+ case Event.KEY_UP:
206
+ if (this.current && this.current.up('.record-select')) elem = this.current.previous();
207
+ if (!elem) elem = this.container.getElementsBySelector('ol li.record').last();
208
+ this.highlight(elem);
209
+ break;
210
+ case Event.KEY_DOWN:
211
+ if (this.current && this.current.up('.record-select')) elem = this.current.next();
212
+ if (!elem) elem = this.container.getElementsBySelector('ol li.record').first();
213
+ this.highlight(elem);
214
+ break;
215
+ case Event.KEY_SPACE:
216
+ case Event.KEY_RETURN:
217
+ if (this.current) this.current.down('a').onclick();
218
+ break;
219
+ case Event.KEY_RIGHT:
220
+ elem = this.container.down('li.pagination.next');
221
+ if (elem) elem.down('a').onclick();
222
+ break;
223
+ case Event.KEY_LEFT:
224
+ elem = this.container.down('li.pagination.previous');
225
+ if (elem) elem.down('a').onclick();
226
+ break;
227
+ case Event.KEY_ESC:
228
+ this.close();
229
+ break;
230
+ default:
231
+ return;
232
+ }
233
+ Event.stop(ev); // so "enter" doesn't submit the form, among other things(?)
234
+ },
235
+
236
+ /**
237
+ * moves the highlight to a new object
238
+ */
239
+ highlight: function(obj) {
240
+ if (this.current) this.current.removeClassName('current');
241
+ this.current = $(obj);
242
+ obj.addClassName('current');
243
+ }
244
+ });
245
+
246
+ /**
247
+ * Used by link_to_record_select
248
+ * The options hash should contain a onselect: key, with a javascript function as value
249
+ */
250
+ RecordSelect.Dialog = Class.create();
251
+ RecordSelect.Dialog.prototype = Object.extend(new RecordSelect.Abstract(), {
252
+ onload: function() {
253
+ this.container = this.create_container();
254
+ this.obj.observe('click', this.toggle.bind(this));
255
+
256
+ if (this.onkeypress) this.obj.observe('keypress', this.onkeypress.bind(this));
257
+ },
258
+
259
+ onselect: function(id, value) {
260
+ if (this.options.onselect(id, value) != false) this.close();
261
+ },
262
+
263
+ toggle: function() {
264
+ if (this.is_open()) this.close();
265
+ else this.open();
266
+ }
267
+ });
268
+
269
+ /**
270
+ * Used by record_select_field helper
271
+ * The options hash may contain id: and label: keys, designating the current value
272
+ * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine.
273
+ */
274
+ RecordSelect.Single = Class.create();
275
+ RecordSelect.Single.prototype = Object.extend(new RecordSelect.Abstract(), {
276
+ onload: function() {
277
+ // initialize the container
278
+ this.container = this.create_container();
279
+ this.container.addClassName('record-select-autocomplete');
280
+
281
+ // create the hidden input
282
+ new Insertion.After(this.obj, '<input type="hidden" name="" value="" />')
283
+ this.hidden_input = this.obj.next();
284
+
285
+ // transfer the input name from the text input to the hidden input
286
+ this.hidden_input.name = this.obj.name;
287
+ this.obj.name = '';
288
+
289
+ // initialize the values
290
+ this.set(this.options.id, this.options.label);
291
+
292
+ this._respond_to_text_field(this.obj);
293
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
294
+ },
295
+
296
+ close: function() {
297
+ // if they close the dialog with the text field empty, then delete the id value
298
+ if (this.obj.value == '') this.set('', '');
299
+
300
+ RecordSelect.Abstract.prototype.close.call(this);
301
+ },
302
+
303
+ onselect: function(id, value) {
304
+ if (this.options.onchange) this.options.onchange(id, value);
305
+ this.set(id, value);
306
+ this.close();
307
+ },
308
+
309
+ /**
310
+ * sets the id/label
311
+ */
312
+ set: function(id, label) {
313
+ this.obj.value = label.unescapeHTML();
314
+ this.hidden_input.value = id;
315
+ }
316
+ });
317
+
318
+ /**
319
+ * Used by record_multi_select_field helper.
320
+ * Options:
321
+ * list - the id (or object) of the <ul> to contain the <li>s of selected entries
322
+ * current - an array of id:/label: keys designating the currently selected entries
323
+ */
324
+ RecordSelect.Multiple = Class.create();
325
+ RecordSelect.Multiple.prototype = Object.extend(new RecordSelect.Abstract(), {
326
+ onload: function() {
327
+ // initialize the container
328
+ this.container = this.create_container();
329
+ this.container.addClassName('record-select-autocomplete');
330
+
331
+ // decide where the <li> entries should be placed
332
+ if (this.options.list) this.list_container = $(this.options.list);
333
+ else this.list_container = this.obj.next('ul');
334
+
335
+ // take the input name from the text input, and store it for this.add()
336
+ this.input_name = this.obj.name;
337
+ this.obj.name = '';
338
+
339
+ // initialize the list
340
+ $A(this.options.current).each(function(c) {
341
+ this.add(c.id, c.label);
342
+ }.bind(this));
343
+
344
+ this._respond_to_text_field(this.obj);
345
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
346
+ },
347
+
348
+ onselect: function(id, value) {
349
+ this.add(id, value);
350
+ },
351
+
352
+ /**
353
+ * Adds a record to the selected list
354
+ */
355
+ add: function(id, label) {
356
+ // return silently if this value has already been selected
357
+ var already_selected = this.list_container.getElementsBySelector('input').any(function(i) {
358
+ return i.value == id
359
+ });
360
+ if (already_selected) return;
361
+
362
+ var entry = '<li>'
363
+ + '<a href="#" onclick="$(this.parentNode).remove(); return false;" class="remove">remove</a>'
364
+ + '<input type="hidden" name="' + this.input_name + '" value="' + id + '" />'
365
+ + '<label>' + label + '</label>'
366
+ + '</li>';
367
+ new Insertion.Top(this.list_container, entry);
368
+ }
369
+ });
@@ -0,0 +1,133 @@
1
+ .record-select {
2
+ width: 300px;
3
+ border: 1px solid #afd0f5;
4
+ font-family: sans-serif;
5
+ background-color: #fff;
6
+ font-size: 11px;
7
+ }
8
+
9
+ .record-select img {
10
+ border-width: 0px;
11
+ }
12
+
13
+ .record-select form {
14
+ display: inline;
15
+ }
16
+
17
+ .record-select form .text-input {
18
+ width: 294px;
19
+ margin: 2px auto 1px auto;
20
+ display: block;
21
+ border: 1px solid #999;
22
+ }
23
+
24
+ .record-select form input.example {
25
+ color: #999;
26
+ text-align: center;
27
+ }
28
+
29
+ .record-select form .search_submit {
30
+ display: none;
31
+ }
32
+
33
+ .record-select ol,
34
+ .record-select li {
35
+ margin: 0px;
36
+ padding: 0px;
37
+ list-style: none;
38
+ clear: both;
39
+ }
40
+
41
+ .record-select a {
42
+ color: #0066cc;
43
+ text-decoration: none;
44
+ }
45
+
46
+ .record-select ol a {
47
+ display: block;
48
+ zoom: 1;
49
+ background-color: #e6f2ff;
50
+ padding: 2px 4px;
51
+ }
52
+
53
+ .record-select ol .even a {
54
+ background-color: #ffffff;
55
+ }
56
+
57
+ .record-select ol .pagination a {
58
+ background-color: #eee;
59
+ }
60
+
61
+ .record-select ol .previous a {
62
+ border-bottom: 1px solid #afd0f5;
63
+ }
64
+
65
+ .record-select ol .next a {
66
+ border-top: 1px solid #afd0f5;
67
+ }
68
+
69
+ .record-select ol .pagination a img {
70
+ vertical-align: middle;
71
+ }
72
+
73
+ .record-select ol .found {
74
+ text-align: center;
75
+ font-style: italic;
76
+ color: #999;
77
+ padding: 1px 4px;
78
+ border-bottom: 1px solid #afd0f5;
79
+ }
80
+
81
+ .record-select ol .current a,
82
+ .record-select ol a:hover {
83
+ background-color: #ffff88;
84
+ }
85
+
86
+ .record-select ol a.selected {
87
+ background-color: #666;
88
+ color: #fff;
89
+ }
90
+
91
+ .record-select-container {
92
+ position: absolute;
93
+ z-index: 100;
94
+ }
95
+
96
+ iframe.record-select-mask {
97
+ /* to mask windowed elements in IE6 */
98
+ position: absolute;
99
+ z-index: 99;
100
+ filter: progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0);
101
+ }
102
+
103
+ .record-select-autocomplete form .text-input {
104
+ display: none;
105
+ }
106
+
107
+ .record-select-list {
108
+ padding: 0px;
109
+ margin: 0px;
110
+ list-style: none;
111
+ }
112
+
113
+ .record-select-list li {
114
+ overflow: auto;
115
+ zoom: 1;
116
+ margin-left: 10px;
117
+ font-size: 80%;
118
+ }
119
+
120
+ .record-select-list label {
121
+ float: left;
122
+ }
123
+
124
+ .record-select-list a.remove {
125
+ display: block;
126
+ width: 0px;
127
+ height: 16px;
128
+ padding-left: 16px;
129
+ background: url('../../images/record_select/cross.gif') no-repeat 0 0;
130
+ overflow: hidden;
131
+ float: left;
132
+ margin-right: 5px;
133
+ }
@@ -2,7 +2,7 @@ module RecordSelect
2
2
  module Version
3
3
  MAJOR = 3
4
4
  MINOR = 0
5
- PATCH = 205
5
+ PATCH = 206
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_vho
3
3
  version: !ruby/object:Gem::Version
4
- hash: 413
4
+ hash: 411
5
5
  prerelease:
6
6
  segments:
7
7
  - 3
8
8
  - 0
9
- - 205
10
- version: 3.0.205
9
+ - 206
10
+ version: 3.0.206
11
11
  platform: ruby
12
12
  authors:
13
13
  - Sergio Cambra
@@ -105,6 +105,12 @@ files:
105
105
  - lib/record_select/form_builder.rb
106
106
  - lib/record_select_assets.rb
107
107
  - lib/record_select.rb
108
+ - assets/javascripts/prototype/record_select.js
109
+ - assets/javascripts/jquery/record_select.js
110
+ - assets/stylesheets/record_select.css
111
+ - assets/images/next.gif
112
+ - assets/images/previous.gif
113
+ - assets/images/cross.gif
108
114
  - MIT-LICENSE
109
115
  - CHANGELOG
110
116
  - README