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,419 @@
1
+ document.observe("dom:loaded", function() {
2
+ RecordSelect.document_loaded = true;
3
+ document.on('ajax:before', 'div.record-select * li.record a', function(event) {
4
+ var link = event.findElement();
5
+ if (link) {
6
+ if (RecordSelect.notify(link) == false) {
7
+ event.stop();
8
+ } else {
9
+ link.toggleClassName("selected");
10
+ }
11
+ }
12
+ return true;
13
+ });
14
+ });
15
+
16
+ Form.Element.AfterActivity = function(element, callback, delay) {
17
+ element = $(element);
18
+ if (!delay) delay = 0.25;
19
+ new Form.Element.Observer(element, delay, function(element, value) {
20
+ // TODO: display loading indicator
21
+ if (element.activity_timer) clearTimeout(element.activity_timer);
22
+ element.activity_timer = setTimeout(function() {
23
+ callback(element.value);
24
+ }, delay * 1000 + 50);
25
+ });
26
+ }
27
+
28
+ var RecordSelect = new Object();
29
+ RecordSelect.document_loaded = false;
30
+
31
+ RecordSelect.notify = function(item) {
32
+ var e = Element.up(item, '.record-select-handler');
33
+ var onselect = e.onselect || e.getAttribute('onselect');
34
+ if (typeof onselect != 'function') onselect = eval(onselect);
35
+ if (onselect)
36
+ {
37
+ try {
38
+ onselect(item.parentNode.id.substr(2), (item.down('label') || item).innerHTML.unescapeHTML(), e);
39
+ } catch(e) {
40
+ alert(e);
41
+ }
42
+ return false;
43
+ }
44
+ else return true;
45
+ };
46
+
47
+ RecordSelect.render_page = function(record_select_id, page) {
48
+ var page_element = $$('#' + record_select_id + ' ol')[0];
49
+ if (page_element) Element.replace(page_element, page);
50
+ };
51
+
52
+ RecordSelect.Abstract = Class.create();
53
+ Object.extend(RecordSelect.Abstract.prototype, {
54
+ /**
55
+ * obj - the id or element that will anchor the recordselect to the page
56
+ * url - the url to run the recordselect
57
+ * options - ??? (check concrete classes)
58
+ */
59
+ initialize: function(obj, url, options) {
60
+ this.obj = $(obj);
61
+ this.url = url;
62
+ this.options = options;
63
+ this.container;
64
+ if (this.options.onchange && typeof this.options.onchange != 'function') {
65
+ this.options.onchange = eval(this.options.onchange);
66
+ }
67
+
68
+ if (RecordSelect.document_loaded) this.onload();
69
+ else Event.observe(window, 'load', this.onload.bind(this));
70
+ },
71
+
72
+ /**
73
+ * Finish the setup - IE doesn't like doing certain things before the page loads
74
+ * --override--
75
+ */
76
+ onload: function() {},
77
+
78
+ /**
79
+ * the onselect event handler - when someone clicks on a record
80
+ * --override--
81
+ */
82
+ onselect: function(id, value) {
83
+ alert(id + ': ' + value);
84
+ },
85
+
86
+ /**
87
+ * opens the recordselect
88
+ */
89
+ open: function() {
90
+ if (this.is_open()) return;
91
+
92
+ new Ajax.Updater(this.container, this.url, {
93
+ method: 'get',
94
+ evalScripts: true,
95
+ asynchronous: true,
96
+ onComplete: function() {
97
+ this.show();
98
+ // needs to be mousedown so the event doesn't get canceled by other code (see issue #26)
99
+ Element.observe(document.body, 'mousedown', this.onbodyclick.bindAsEventListener(this));
100
+ }.bind(this)
101
+ });
102
+ },
103
+
104
+ /**
105
+ * positions and reveals the recordselect
106
+ */
107
+ show: function() {
108
+ var offset = Position.cumulativeOffset(this.obj),
109
+ top = Element.getHeight(this.obj) + offset[1],
110
+ window_height = document.viewport.getHeight();
111
+ this.container.style.left = offset[0] + 'px';
112
+ if (top + Element.getHeight(this.container) > window_height)
113
+ this.container.style.bottom = (window_height - offset[1]) + 'px';
114
+ else this.container.style.top = top + 'px';
115
+
116
+ if (this._use_iframe_mask()) {
117
+ this.container.insertAdjacentHTML('afterEnd', '<iframe src="javascript:false;" class="record-select-mask" />');
118
+ var mask = this.container.next('iframe');
119
+ mask.style.left = this.container.style.left;
120
+ mask.style.top = this.container.style.top;
121
+ }
122
+
123
+ this.container.show();
124
+
125
+ if (this._use_iframe_mask()) {
126
+ var dimensions = this.container.immediateDescendants().first().getDimensions();
127
+ mask.style.width = dimensions.width + 'px';
128
+ mask.style.height = dimensions.height + 'px';
129
+ }
130
+ },
131
+
132
+ /**
133
+ * closes the recordselect by emptying the container
134
+ */
135
+ close: function() {
136
+ if (this._use_iframe_mask()) {
137
+ this.container.next('iframe').remove();
138
+ }
139
+
140
+ this.container.hide();
141
+ // hopefully by using remove() instead of innerHTML we won't leak memory
142
+ this.container.immediateDescendants().invoke('remove');
143
+ },
144
+
145
+ /**
146
+ * returns true/false for whether the recordselect is open
147
+ */
148
+ is_open: function() {
149
+ return (!this.container.innerHTML.blank())
150
+ },
151
+
152
+ /**
153
+ * when the user clicks outside the dropdown
154
+ */
155
+ onbodyclick: function(ev) {
156
+ if (!this.is_open()) return;
157
+ var elem = $(Event.element(ev));
158
+ var ancestors = elem.ancestors();
159
+ ancestors.push(elem);
160
+ if (ancestors.include(this.container) || ancestors.include(this.obj)) return;
161
+ this.close();
162
+ },
163
+
164
+ /**
165
+ * creates and initializes (and returns) the recordselect container
166
+ */
167
+ create_container: function() {
168
+ new Insertion.Bottom(document.body, '<div class="record-select-container record-select-handler"></div>');
169
+ e = document.body.childNodes[document.body.childNodes.length - 1];
170
+ e.onselect = this.onselect.bind(this);
171
+ e.style.display = 'none';
172
+
173
+ return $(e);
174
+ },
175
+
176
+ /**
177
+ * all the behavior to respond to a text field as a search box
178
+ */
179
+ _respond_to_text_field: function(text_field) {
180
+ // attach the events to start this party
181
+ text_field.observe('focus', this.open.bind(this));
182
+
183
+ // the autosearch event - needs to happen slightly late (keyup is later than keypress)
184
+ text_field.observe('keyup', function() {
185
+ if (!this.is_open()) return;
186
+ this.container.down('.text-input').value = text_field.value;
187
+ }.bind(this));
188
+
189
+ // keyboard navigation, if available
190
+ if (this.onkeydown) {
191
+ text_field.observe('keydown', this.onkeydown.bind(this));
192
+ }
193
+ },
194
+
195
+ _use_iframe_mask: function() {
196
+ return this.container.insertAdjacentHTML ? true : false;
197
+ }
198
+ });
199
+
200
+ /**
201
+ * Adds keyboard navigation to RecordSelect objects
202
+ */
203
+ Object.extend(RecordSelect.Abstract.prototype, {
204
+ current: null,
205
+
206
+ /**
207
+ * keyboard navigation - where to intercept the keys is up to the concrete class
208
+ */
209
+ onkeydown: function(ev) {
210
+ var elem;
211
+ switch (ev.keyCode) {
212
+ case Event.KEY_UP:
213
+ if (this.current && this.current.up('.record-select')) elem = this.current.previous();
214
+ if (!elem) elem = this.container.getElementsBySelector('ol li.record').last();
215
+ this.highlight(elem);
216
+ break;
217
+ case Event.KEY_DOWN:
218
+ if (this.current && this.current.up('.record-select')) elem = this.current.next();
219
+ if (!elem) elem = this.container.getElementsBySelector('ol li.record').first();
220
+ this.highlight(elem);
221
+ break;
222
+ case Event.KEY_SPACE:
223
+ case Event.KEY_RETURN:
224
+ if (this.current) this.current.down('a').onclick();
225
+ break;
226
+ case Event.KEY_RIGHT:
227
+ elem = this.container.down('li.pagination.next');
228
+ if (elem) elem.down('a').onclick();
229
+ break;
230
+ case Event.KEY_LEFT:
231
+ elem = this.container.down('li.pagination.previous');
232
+ if (elem) elem.down('a').onclick();
233
+ break;
234
+ case Event.KEY_ESC:
235
+ this.close();
236
+ break;
237
+ default:
238
+ return;
239
+ }
240
+ Event.stop(ev); // so "enter" doesn't submit the form, among other things(?)
241
+ },
242
+
243
+ /**
244
+ * moves the highlight to a new object
245
+ */
246
+ highlight: function(obj) {
247
+ if (this.current) this.current.removeClassName('current');
248
+ this.current = $(obj);
249
+ obj.addClassName('current');
250
+ }
251
+ });
252
+
253
+ /**
254
+ * Used by link_to_record_select
255
+ * The options hash should contain a onselect: key, with a javascript function as value
256
+ */
257
+ RecordSelect.Dialog = Class.create();
258
+ RecordSelect.Dialog.prototype = Object.extend(new RecordSelect.Abstract(), {
259
+ onload: function() {
260
+ this.container = this.create_container();
261
+ this.obj.observe('click', this.toggle.bind(this));
262
+
263
+ if (this.onkeypress) this.obj.observe('keypress', this.onkeypress.bind(this));
264
+ },
265
+
266
+ onselect: function(id, value) {
267
+ if (this.options.onselect(id, value) != false) this.close();
268
+ },
269
+
270
+ toggle: function() {
271
+ if (this.is_open()) this.close();
272
+ else this.open();
273
+ }
274
+ });
275
+
276
+ /**
277
+ * Used by record_select_field helper
278
+ * The options hash may contain id: and label: keys, designating the current value
279
+ * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine
280
+ * and field_name: key, where value will be set as name of the input field.
281
+ */
282
+ RecordSelect.Single = Class.create();
283
+ RecordSelect.Single.prototype = Object.extend(new RecordSelect.Abstract(), {
284
+ onload: function() {
285
+ // initialize the container
286
+ this.container = this.create_container();
287
+ this.container.addClassName('record-select-autocomplete');
288
+
289
+ // create the hidden input
290
+ new Insertion.After(this.obj, '<input type="hidden" name="" value="" />')
291
+ this.hidden_input = this.obj.next();
292
+
293
+ // transfer the input name from the text input to the hidden input
294
+ this.hidden_input.name = this.obj.name;
295
+ this.obj.name = this.options.field_name || '';
296
+
297
+ // initialize the values
298
+ if (this.options.label) this.set(this.options.id, this.options.label);
299
+
300
+ this._respond_to_text_field(this.obj);
301
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
302
+ },
303
+
304
+ close: function() {
305
+ // if they close the dialog with the text field empty, then delete the id value
306
+ if (this.obj.value == '') this.set('', '');
307
+
308
+ RecordSelect.Abstract.prototype.close.call(this);
309
+ },
310
+
311
+ onselect: function(id, value) {
312
+ this.set(id, value);
313
+ if (this.options.onchange) this.options.onchange.call(this, id, value);
314
+ this.obj.fire('recordselect:change', {"id": id, "label": value});
315
+ this.close();
316
+ },
317
+
318
+ /**
319
+ * sets the id/label
320
+ */
321
+ set: function(id, label) {
322
+ this.obj.value = label.unescapeHTML();
323
+ this.hidden_input.value = id;
324
+ }
325
+ });
326
+
327
+ /**
328
+ * Used by record_select_autocomplete helper
329
+ * The options hash may contain label: key, designating the current value
330
+ * The options hash may also include an onchange: key, where the value is a javascript function (or eval-able string) for an callback routine.
331
+ */
332
+ RecordSelect.Autocomplete = Class.create();
333
+ RecordSelect.Autocomplete.prototype = Object.extend(new RecordSelect.Abstract(), {
334
+ onload: function() {
335
+ // initialize the container
336
+ this.container = this.create_container();
337
+ this.container.addClassName('record-select-autocomplete');
338
+
339
+ // initialize the values
340
+ if (this.options.label) this.set(this.options.label);
341
+
342
+ this._respond_to_text_field(this.obj);
343
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
344
+ },
345
+
346
+ close: function() {
347
+ // if they close the dialog with the text field empty, then delete the id value
348
+ if (this.obj.value == '') this.set('', '');
349
+
350
+ RecordSelect.Abstract.prototype.close.call(this);
351
+ },
352
+
353
+ onselect: function(id, value) {
354
+ this.set(value);
355
+ if (this.options.onchange) this.options.onchange.call(this, id, value);
356
+ this.obj.fire('recordselect:change', {"id": id, "label": value});
357
+ this.close();
358
+ },
359
+
360
+ /**
361
+ * sets the id/label
362
+ */
363
+ set: function(label) {
364
+ this.obj.value = label.unescapeHTML();
365
+ }
366
+ });
367
+
368
+ /**
369
+ * Used by record_multi_select_field helper.
370
+ * Options:
371
+ * list - the id (or object) of the <ul> to contain the <li>s of selected entries
372
+ * current - an array of id:/label: keys designating the currently selected entries
373
+ */
374
+ RecordSelect.Multiple = Class.create();
375
+ RecordSelect.Multiple.prototype = Object.extend(new RecordSelect.Abstract(), {
376
+ onload: function() {
377
+ // initialize the container
378
+ this.container = this.create_container();
379
+ this.container.addClassName('record-select-autocomplete');
380
+
381
+ // decide where the <li> entries should be placed
382
+ if (this.options.list) this.list_container = $(this.options.list);
383
+ else this.list_container = this.obj.next('ul');
384
+
385
+ // take the input name from the text input, and store it for this.add()
386
+ this.input_name = this.obj.name;
387
+ this.obj.name = '';
388
+
389
+ // initialize the list
390
+ $A(this.options.current).each(function(c) {
391
+ this.add(c.id, c.label);
392
+ }.bind(this));
393
+
394
+ this._respond_to_text_field(this.obj);
395
+ if (this.obj.focused) this.open(); // if it was focused before we could attach observers
396
+ },
397
+
398
+ onselect: function(id, value) {
399
+ this.add(id, value);
400
+ },
401
+
402
+ /**
403
+ * Adds a record to the selected list
404
+ */
405
+ add: function(id, label) {
406
+ // return silently if this value has already been selected
407
+ var already_selected = this.list_container.getElementsBySelector('input').any(function(i) {
408
+ return i.value == id
409
+ });
410
+ if (already_selected) return;
411
+
412
+ var entry = '<li>'
413
+ + '<a href="#" onclick="$(this.parentNode).remove(); return false;" class="remove">remove</a>'
414
+ + '<input type="hidden" name="' + this.input_name + '" value="' + id + '" />'
415
+ + '<label>' + label + '</label>'
416
+ + '</li>';
417
+ new Insertion.Top(this.list_container, entry);
418
+ }
419
+ });
@@ -0,0 +1,5 @@
1
+ <% if RecordSelect::Config.js_framework == :jquery %>
2
+ <% require_asset "jquery/record_select" %>
3
+ <% else %>
4
+ <% require_asset "prototype/record_select" %>
5
+ <% end %>
@@ -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(<%= image_path 'record_select/cross.gif' %>) no-repeat 0 0;
130
+ overflow: hidden;
131
+ float: left;
132
+ margin-right: 5px;
133
+ }