Skip to content

Commit a9e7927

Browse files
author
Tomas Kirda
committed
Merge pull request #208 from tttomekkk/master
Vertical orientation of suggestions. fixPosition and noSuggestionNotice developed.
2 parents 09c4e18 + 562f152 commit a9e7927

File tree

2 files changed

+90
-26
lines changed

2 files changed

+90
-26
lines changed

readme.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ The standard jquery.autocomplete.js file is around 2.7KB when minified via Closu
4242
* `paramName`: Default `query`. The name of the request parameter that contains the query.
4343
* `transformResult`: `function(response, originalQuery) {}` called after the result of the query is ready. Converts the result into response.suggestions format.
4444
* `autoSelectFirst`: if set to `true`, first item will be selected when showing suggestions. Default value `false`.
45-
* `appendTo`: container where suggestions will be appended. Default value `body`. Can be jQuery object, selector or html element. Make sure to set `position: absolute` or `position: relative` for that element.
45+
* `appendTo`: container where suggestions will be appended. Default value `document.body`. Can be jQuery object, selector or html element. Make sure to set `position: absolute` or `position: relative` for that element.
4646
* `dataType`: type of data returned from server. Either 'text' (default) or 'jsonp', which will cause the autocomplete to use jsonp. You may return a json object in your callback when using jsonp.
4747
* `showNoSuggestionNotice`: Default `false`. When no matching results, display a notification label.
48-
* `noSuggestionNotice`: Default `No results`. Text for no matching results label.
48+
* `noSuggestionNotice`: Default `No results`. Text or htmlString or Element or jQuery object for no matching results label.
49+
* `forceFixPosition`: Default: `false`. Suggestions are automatically positioned when their container is appended to body (look at `appendTo` option), in other cases suggestions are rendered but no positioning is applied.
50+
Set this option to force auto positioning in other cases.
51+
* `orientation`: Default `bottom`. Vertical orientation of the displayed suggestions, available values are `auto`, `top`, `bottom`.
52+
If set to `auto`, the suggestions will be orientated it the way that place them closer to middle of the view port.
4953

5054
Autocomplete instance has following methods:
5155

src/jquery.autocomplete.js

Lines changed: 84 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
that = this,
5555
defaults = {
5656
autoSelectFirst: false,
57-
appendTo: 'body',
57+
appendTo: document.body,
5858
serviceUrl: null,
5959
lookup: null,
6060
onSelect: null,
@@ -85,7 +85,9 @@
8585
return typeof response === 'string' ? $.parseJSON(response) : response;
8686
},
8787
showNoSuggestionNotice: false,
88-
noSuggestionNotice: 'No results'
88+
noSuggestionNotice: 'No results',
89+
orientation: 'bottom',
90+
forceFixPosition: false
8991
};
9092

9193
// Shared variables:
@@ -101,6 +103,7 @@
101103
that.onChange = null;
102104
that.isLocal = false;
103105
that.suggestionsContainer = null;
106+
that.noSuggestionsContainer = null;
104107
that.options = $.extend({}, defaults, options);
105108
that.classes = {
106109
selected: 'autocomplete-selected',
@@ -134,7 +137,8 @@
134137
suggestionSelector = '.' + that.classes.suggestion,
135138
selected = that.classes.selected,
136139
options = that.options,
137-
container;
140+
container,
141+
noSuggestionsContainer;
138142

139143
// Remove autocomplete attribute to prevent native suggestions:
140144
that.element.setAttribute('autocomplete', 'off');
@@ -146,6 +150,10 @@
146150
}
147151
};
148152

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+
149157
that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
150158

151159
container = $(that.suggestionsContainer);
@@ -173,8 +181,6 @@
173181
that.select($(this).data('index'));
174182
});
175183

176-
that.fixPosition();
177-
178184
that.fixPositionCapture = function () {
179185
if (that.visible) {
180186
that.fixPosition();
@@ -214,6 +220,8 @@
214220
options.lookup = that.verifySuggestionsFormat(options.lookup);
215221
}
216222

223+
options.orientation = that.validateOrientation(options.orientation, 'bottom');
224+
217225
// Adjust height, width and z-index:
218226
$(that.suggestionsContainer).css({
219227
'max-height': options.maxHeight + 'px',
@@ -222,6 +230,7 @@
222230
});
223231
},
224232

233+
225234
clearCache: function () {
226235
this.cachedResponse = {};
227236
this.badQueries = [];
@@ -246,27 +255,62 @@
246255
},
247256

248257
fixPosition: function () {
249-
var that = this,
250-
offset,
251-
styles;
258+
// Use only when container has already its content
252259

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)
255266
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';
256285
}
257286

258-
offset = that.el.offset();
287+
if (orientation === 'top')
288+
styles.top += -containerHeight;
289+
else
290+
styles.top += height;
259291

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;
264303

304+
if (!that.visible)
305+
$container.css('opacity', opacity).hide();
306+
}
307+
308+
// -2px to account for suggestions border.
265309
if (that.options.width === 'auto') {
266310
styles.width = (that.el.outerWidth() - 2) + 'px';
267311
}
268312

269-
$(that.suggestionsContainer).css(styles);
313+
$container.css(styles);
270314
},
271315

272316
enableKillerFn: function () {
@@ -557,6 +601,7 @@
557601
className = that.classes.suggestion,
558602
classSelected = that.classes.selected,
559603
container = $(that.suggestionsContainer),
604+
noSuggestionsContainer = $(that.noSuggestionsContainer),
560605
beforeRender = options.beforeRender,
561606
html = '',
562607
index,
@@ -577,6 +622,7 @@
577622

578623
this.adjustContainerWidth();
579624

625+
noSuggestionsContainer.detach();
580626
container.html(html);
581627

582628
// Select first value by default:
@@ -589,6 +635,8 @@
589635
beforeRender.call(that.element, container);
590636
}
591637

638+
that.fixPosition();
639+
592640
container.show();
593641
that.visible = true;
594642

@@ -597,14 +645,19 @@
597645

598646
noSuggestions: function() {
599647
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);
605650

606651
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+
608661
container.show();
609662
that.visible = true;
610663
},
@@ -613,7 +666,7 @@
613666
var that = this,
614667
options = that.options,
615668
width,
616-
container = $(that.suggestionsContainer)
669+
container = $(that.suggestionsContainer);
617670

618671
// If width is auto, adjust width before displaying suggestions,
619672
// because if instance was created before input had width, it will be zero.
@@ -669,6 +722,13 @@
669722
return suggestions;
670723
},
671724

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+
672732
processResponse: function (result, originalQuery, cacheKey) {
673733
var that = this,
674734
options = that.options;
@@ -829,7 +889,7 @@
829889
};
830890

831891
// Create chainable jQuery plugin:
832-
$.fn.autocomplete = function (options, args) {
892+
$.fn.autocomplete = function (options, args) {
833893
var dataKey = 'autocomplete';
834894
// If function invoked without argument return
835895
// instance of the first matched element:

0 commit comments

Comments
 (0)