AutorÃa | Ultima modificación | Ver Log |
YUI.add('autocomplete-list', function (Y, NAME) {/**Traditional autocomplete dropdown list widget, just like Mom used to make.@module autocomplete@submodule autocomplete-list**//**Traditional autocomplete dropdown list widget, just like Mom used to make.@class AutoCompleteList@extends Widget@uses AutoCompleteBase@uses WidgetPosition@uses WidgetPositionAlign@constructor@param {Object} config Configuration object.**/var Lang = Y.Lang,Node = Y.Node,YArray = Y.Array,// Whether or not we need an iframe shim.useShim = Y.UA.ie && Y.UA.ie < 7,// keyCode constants.KEY_TAB = 9,// String shorthand._CLASS_ITEM = '_CLASS_ITEM',_CLASS_ITEM_ACTIVE = '_CLASS_ITEM_ACTIVE',_CLASS_ITEM_HOVER = '_CLASS_ITEM_HOVER',_SELECTOR_ITEM = '_SELECTOR_ITEM',ACTIVE_ITEM = 'activeItem',ALWAYS_SHOW_LIST = 'alwaysShowList',CIRCULAR = 'circular',HOVERED_ITEM = 'hoveredItem',ID = 'id',ITEM = 'item',LIST = 'list',RESULT = 'result',RESULTS = 'results',VISIBLE = 'visible',WIDTH = 'width',// Event names.EVT_SELECT = 'select',List = Y.Base.create('autocompleteList', Y.Widget, [Y.AutoCompleteBase,Y.WidgetPosition,Y.WidgetPositionAlign], {// -- Prototype Properties -------------------------------------------------ARIA_TEMPLATE: '<div/>',ITEM_TEMPLATE: '<li/>',LIST_TEMPLATE: '<ul/>',// Widget automatically attaches delegated event handlers to everything in// Y.Node.DOM_EVENTS, including synthetic events. Since Widget's event// delegation won't work for the synthetic valuechange event, and since// it creates a name collision between the backcompat "valueChange" synth// event alias and AutoCompleteList's "valueChange" event for the "value"// attr, this hack is necessary in order to prevent Widget from attaching// valuechange handlers.UI_EVENTS: (function () {var uiEvents = Y.merge(Y.Node.DOM_EVENTS);delete uiEvents.valuechange;delete uiEvents.valueChange;return uiEvents;}()),// -- Lifecycle Prototype Methods ------------------------------------------initializer: function () {var inputNode = this.get('inputNode');if (!inputNode) {Y.error('No inputNode specified.');return;}this._inputNode = inputNode;this._listEvents = [];// This ensures that the list is rendered inside the same parent as the// input node by default, which is necessary for proper ARIA support.this.DEF_PARENT_NODE = inputNode.get('parentNode');// Cache commonly used classnames and selectors for performance.this[_CLASS_ITEM] = this.getClassName(ITEM);this[_CLASS_ITEM_ACTIVE] = this.getClassName(ITEM, 'active');this[_CLASS_ITEM_HOVER] = this.getClassName(ITEM, 'hover');this[_SELECTOR_ITEM] = '.' + this[_CLASS_ITEM];/**Fires when an autocomplete suggestion is selected from the list,typically via a keyboard action or mouse click.@event select@param {Node} itemNode List item node that was selected.@param {Object} result AutoComplete result object.@preventable _defSelectFn**/this.publish(EVT_SELECT, {defaultFn: this._defSelectFn});},destructor: function () {while (this._listEvents.length) {this._listEvents.pop().detach();}if (this._ariaNode) {this._ariaNode.remove().destroy(true);}},bindUI: function () {this._bindInput();this._bindList();},renderUI: function () {var ariaNode = this._createAriaNode(),boundingBox = this.get('boundingBox'),contentBox = this.get('contentBox'),inputNode = this._inputNode,listNode = this._createListNode(),parentNode = inputNode.get('parentNode');inputNode.addClass(this.getClassName('input')).setAttrs({'aria-autocomplete': LIST,'aria-expanded' : false,'aria-owns' : listNode.get('id')});// ARIA node must be outside the widget or announcements won't be made// when the widget is hidden.parentNode.append(ariaNode);// Add an iframe shim for IE6.if (useShim) {boundingBox.plug(Y.Plugin.Shim);}this._ariaNode = ariaNode;this._boundingBox = boundingBox;this._contentBox = contentBox;this._listNode = listNode;this._parentNode = parentNode;},syncUI: function () {// No need to call _syncPosition() here; the other _sync methods will// call it when necessary.this._syncResults();this._syncVisibility();},// -- Public Prototype Methods ---------------------------------------------/**Hides the list, unless the `alwaysShowList` attribute is `true`.@method hide@see show@chainable**/hide: function () {return this.get(ALWAYS_SHOW_LIST) ? this : this.set(VISIBLE, false);},/**Selects the specified _itemNode_, or the current `activeItem` if _itemNode_is not specified.@method selectItem@param {Node} [itemNode] Item node to select.@param {EventFacade} [originEvent] Event that triggered the selection, ifany.@chainable**/selectItem: function (itemNode, originEvent) {if (itemNode) {if (!itemNode.hasClass(this[_CLASS_ITEM])) {return this;}} else {itemNode = this.get(ACTIVE_ITEM);if (!itemNode) {return this;}}this.fire(EVT_SELECT, {itemNode : itemNode,originEvent: originEvent || null,result : itemNode.getData(RESULT)});return this;},// -- Protected Prototype Methods ------------------------------------------/**Activates the next item after the currently active item. If there is no nextitem and the `circular` attribute is `true`, focus will wrap back to theinput node.@method _activateNextItem@chainable@protected**/_activateNextItem: function () {var item = this.get(ACTIVE_ITEM),nextItem;if (item) {nextItem = item.next(this[_SELECTOR_ITEM]) ||(this.get(CIRCULAR) ? null : item);} else {nextItem = this._getFirstItemNode();}this.set(ACTIVE_ITEM, nextItem);return this;},/**Activates the item previous to the currently active item. If there is noprevious item and the `circular` attribute is `true`, focus will wrap backto the input node.@method _activatePrevItem@chainable@protected**/_activatePrevItem: function () {var item = this.get(ACTIVE_ITEM),prevItem = item ? item.previous(this[_SELECTOR_ITEM]) :this.get(CIRCULAR) && this._getLastItemNode();this.set(ACTIVE_ITEM, prevItem || null);return this;},/**Appends the specified result _items_ to the list inside a new item node.@method _add@param {Array|Node|HTMLElement|String} items Result item or array ofresult items.@return {NodeList} Added nodes.@protected**/_add: function (items) {var itemNodes = [];YArray.each(Lang.isArray(items) ? items : [items], function (item) {itemNodes.push(this._createItemNode(item).setData(RESULT, item));}, this);itemNodes = Y.all(itemNodes);this._listNode.append(itemNodes.toFrag());return itemNodes;},/**Updates the ARIA live region with the specified message.@method _ariaSay@param {String} stringId String id (from the `strings` attribute) of themessage to speak.@param {Object} [subs] Substitutions for placeholders in the string.@protected**/_ariaSay: function (stringId, subs) {var message = this.get('strings.' + stringId);this._ariaNode.set('text', subs ? Lang.sub(message, subs) : message);},/**Binds `inputNode` events and behavior.@method _bindInput@protected**/_bindInput: function () {var inputNode = this._inputNode,alignNode, alignWidth, tokenInput;// Null align means we can auto-align. Set align to false to prevent// auto-alignment, or a valid alignment config to customize the// alignment.if (this.get('align') === null) {// If this is a tokenInput, align with its bounding box.// Otherwise, align with the inputNode. Bit of a cheat.tokenInput = this.get('tokenInput');alignNode = (tokenInput && tokenInput.get('boundingBox')) || inputNode;this.set('align', {node : alignNode,points: ['tl', 'bl']});// If no width config is set, attempt to set the list's width to the// width of the alignment node. If the alignment node's width is// falsy, do nothing.if (!this.get(WIDTH) && (alignWidth = alignNode.get('offsetWidth'))) {this.set(WIDTH, alignWidth);}}// Attach inputNode events.this._listEvents = this._listEvents.concat([inputNode.after('blur', this._afterListInputBlur, this),inputNode.after('focus', this._afterListInputFocus, this)]);},/**Binds list events.@method _bindList@protected**/_bindList: function () {this._listEvents = this._listEvents.concat([Y.one('doc').after('click', this._afterDocClick, this),Y.one('win').after('windowresize', this._syncPosition, this),this.after({mouseover: this._afterMouseOver,mouseout : this._afterMouseOut,activeItemChange : this._afterActiveItemChange,alwaysShowListChange: this._afterAlwaysShowListChange,hoveredItemChange : this._afterHoveredItemChange,resultsChange : this._afterResultsChange,visibleChange : this._afterVisibleChange}),this._listNode.delegate('click', this._onItemClick,this[_SELECTOR_ITEM], this)]);},/**Clears the contents of the tray.@method _clear@protected**/_clear: function () {this.set(ACTIVE_ITEM, null);this._set(HOVERED_ITEM, null);this._listNode.get('children').remove(true);},/**Creates and returns an ARIA live region node.@method _createAriaNode@return {Node} ARIA node.@protected**/_createAriaNode: function () {var ariaNode = Node.create(this.ARIA_TEMPLATE);return ariaNode.addClass(this.getClassName('aria')).setAttrs({'aria-live': 'polite',role : 'status'});},/**Creates and returns an item node with the specified _content_.@method _createItemNode@param {Object} result Result object.@return {Node} Item node.@protected**/_createItemNode: function (result) {var itemNode = Node.create(this.ITEM_TEMPLATE);return itemNode.addClass(this[_CLASS_ITEM]).setAttrs({id : Y.stamp(itemNode),role: 'option'}).setAttribute('data-text', result.text).append(result.display);},/**Creates and returns a list node. If the `listNode` attribute is already setto an existing node, that node will be used.@method _createListNode@return {Node} List node.@protected**/_createListNode: function () {var listNode = this.get('listNode') || Node.create(this.LIST_TEMPLATE);listNode.addClass(this.getClassName(LIST)).setAttrs({id : Y.stamp(listNode),role: 'listbox'});this._set('listNode', listNode);this.get('contentBox').append(listNode);return listNode;},/**Gets the first item node in the list, or `null` if the list is empty.@method _getFirstItemNode@return {Node|null}@protected**/_getFirstItemNode: function () {return this._listNode.one(this[_SELECTOR_ITEM]);},/**Gets the last item node in the list, or `null` if the list is empty.@method _getLastItemNode@return {Node|null}@protected**/_getLastItemNode: function () {return this._listNode.one(this[_SELECTOR_ITEM] + ':last-child');},/**Synchronizes the result list's position and alignment.@method _syncPosition@protected**/_syncPosition: function () {// Force WidgetPositionAlign to refresh its alignment.this._syncUIPosAlign();// Resize the IE6 iframe shim to match the list's dimensions.this._syncShim();},/**Synchronizes the results displayed in the list with those in the _results_argument, or with the `results` attribute if an argument is not provided.@method _syncResults@param {Array} [results] Results.@protected**/_syncResults: function (results) {if (!results) {results = this.get(RESULTS);}this._clear();if (results.length) {this._add(results);this._ariaSay('items_available');}this._syncPosition();if (this.get('activateFirstItem') && !this.get(ACTIVE_ITEM)) {this.set(ACTIVE_ITEM, this._getFirstItemNode());}},/**Synchronizes the size of the iframe shim used for IE6 and lower. In otherbrowsers, this method is a noop.@method _syncShim@protected**/_syncShim: useShim ? function () {var shim = this._boundingBox.shim;if (shim) {shim.sync();}} : function () {},/**Synchronizes the visibility of the tray with the _visible_ argument, or withthe `visible` attribute if an argument is not provided.@method _syncVisibility@param {Boolean} [visible] Visibility.@protected**/_syncVisibility: function (visible) {if (this.get(ALWAYS_SHOW_LIST)) {visible = true;this.set(VISIBLE, visible);}if (typeof visible === 'undefined') {visible = this.get(VISIBLE);}this._inputNode.set('aria-expanded', visible);this._boundingBox.set('aria-hidden', !visible);if (visible) {this._syncPosition();} else {this.set(ACTIVE_ITEM, null);this._set(HOVERED_ITEM, null);// Force a reflow to work around a glitch in IE6 and 7 where some of// the contents of the list will sometimes remain visible after the// container is hidden.this._boundingBox.get('offsetWidth');}// In some pages, IE7 fails to repaint the contents of the list after it// becomes visible. Toggling a bogus class on the body forces a repaint// that fixes the issue.if (Y.UA.ie === 7) {// Note: We don't actually need to use ClassNameManager here. This// class isn't applying any actual styles; it's just frobbing the// body element to force a repaint. The actual class name doesn't// really matter.Y.one('body').addClass('yui3-ie7-sucks').removeClass('yui3-ie7-sucks');}},// -- Protected Event Handlers ---------------------------------------------/**Handles `activeItemChange` events.@method _afterActiveItemChange@param {EventFacade} e@protected**/_afterActiveItemChange: function (e) {var inputNode = this._inputNode,newVal = e.newVal,prevVal = e.prevVal,node;// The previous item may have disappeared by the time this handler runs,// so we need to be careful.if (prevVal && prevVal._node) {prevVal.removeClass(this[_CLASS_ITEM_ACTIVE]);}if (newVal) {newVal.addClass(this[_CLASS_ITEM_ACTIVE]);inputNode.set('aria-activedescendant', newVal.get(ID));} else {inputNode.removeAttribute('aria-activedescendant');}if (this.get('scrollIntoView')) {node = newVal || inputNode;if (!node.inRegion(Y.DOM.viewportRegion(), true)|| !node.inRegion(this._contentBox, true)) {node.scrollIntoView();}}},/**Handles `alwaysShowListChange` events.@method _afterAlwaysShowListChange@param {EventFacade} e@protected**/_afterAlwaysShowListChange: function (e) {this.set(VISIBLE, e.newVal || this.get(RESULTS).length > 0);},/**Handles click events on the document. If the click is outside both theinput node and the bounding box, the list will be hidden.@method _afterDocClick@param {EventFacade} e@protected@since 3.5.0**/_afterDocClick: function (e) {var boundingBox = this._boundingBox,target = e.target;if (target !== this._inputNode && target !== boundingBox &&!target.ancestor('#' + boundingBox.get('id'), true)){this.hide();}},/**Handles `hoveredItemChange` events.@method _afterHoveredItemChange@param {EventFacade} e@protected**/_afterHoveredItemChange: function (e) {var newVal = e.newVal,prevVal = e.prevVal;if (prevVal) {prevVal.removeClass(this[_CLASS_ITEM_HOVER]);}if (newVal) {newVal.addClass(this[_CLASS_ITEM_HOVER]);}},/**Handles `inputNode` blur events.@method _afterListInputBlur@protected**/_afterListInputBlur: function () {this._listInputFocused = false;if (this.get(VISIBLE) &&!this._mouseOverList &&(this._lastInputKey !== KEY_TAB ||!this.get('tabSelect') ||!this.get(ACTIVE_ITEM))) {this.hide();}},/**Handles `inputNode` focus events.@method _afterListInputFocus@protected**/_afterListInputFocus: function () {this._listInputFocused = true;},/**Handles `mouseover` events.@method _afterMouseOver@param {EventFacade} e@protected**/_afterMouseOver: function (e) {var itemNode = e.domEvent.target.ancestor(this[_SELECTOR_ITEM], true);this._mouseOverList = true;if (itemNode) {this._set(HOVERED_ITEM, itemNode);}},/**Handles `mouseout` events.@method _afterMouseOut@param {EventFacade} e@protected**/_afterMouseOut: function () {this._mouseOverList = false;this._set(HOVERED_ITEM, null);},/**Handles `resultsChange` events.@method _afterResultsChange@param {EventFacade} e@protected**/_afterResultsChange: function (e) {this._syncResults(e.newVal);if (!this.get(ALWAYS_SHOW_LIST)) {this.set(VISIBLE, !!e.newVal.length);}},/**Handles `visibleChange` events.@method _afterVisibleChange@param {EventFacade} e@protected**/_afterVisibleChange: function (e) {this._syncVisibility(!!e.newVal);},/**Delegated event handler for item `click` events.@method _onItemClick@param {EventFacade} e@protected**/_onItemClick: function (e) {var itemNode = e.currentTarget;this.set(ACTIVE_ITEM, itemNode);this.selectItem(itemNode, e);},// -- Protected Default Event Handlers -------------------------------------/**Default `select` event handler.@method _defSelectFn@param {EventFacade} e@protected**/_defSelectFn: function (e) {var text = e.result.text;// TODO: support typeahead completion, etc.this._inputNode.focus();this._updateValue(text);this._ariaSay('item_selected', {item: text});this.hide();}}, {ATTRS: {/**If `true`, the first item in the list will be activated by default whenthe list is initially displayed and when results change.@attribute activateFirstItem@type Boolean@default false**/activateFirstItem: {value: false},/**Item that's currently active, if any. When the user presses enter, thisis the item that will be selected.@attribute activeItem@type Node**/activeItem: {setter: Y.one,value: null},/**If `true`, the list will remain visible even when there are no resultsto display.@attribute alwaysShowList@type Boolean@default false**/alwaysShowList: {value: false},/**If `true`, keyboard navigation will wrap around to the opposite end ofthe 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},/**Node that will contain result items.@attribute listNode@type Node|null@initOnly**/listNode: {writeOnce: 'initOnly',value: null},/**If `true`, the viewport will be scrolled to ensure that the active listitem is visible when necessary.@attribute scrollIntoView@type Boolean@default false**/scrollIntoView: {value: false},/**Translatable strings used by the AutoCompleteList widget.@attribute strings@type Object**/strings: {valueFn: function () {return Y.Intl.get('autocomplete-list');}},/**If `true`, pressing the tab key while the list is visible will selectthe active item, if any.@attribute tabSelect@type Boolean@default true**/tabSelect: {value: true},// 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">`AutoCompleteList`</a>. See that classfor API docs.@class AutoComplete**/Y.AutoComplete = List;}, '3.18.1', {"lang": ["en","es","hu","it"],"requires": ["autocomplete-base","event-resize","node-screen","selector-css3","shim-plugin","widget","widget-position","widget-position-align"],"skinnable": true});