recordselect 3.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ this.set(id, value);
305
+ if (this.options.onchange) this.options.onchange.call(this, 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
+ }