recordselect 3.10.9 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- });