Skip to content

Commit e40cbd0

Browse files
committed
feat(appendTo): new parameter
1 parent be1dc19 commit e40cbd0

13 files changed

+371
-241
lines changed

src/angular/directive.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ angular.module('algolia.autocomplete', [])
7575
debug: scope.options.debug,
7676
cssClasses: scope.options.cssClasses,
7777
datasets: scope.datasets,
78-
keyboardShortcuts: scope.options.keyboardShortcuts
78+
keyboardShortcuts: scope.options.keyboardShortcuts,
79+
appendTo: scope.options.appendTo
7980
});
8081
}
8182

src/autocomplete/css.js

+12
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ var css = {
6161
cursor: 'cursor',
6262
dataset: 'dataset',
6363
empty: 'empty'
64+
},
65+
appendTo: {
66+
wrapper: {
67+
position: 'absolute',
68+
zIndex: '100',
69+
display: 'none'
70+
},
71+
input: {},
72+
inputWithNoHint: {},
73+
dropdown: {
74+
display: 'block'
75+
}
6476
}
6577
};
6678

src/autocomplete/dataset.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ function Dataset(o) {
3737

3838
this.templates = getTemplates(o.templates, this.displayFn);
3939

40+
this.css = _.mixin({}, css, o.appendTo ? css.appendTo : {});
4041
this.cssClasses = _.mixin({}, css.defaultClasses, o.cssClasses || {});
4142

4243
var clazz = _.className(this.cssClasses.prefix, this.cssClasses.dataset);
@@ -126,7 +127,7 @@ _.mixin(Dataset.prototype, EventEmitter, {
126127
var suggestionsHtml = html.suggestions.
127128
replace('%PREFIX%', this.cssClasses.prefix).
128129
replace('%SUGGESTIONS%', this.cssClasses.suggestions);
129-
$suggestions = DOM.element(suggestionsHtml).css(css.suggestions);
130+
$suggestions = DOM.element(suggestionsHtml).css(this.css.suggestions);
130131

131132
// jQuery#append doesn't support arrays as the first argument
132133
// until version 1.8, see http://bugs.jquery.com/ticket/11231
@@ -147,7 +148,7 @@ _.mixin(Dataset.prototype, EventEmitter, {
147148
$el.data(datasetKey, that.name);
148149
$el.data(valueKey, that.displayFn(suggestion) || undefined); // this led to undefined return value
149150
$el.data(datumKey, JSON.stringify(suggestion));
150-
$el.children().each(function() { DOM.element(this).css(css.suggestionChild); });
151+
$el.children().each(function() { DOM.element(this).css(self.css.suggestionChild); });
151152

152153
return $el;
153154
}

src/autocomplete/dropdown.js

+37-4
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ function Dropdown(o) {
3131
this.isOpen = false;
3232
this.isEmpty = true;
3333
this.minLength = o.minLength || 0;
34-
this.cssClasses = _.mixin({}, css.defaultClasses, o.cssClasses || {});
3534
this.templates = {};
35+
this.appendTo = o.appendTo || false;
36+
this.css = _.mixin({}, css, o.appendTo ? css.appendTo : {});
37+
this.cssClasses = o.cssClasses = _.mixin({}, css.defaultClasses, o.cssClasses || {});
3638

3739
// bound functions
3840
onSuggestionClick = _.bind(this._onSuggestionClick, this);
@@ -45,6 +47,11 @@ function Dropdown(o) {
4547
.on('mouseenter.aa', cssClass, onSuggestionMouseEnter)
4648
.on('mouseleave.aa', cssClass, onSuggestionMouseLeave);
4749

50+
this.$input = o.input;
51+
this.$wrapper = o.wrapper;
52+
53+
this.$container = o.appendTo ? this.$wrapper : this.$menu;
54+
4855
if (o.templates && o.templates.header) {
4956
this.templates.header = _.templatify(o.templates.header);
5057
this.$menu.prepend(this.templates.header());
@@ -73,6 +80,11 @@ function Dropdown(o) {
7380
this.templates.footer = _.templatify(o.templates.footer);
7481
this.$menu.append(this.templates.footer());
7582
}
83+
84+
var self = this;
85+
DOM.element(window).resize(function() {
86+
self._redraw();
87+
});
7688
}
7789

7890
// instance methods
@@ -162,17 +174,38 @@ _.mixin(Dropdown.prototype, EventEmitter, {
162174
},
163175

164176
_hide: function() {
165-
this.$menu.hide();
177+
this.$container.hide();
166178
},
167179

168180
_show: function() {
169181
// can't use jQuery#show because $menu is a span element we want
170182
// display: block; not dislay: inline;
171-
this.$menu.css('display', 'block');
183+
this.$container.css('display', 'block');
184+
185+
this._redraw();
172186

173187
this.trigger('shown');
174188
},
175189

190+
_redraw: function redraw() {
191+
if (!this.isOpen || !this.appendTo) return;
192+
193+
var inputRect = this.$input[0].getBoundingClientRect();
194+
195+
this.$wrapper.css('width', inputRect.width + 'px');
196+
this.$wrapper.css('top', 0 + 'px');
197+
this.$wrapper.css('left', 0 + 'px');
198+
199+
var wrapperRect = this.$wrapper[0].getBoundingClientRect();
200+
201+
var top = inputRect.bottom - wrapperRect.top;
202+
this.$wrapper.css('top', top + 'px');
203+
var left = inputRect.left - wrapperRect.left;
204+
this.$wrapper.css('left', left + 'px');
205+
206+
this.trigger('redrawn');
207+
},
208+
176209
_getSuggestions: function getSuggestions() {
177210
return this.$menu.find(_.className(this.cssClasses.prefix, this.cssClasses.suggestion));
178211
},
@@ -272,7 +305,7 @@ _.mixin(Dropdown.prototype, EventEmitter, {
272305
},
273306

274307
setLanguageDirection: function setLanguageDirection(dir) {
275-
this.$menu.css(dir === 'ltr' ? css.ltr : css.rtl);
308+
this.$menu.css(dir === 'ltr' ? this.css.ltr : this.css.rtl);
276309
},
277310

278311
moveCursorUp: function moveCursorUp() {

src/autocomplete/typeahead.js

+41-13
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,22 @@ function Typeahead(o) {
3131
this.autoselectOnBlur = !!o.autoselectOnBlur;
3232
this.openOnFocus = !!o.openOnFocus;
3333
this.minLength = _.isNumber(o.minLength) ? o.minLength : 1;
34+
35+
o.hint = !!o.hint;
36+
37+
if (o.hint && o.appendTo) {
38+
throw new Error('[autocomplete.js] hint and appendTo options can\'t be used at the same time');
39+
}
40+
41+
this.css = o.css = _.mixin({}, css, o.appendTo ? css.appendTo : {});
3442
this.cssClasses = o.cssClasses = _.mixin({}, css.defaultClasses, o.cssClasses || {});
35-
this.$node = buildDom(o);
3643

37-
$menu = this.$node.find(_.className(this.cssClasses.prefix, this.cssClasses.dropdownMenu));
38-
$input = this.$node.find(_.className(this.cssClasses.prefix, this.cssClasses.input));
39-
$hint = this.$node.find(_.className(this.cssClasses.prefix, this.cssClasses.hint));
44+
var domElts = buildDom(o);
45+
46+
this.$node = domElts.wrapper;
47+
$menu = domElts.menu;
48+
$input = domElts.input;
49+
$hint = domElts.hint;
4050

4151
if (o.dropdownMenuContainer) {
4252
DOM.element(o.dropdownMenuContainer)
@@ -64,7 +74,16 @@ function Typeahead(o) {
6474

6575
this.eventBus = o.eventBus || new EventBus({el: $input});
6676

67-
this.dropdown = new Typeahead.Dropdown({menu: $menu, datasets: o.datasets, templates: o.templates, cssClasses: this.cssClasses, minLength: this.minLength})
77+
this.dropdown = new Typeahead.Dropdown({
78+
input: $input,
79+
appendTo: o.appendTo,
80+
wrapper: this.$node,
81+
menu: $menu,
82+
datasets: o.datasets,
83+
templates: o.templates,
84+
cssClasses: o.cssClasses,
85+
minLength: this.minLength
86+
})
6887
.onSync('suggestionClicked', this._onSuggestionClicked, this)
6988
.onSync('cursorMoved', this._onCursorMoved, this)
7089
.onSync('cursorRemoved', this._onCursorRemoved, this)
@@ -439,21 +458,21 @@ function buildDom(options) {
439458
var $hint;
440459

441460
$input = DOM.element(options.input);
442-
$wrapper = DOM.element(html.wrapper.replace('%ROOT%', options.cssClasses.root)).css(css.wrapper);
461+
$wrapper = DOM.element(html.wrapper.replace('%ROOT%', options.cssClasses.root)).css(options.css.wrapper);
443462
// override the display property with the table-cell value
444463
// if the parent element is a table and the original input was a block
445464
// -> https://github.com/algolia/autocomplete.js/issues/16
446-
if ($input.css('display') === 'block' && $input.parent().css('display') === 'table') {
465+
if (!options.appendTo && $input.css('display') === 'block' && $input.parent().css('display') === 'table') {
447466
$wrapper.css('display', 'table-cell');
448467
}
449468
var dropdownHtml = html.dropdown.
450469
replace('%PREFIX%', options.cssClasses.prefix).
451470
replace('%DROPDOWN_MENU%', options.cssClasses.dropdownMenu);
452-
$dropdown = DOM.element(dropdownHtml).css(css.dropdown);
471+
$dropdown = DOM.element(dropdownHtml).css(options.css.dropdown);
453472
if (options.templates && options.templates.dropdownMenu) {
454473
$dropdown.html(_.templatify(options.templates.dropdownMenu)());
455474
}
456-
$hint = $input.clone().css(css.hint).css(getBackgroundStyles($input));
475+
$hint = $input.clone().css(options.css.hint).css(getBackgroundStyles($input));
457476

458477
$hint
459478
.val('')
@@ -477,7 +496,7 @@ function buildDom(options) {
477496
$input
478497
.addClass(_.className(options.cssClasses.prefix, options.cssClasses.input, true))
479498
.attr({autocomplete: 'off', spellcheck: false})
480-
.css(options.hint ? css.input : css.inputWithNoHint);
499+
.css(options.hint ? options.css.input : options.css.inputWithNoHint);
481500

482501
// ie7 does not like it when dir is set to auto
483502
try {
@@ -488,11 +507,20 @@ function buildDom(options) {
488507
// ignore
489508
}
490509

491-
return $input
492-
.wrap($wrapper)
493-
.parent()
510+
$wrapper = options.appendTo
511+
? $wrapper.appendTo(DOM.element(options.appendTo).eq(0)).eq(0)
512+
: $input.wrap($wrapper).parent();
513+
514+
$wrapper
494515
.prepend(options.hint ? $hint : null)
495516
.append($dropdown);
517+
518+
return {
519+
wrapper: $wrapper,
520+
input: $input,
521+
hint: $hint,
522+
menu: $dropdown
523+
};
496524
}
497525

498526
function getBackgroundStyles($el) {

src/jquery/plugin.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ methods = {
6262
debug: o.debug,
6363
cssClasses: o.cssClasses,
6464
datasets: datasets,
65-
keyboardShortcuts: o.keyboardShortcuts
65+
keyboardShortcuts: o.keyboardShortcuts,
66+
appendTo: o.appendTo
6667
});
6768

6869
$input.data(typeaheadKey, typeahead);

src/standalone/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ function autocomplete(selector, options, datasets, typeaheadObject) {
4646
debug: options.debug,
4747
cssClasses: options.cssClasses,
4848
datasets: datasets,
49-
keyboardShortcuts: options.keyboardShortcuts
49+
keyboardShortcuts: options.keyboardShortcuts,
50+
appendTo: options.appendTo
5051
});
5152

5253
$input.data(typeaheadKey, typeahead);

test/integration/test.html

+24-15
Original file line numberDiff line numberDiff line change
@@ -95,24 +95,33 @@
9595
'this is a very long value so deal with it otherwise you gonna have a hard time'
9696
];
9797

98-
$('#states').autocomplete({ }, [
99-
{
100-
displayKey: 'name',
101-
source: function(q, cb) {
102-
var res = [];
103-
if (!q) {
104-
cb([]);
105-
return;
106-
}
107-
for (var i = 0; i < states.length; ++i) {
108-
if (states[i].toLowerCase().indexOf(q.toLowerCase()) === 0) {
109-
res.push({ name: states[i] });
98+
function buildAutocomplete (options) {
99+
if (options === undefined) options = { };
100+
if (window.autocomplete) {
101+
window.autocomplete.autocomplete.destroy();
102+
window.autocomplete = null;
103+
}
104+
window.autocomplete = $('#states').autocomplete(options, [
105+
{
106+
displayKey: 'name',
107+
source: function(q, cb) {
108+
var res = [];
109+
if (!q) {
110+
cb([]);
111+
return;
112+
}
113+
for (var i = 0; i < states.length; ++i) {
114+
if (states[i].toLowerCase().indexOf(q.toLowerCase()) === 0) {
115+
res.push({ name: states[i] });
116+
}
110117
}
118+
cb(res);
111119
}
112-
cb(res);
113120
}
114-
}
115-
]);
121+
]);
122+
}
123+
124+
buildAutocomplete();
116125
</script>
117126
</body>
118127
</html>

0 commit comments

Comments
 (0)