autocomplete-list.js revision 766ecb8023c2374bba052ae9e11d25c5c5e8305b
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore/**
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore * Traditional autocomplete dropdown list widget, just like Mom used to make.
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove *
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore * @module autocomplete
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore * @submodule autocomplete-list
3f3aa287185afb5d48d7ef0717054a154c372dc9Adam Moore * @class AutoCompleteList
d408aa66c7199d6b6a133c20c2116414dc70fa0aAdam Moore * @extends Widget
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith * @uses AutoCompleteBase
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith * @uses WidgetPosition
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith * @uses WidgetPositionAlign
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * @uses WidgetStack
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * @constructor
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith * @param {Object} config Configuration object.
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith */
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smithvar Node = Y.Node,
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith YArray = Y.Array,
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith // keyCode constants.
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith KEY_DOWN = 40,
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith KEY_ENTER = 13,
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith KEY_ESC = 27,
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith KEY_TAB = 9,
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith KEY_UP = 38,
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith // String shorthand.
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith _CLASS_ITEM = '_CLASS_ITEM',
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith _CLASS_ITEM_ACTIVE = '_CLASS_ITEM_ACTIVE',
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith _CLASS_ITEM_HOVER = '_CLASS_ITEM_HOVER',
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith _SELECTOR_ITEM = '_SELECTOR_ITEM',
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith ACTIVE_ITEM = 'activeItem',
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith CIRCULAR = 'circular',
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith HOVERED_ITEM = 'hoveredItem',
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith INPUT_NODE = 'inputNode',
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith ITEM = 'item',
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith VISIBLE = 'visible',
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith WIDTH = 'width',
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith // Event names.
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith EVT_SELECT = 'select',
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith
680f13616a493c7bf3a794982e07d10abd9763b3Luke SmithList = Y.Base.create('autocompleteList', Y.Widget, [
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith Y.AutoCompleteBase,
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith Y.WidgetPosition,
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith Y.WidgetPositionAlign,
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith Y.WidgetStack
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith], {
f60d4516da25663a6679c02654d8c2da2701657bLuke Smith // -- Prototype Properties -------------------------------------------------
f60d4516da25663a6679c02654d8c2da2701657bLuke Smith CONTENT_TEMPLATE: '<ul/>',
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith ITEM_TEMPLATE: '<li/>',
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith // -- Lifecycle Prototype Methods ------------------------------------------
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith initializer: function () {
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith /**
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * Fires when an autocomplete suggestion is selected from the list by
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove * a keyboard action or mouse click.
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove *
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * @event select
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * @param {EventFacade} e Event facade with the following additional
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * properties:
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith *
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * <dl>
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * <dt>result (Object)</dt>
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * <dd>
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * AutoComplete result object.
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * </dd>
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove * </dl>
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove *
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith * @preventable _defResultsFn
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith */
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith this.publish(EVT_SELECT, {
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith defaultFn: this._defSelectFn
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith });
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith this._events = [];
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith this._inputNode = this.get(INPUT_NODE);
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith // Cache commonly used classnames and selectors for performance.
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith this[_CLASS_ITEM] = this.getClassName(ITEM);
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith this[_CLASS_ITEM_ACTIVE] = this.getClassName(ITEM, 'active');
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith this[_CLASS_ITEM_HOVER] = this.getClassName(ITEM, 'hover');
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith this[_SELECTOR_ITEM] = '.' + this[_CLASS_ITEM];
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith if (!this._inputNode) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith Y.error('No inputNode specified.');
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith }
2f788db79418bb9fff1a97f29f12dad42f4909efLuke Smith
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith if (!this.get('align.node')) {
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore this.set('align.node', this._inputNode);
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith }
43c15c82a8f3e7361f944d6daa02f1885c88ebf0Adam Moore
030b855bbf1937a46e1e2b88025d61e72a205469Luke Smith if (!this.get(WIDTH)) {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith this.set(WIDTH, this._inputNode.get('offsetWidth'));
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
edac37f62b5675c375833896993369855211cbfdTodd Kloots },
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith destructor: function () {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith this.unbindInput();
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith while (this._events.length) {
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore this._events.pop().detach();
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith }
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore },
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith bindUI: function () {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith this._bindInput();
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith this._bindList();
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith },
d408aa66c7199d6b6a133c20c2116414dc70fa0aAdam Moore
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith renderUI: function () {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith // See http://www.w3.org/WAI/PF/aria/roles#combobox for ARIA details.
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots this._contentBox = this.get('contentBox').set('role', 'listbox');
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith this._inputNode.addClass(this.getClassName('input')).setAttrs({
63d012ee193ba8c768b2a2aade99081422759213Luke Smith 'aria-autocomplete': 'list',
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith 'aria-owns': this._contentBox.get('id'),
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith role: 'combobox'
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots });
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith },
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith syncUI: function () {
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith this.syncInput();
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith this._syncResults();
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith this._syncVisibility();
73cf5b20418beae941f34ec39a8d87035ae01711Luke Smith },
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots // -- Public Prototype Methods ---------------------------------------------
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith /**
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * Hides the list.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith *
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * @method hide
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * @see show
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * @chainable
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith */
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith hide: function () {
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith return this.set(VISIBLE, false);
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith },
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith /**
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * Selects the specified <i>itemNode</i>, or the current
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * <code>activeItem</code> if <i>itemNode</i> is not specified.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith *
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * @method selectItem
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * @param {Node} itemNode (optional) Item node to select.
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * @chainable
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith */
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith selectItem: function (itemNode) {
426d8b6487a18aa68071eecf43ed7bc4f58f36d4Luke Smith if (itemNode) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith if (!itemNode.hasClass(this[_CLASS_ITEM])) {
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots return;
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith }
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith } else {
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith itemNode = this.get(ACTIVE_ITEM);
851a447adf0cc52bb286ca6e8d71c3f6e088030dLuke Smith
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots if (!itemNode) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith return;
14017741ef334485c65013d129608513161db7c2Luke Smith }
14017741ef334485c65013d129608513161db7c2Luke Smith }
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith this.fire(EVT_SELECT, {
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith result: itemNode.getData('result')
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith });
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots return this;
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith },
d9462785fbdde2766d901cb36d19c9d6810c7954Ryan Grove
851a447adf0cc52bb286ca6e8d71c3f6e088030dLuke Smith /**
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith * Shows the list.
eea24ae7b751e818f5a88c631ddfa3799e963cd4Adam Moore *
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith * @method show
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots * @see hide
0c4bb3413c8172f27fcebdf7242f5798d026064bLuke Smith * @chainable
0c4bb3413c8172f27fcebdf7242f5798d026064bLuke Smith */
14017741ef334485c65013d129608513161db7c2Luke Smith show: function () {
d408aa66c7199d6b6a133c20c2116414dc70fa0aAdam Moore return this.set(VISIBLE, true);
7a925457f765fe5ea2507b4629476fa3e100e4eeTodd Kloots },
14017741ef334485c65013d129608513161db7c2Luke Smith
14017741ef334485c65013d129608513161db7c2Luke Smith // -- Protected Prototype Methods ------------------------------------------
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots /**
680f13616a493c7bf3a794982e07d10abd9763b3Luke Smith * Activates the next item after the currently active item. If there is no
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * next item and the <code>circular</code> attribute is <code>true</code>,
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * the first item in the list will be activated.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith *
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * @method _activateNextItem
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * @protected
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith */
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith _activateNextItem: function () {
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith var item = this.get(ACTIVE_ITEM),
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith selector = this[_SELECTOR_ITEM],
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith nextItem;
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith if (item) {
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith // Get the next item. If there isn't a next item, circle back around
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith // and get the first item.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith nextItem = item.next(selector) ||
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith (this.get(CIRCULAR) && item.get('parentNode').one(selector));
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith if (nextItem) {
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith this._set(ACTIVE_ITEM, nextItem);
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith }
0211eb547f9f237c08793afee4008af3e8bd3da9Luke Smith }
0211eb547f9f237c08793afee4008af3e8bd3da9Luke Smith
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith return this;
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith },
a5f89af11a2a0cd671fb5d37a001dfc929eba3b1Todd Kloots
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith /**
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * Activates the item previous to the currently active item. If there is no
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * previous item and the <code>circular</code> attribute is
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * <code>true</code>, the last item in the list will be activated.
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith *
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * @method _activatePrevItem
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith * @protected
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith */
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith _activatePrevItem: function () {
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith var item = this.get(ACTIVE_ITEM),
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith selector = this[_SELECTOR_ITEM],
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith prevItem;
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith if (item) {
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith // Get the previous item. If there isn't a previous item, circle
10d8bafc5c24f3a4285cf6060a1935ba5cfc4b85Luke Smith // back around and get the last item.
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith prevItem = item.previous(selector) ||
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith (this.get(CIRCULAR) && item.get('parentNode').one(selector + ':last-child'));
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith if (prevItem) {
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith this._set(ACTIVE_ITEM, prevItem);
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith }
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith }
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith return this;
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith },
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith /**
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * Appends the specified result <i>items</i> to the list inside a new item
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * node.
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith *
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * @method _add
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * @param {Array|Node|HTMLElement|String} items Result item or array of
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith * result items.
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith * @returns {NodeList} Added nodes.
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith * @protected
0211eb547f9f237c08793afee4008af3e8bd3da9Luke Smith */
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith _add: function (items) {
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith var itemNodes = [];
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith YArray.each(Y.Lang.isArray(items) ? items : [items], function (item) {
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith itemNodes.push(this._createItemNode(item.display).setData('result', item));
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith }, this);
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith itemNodes = Y.all(itemNodes);
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith this._contentBox.append(itemNodes.toFrag());
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith return itemNodes;
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith },
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith /**
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith * Binds <code>inputNode</code> events, in addition to those already bound
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith * by <code>AutoCompleteBase</code>'s public <code>bindInput()</code>
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith * method.
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith *
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith * @method _bindInput
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith * @protected
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith */
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith _bindInput: function () {
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith var inputNode = this._inputNode;
3e72e854188ef0c1927857102f15b449dc598fafLuke Smith
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith // Call AutoCompleteBase's bind method first.
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith this.bindInput();
14017741ef334485c65013d129608513161db7c2Luke Smith
669975fc1822c49f2f84c92ac2b7809df46b1093Luke Smith this._events.concat([
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith inputNode.on('blur', this._onInputBlur, this),
14017741ef334485c65013d129608513161db7c2Luke Smith inputNode.on(Y.UA.gecko ? 'keypress' : 'keydown', this._onInputKey, this)
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith ]);
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith },
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith /**
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * Binds list events.
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith *
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * @method _bindList
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith * @protected
fb83a09fe023a741781ee955f4e9538d3cbe21a2Luke Smith */
aadc0b0e666b9b335884a2437510798ae8949343Adam Moore _bindList: function () {
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots this._events.concat([
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots this.after('mouseover', this._afterMouseOver, this),
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith this.after('mouseout', this._afterMouseOut, this),
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith this.after('activeItemChange', this._afterActiveItemChange, this),
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots this.after('hoveredItemChange', this._afterHoveredItemChange, this),
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots this.after('resultsChange', this._afterResultsChange, this),
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots this.after('visibleChange', this._afterVisibleChange, this),
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith this._contentBox.delegate('click', this._onItemClick, this[_SELECTOR_ITEM], this)
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith ]);
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith },
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith /**
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * Clears the contents of the tray.
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith *
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * @method _clear
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * @protected
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots */
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots _clear: function () {
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots this._set(ACTIVE_ITEM, null);
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots this._set(HOVERED_ITEM, null);
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots this._contentBox.get('children').remove(true);
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith },
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots /**
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * Creates an item node with the specified <i>content</i>.
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots *
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * @method _createItemNode
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * @param {Node|HTMLElement|String} content
67d8986c26b16b1d9ea6e5b62e753c55af9a4f52Todd Kloots * @protected
7b4d1363155303b5bfb852e5639b851bbd4dc255Luke Smith * @returns {Node} Item node.
*/
_createItemNode: function (content) {
var itemNode = Node.create(this.ITEM_TEMPLATE);
return itemNode.append(content).setAttrs({
id : Y.stamp(itemNode),
role: 'option'
}).addClass(this[_CLASS_ITEM]);
},
/**
* Synchronizes the results displayed in the list with those in the
* <i>results</i> argument, or with the <code>results</code> attribute if an
* argument is not provided.
*
* @method _syncResults
* @param {Array} results (optional) Results.
* @protected
*/
_syncResults: function (results) {
var items;
if (!results) {
results = this.get('results');
}
this._clear();
if (results.length) {
items = this._add(results);
this._set(ACTIVE_ITEM, items.item(0));
}
},
/**
* Synchronizes the visibility of the tray with the <i>visible</i> argument,
* or with the <code>visible</code> attribute if an argument is not
* provided.
*
* @method _syncVisibility
* @param {Boolean} visible (optional) Visibility.
* @protected
*/
_syncVisibility: function (visible) {
if (visible === undefined) {
visible = this.get(VISIBLE);
}
this._contentBox.set('aria-hidden', !visible);
if (!visible) {
this._set(ACTIVE_ITEM, null);
this._set(HOVERED_ITEM, null);
}
},
// -- Protected Event Handlers ---------------------------------------------
/**
* Handles <code>activeItemChange</code> events.
*
* @method _afterActiveItemChange
* @param {EventTarget} e
* @protected
*/
_afterActiveItemChange: function (e) {
if (e.prevVal) {
e.prevVal.removeClass(this[_CLASS_ITEM_ACTIVE]);
}
if (e.newVal) {
e.newVal.addClass(this[_CLASS_ITEM_ACTIVE]);
}
},
/**
* Handles <code>hoveredItemChange</code> events.
*
* @method _afterHoveredItemChange
* @param {EventTarget} e
* @protected
*/
_afterHoveredItemChange: function (e) {
if (e.prevVal) {
e.prevVal.removeClass(this[_CLASS_ITEM_HOVER]);
}
if (e.newVal) {
e.newVal.addClass(this[_CLASS_ITEM_HOVER]);
}
},
/**
* Handles <code>mouseover</code> events.
*
* @method _afterMouseOver
* @param {EventTarget} e
* @protected
*/
_afterMouseOver: function (e) {
var itemNode = e.domEvent.target.ancestor('.' + this[_CLASS_ITEM], true);
this._mouseOverList = true;
if (itemNode) {
this._set(HOVERED_ITEM, itemNode);
}
},
/**
* Handles <code>mouseout</code> events.
*
* @method _afterMouseOut
* @param {EventTarget} e
* @protected
*/
_afterMouseOut: function () {
this._mouseOverList = false;
this._set(HOVERED_ITEM, null);
},
/**
* Handles <code>resultsChange</code> events.
*
* @method _afterResultsChange
* @param {EventFacade} e
* @protected
*/
_afterResultsChange: function (e) {
this._syncResults(e.newVal);
this.set(VISIBLE, !!e.newVal.length);
},
/**
* Handles <code>visibleChange</code> events.
*
* @method _afterVisibleChange
* @param {EventFacade} e
* @protected
*/
_afterVisibleChange: function (e) {
this._syncVisibility(!!e.newVal);
},
/**
* Handles <code>inputNode</code> <code>blur</code> events.
*
* @method _onInputBlur
* @param {EventTarget} e
* @protected
*/
_onInputBlur: function (e) {
// Hide the list on inputNode blur events, unless the mouse is currently
// over the list (which indicates that the user is probably interacting
// with it) or the tab key was pressed.
if (this._mouseOverList && this._lastInputKey !== KEY_TAB) {
this._inputNode.focus();
} else {
this.hide();
}
},
/**
* Handles <code>inputNode</code> key events.
*
* @method _onInputKey
* @param {EventTarget} e
* @protected
*/
_onInputKey: function (e) {
var keyCode = e.keyCode;
this._lastInputKey = keyCode;
if (this.get(VISIBLE)) {
switch (keyCode) {
case KEY_DOWN:
this._activateNextItem();
break;
case KEY_ENTER:
this.selectItem();
break;
case KEY_ESC:
this.hide();
break;
// case KEY_TAB:
// break;
case KEY_UP:
this._activatePrevItem();
break;
default:
return;
}
e.preventDefault();
}
},
/**
* Delegated event handler for item <code>click</code> events.
*
* @method _onItemClick
* @param {EventTarget} e
* @protected
*/
_onItemClick: function (e) {
e.preventDefault();
this.selectItem(e.currentTarget);
},
// -- Protected Default Event Handlers -------------------------------------
/**
* Default <code>select</code> event handler.
*
* @method _defSelectFn
* @param {EventTarget} e
* @protected
*/
_defSelectFn: function (e) {
// TODO: support query delimiters, typeahead completion, etc.
this.hide();
this._inputNode.set('value', e.result.text).focus();
}
}, {
ATTRS: {
/**
* Item that's currently active, if any. When the user presses enter,
* this is the item that will be selected.
*
* @attribute activeItem
* @type Node
* @readonly
*/
activeItem: {
readOnly: true,
value: null
},
// The "align" attribute is documented in WidgetPositionAlign.
align: {
value: {
points: ['tl', 'bl']
}
},
/**
* If <code>true</code>, keyboard navigation will wrap around to the
* opposite end of the list when navigating past the first or last item.
*
* @attribute circular
* @type Boolean
* @default true
*/
circular: {
value: true
},
/**
* Item currently being hovered over by the mouse, if any.
*
* @attribute hoveredItem
* @type Node|null
* @readonly
*/
hoveredItem: {
readOnly: true,
value: null
},
// The "visible" attribute is documented in Widget.
visible: {
value: false
}
},
CSS_PREFIX: Y.ClassNameManager.getClassName('aclist')
});
Y.AutoCompleteList = List;
/**
* Alias for <a href="AutoCompleteList.html"><code>AutoCompleteList</code></a>.
* See that class for API docs.
*
* @class AutoComplete
*/
Y.AutoComplete = List;