recordselect 3.10.8 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,632 +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
- $.extend($.fn, {
78
- delayedObserver: function(callback, delay, options){
79
- return this.each(function(){
80
- var el = $(this);
81
- var op = options || {};
82
- el.data('oldval', el.val())
83
- .data('delay', delay === 0 ? delay : (delay || 0.5))
84
- .data('condition', op.condition || function() { return ($(this).data('oldval') == $(this).val()); })
85
- .data('callback', callback)
86
- [(op.event||'keyup')](function(){
87
- if (el.data('condition').apply(el)) { return; }
88
- else {
89
- if (el.data('timer')) { clearTimeout(el.data('timer')); }
90
- el.data('timer', setTimeout(function(){
91
- var callback = el.data('callback');
92
- if (callback) callback.apply(el);
93
- }, el.data('delay') * 1000));
94
- el.data('oldval', el.val());
95
- }
96
- });
97
- });
98
- }
99
- });
100
- })(jQuery);
101
- }
102
-
103
- jQuery(document).ready(function() {
104
- RecordSelect.document_loaded = true;
105
- jQuery(document).on('click', 'div.record-select li.record', function(event) {
106
- var link = jQuery(this);
107
- if (link.length) {
108
- RecordSelect.select_item(link);
109
- return false;
110
- }
111
- return true;
112
- });
113
- jQuery(document).on('click', '.record-select-option .remove', function(event) {
114
- var line = jQuery(this).parent(), value = line.find(':input:hidden').val();
115
- line.parent().data('recordselect').obj.trigger('recordselect:remove', [value]);
116
- line.remove();
117
- return false;
118
- });
119
- jQuery(document).on('ajax:beforeSend', '.record-select-container', function(event, xhr) {
120
- var rs = jQuery(this).data('recordselect'), cur = rs.current_xhr, found = jQuery(this).find('.found');
121
- jQuery(this).find('.record, .pagination').remove();
122
- found.html(found.data('searching'));
123
- rs.current_xhr = xhr;
124
- console.log(rs.current_xhr);
125
- if (cur) cur.abort();
126
- });
127
- jQuery(document).on('ajax:complete', '.record-select-container', function(event, xhr, status) {
128
- var rs = jQuery(this).data('recordselect');
129
- if (status != 'abort' || rs.current_xhr == xhr) {
130
- if (rs.is_open()) rs.show();
131
- rs.current_xhr = null;
132
- }
133
- });
134
- jQuery(document).on('click', 'input.recordselect ~ .clear-input-button', function() {
135
- var $clear_button = jQuery(event.target), $input = $clear_button.prevAll('input');
136
- if (!$input.length) return;
137
- $input.val('').removeClass('selected');
138
- $clear_button.removeClass('enabled');
139
- });
140
- jQuery(document).on('input recordselect:change', 'input.recordselect', function(event) {
141
- var $clear_button = jQuery(event.target).nextAll('.clear-input-button').first();
142
- if (!$clear_button.length) return;
143
- if (jQuery(event.target).val()) $clear_button.addClass('enabled');
144
- else $clear_button.removeClass('enabled');
145
- });
146
- jQuery(document).on('ajax:beforeSend', 'a.rs-mode', function() {
147
- $(this).closest('.record-select').find('form input[name="rs_mode"]').val($(this).data('value'));
148
- });
149
- });
150
-
151
- var RecordSelect = new Object();
152
- RecordSelect.document_loaded = false;
153
-
154
- RecordSelect.select_item = function(item) {
155
- var rs = item.closest('.record-select-handler').data('recordselect');
156
- if (rs) {
157
- try {
158
- var label = item.find('label').first().text().trim(), text = item.text().trim();
159
- rs.obj.focus();
160
- rs.onselect(item.attr('id').substr(2), label || text, text, item);
161
- } catch(e) {
162
- alert(e);
163
- }
164
- }
165
- }
166
-
167
- RecordSelect.observe = function(form) {
168
- if (typeof(form) == 'string') form = '#' + form;
169
- form = jQuery(form);
170
- var min_length = 0, rs = form.closest('.record-select-container').data('recordselect');
171
- if (rs) min_length = rs.min_length;
172
- var callback = function() {
173
- if (form.closest('body').length) form.trigger("submit");
174
- };
175
- var delay = parseFloat(rs.obj.data('rsDelay'));
176
- if (isNaN(delay)) delay = 0.35;
177
- form.find('input.text-input').delayedObserver(callback, delay, {
178
- condition: function() {
179
- var item = jQuery(this);
180
- return item.data('oldval') == item.val() || item.val().length < min_length;
181
- }
182
- }).on('paste', function() {
183
- if (form.closest('body').length) form.trigger("submit");
184
- });
185
- }
186
-
187
- RecordSelect.render_page = function(record_select_id, page) {
188
- jQuery('#' + record_select_id + ' ol').first().replaceWith(page);
189
- };
190
-
191
- RecordSelect.Abstract = Class.extend({
192
- /**
193
- * obj - the id or element that will anchor the recordselect to the page
194
- * url - the url to run the recordselect
195
- * options - ??? (check concrete classes)
196
- */
197
- init: function(obj, url, options) {
198
- if (typeof(obj) == 'string') obj = '#' + obj;
199
- this.obj = jQuery(obj);
200
- this.url = url;
201
- this.options = options;
202
- this.container;
203
- this.min_length = options.min_length || 0;
204
- if (this.options.onchange && typeof this.options.onchange != 'function') {
205
- this.options.onchange = eval(this.options.onchange);
206
- }
207
- if (this.options.onselect && typeof this.options.onselect != 'function') {
208
- this.options.onselect = eval(this.options.onselect);
209
- }
210
-
211
- if (RecordSelect.document_loaded) {
212
- this.onload();
213
- } else {
214
- var _this = this; jQuery(document).ready(function() { _this.onload(); })
215
- }
216
- },
217
-
218
- /**
219
- * Finish the setup - IE doesn't like doing certain things before the page loads
220
- * --override--
221
- */
222
- onload: function() {},
223
-
224
- /**
225
- * the onselect event handler - when someone clicks on a record
226
- * --override--
227
- */
228
- onselect: function(id, value, text, item) {
229
- alert(id + ': ' + value);
230
- },
231
-
232
- /**
233
- * opens the recordselect
234
- */
235
- open: function() {
236
- if (this.is_open()) return;
237
- var _this = this;
238
- jQuery.rails.fire(_this.obj, 'recordselect:before', this);
239
- _this.create_form();
240
- _this.container.show();
241
- var params = _this.obj.data('params'), text = _this.obj.val();
242
-
243
- var search_params = jQuery.param({search: text});
244
- params = params ? [params, search_params].join("&") : search_params;
245
- this.current_xhr = jQuery.ajax({
246
- url: this.url,
247
- data: params,
248
- success: function(data, status, xhr) {
249
- if (_this.current_xhr != xhr) return;
250
- if (status != 'abort') _this.current_xhr = null;
251
- _this.container.html(data);
252
- if (!_this.container.is(':visible')) _this.close();
253
- else {
254
- _this.container.find('.text-input').val(_this.obj.val());
255
- RecordSelect.observe(_this.container.find('form'));
256
- _this.container.hide(); // needed to get right document height to position first time
257
- if (text.length >= _this.min_length) _this.show();
258
- }
259
- }
260
- });
261
- },
262
-
263
- /**
264
- * positions and reveals the recordselect
265
- */
266
- show: function() {
267
- var offset = this.obj.offset(), scroll = jQuery(window).scrollTop(), window_height = jQuery(window).height();
268
- if (this.fixed) offset.top -= scroll; // get fixed position
269
- var top = this.obj.outerHeight() + offset.top, document_height = jQuery(document).height();
270
-
271
- this.container.show();
272
- var height = this.container.outerHeight();
273
- this.container.css('left', offset.left);
274
- this.container.css('top', '');
275
- this.container.css('bottom', '');
276
- if (this.above || (this.fixed || this.body_static) && top + height > window_height) {
277
- this.container.css('bottom', window_height - offset.top);
278
- } else {
279
- var below_space = window_height-(top-scroll), above_space = offset.top - scroll, position;
280
- if (below_space < height) {
281
- if (above_space >= height) position = 'bottom';
282
- else position = above_space < below_space ? 'top' : 'bottom';
283
- } else position = 'top';
284
- if (position == 'top') this.container.css('top', top);
285
- else {
286
- var bottom = document_height - offset.top, body_height = $(document.body).height();
287
- if (body_height < document_height) bottom -= document_height - body_height;
288
- this.container.css('bottom', bottom);
289
- }
290
- }
291
-
292
- if (this._use_iframe_mask()) {
293
- this.container.after('<iframe src="javascript:false;" class="record-select-mask" />');
294
- var mask = this.container.next('iframe');
295
- mask.css('left', this.container.css('left'))
296
- .css('top', this.container.css('top'));
297
- }
298
-
299
- if (this._use_iframe_mask()) {
300
- var dimensions = this.container.children().first();
301
- mask.css('width', dimensions.css('width'))
302
- .css('height', dimensions.css('height'));
303
- }
304
- },
305
-
306
- /**
307
- * closes the recordselect by emptying the container
308
- */
309
- close: function() {
310
- if (this._use_iframe_mask()) {
311
- this.container.next('iframe').remove();
312
- }
313
-
314
- this.container.hide();
315
- // hopefully by using remove() instead of innerHTML we won't leak memory
316
- this.container.children().remove();
317
- },
318
-
319
- /**
320
- * returns true/false for whether the recordselect is open
321
- */
322
- is_open: function() {
323
- return jQuery.trim(this.container.html()).length != 0;
324
- },
325
-
326
- /**
327
- * when the user clicks outside the dropdown
328
- */
329
- onbodyclick: function(event) {
330
- if (!this.is_open()) return;
331
- if (this.container.has(jQuery(event.target)).length > 0) {
332
- return;
333
- } else if (!this.obj.is(event.target)) {
334
- this.close();
335
- }
336
- },
337
-
338
- /**
339
- * creates and initializes (and returns) the recordselect container
340
- */
341
- create_container: function() {
342
- var e = jQuery("<div />", {'class': "record-select-container record-select-handler"}), rs = this;
343
- e.css('display', 'none');
344
- e.data('recordselect', rs);
345
- jQuery(this.obj).add(this.obj.parents()).each(function() {
346
- if (jQuery(this).css('position') == 'fixed') {
347
- rs.fixed = jQuery(this);
348
- e.css('position', 'fixed');
349
- return false;
350
- }
351
- });
352
- jQuery(this.obj).each(function() {
353
- if (jQuery(this).data('rs-above')) {
354
- rs.above = true;
355
- return false;
356
- }
357
- });
358
- jQuery(document.body).append(e);
359
- jQuery(document.body).mousedown(jQuery.proxy(this, "onbodyclick"));
360
- if (!rs.fixed && e.offsetParent().css('position') == 'static') rs.body_static = true;
361
- e.get(0).onselect = jQuery.proxy(this, "onselect")
362
- return e;
363
- },
364
-
365
- create_form: function() {
366
- var div = jQuery('<div>').addClass('record-select').attr('id', this.options.id);
367
- var form = jQuery('<form>').attr('action', this.url).attr('data-remote', true).attr('method', 'get');
368
- form.append(jQuery('<input type="text" name="search" class="text-input">'));
369
- form.append(jQuery('<input type="hidden" name="page" value="1">'));
370
- form.append(jQuery('<input type="hidden" name="update" value="1">'));
371
- div.append(form).append(jQuery('<ol>'));
372
- this.container.html(div);
373
- RecordSelect.observe(form);
374
- },
375
-
376
- onkeyup: function(event) {
377
- if (!this.is_open()) return;
378
- this.container.find('.text-input').val(this.obj.val()).trigger(event);
379
- },
380
-
381
- onpaste: function(event) {
382
- if (!this.is_open()) return;
383
- setTimeout(function () {
384
- this.container.find('.text-input').val(this.obj.val()).trigger(event);
385
- }.bind(this), 0);
386
- },
387
-
388
- /**
389
- * all the behavior to respond to a text field as a search box
390
- */
391
- _respond_to_text_field: function(text_field) {
392
- // attach the events to start this party
393
- text_field.focus(jQuery.proxy(this, 'open'));
394
-
395
- // the autosearch event - needs to happen slightly late (keyup is later than keypress)
396
- text_field.keyup(jQuery.proxy(this, 'onkeyup'));
397
-
398
- // the autosearch event - needs to happen slightly late (keyup is later than keypress)
399
- text_field.on('paste', jQuery.proxy(this, 'onpaste'));
400
-
401
- // keyboard navigation, if available
402
- if (this.onkeydown) {
403
- text_field.keydown(jQuery.proxy(this, "onkeydown"));
404
- }
405
- },
406
-
407
- _use_iframe_mask: function() {
408
- return this.container.insertAdjacentHTML ? true : false;
409
- }
410
- });
411
-
412
-
413
-
414
- /**
415
- * Adds keyboard navigation to RecordSelect objects
416
- */
417
- jQuery.extend(RecordSelect.Abstract.prototype, {
418
- current: null,
419
-
420
- /**
421
- * keyboard navigation - where to intercept the keys is up to the concrete class
422
- */
423
- onkeydown: function(ev) {
424
- var elem;
425
- switch (ev.keyCode) {
426
- case 38: //Event.KEY_UP
427
- if (this.current && this.current.closest('html').length) elem = this.current.prev();
428
- if (!elem) elem = this.container.find('ol li.record').last();
429
- this.highlight(elem);
430
- break;
431
- case 40: //Event.KEY_DOWN
432
- if (this.current && this.current.closest('html').length) elem = this.current.next();
433
- if (!elem) elem = this.container.find('ol li.record').first();
434
- this.highlight(elem);
435
- break;
436
- case 13: // Event.KEY_RETURN
437
- if (this.current) this.current.click();
438
- break;
439
- case 39: // Event.KEY_RIGHT
440
- elem = this.container.find('li.pagination.next');
441
- if (elem) elem.find('a').click();
442
- break;
443
- case 37: // Event.KEY_LEFT
444
- elem = this.container.find('li.pagination.previous');
445
- if (elem) elem.find('a').click();
446
- break;
447
- case 27: // Event.KEY_ESC
448
- case 9: // Event.KEY_TAB
449
- this.close();
450
- break;
451
- default:
452
- return true;
453
- }
454
- if (ev.keyCode != 9) { // don't prevent tabbing
455
- ev.preventDefault(); // so "enter" doesn't submit the form, among other things(?)
456
- }
457
- },
458
-
459
- /**
460
- * moves the highlight to a new object
461
- */
462
- highlight: function(obj) {
463
- if (this.current) this.current.removeClass('current');
464
- this.current = jQuery(obj);
465
- obj.addClass('current');
466
- }
467
- });
468
-
469
- /**
470
- * Used by link_to_record_select
471
- * The options hash should contain a onselect: key, with a javascript function as value
472
- */
473
- RecordSelect.Dialog = RecordSelect.Abstract.extend({
474
- onload: function() {
475
- this.container = this.create_container();
476
- this.obj.click(jQuery.proxy(this, "toggle"));
477
- if (this.onkeypress) this.obj.keypress(jQuery.proxy(this, 'onkeypress'));
478
- },
479
-
480
- onselect: function(id, value, text, item) {
481
- if (this.options.onselect(id, value, text, item) != false) this.close();
482
- },
483
-
484
- toggle: function(e) {
485
- e.preventDefault();
486
- if (this.is_open()) this.close();
487
- else this.open();
488
- }
489
- });
490
-
491
- /**
492
- * Used by record_select_field helper
493
- * The options hash may contain id: and label: keys, designating the current value
494
- * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine
495
- * and field_name: key, where value will be set as name of the input field.
496
- */
497
- RecordSelect.Single = RecordSelect.Abstract.extend({
498
- onload: function() {
499
- var rs = this;
500
- // initialize the container
501
- this.container = this.create_container();
502
- this.container.addClass('record-select-autocomplete');
503
- this.container.submit(function() {
504
- rs.hidden_input.val('');
505
- rs.obj.removeClass('selected');
506
- rs.obj.trigger('recordselect:unset', [rs]);
507
- });
508
-
509
- // create the hidden input
510
- this.obj.after('<input type="hidden" name="" value="" />');
511
- this.hidden_input = this.obj.next();
512
-
513
- // transfer the input name from the text input to the hidden input
514
- this.hidden_input.attr('name', this.obj.attr('name'));
515
- this.obj.attr('name', this.options.field_name || '');
516
-
517
- // initialize the values
518
- if (this.options.label) this.set(this.options.id, this.options.label);
519
-
520
- this._respond_to_text_field(this.obj);
521
- if (this.obj.prop('focused')) this.open(); // if it was focused before we could attach observers
522
- },
523
-
524
- onselect: function(id, value, text, item) {
525
- this.set(id, value);
526
- if (this.options.onchange) this.options.onchange.call(this, id, value, text, item);
527
- this.obj.trigger("recordselect:change", [id, value, text, item]);
528
- this.close();
529
- },
530
-
531
- /**
532
- * sets the id/label
533
- */
534
- set: function(id, label) {
535
- // unescaped html missing for label
536
- this.obj.val(label);
537
- this.hidden_input.val(id);
538
- this.obj.addClass('selected');
539
- }
540
- });
541
-
542
- /**
543
- * Used by record_select_autocomplete helper
544
- * The options hash may contain label: key, designating the current value
545
- * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine.
546
- */
547
- RecordSelect.Autocomplete = RecordSelect.Abstract.extend({
548
- onload: function() {
549
- // initialize the container
550
- this.container = this.create_container();
551
- this.container.addClass('record-select-autocomplete');
552
-
553
- // initialize the values
554
- if (this.options.label) this.set(this.options.label);
555
-
556
- this._respond_to_text_field(this.obj);
557
- if (this.obj.prop('focused')) this.open(); // if it was focused before we could attach observers
558
- },
559
-
560
- close: function() {
561
- // if they close the dialog with the text field empty, then delete the id value
562
- if (this.obj.val() == '') this.set('');
563
-
564
- RecordSelect.Abstract.prototype.close.call(this);
565
- },
566
-
567
- onselect: function(id, value, text, item) {
568
- this.set(value);
569
- if (this.options.onchange) this.options.onchange.call(this, id, value, text, item);
570
- this.obj.trigger("recordselect:change", [id, value, text, item]);
571
- this.close();
572
- },
573
-
574
- /**
575
- * sets the id/label
576
- */
577
- set: function(label) {
578
- // unescaped html missing for label
579
- this.obj.val(label);
580
- }
581
- });
582
-
583
- /**
584
- * Used by record_multi_select_field helper.
585
- * Options:
586
- * list - the id (or object) of the <ul> to contain the <li>s of selected entries
587
- * current - an array of id:/label: keys designating the currently selected entries
588
- */
589
- RecordSelect.Multiple = RecordSelect.Abstract.extend({
590
- onload: function() {
591
- // initialize the container
592
- this.container = this.create_container();
593
- this.container.addClass('record-select-autocomplete');
594
-
595
- // decide where the <li> entries should be placed
596
- if (this.options.list) this.list_container = jQuery(this.options.list);
597
- else this.list_container = this.obj.siblings('ul');
598
- this.list_container.data('recordselect', this);
599
-
600
- // take the input name from the text input, and store it for this.add()
601
- this.input_name = this.obj.attr('name');
602
- this.obj.attr('name', '');
603
-
604
- // initialize the list
605
- for(var i = 0, length = this.options.current.length; i < length; i++) {
606
- this.add(this.options.current[i].id, this.options.current[i].label);
607
- }
608
-
609
- this._respond_to_text_field(this.obj);
610
- if (this.obj.prop('focused')) this.open(); // if it was focused before we could attach observers
611
- },
612
-
613
- onselect: function(id, value, text, item) {
614
- this.add(id, value);
615
- this.obj.trigger("recordselect:add", [id, value, text, item]);
616
- },
617
-
618
- /**
619
- * Adds a record to the selected list
620
- */
621
- add: function(id, label) {
622
- // return silently if this value has already been selected
623
- if (this.list_container.has('input[value=' + id + ']').length > 0) return;
624
-
625
- var entry = '<li class="record-select-option">'
626
- + '<a href="#" class="remove">remove</a>'
627
- + '<input type="hidden" name="' + this.input_name + '" value="' + id + '" />'
628
- + '<label>' + label + '</label>'
629
- + '</li>';
630
- this.list_container.prepend(entry)
631
- }
632
- });