|
54 | 54 | that = this,
|
55 | 55 | defaults = {
|
56 | 56 | autoSelectFirst: false,
|
57 |
| - appendTo: 'body', |
| 57 | + appendTo: document.body, |
58 | 58 | serviceUrl: null,
|
59 | 59 | lookup: null,
|
60 | 60 | onSelect: null,
|
|
85 | 85 | return typeof response === 'string' ? $.parseJSON(response) : response;
|
86 | 86 | },
|
87 | 87 | showNoSuggestionNotice: false,
|
88 |
| - noSuggestionNotice: 'No results' |
| 88 | + noSuggestionNotice: 'No results', |
| 89 | + orientation: 'bottom', |
| 90 | + forceFixPosition: false |
89 | 91 | };
|
90 | 92 |
|
91 | 93 | // Shared variables:
|
|
101 | 103 | that.onChange = null;
|
102 | 104 | that.isLocal = false;
|
103 | 105 | that.suggestionsContainer = null;
|
| 106 | + that.noSuggestionsContainer = null; |
104 | 107 | that.options = $.extend({}, defaults, options);
|
105 | 108 | that.classes = {
|
106 | 109 | selected: 'autocomplete-selected',
|
|
134 | 137 | suggestionSelector = '.' + that.classes.suggestion,
|
135 | 138 | selected = that.classes.selected,
|
136 | 139 | options = that.options,
|
137 |
| - container; |
| 140 | + container, |
| 141 | + noSuggestionsContainer; |
138 | 142 |
|
139 | 143 | // Remove autocomplete attribute to prevent native suggestions:
|
140 | 144 | that.element.setAttribute('autocomplete', 'off');
|
|
146 | 150 | }
|
147 | 151 | };
|
148 | 152 |
|
| 153 | + // html() deals with many types: htmlString or Element or Array or jQuery |
| 154 | + that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>') |
| 155 | + .html(this.options.noSuggestionNotice).get(0); |
| 156 | + |
149 | 157 | that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
|
150 | 158 |
|
151 | 159 | container = $(that.suggestionsContainer);
|
|
173 | 181 | that.select($(this).data('index'));
|
174 | 182 | });
|
175 | 183 |
|
176 |
| - that.fixPosition(); |
177 |
| - |
178 | 184 | that.fixPositionCapture = function () {
|
179 | 185 | if (that.visible) {
|
180 | 186 | that.fixPosition();
|
|
214 | 220 | options.lookup = that.verifySuggestionsFormat(options.lookup);
|
215 | 221 | }
|
216 | 222 |
|
| 223 | + options.orientation = that.validateOrientation(options.orientation, 'bottom'); |
| 224 | + |
217 | 225 | // Adjust height, width and z-index:
|
218 | 226 | $(that.suggestionsContainer).css({
|
219 | 227 | 'max-height': options.maxHeight + 'px',
|
|
222 | 230 | });
|
223 | 231 | },
|
224 | 232 |
|
| 233 | + |
225 | 234 | clearCache: function () {
|
226 | 235 | this.cachedResponse = {};
|
227 | 236 | this.badQueries = [];
|
|
246 | 255 | },
|
247 | 256 |
|
248 | 257 | fixPosition: function () {
|
249 |
| - var that = this, |
250 |
| - offset, |
251 |
| - styles; |
| 258 | + // Use only when container has already its content |
252 | 259 |
|
253 |
| - // Don't adjsut position if custom container has been specified: |
254 |
| - if (that.options.appendTo !== 'body') { |
| 260 | + var that = this, |
| 261 | + $container = $(that.suggestionsContainer), |
| 262 | + containerParent = $container.parent().get(0); |
| 263 | + // Fix position automatically when appended to body. |
| 264 | + // In other cases force parameter must be given. |
| 265 | + if (containerParent !== document.body && !that.options.forceFixPosition) |
255 | 266 | return;
|
| 267 | + |
| 268 | + // Choose orientation |
| 269 | + var orientation = that.options.orientation, |
| 270 | + containerHeight = $container.outerHeight(), |
| 271 | + height = that.el.outerHeight(), |
| 272 | + offset = that.el.offset(), |
| 273 | + styles = { 'top': offset.top, 'left': offset.left }; |
| 274 | + |
| 275 | + if (orientation == 'auto') { |
| 276 | + var viewPortHeight = $(window).height(), |
| 277 | + scrollTop = $(window).scrollTop(), |
| 278 | + top_overflow = -scrollTop + offset.top - containerHeight, |
| 279 | + bottom_overflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight); |
| 280 | + |
| 281 | + if (Math.max(top_overflow, bottom_overflow) === top_overflow) |
| 282 | + orientation = 'top'; |
| 283 | + else |
| 284 | + orientation = 'bottom'; |
256 | 285 | }
|
257 | 286 |
|
258 |
| - offset = that.el.offset(); |
| 287 | + if (orientation === 'top') |
| 288 | + styles.top += -containerHeight; |
| 289 | + else |
| 290 | + styles.top += height; |
259 | 291 |
|
260 |
| - styles = { |
261 |
| - top: (offset.top + that.el.outerHeight()) + 'px', |
262 |
| - left: offset.left + 'px' |
263 |
| - }; |
| 292 | + // If container is not positioned to body, |
| 293 | + // correct its position using offset parent offset |
| 294 | + if(containerParent !== document.body) { |
| 295 | + var opacity = $container.css('opacity'), |
| 296 | + parentOffsetDiff; |
| 297 | + if (!that.visible) |
| 298 | + $container.css('opacity', 0).show(); |
| 299 | + |
| 300 | + parentOffsetDiff = $container.offsetParent().offset(); |
| 301 | + styles.top -= parentOffsetDiff.top; |
| 302 | + styles.left -= parentOffsetDiff.left; |
264 | 303 |
|
| 304 | + if (!that.visible) |
| 305 | + $container.css('opacity', opacity).hide(); |
| 306 | + } |
| 307 | + |
| 308 | + // -2px to account for suggestions border. |
265 | 309 | if (that.options.width === 'auto') {
|
266 | 310 | styles.width = (that.el.outerWidth() - 2) + 'px';
|
267 | 311 | }
|
268 | 312 |
|
269 |
| - $(that.suggestionsContainer).css(styles); |
| 313 | + $container.css(styles); |
270 | 314 | },
|
271 | 315 |
|
272 | 316 | enableKillerFn: function () {
|
|
557 | 601 | className = that.classes.suggestion,
|
558 | 602 | classSelected = that.classes.selected,
|
559 | 603 | container = $(that.suggestionsContainer),
|
| 604 | + noSuggestionsContainer = $(that.noSuggestionsContainer), |
560 | 605 | beforeRender = options.beforeRender,
|
561 | 606 | html = '',
|
562 | 607 | index,
|
|
577 | 622 |
|
578 | 623 | this.adjustContainerWidth();
|
579 | 624 |
|
| 625 | + noSuggestionsContainer.detach(); |
580 | 626 | container.html(html);
|
581 | 627 |
|
582 | 628 | // Select first value by default:
|
|
589 | 635 | beforeRender.call(that.element, container);
|
590 | 636 | }
|
591 | 637 |
|
| 638 | + that.fixPosition(); |
| 639 | + |
592 | 640 | container.show();
|
593 | 641 | that.visible = true;
|
594 | 642 |
|
|
597 | 645 |
|
598 | 646 | noSuggestions: function() {
|
599 | 647 | var that = this,
|
600 |
| - container = $(that.suggestionsContainer), |
601 |
| - html = '', |
602 |
| - width; |
603 |
| - |
604 |
| - html += '<div class="autocomplete-no-suggestion">' + this.options.noSuggestionNotice + '</div>'; |
| 648 | + container = $(that.suggestionsContainer), |
| 649 | + noSuggestionsContainer = $(that.noSuggestionsContainer); |
605 | 650 |
|
606 | 651 | this.adjustContainerWidth();
|
607 |
| - container.html(html); |
| 652 | + |
| 653 | + // Some explicit steps. Be careful here as it easy to get |
| 654 | + // noSuggestionsContainer removed from DOM if not detached properly. |
| 655 | + noSuggestionsContainer.detach(); |
| 656 | + container.empty(); // clean suggestions if any |
| 657 | + container.append(noSuggestionsContainer); |
| 658 | + |
| 659 | + that.fixPosition(); |
| 660 | + |
608 | 661 | container.show();
|
609 | 662 | that.visible = true;
|
610 | 663 | },
|
|
613 | 666 | var that = this,
|
614 | 667 | options = that.options,
|
615 | 668 | width,
|
616 |
| - container = $(that.suggestionsContainer) |
| 669 | + container = $(that.suggestionsContainer); |
617 | 670 |
|
618 | 671 | // If width is auto, adjust width before displaying suggestions,
|
619 | 672 | // because if instance was created before input had width, it will be zero.
|
|
669 | 722 | return suggestions;
|
670 | 723 | },
|
671 | 724 |
|
| 725 | + validateOrientation: function(orientation, fallback) { |
| 726 | + orientation = orientation.trim().toLowerCase(); |
| 727 | + if(['auto', 'bottom', 'top'].indexOf(orientation) == '-1') |
| 728 | + orientation = fallback; |
| 729 | + return orientation |
| 730 | + }, |
| 731 | + |
672 | 732 | processResponse: function (result, originalQuery, cacheKey) {
|
673 | 733 | var that = this,
|
674 | 734 | options = that.options;
|
|
829 | 889 | };
|
830 | 890 |
|
831 | 891 | // Create chainable jQuery plugin:
|
832 |
| - $.fn.autocomplete = function (options, args) { |
| 892 | + $.fn.autocomplete = function (options, args) { |
833 | 893 | var dataKey = 'autocomplete';
|
834 | 894 | // If function invoked without argument return
|
835 | 895 | // instance of the first matched element:
|
|
0 commit comments