textext-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1057 @@
1
+ /**
2
+ * jQuery TextExt Plugin
3
+ * http://alexgorbatchev.com/textext
4
+ *
5
+ * @version 1.2.0
6
+ * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
7
+ * @license MIT License
8
+ */
9
+ (function($)
10
+ {
11
+ /**
12
+ * Autocomplete plugin brings the classic autocomplete functionality to the TextExt echosystem.
13
+ * The gist of functionality is when user starts typing in, for example a term or a tag, a
14
+ * dropdown would be presented with possible suggestions to complete the input quicker.
15
+ *
16
+ * @author agorbatchev
17
+ * @date 2011/08/17
18
+ * @id TextExtAutocomplete
19
+ */
20
+ function TextExtAutocomplete() {};
21
+
22
+ $.fn.textext.TextExtAutocomplete = TextExtAutocomplete;
23
+ $.fn.textext.addPlugin('autocomplete', TextExtAutocomplete);
24
+
25
+ var p = TextExtAutocomplete.prototype,
26
+
27
+ CSS_DOT = '.',
28
+ CSS_SELECTED = 'text-selected',
29
+ CSS_DOT_SELECTED = CSS_DOT + CSS_SELECTED,
30
+ CSS_SUGGESTION = 'text-suggestion',
31
+ CSS_DOT_SUGGESTION = CSS_DOT + CSS_SUGGESTION,
32
+
33
+ /**
34
+ * Autocomplete plugin options are grouped under `autocomplete` when passed to the
35
+ * `$().textext()` function. For example:
36
+ *
37
+ * $('textarea').textext({
38
+ * plugins: 'autocomplete',
39
+ * autocomplete: {
40
+ * dropdownPosition: 'above'
41
+ * }
42
+ * })
43
+ *
44
+ * @author agorbatchev
45
+ * @date 2011/08/17
46
+ * @id TextExtAutocomplete.options
47
+ */
48
+
49
+ /**
50
+ * This is a toggle switch to enable or disable the Autucomplete plugin. The value is checked
51
+ * each time at the top level which allows you to toggle this setting on the fly.
52
+ *
53
+ * @name autocomplete.enabled
54
+ * @default true
55
+ * @author agorbatchev
56
+ * @date 2011/08/17
57
+ * @id TextExtAutocomplete.options.autocomplete.enabled
58
+ */
59
+ OPT_ENABLED = 'autocomplete.enabled',
60
+
61
+ /**
62
+ * This option allows to specify position of the dropdown. The two possible values
63
+ * are `above` and `below`.
64
+ *
65
+ * @name autocomplete.dropdown.position
66
+ * @default "below"
67
+ * @author agorbatchev
68
+ * @date 2011/08/17
69
+ * @id TextExtAutocomplete.options.autocomplete.dropdown.position
70
+ */
71
+ OPT_POSITION = 'autocomplete.dropdown.position',
72
+
73
+ /**
74
+ * This option allows to specify maximum height of the dropdown. Value is taken directly, so
75
+ * if desired height is 200 pixels, value must be `200px`.
76
+ *
77
+ * @name autocomplete.dropdown.maxHeight
78
+ * @default "100px"
79
+ * @author agorbatchev
80
+ * @date 2011/12/29
81
+ * @id TextExtAutocomplete.options.autocomplete.dropdown.maxHeight
82
+ * @version 1.1
83
+ */
84
+ OPT_MAX_HEIGHT = 'autocomplete.dropdown.maxHeight',
85
+
86
+ /**
87
+ * This option allows to override how a suggestion item is rendered. The value should be
88
+ * a function, the first argument of which is suggestion to be rendered and `this` context
89
+ * is the current instance of `TextExtAutocomplete`.
90
+ *
91
+ * [Click here](/manual/examples/autocomplete-with-custom-render.html) to see a demo.
92
+ *
93
+ * For example:
94
+ *
95
+ * $('textarea').textext({
96
+ * plugins: 'autocomplete',
97
+ * autocomplete: {
98
+ * render: function(suggestion)
99
+ * {
100
+ * return '<b>' + suggestion + '</b>';
101
+ * }
102
+ * }
103
+ * })
104
+ *
105
+ * @name autocomplete.render
106
+ * @default null
107
+ * @author agorbatchev
108
+ * @date 2011/12/23
109
+ * @id TextExtAutocomplete.options.autocomplete.render
110
+ * @version 1.1
111
+ */
112
+ OPT_RENDER = 'autocomplete.render',
113
+
114
+ /**
115
+ * HTML source that is used to generate the dropdown.
116
+ *
117
+ * @name html.dropdown
118
+ * @default '<div class="text-dropdown"><div class="text-list"/></div>'
119
+ * @author agorbatchev
120
+ * @date 2011/08/17
121
+ * @id TextExtAutocomplete.options.html.dropdown
122
+ */
123
+ OPT_HTML_DROPDOWN = 'html.dropdown',
124
+
125
+ /**
126
+ * HTML source that is used to generate each suggestion.
127
+ *
128
+ * @name html.suggestion
129
+ * @default '<div class="text-suggestion"><span class="text-label"/></div>'
130
+ * @author agorbatchev
131
+ * @date 2011/08/17
132
+ * @id TextExtAutocomplete.options.html.suggestion
133
+ */
134
+ OPT_HTML_SUGGESTION = 'html.suggestion',
135
+
136
+ /**
137
+ * Autocomplete plugin triggers or reacts to the following events.
138
+ *
139
+ * @author agorbatchev
140
+ * @date 2011/08/17
141
+ * @id TextExtAutocomplete.events
142
+ */
143
+
144
+ /**
145
+ * Autocomplete plugin triggers and reacts to the `hideDropdown` to hide the dropdown if it's
146
+ * already visible.
147
+ *
148
+ * @name hideDropdown
149
+ * @author agorbatchev
150
+ * @date 2011/08/17
151
+ * @id TextExtAutocomplete.events.hideDropdown
152
+ */
153
+ EVENT_HIDE_DROPDOWN = 'hideDropdown',
154
+
155
+ /**
156
+ * Autocomplete plugin triggers and reacts to the `showDropdown` to show the dropdown if it's
157
+ * not already visible.
158
+ *
159
+ * It's possible to pass a render callback function which will be called instead of the
160
+ * default `TextExtAutocomplete.renderSuggestions()`.
161
+ *
162
+ * Here's how another plugin should trigger this event with the optional render callback:
163
+ *
164
+ * this.trigger('showDropdown', function(autocomplete)
165
+ * {
166
+ * autocomplete.clearItems();
167
+ * var node = autocomplete.addDropdownItem('<b>Item</b>');
168
+ * node.addClass('new-look');
169
+ * });
170
+ *
171
+ * @name showDropdown
172
+ * @author agorbatchev
173
+ * @date 2011/08/17
174
+ * @id TextExtAutocomplete.events.showDropdown
175
+ */
176
+ EVENT_SHOW_DROPDOWN = 'showDropdown',
177
+
178
+ /**
179
+ * Autocomplete plugin reacts to the `setSuggestions` event triggered by other plugins which
180
+ * wish to populate the suggestion items. Suggestions should be passed as event argument in the
181
+ * following format: `{ data : [ ... ] }`.
182
+ *
183
+ * Here's how another plugin should trigger this event:
184
+ *
185
+ * this.trigger('setSuggestions', { data : [ "item1", "item2" ] });
186
+ *
187
+ * @name setSuggestions
188
+ * @author agorbatchev
189
+ * @date 2011/08/17
190
+ * @id TextExtAutocomplete.events.setSuggestions
191
+ */
192
+
193
+ /**
194
+ * Autocomplete plugin triggers the `getSuggestions` event and expects to get results by listening for
195
+ * the `setSuggestions` event.
196
+ *
197
+ * @name getSuggestions
198
+ * @author agorbatchev
199
+ * @date 2011/08/17
200
+ * @id TextExtAutocomplete.events.getSuggestions
201
+ */
202
+ EVENT_GET_SUGGESTIONS = 'getSuggestions',
203
+
204
+ /**
205
+ * Autocomplete plugin triggers `getFormData` event with the current suggestion so that the the core
206
+ * will be updated with serialized data to be submitted with the HTML form.
207
+ *
208
+ * @name getFormData
209
+ * @author agorbatchev
210
+ * @date 2011/08/18
211
+ * @id TextExtAutocomplete.events.getFormData
212
+ */
213
+ EVENT_GET_FORM_DATA = 'getFormData',
214
+
215
+ /**
216
+ * Autocomplete plugin reacts to `toggleDropdown` event and either shows or hides the dropdown
217
+ * depending if it's currently hidden or visible.
218
+ *
219
+ * @name toggleDropdown
220
+ * @author agorbatchev
221
+ * @date 2011/12/27
222
+ * @id TextExtAutocomplete.events.toggleDropdown
223
+ * @version 1.1
224
+ */
225
+ EVENT_TOGGLE_DROPDOWN = 'toggleDropdown',
226
+
227
+ POSITION_ABOVE = 'above',
228
+ POSITION_BELOW = 'below',
229
+
230
+ DEFAULT_OPTS = {
231
+ autocomplete : {
232
+ enabled : true,
233
+ dropdown : {
234
+ position : POSITION_BELOW,
235
+ maxHeight : '100px'
236
+ }
237
+ },
238
+
239
+ html : {
240
+ dropdown : '<div class="text-dropdown"><div class="text-list"/></div>',
241
+ suggestion : '<div class="text-suggestion"><span class="text-label"/></div>'
242
+ }
243
+ }
244
+ ;
245
+
246
+ /**
247
+ * Initialization method called by the core during plugin instantiation.
248
+ *
249
+ * @signature TextExtAutocomplete.init(core)
250
+ *
251
+ * @param core {TextExt} Instance of the TextExt core class.
252
+ *
253
+ * @author agorbatchev
254
+ * @date 2011/08/17
255
+ * @id TextExtAutocomplete.init
256
+ */
257
+ p.init = function(core)
258
+ {
259
+ var self = this;
260
+
261
+ self.baseInit(core, DEFAULT_OPTS);
262
+
263
+ var input = self.input(),
264
+ container
265
+ ;
266
+
267
+ if(self.opts(OPT_ENABLED) === true)
268
+ {
269
+ self.on({
270
+ blur : self.onBlur,
271
+ anyKeyUp : self.onAnyKeyUp,
272
+ deleteKeyUp : self.onAnyKeyUp,
273
+ backspaceKeyPress : self.onBackspaceKeyPress,
274
+ enterKeyPress : self.onEnterKeyPress,
275
+ escapeKeyPress : self.onEscapeKeyPress,
276
+ setSuggestions : self.onSetSuggestions,
277
+ showDropdown : self.onShowDropdown,
278
+ hideDropdown : self.onHideDropdown,
279
+ toggleDropdown : self.onToggleDropdown,
280
+ postInvalidate : self.positionDropdown,
281
+ getFormData : self.onGetFormData,
282
+
283
+ // using keyDown for up/down keys so that repeat events are
284
+ // captured and user can scroll up/down by holding the keys
285
+ downKeyDown : self.onDownKeyDown,
286
+ upKeyDown : self.onUpKeyDown
287
+ });
288
+
289
+ container = $(self.opts(OPT_HTML_DROPDOWN));
290
+ container.insertAfter(input);
291
+
292
+ self.on(container, {
293
+ mouseover : self.onMouseOver,
294
+ click : self.onClick
295
+ });
296
+
297
+ container
298
+ .css('maxHeight', self.opts(OPT_MAX_HEIGHT))
299
+ .addClass('text-position-' + self.opts(OPT_POSITION))
300
+ ;
301
+
302
+ $(self).data('container', container);
303
+
304
+ self.positionDropdown();
305
+ }
306
+ };
307
+
308
+ /**
309
+ * Returns top level dropdown container HTML element.
310
+ *
311
+ * @signature TextExtAutocomplete.containerElement()
312
+ *
313
+ * @author agorbatchev
314
+ * @date 2011/08/15
315
+ * @id TextExtAutocomplete.containerElement
316
+ */
317
+ p.containerElement = function()
318
+ {
319
+ return $(this).data('container');
320
+ };
321
+
322
+ //--------------------------------------------------------------------------------
323
+ // User mouse/keyboard input
324
+
325
+ /**
326
+ * Reacts to the `mouseOver` event triggered by the TextExt core.
327
+ *
328
+ * @signature TextExtAutocomplete.onMouseOver(e)
329
+ *
330
+ * @param e {Object} jQuery event.
331
+ *
332
+ * @author agorbatchev
333
+ * @date 2011/08/17
334
+ * @id TextExtAutocomplete.onMouseOver
335
+ */
336
+ p.onMouseOver = function(e)
337
+ {
338
+ var self = this,
339
+ target = $(e.target)
340
+ ;
341
+
342
+ if(target.is(CSS_DOT_SUGGESTION))
343
+ {
344
+ self.clearSelected();
345
+ target.addClass(CSS_SELECTED);
346
+ }
347
+ };
348
+
349
+ /**
350
+ * Reacts to the `click` event triggered by the TextExt core.
351
+ *
352
+ * @signature TextExtAutocomplete.onClick(e)
353
+ *
354
+ * @param e {Object} jQuery event.
355
+ *
356
+ * @author agorbatchev
357
+ * @date 2011/08/17
358
+ * @id TextExtAutocomplete.onClick
359
+ */
360
+ p.onClick = function(e)
361
+ {
362
+ var self = this,
363
+ target = $(e.target)
364
+ ;
365
+
366
+ if(target.is(CSS_DOT_SUGGESTION))
367
+ self.selectFromDropdown();
368
+ };
369
+
370
+ /**
371
+ * Reacts to the `blur` event triggered by the TextExt core.
372
+ *
373
+ * @signature TextExtAutocomplete.onBlur(e)
374
+ *
375
+ * @param e {Object} jQuery event.
376
+ *
377
+ * @author agorbatchev
378
+ * @date 2011/08/17
379
+ * @id TextExtAutocomplete.onBlur
380
+ */
381
+ p.onBlur = function(e)
382
+ {
383
+ var self = this;
384
+
385
+ // use timeout here so that onClick has a chance to fire because if
386
+ // dropdown is hidden when clicked, onClick doesn't fire
387
+ if(self.isDropdownVisible())
388
+ setTimeout(function() { self.trigger(EVENT_HIDE_DROPDOWN) }, 100);
389
+ };
390
+
391
+ /**
392
+ * Reacts to the `backspaceKeyPress` event triggered by the TextExt core.
393
+ *
394
+ * @signature TextExtAutocomplete.onBackspaceKeyPress(e)
395
+ *
396
+ * @param e {Object} jQuery event.
397
+ *
398
+ * @author agorbatchev
399
+ * @date 2011/08/17
400
+ * @id TextExtAutocomplete.onBackspaceKeyPress
401
+ */
402
+ p.onBackspaceKeyPress = function(e)
403
+ {
404
+ var self = this,
405
+ isEmpty = self.val().length > 0
406
+ ;
407
+
408
+ if(isEmpty || self.isDropdownVisible())
409
+ self.getSuggestions();
410
+ };
411
+
412
+ /**
413
+ * Reacts to the `anyKeyUp` event triggered by the TextExt core.
414
+ *
415
+ * @signature TextExtAutocomplete.onAnyKeyUp(e)
416
+ *
417
+ * @param e {Object} jQuery event.
418
+ *
419
+ * @author agorbatchev
420
+ * @date 2011/08/17
421
+ * @id TextExtAutocomplete.onAnyKeyUp
422
+ */
423
+ p.onAnyKeyUp = function(e, keyCode)
424
+ {
425
+ var self = this,
426
+ isFunctionKey = self.opts('keys.' + keyCode) != null
427
+ ;
428
+
429
+ if(self.val().length > 0 && !isFunctionKey)
430
+ self.getSuggestions();
431
+ };
432
+
433
+ /**
434
+ * Reacts to the `downKeyDown` event triggered by the TextExt core.
435
+ *
436
+ * @signature TextExtAutocomplete.onDownKeyDown(e)
437
+ *
438
+ * @param e {Object} jQuery event.
439
+ *
440
+ * @author agorbatchev
441
+ * @date 2011/08/17
442
+ * @id TextExtAutocomplete.onDownKeyDown
443
+ */
444
+ p.onDownKeyDown = function(e)
445
+ {
446
+ var self = this;
447
+
448
+ self.isDropdownVisible()
449
+ ? self.toggleNextSuggestion()
450
+ : self.getSuggestions()
451
+ ;
452
+ };
453
+
454
+ /**
455
+ * Reacts to the `upKeyDown` event triggered by the TextExt core.
456
+ *
457
+ * @signature TextExtAutocomplete.onUpKeyDown(e)
458
+ *
459
+ * @param e {Object} jQuery event.
460
+ *
461
+ * @author agorbatchev
462
+ * @date 2011/08/17
463
+ * @id TextExtAutocomplete.onUpKeyDown
464
+ */
465
+ p.onUpKeyDown = function(e)
466
+ {
467
+ this.togglePreviousSuggestion();
468
+ };
469
+
470
+ /**
471
+ * Reacts to the `enterKeyPress` event triggered by the TextExt core.
472
+ *
473
+ * @signature TextExtAutocomplete.onEnterKeyPress(e)
474
+ *
475
+ * @param e {Object} jQuery event.
476
+ *
477
+ * @author agorbatchev
478
+ * @date 2011/08/17
479
+ * @id TextExtAutocomplete.onEnterKeyPress
480
+ */
481
+ p.onEnterKeyPress = function(e)
482
+ {
483
+ var self = this;
484
+
485
+ if(self.isDropdownVisible())
486
+ self.selectFromDropdown();
487
+ };
488
+
489
+ /**
490
+ * Reacts to the `escapeKeyPress` event triggered by the TextExt core. Hides the dropdown
491
+ * if it's currently visible.
492
+ *
493
+ * @signature TextExtAutocomplete.onEscapeKeyPress(e)
494
+ *
495
+ * @param e {Object} jQuery event.
496
+ *
497
+ * @author agorbatchev
498
+ * @date 2011/08/17
499
+ * @id TextExtAutocomplete.onEscapeKeyPress
500
+ */
501
+ p.onEscapeKeyPress = function(e)
502
+ {
503
+ var self = this;
504
+
505
+ if(self.isDropdownVisible())
506
+ self.trigger(EVENT_HIDE_DROPDOWN);
507
+ };
508
+
509
+ //--------------------------------------------------------------------------------
510
+ // Core functionality
511
+
512
+ /**
513
+ * Positions dropdown either below or above the input based on the `autocomplete.dropdown.position`
514
+ * option specified, which could be either `above` or `below`.
515
+ *
516
+ * @signature TextExtAutocomplete.positionDropdown()
517
+ *
518
+ * @author agorbatchev
519
+ * @date 2011/08/15
520
+ * @id TextExtAutocomplete.positionDropdown
521
+ */
522
+ p.positionDropdown = function()
523
+ {
524
+ var self = this,
525
+ container = self.containerElement(),
526
+ direction = self.opts(OPT_POSITION),
527
+ height = self.core().wrapElement().outerHeight(),
528
+ css = {}
529
+ ;
530
+
531
+ css[direction === POSITION_ABOVE ? 'bottom' : 'top'] = height + 'px';
532
+ container.css(css);
533
+ };
534
+
535
+ /**
536
+ * Returns list of all the suggestion HTML elements in the dropdown.
537
+ *
538
+ * @signature TextExtAutocomplete.suggestionElements()
539
+ *
540
+ * @author agorbatchev
541
+ * @date 2011/08/17
542
+ * @id TextExtAutocomplete.suggestionElements
543
+ */
544
+ p.suggestionElements = function()
545
+ {
546
+ return this.containerElement().find(CSS_DOT_SUGGESTION);
547
+ };
548
+
549
+
550
+ /**
551
+ * Highlights specified suggestion as selected in the dropdown.
552
+ *
553
+ * @signature TextExtAutocomplete.setSelectedSuggestion(suggestion)
554
+ *
555
+ * @param suggestion {Object} Suggestion object. With the default `ItemManager` this
556
+ * is expected to be a string, anything else with custom implementations.
557
+ *
558
+ * @author agorbatchev
559
+ * @date 2011/08/17
560
+ * @id TextExtAutocomplete.setSelectedSuggestion
561
+ */
562
+ p.setSelectedSuggestion = function(suggestion)
563
+ {
564
+ if(!suggestion)
565
+ return;
566
+
567
+ var self = this,
568
+ all = self.suggestionElements(),
569
+ target = all.first(),
570
+ item, i
571
+ ;
572
+
573
+ self.clearSelected();
574
+
575
+ for(i = 0; i < all.length; i++)
576
+ {
577
+ item = $(all[i]);
578
+
579
+ if(self.itemManager().compareItems(item.data(CSS_SUGGESTION), suggestion))
580
+ {
581
+ target = item.addClass(CSS_SELECTED);
582
+ break;
583
+ }
584
+ }
585
+
586
+ target.addClass(CSS_SELECTED);
587
+ self.scrollSuggestionIntoView(target);
588
+ };
589
+
590
+ /**
591
+ * Returns the first suggestion HTML element from the dropdown that is highlighted as selected.
592
+ *
593
+ * @signature TextExtAutocomplete.selectedSuggestionElement()
594
+ *
595
+ * @author agorbatchev
596
+ * @date 2011/08/17
597
+ * @id TextExtAutocomplete.selectedSuggestionElement
598
+ */
599
+ p.selectedSuggestionElement = function()
600
+ {
601
+ return this.suggestionElements().filter(CSS_DOT_SELECTED).first();
602
+ };
603
+
604
+ /**
605
+ * Returns `true` if dropdown is currently visible, `false` otherwise.
606
+ *
607
+ * @signature TextExtAutocomplete.isDropdownVisible()
608
+ *
609
+ * @author agorbatchev
610
+ * @date 2011/08/17
611
+ * @id TextExtAutocomplete.isDropdownVisible
612
+ */
613
+ p.isDropdownVisible = function()
614
+ {
615
+ return this.containerElement().is(':visible') === true;
616
+ };
617
+
618
+ /**
619
+ * Reacts to the `getFormData` event triggered by the core. Returns data with the
620
+ * weight of 100 to be *less than the Tags plugin* data weight. The weights system is
621
+ * covered in greater detail in the [`getFormData`][1] event documentation.
622
+ *
623
+ * [1]: /manual/textext.html#getformdata
624
+ *
625
+ * @signature TextExtAutocomplete.onGetFormData(e, data, keyCode)
626
+ *
627
+ * @param e {Object} jQuery event.
628
+ * @param data {Object} Data object to be populated.
629
+ * @param keyCode {Number} Key code that triggered the original update request.
630
+ *
631
+ * @author agorbatchev
632
+ * @date 2011/08/22
633
+ * @id TextExtAutocomplete.onGetFormData
634
+ */
635
+ p.onGetFormData = function(e, data, keyCode)
636
+ {
637
+ var self = this,
638
+ val = self.val(),
639
+ inputValue = val,
640
+ formValue = val
641
+ ;
642
+ data[100] = self.formDataObject(inputValue, formValue);
643
+ };
644
+
645
+ /**
646
+ * Returns initialization priority of the Autocomplete plugin which is expected to be
647
+ * *greater than the Tags plugin* because of the dependencies. The value is 200.
648
+ *
649
+ * @signature TextExtAutocomplete.initPriority()
650
+ *
651
+ * @author agorbatchev
652
+ * @date 2011/08/22
653
+ * @id TextExtAutocomplete.initPriority
654
+ */
655
+ p.initPriority = function()
656
+ {
657
+ return 200;
658
+ };
659
+
660
+ /**
661
+ * Reacts to the `hideDropdown` event and hides the dropdown if it's already visible.
662
+ *
663
+ * @signature TextExtAutocomplete.onHideDropdown(e)
664
+ *
665
+ * @param e {Object} jQuery event.
666
+ *
667
+ * @author agorbatchev
668
+ * @date 2011/08/17
669
+ * @id TextExtAutocomplete.onHideDropdown
670
+ */
671
+ p.onHideDropdown = function(e)
672
+ {
673
+ this.hideDropdown();
674
+ };
675
+
676
+ /**
677
+ * Reacts to the 'toggleDropdown` event and shows or hides the dropdown depending if
678
+ * it's currently hidden or visible.
679
+ *
680
+ * @signature TextExtAutocomplete.onToggleDropdown(e)
681
+ *
682
+ * @param e {Object} jQuery event.
683
+ *
684
+ * @author agorbatchev
685
+ * @date 2011/12/27
686
+ * @id TextExtAutocomplete.onToggleDropdown
687
+ * @version 1.1
688
+ */
689
+ p.onToggleDropdown = function(e)
690
+ {
691
+ var self = this;
692
+ self.trigger(self.containerElement().is(':visible') ? EVENT_HIDE_DROPDOWN : EVENT_SHOW_DROPDOWN);
693
+ };
694
+
695
+ /**
696
+ * Reacts to the `showDropdown` event and shows the dropdown if it's not already visible.
697
+ * It's possible to pass a render callback function which will be called instead of the
698
+ * default `TextExtAutocomplete.renderSuggestions()`.
699
+ *
700
+ * If no suggestion were previously loaded, it will fire `getSuggestions` event and exit.
701
+ *
702
+ * Here's how another plugin should trigger this event with the optional render callback:
703
+ *
704
+ * this.trigger('showDropdown', function(autocomplete)
705
+ * {
706
+ * autocomplete.clearItems();
707
+ * var node = autocomplete.addDropdownItem('<b>Item</b>');
708
+ * node.addClass('new-look');
709
+ * });
710
+ *
711
+ * @signature TextExtAutocomplete.onShowDropdown(e, renderCallback)
712
+ *
713
+ * @param e {Object} jQuery event.
714
+ * @param renderCallback {Function} Optional callback function which would be used to
715
+ * render dropdown items. As a first argument, reference to the current instance of
716
+ * Autocomplete plugin will be supplied. It's assumed, that if this callback is provided
717
+ * rendering will be handled completely manually.
718
+ *
719
+ * @author agorbatchev
720
+ * @date 2011/08/17
721
+ * @id TextExtAutocomplete.onShowDropdown
722
+ */
723
+ p.onShowDropdown = function(e, renderCallback)
724
+ {
725
+ var self = this,
726
+ current = self.selectedSuggestionElement().data(CSS_SUGGESTION),
727
+ suggestions = self._suggestions
728
+ ;
729
+
730
+ if(!suggestions)
731
+ return self.trigger(EVENT_GET_SUGGESTIONS);
732
+
733
+ if($.isFunction(renderCallback))
734
+ {
735
+ renderCallback(self);
736
+ }
737
+ else
738
+ {
739
+ self.renderSuggestions(self._suggestions);
740
+ self.toggleNextSuggestion();
741
+ }
742
+
743
+ self.showDropdown(self.containerElement());
744
+ self.setSelectedSuggestion(current);
745
+ };
746
+
747
+ /**
748
+ * Reacts to the `setSuggestions` event. Expects to recieve the payload as the second argument
749
+ * in the following structure:
750
+ *
751
+ * {
752
+ * result : [ "item1", "item2" ],
753
+ * showHideDropdown : false
754
+ * }
755
+ *
756
+ * Notice the optional `showHideDropdown` option. By default, ie without the `showHideDropdown`
757
+ * value the method will trigger either `showDropdown` or `hideDropdown` depending if there are
758
+ * suggestions. If set to `false`, no event is triggered.
759
+ *
760
+ * @signature TextExtAutocomplete.onSetSuggestions(e, data)
761
+ *
762
+ * @param data {Object} Data payload.
763
+ *
764
+ * @author agorbatchev
765
+ * @date 2011/08/17
766
+ * @id TextExtAutocomplete.onSetSuggestions
767
+ */
768
+ p.onSetSuggestions = function(e, data)
769
+ {
770
+ var self = this,
771
+ suggestions = self._suggestions = data.result
772
+ ;
773
+
774
+ if(data.showHideDropdown !== false)
775
+ self.trigger(suggestions === null || suggestions.length === 0 ? EVENT_HIDE_DROPDOWN : EVENT_SHOW_DROPDOWN);
776
+ };
777
+
778
+ /**
779
+ * Prepears for and triggers the `getSuggestions` event with the `{ query : {String} }` as second
780
+ * argument.
781
+ *
782
+ * @signature TextExtAutocomplete.getSuggestions()
783
+ *
784
+ * @author agorbatchev
785
+ * @date 2011/08/17
786
+ * @id TextExtAutocomplete.getSuggestions
787
+ */
788
+ p.getSuggestions = function()
789
+ {
790
+ var self = this,
791
+ val = self.val()
792
+ ;
793
+
794
+ if(self._previousInputValue == val)
795
+ return;
796
+
797
+ // if user clears input, then we want to select first suggestion
798
+ // instead of the last one
799
+ if(val == '')
800
+ current = null;
801
+
802
+ self._previousInputValue = val;
803
+ self.trigger(EVENT_GET_SUGGESTIONS, { query : val });
804
+ };
805
+
806
+ /**
807
+ * Removes all HTML suggestion items from the dropdown.
808
+ *
809
+ * @signature TextExtAutocomplete.clearItems()
810
+ *
811
+ * @author agorbatchev
812
+ * @date 2011/08/17
813
+ * @id TextExtAutocomplete.clearItems
814
+ */
815
+ p.clearItems = function()
816
+ {
817
+ this.containerElement().find('.text-list').children().remove();
818
+ };
819
+
820
+ /**
821
+ * Clears all and renders passed suggestions.
822
+ *
823
+ * @signature TextExtAutocomplete.renderSuggestions(suggestions)
824
+ *
825
+ * @param suggestions {Array} List of suggestions to render.
826
+ *
827
+ * @author agorbatchev
828
+ * @date 2011/08/17
829
+ * @id TextExtAutocomplete.renderSuggestions
830
+ */
831
+ p.renderSuggestions = function(suggestions)
832
+ {
833
+ var self = this;
834
+
835
+ self.clearItems();
836
+
837
+ $.each(suggestions || [], function(index, item)
838
+ {
839
+ self.addSuggestion(item);
840
+ });
841
+ };
842
+
843
+ /**
844
+ * Shows the dropdown.
845
+ *
846
+ * @signature TextExtAutocomplete.showDropdown()
847
+ *
848
+ * @author agorbatchev
849
+ * @date 2011/08/17
850
+ * @id TextExtAutocomplete.showDropdown
851
+ */
852
+ p.showDropdown = function()
853
+ {
854
+ this.containerElement().show();
855
+ };
856
+
857
+ /**
858
+ * Hides the dropdown.
859
+ *
860
+ * @signature TextExtAutocomplete.hideDropdown()
861
+ *
862
+ * @author agorbatchev
863
+ * @date 2011/08/17
864
+ * @id TextExtAutocomplete.hideDropdown
865
+ */
866
+ p.hideDropdown = function()
867
+ {
868
+ var self = this,
869
+ dropdown = self.containerElement()
870
+ ;
871
+
872
+ self._previousInputValue = null;
873
+ dropdown.hide();
874
+ };
875
+
876
+ /**
877
+ * Adds single suggestion to the bottom of the dropdown. Uses `ItemManager.itemToString()` to
878
+ * serialize provided suggestion to string.
879
+ *
880
+ * @signature TextExtAutocomplete.addSuggestion(suggestion)
881
+ *
882
+ * @param suggestion {Object} Suggestion item. By default expected to be a string.
883
+ *
884
+ * @author agorbatchev
885
+ * @date 2011/08/17
886
+ * @id TextExtAutocomplete.addSuggestion
887
+ */
888
+ p.addSuggestion = function(suggestion)
889
+ {
890
+ var self = this,
891
+ renderer = self.opts(OPT_RENDER),
892
+ node = self.addDropdownItem(renderer ? renderer.call(self, suggestion) : self.itemManager().itemToString(suggestion))
893
+ ;
894
+
895
+ node.data(CSS_SUGGESTION, suggestion);
896
+ };
897
+
898
+ /**
899
+ * Adds and returns HTML node to the bottom of the dropdown.
900
+ *
901
+ * @signature TextExtAutocomplete.addDropdownItem(html)
902
+ *
903
+ * @param html {String} HTML to be inserted into the item.
904
+ *
905
+ * @author agorbatchev
906
+ * @date 2011/08/17
907
+ * @id TextExtAutocomplete.addDropdownItem
908
+ */
909
+ p.addDropdownItem = function(html)
910
+ {
911
+ var self = this,
912
+ container = self.containerElement().find('.text-list'),
913
+ node = $(self.opts(OPT_HTML_SUGGESTION))
914
+ ;
915
+
916
+ node.find('.text-label').html(html);
917
+ container.append(node);
918
+ return node;
919
+ };
920
+
921
+ /**
922
+ * Removes selection highlight from all suggestion elements.
923
+ *
924
+ * @signature TextExtAutocomplete.clearSelected()
925
+ *
926
+ * @author agorbatchev
927
+ * @date 2011/08/02
928
+ * @id TextExtAutocomplete.clearSelected
929
+ */
930
+ p.clearSelected = function()
931
+ {
932
+ this.suggestionElements().removeClass(CSS_SELECTED);
933
+ };
934
+
935
+ /**
936
+ * Selects next suggestion relative to the current one. If there's no
937
+ * currently selected suggestion, it will select the first one. Selected
938
+ * suggestion will always be scrolled into view.
939
+ *
940
+ * @signature TextExtAutocomplete.toggleNextSuggestion()
941
+ *
942
+ * @author agorbatchev
943
+ * @date 2011/08/02
944
+ * @id TextExtAutocomplete.toggleNextSuggestion
945
+ */
946
+ p.toggleNextSuggestion = function()
947
+ {
948
+ var self = this,
949
+ selected = self.selectedSuggestionElement(),
950
+ next
951
+ ;
952
+
953
+ if(selected.length > 0)
954
+ {
955
+ next = selected.next();
956
+
957
+ if(next.length > 0)
958
+ selected.removeClass(CSS_SELECTED);
959
+ }
960
+ else
961
+ {
962
+ next = self.suggestionElements().first();
963
+ }
964
+
965
+ next.addClass(CSS_SELECTED);
966
+ self.scrollSuggestionIntoView(next);
967
+ };
968
+
969
+ /**
970
+ * Selects previous suggestion relative to the current one. Selected
971
+ * suggestion will always be scrolled into view.
972
+ *
973
+ * @signature TextExtAutocomplete.togglePreviousSuggestion()
974
+ *
975
+ * @author agorbatchev
976
+ * @date 2011/08/02
977
+ * @id TextExtAutocomplete.togglePreviousSuggestion
978
+ */
979
+ p.togglePreviousSuggestion = function()
980
+ {
981
+ var self = this,
982
+ selected = self.selectedSuggestionElement(),
983
+ prev = selected.prev()
984
+ ;
985
+
986
+ if(prev.length == 0)
987
+ return;
988
+
989
+ self.clearSelected();
990
+ prev.addClass(CSS_SELECTED);
991
+ self.scrollSuggestionIntoView(prev);
992
+ };
993
+
994
+ /**
995
+ * Scrolls specified HTML suggestion element into the view.
996
+ *
997
+ * @signature TextExtAutocomplete.scrollSuggestionIntoView(item)
998
+ *
999
+ * @param item {HTMLElement} jQuery HTML suggestion element which needs to
1000
+ * scrolled into view.
1001
+ *
1002
+ * @author agorbatchev
1003
+ * @date 2011/08/17
1004
+ * @id TextExtAutocomplete.scrollSuggestionIntoView
1005
+ */
1006
+ p.scrollSuggestionIntoView = function(item)
1007
+ {
1008
+ var itemHeight = item.outerHeight(),
1009
+ dropdown = this.containerElement(),
1010
+ dropdownHeight = dropdown.innerHeight(),
1011
+ scrollPos = dropdown.scrollTop(),
1012
+ itemTop = (item.position() || {}).top,
1013
+ scrollTo = null,
1014
+ paddingTop = parseInt(dropdown.css('paddingTop'))
1015
+ ;
1016
+
1017
+ if(itemTop == null)
1018
+ return;
1019
+
1020
+ // if scrolling down and item is below the bottom fold
1021
+ if(itemTop + itemHeight > dropdownHeight)
1022
+ scrollTo = itemTop + scrollPos + itemHeight - dropdownHeight + paddingTop;
1023
+
1024
+ // if scrolling up and item is above the top fold
1025
+ if(itemTop < 0)
1026
+ scrollTo = itemTop + scrollPos - paddingTop;
1027
+
1028
+ if(scrollTo != null)
1029
+ dropdown.scrollTop(scrollTo);
1030
+ };
1031
+
1032
+ /**
1033
+ * Uses the value from the text input to finish autocomplete action. Currently selected
1034
+ * suggestion from the dropdown will be used to complete the action. Triggers `hideDropdown`
1035
+ * event.
1036
+ *
1037
+ * @signature TextExtAutocomplete.selectFromDropdown()
1038
+ *
1039
+ * @author agorbatchev
1040
+ * @date 2011/08/17
1041
+ * @id TextExtAutocomplete.selectFromDropdown
1042
+ */
1043
+ p.selectFromDropdown = function()
1044
+ {
1045
+ var self = this,
1046
+ suggestion = self.selectedSuggestionElement().data(CSS_SUGGESTION)
1047
+ ;
1048
+
1049
+ if(suggestion)
1050
+ {
1051
+ self.val(self.itemManager().itemToString(suggestion));
1052
+ self.core().getFormData();
1053
+ }
1054
+
1055
+ self.trigger(EVENT_HIDE_DROPDOWN);
1056
+ };
1057
+ })(jQuery);