recordselect 3.2.5 → 3.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,544 @@
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 = jQuery(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
+ jQuery(document).ready(function() {
126
+ RecordSelect.document_loaded = true;
127
+ jQuery(document).on('ajax:before', 'div.record-select * li.record a', function(event) {
128
+ var link = jQuery(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 = jQuery(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 = jQuery.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
+ jQuery('#' + 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 = jQuery(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; jQuery(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
+ $.rails.fire(_this.obj, 'rs:before');
223
+ jQuery.ajax({
224
+ url: this.url,
225
+ //type: "POST",
226
+ data: _this.obj.data('params'),
227
+ //dataType: options.ajax_data_type,
228
+ success: function(data){
229
+ _this.container.html(data);
230
+ _this.show();
231
+ jQuery(document.body).mousedown(jQuery.proxy(_this, "onbodyclick"));
232
+ }
233
+ });
234
+ },
235
+
236
+ /**
237
+ * positions and reveals the recordselect
238
+ */
239
+ show: function() {
240
+ var offset = this.obj.offset(), top = this.obj.height() + offset.top;
241
+ this.container.show();
242
+ this.container.css('left', offset.left);
243
+ if (top + this.container.height() > jQuery(window).height())
244
+ this.container.css('bottom', jQuery(window).height() - offset.top);
245
+ else this.container.css('top', top);
246
+
247
+ if (this._use_iframe_mask()) {
248
+ this.container.after('<iframe src="javascript:false;" class="record-select-mask" />');
249
+ var mask = this.container.next('iframe');
250
+ mask.css('left', this.container.css('left'))
251
+ .css('top', this.container.css('top'));
252
+ }
253
+
254
+ if (this._use_iframe_mask()) {
255
+ var dimensions = this.container.children().first();
256
+ mask.css('width', dimensions.css('width'))
257
+ .css('height', dimensions.css('height'));
258
+ }
259
+ },
260
+
261
+ /**
262
+ * closes the recordselect by emptying the container
263
+ */
264
+ close: function() {
265
+ if (this._use_iframe_mask()) {
266
+ this.container.next('iframe').remove();
267
+ }
268
+
269
+ this.container.hide();
270
+ // hopefully by using remove() instead of innerHTML we won't leak memory
271
+ this.container.children().remove();
272
+ },
273
+
274
+ /**
275
+ * returns true/false for whether the recordselect is open
276
+ */
277
+ is_open: function() {
278
+ return (!(jQuery.trim(this.container.html()).length == 0))
279
+ },
280
+
281
+ /**
282
+ * when the user clicks outside the dropdown
283
+ */
284
+ onbodyclick: function(event) {
285
+ if (!this.is_open()) return;
286
+ if (this.container.has(jQuery(event.target)).length > 0) {
287
+ return;
288
+ } else if (!this.obj.is(event.target)) {
289
+ this.close();
290
+ }
291
+ },
292
+
293
+ /**
294
+ * creates and initializes (and returns) the recordselect container
295
+ */
296
+ create_container: function() {
297
+ var e = jQuery("<div />", {'class': "record-select-container record-select-handler"});
298
+ e.css('display', 'none')
299
+ jQuery(document.body).append(e);
300
+ e.get(0).onselect = jQuery.proxy(this, "onselect")
301
+ return e;
302
+ },
303
+
304
+ onkeyup: function(event) {
305
+ if (!this.is_open()) return;
306
+ this.container.find('.text-input').val(this.obj.val()).trigger(event);
307
+ },
308
+
309
+ /**
310
+ * all the behavior to respond to a text field as a search box
311
+ */
312
+ _respond_to_text_field: function(text_field) {
313
+ // attach the events to start this party
314
+ text_field.focus(jQuery.proxy(this, 'open'));
315
+
316
+ // the autosearch event - needs to happen slightly late (keyup is later than keypress)
317
+ text_field.keyup(jQuery.proxy(this, 'onkeyup'));
318
+
319
+ // keyboard navigation, if available
320
+ if (this.onkeydown) {
321
+ text_field.keydown(jQuery.proxy(this, "onkeydown"));
322
+ }
323
+ },
324
+
325
+ _use_iframe_mask: function() {
326
+ return this.container.insertAdjacentHTML ? true : false;
327
+ }
328
+ });
329
+
330
+
331
+
332
+ /**
333
+ * Adds keyboard navigation to RecordSelect objects
334
+ */
335
+ jQuery.extend(RecordSelect.Abstract.prototype, {
336
+ current: null,
337
+
338
+ /**
339
+ * keyboard navigation - where to intercept the keys is up to the concrete class
340
+ */
341
+ onkeydown: function(ev) {
342
+ var elem;
343
+ switch (ev.keyCode) {
344
+ case 38: //Event.KEY_UP
345
+ if (this.current && this.current.closest('html').length) elem = this.current.prev();
346
+ if (!elem) elem = this.container.find('ol li.record').last();
347
+ this.highlight(elem);
348
+ break;
349
+ case 40: //Event.KEY_DOWN
350
+ if (this.current && this.current.closest('html').length) elem = this.current.next();
351
+ if (!elem) elem = this.container.find('ol li.record').first();
352
+ this.highlight(elem);
353
+ break;
354
+ case 13: // Event.KEY_RETURN
355
+ if (this.current) this.current.find('a').click();
356
+ break;
357
+ case 39: // Event.KEY_RIGHT
358
+ elem = this.container.find('li.pagination.next');
359
+ if (elem) elem.find('a').click();
360
+ break;
361
+ case 37: // Event.KEY_LEFT
362
+ elem = this.container.find('li.pagination.previous');
363
+ if (elem) elem.find('a').click();
364
+ break;
365
+ case 27: // Event.KEY_ESC
366
+ this.close();
367
+ break;
368
+ default:
369
+ return true;
370
+ }
371
+ ev.preventDefault(); // so "enter" doesn't submit the form, among other things(?)
372
+ },
373
+
374
+ /**
375
+ * moves the highlight to a new object
376
+ */
377
+ highlight: function(obj) {
378
+ if (this.current) this.current.removeClass('current');
379
+ this.current = jQuery(obj);
380
+ obj.addClass('current');
381
+ }
382
+ });
383
+
384
+ /**
385
+ * Used by link_to_record_select
386
+ * The options hash should contain a onselect: key, with a javascript function as value
387
+ */
388
+ RecordSelect.Dialog = RecordSelect.Abstract.extend({
389
+ onload: function() {
390
+ this.container = this.create_container();
391
+ this.obj.click(jQuery.proxy(this, "toggle"));
392
+ if (this.onkeypress) this.obj.keypress(jQuery.proxy(this, 'onkeypress'));
393
+ },
394
+
395
+ onselect: function(id, value) {
396
+ if (this.options.onselect(id, value) != false) this.close();
397
+ },
398
+
399
+ toggle: function() {
400
+ if (this.is_open()) this.close();
401
+ else this.open();
402
+ }
403
+ });
404
+
405
+ /**
406
+ * Used by record_select_field helper
407
+ * The options hash may contain id: and label: keys, designating the current value
408
+ * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine
409
+ * and field_name: key, where value will be set as name of the input field.
410
+ */
411
+ RecordSelect.Single = RecordSelect.Abstract.extend({
412
+ onload: function() {
413
+ // initialize the container
414
+ this.container = this.create_container();
415
+ this.container.addClass('record-select-autocomplete');
416
+
417
+ // create the hidden input
418
+ this.obj.after('<input type="hidden" name="" value="" />');
419
+ this.hidden_input = this.obj.next();
420
+
421
+ // transfer the input name from the text input to the hidden input
422
+ this.hidden_input.attr('name', this.obj.attr('name'));
423
+ this.obj.attr('name', this.options.field_name || '');
424
+
425
+ // initialize the values
426
+ if (this.options.label) this.set(this.options.id, this.options.label);
427
+
428
+ this._respond_to_text_field(this.obj);
429
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
430
+ },
431
+
432
+ close: function() {
433
+ // if they close the dialog with the text field empty, then delete the id value
434
+ if (this.obj.val() == '') this.set('', '');
435
+
436
+ RecordSelect.Abstract.prototype.close.call(this);
437
+ },
438
+
439
+ onselect: function(id, value) {
440
+ this.set(id, value);
441
+ if (this.options.onchange) this.options.onchange.call(this, id, value);
442
+ this.obj.trigger("recordselect:change", [id, value]);
443
+ this.close();
444
+ },
445
+
446
+ /**
447
+ * sets the id/label
448
+ */
449
+ set: function(id, label) {
450
+ // unescaped html missing for label
451
+ this.obj.val(label);
452
+ this.hidden_input.val(id);
453
+ }
454
+ });
455
+
456
+ /**
457
+ * Used by record_select_autocomplete helper
458
+ * The options hash may contain label: key, designating the current value
459
+ * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine.
460
+ */
461
+ RecordSelect.Autocomplete = RecordSelect.Abstract.extend({
462
+ onload: function() {
463
+ // initialize the container
464
+ this.container = this.create_container();
465
+ this.container.addClass('record-select-autocomplete');
466
+
467
+ // initialize the values
468
+ if (this.options.label) this.set(this.options.label);
469
+
470
+ this._respond_to_text_field(this.obj);
471
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
472
+ },
473
+
474
+ close: function() {
475
+ // if they close the dialog with the text field empty, then delete the id value
476
+ if (this.obj.val() == '') this.set('');
477
+
478
+ RecordSelect.Abstract.prototype.close.call(this);
479
+ },
480
+
481
+ onselect: function(id, value) {
482
+ this.set(value);
483
+ if (this.options.onchange) this.options.onchange.call(this, id, value);
484
+ this.obj.trigger("recordselect:change", [id, value]);
485
+ this.close();
486
+ },
487
+
488
+ /**
489
+ * sets the id/label
490
+ */
491
+ set: function(label) {
492
+ // unescaped html missing for label
493
+ this.obj.val(label);
494
+ }
495
+ });
496
+
497
+ /**
498
+ * Used by record_multi_select_field helper.
499
+ * Options:
500
+ * list - the id (or object) of the <ul> to contain the <li>s of selected entries
501
+ * current - an array of id:/label: keys designating the currently selected entries
502
+ */
503
+ RecordSelect.Multiple = RecordSelect.Abstract.extend({
504
+ onload: function() {
505
+ // initialize the container
506
+ this.container = this.create_container();
507
+ this.container.addClass('record-select-autocomplete');
508
+
509
+ // decide where the <li> entries should be placed
510
+ if (this.options.list) this.list_container = jQuery(this.options.list);
511
+ else this.list_container = this.obj.siblings('ul');
512
+
513
+ // take the input name from the text input, and store it for this.add()
514
+ this.input_name = this.obj.attr('name');
515
+ this.obj.attr('name', '');
516
+
517
+ // initialize the list
518
+ for(var i = 0, length = this.options.current.length; i < length; i++) {
519
+ this.add(this.options.current[i].id, this.options.current[i].label);
520
+ }
521
+
522
+ this._respond_to_text_field(this.obj);
523
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
524
+ },
525
+
526
+ onselect: function(id, value) {
527
+ this.add(id, value);
528
+ },
529
+
530
+ /**
531
+ * Adds a record to the selected list
532
+ */
533
+ add: function(id, label) {
534
+ // return silently if this value has already been selected
535
+ if (this.list_container.has('input[value=' + id + ']').length > 0) return;
536
+
537
+ var entry = '<li>'
538
+ + '<a href="#" onclick="jQuery(this).parent().remove(); return false;" class="remove">remove</a>'
539
+ + '<input type="hidden" name="' + this.input_name + '" value="' + id + '" />'
540
+ + '<label>' + label + '</label>'
541
+ + '</li>';
542
+ this.list_container.prepend(entry)
543
+ }
544
+ });