textext-rails 0.1.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.
@@ -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);