首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

aui-autocomplete [ 3 ] 源码解析

2013-01-17 
aui-autocomplete [ 三 ] 源码解析懒了, 只翻译几个目前用到的属性,其实英文写的很明白的,需要花时间看而

aui-autocomplete [ 三 ] 源码解析
懒了, 只翻译几个目前用到的属性,其实英文写的很明白的,需要花时间看而已。
注意, 标记overridable的方法都是可以重写的,如何重写见 “aui-autocomplete [ 一 ]”

AUI.add('aui-autocomplete', function(A) {/** * The AutoComplete Utility * * @module aui-autocomplete */var Lang = A.Lang,isArray = Lang.isArray,isString = Lang.isString,isNull = Lang.isNull,isFunction = Lang.isFunction,getClassName = A.ClassNameManager.getClassName,ALERT = 'alert',CONTENT = 'content',HELPER = 'helper',HIDDEN = 'hidden',ICON = 'icon',ITEM = 'item',LIST = 'list',LOADING = 'loading',NAME = 'autocomplete',RESET = 'reset',RESULTS = 'results',SELECTED = 'selected',ICON_DEFAULT = 'circle-triangle-b',ICON_ERROR = ALERT,ICON_LOADING = LOADING,CSS_HIGLIGHT = getClassName(NAME, SELECTED),CSS_HIDDEN = getClassName(HELPER, HIDDEN),CSS_LIST_ITEM = getClassName(NAME, LIST, ITEM),CSS_RESULTS_LIST = getClassName(HELPER, RESET),CSS_RESULTS_OVERLAY = getClassName(NAME, RESULTS),CSS_RESULTS_OVERLAY_CONTENT = getClassName(NAME, RESULTS, CONTENT),KEY_BACKSPACE = 8,KEY_TAB = 9,KEY_ENTER = 13,KEY_SHIFT = 16,KEY_CTRL = 17,KEY_ALT = 18,KEY_CAPS_LOCK = 20,KEY_ESC = 27,KEY_PAGEUP = 33,KEY_END = 35,KEY_HOME = 36,KEY_UP = 38,KEY_DOWN = 40,KEY_RIGHT = 39,KEY_LEFT = 37,KEY_PRINT_SCREEN = 44,KEY_INSERT = 44,KEY_KOREAN_IME = 229,OVERLAY_ALIGN = {node: null,points: ['tl', 'bl']},BOUNDING_BOX = 'boundingBox',CONTENT_BOX = 'contentBox';/** * <p><img src="assets/images/aui-autocomplete/main.png"/></p> * * A base class for AutoComplete, providing: * <ul> *    <li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li> *    <li>Presenting users choices based on their input</li> *    <li>Separating selected items</li> *    <li>Keyboard interaction for selecting items</li> * </ul> * * Quick Example:<br/> *  * <pre><code>var instance = new A.AutoComplete({ *dataSource: [['AL', 'Alabama', 'The Heart of Dixie'], * ['AK', 'Alaska', 'The Land of the Midnight Sun'], *['AZ', 'Arizona', 'The Grand Canyon State']], *schema: { *resultFields: ['key', 'name', 'description'] *}, *matchKey: 'name', *delimChar: ',', *typeAhead: true, *contentBox: '#myAutoComplete' * }).render(); * </code></pre> * * Check the list of <a href="AutoComplete.html#configattributes">Configuration Attributes</a> available for * AutoComplete. * * @param config {Object} Object literal specifying widget configuration properties. * * @class AutoComplete * @constructor * @extends Component */var AutoComplete = A.Component.create({/** * Static property provides a string to identify the class. * * @property AutoComplete.NAME * @type String * @static */NAME: NAME,/** * Static property used to define the default attribute * configuration for the AutoComplete. * * @property AutoComplete.ATTRS * @type Object * @static */ATTRS: {/** * Always show the results container, instead of only showing when the  * user is requesting them. *  * @attribute alwaysShowContainer * @default false * @type Boolean */alwaysShowContainer: {value: false},/*** 结果集显示时高亮第一个item * Automatically highlight the first item in the list when the results are * made visible. *  * @attribute autoHighlight * @default true * @type Boolean */autoHighlight: {value: true},/** * If set to true, the <a href="AutoComplete.html#method_filterResults">filterResults</a>  * method will be run on the response from the data source. * * @attribute applyLocalFilter * @default true * @type Boolean */applyLocalFilter: {value: null},/** * To use a button 是否需要右侧的button *  * @attribute button * @default true * @type Boolean * @deprecated */button: {value: true},/** * The data source that results will be read from. This can either be * an existing <a href="DataSource.html">DataSource</a> object, or it can be a * value that would be passed to <a href="DataSource.html">DataSource</a>. *  * @attribute dataSource * @default null * @type Object | String | Function | Array */dataSource: {value: null},/** * The type of the data source passed into <a href="AutoComplete.html#config_dataSource">dataSource</a>. * This can be used to explicitly declare what kind of <a href="DataSource.html">DataSource</a> object will * be created. *  * @attribute dataSourceType * @default null * @type String */dataSourceType: {value: null},/*** 不设置,单选; 设置此项可多选,input框中多个选中值用delimChar分隔,如aa,bb,cc  * The character used to indicate the beginning or ending of a new value. Most commonly used * is a ",". *  * @attribute delimChar * @default null * @type String */delimChar: {value: null,setter: function(value) {if (isString(value) && (value.length > 0)) {value = [value];}else if (!isArray(value)) {value = A.Attribute.INVALID_VALUE;}return value;}},/** * The input field which will recieve the users input. * * @attribute input * @default null * @type String | Node */input: {value: null},/*** 用schema中的哪一项匹配输入值, input默认显示选中项的matchKey字段 * The key or numeric index in the schema to match the result against. * * @attribute matchKey * @default 0 * @type String | Number */matchKey: {value: 0},/** * The maximum number of results to display. 结果集最大条数 * *  * @attribute maxResultsDisplayed * @default 10 * @type Number */maxResultsDisplayed: {value: 10},/** * The minimum number of characters required to query the data source. 最少要输入多少个字符才会显示匹配结果集合 * * @attribute minQueryLength * @default 1 * @type Number */minQueryLength: {value: 1},/** * The amount of time in seconds to delay before submitting the query.  * * @attribute queryDelay * @default 0.2 * @type Number */queryDelay: {value: 0.2,getter: function(value) {return value * 1000;}},/** * When IME usage is detected or interval detection is explicitly enabled, * AutoComplete will detect the input value at the given interval and send a * query if the value has changed. * * @attribute queryInterval * @default 0.5 * @type Number */queryInterval: {value: 0.5,getter: function(value) {return value * 1000;}},/** * 匹配结果集时是否大小写敏感 * When <a href="AutoComplete.html#config_applyLocalFilter">applyLocalFilter</a> is true, * setting this to true will match only results with the same case. *  * @attribute queryMatchCase * @default false * @type Boolean */queryMatchCase: {value: false},/** * false,从第一个字符开始匹配; true, 结果集中的item, 任意位置的字符与输入字符相同即匹配。  * When <a href="AutoComplete.html#config_applyLocalFilter">applyLocalFilter</a> is true, * setting this to true will match results which contain the query anywhere in the text, * instead of just matching just items that start with the query. *  * @attribute queryMatchContains * @default false * @type Boolean */queryMatchContains: {value: false},/** * For IO DataSources, AutoComplete will automatically insert a "?" between the server URI and  * the encoded query string. To prevent this behavior, you can * set this value to false. If you need to customize this even further, you * can override the <a href="AutoComplete.html#method_generateRequest">generateRequest</a> method. * * @attribute queryQuestionMark * @default true * @type Boolean */queryQuestionMark: {value: true},/** * A valid configuration object for any of <a href="module_datasource.html">DataSource</a> schema plugins. * * @attribute schema * @default null * @type Object */schema: {value: null},/** * A valid type of <a href="module_datasource.html">DataSource</a> schema plugin, such as array, json, xml, etc. * * @attribute schemaType * @default array * @type String */schemaType: {value: '',validator: isString},/** * Whether or not the input field should be updated with selections. * * @attribute suppressInputUpdate * @default false * @type Boolean */suppressInputUpdate: {value: false},/** * autoHighlight = true时有效 * true, 自动选中第一个匹配结果,即使用户未输入完整, 同时更新值到input框中。 * If <a href="AutoComplete.html#config_autoHighlight">autoHighlight</a> is enabled, whether or not the  * input field should be automatically updated with the first result as the user types,  * automatically selecting the portion of the text the user has not typed yet. * * @attribute typeAhead * @default false * @type Boolean */typeAhead: {value: false},/** * If <a href="AutoComplete.html#config_typeAhead">typeAhead</a> is true, number of seconds  * to delay before updating the input. In order to prevent certain race conditions, this value must * always be greater than the <a href="AutoComplete.html#config_queryDelay">queryDelay</a>. * * @attribute typeAheadDelay * @default 0.2 * @type Number */typeAheadDelay: {value: 0.2,getter: function(value) {return value * 1000;}},/** * 还没研究出来怎么用 * The unique ID of the input element. * * @attribute uniqueName * @default null * @type String */uniqueName: {value: null}},prototype: {/** * Construction logic executed during AutoComplete instantiation. Lifecycle. * * @method initializer * @protected */initializer: function(config) {var instance = this;instance._overlayAlign = A.mix({}, OVERLAY_ALIGN);instance._createDataSource();},/** * Create the DOM structure for the AutoComplete. Lifecycle. * * @method renderUI * @protected */renderUI: function() {var instance = this;instance._renderInput();instance._renderOverlay();},/** * Bind the events on the AutoComplete UI. Lifecycle. * * @method bindUI * @protected */bindUI: function() {var instance = this;var button = instance.button;var inputNode = instance.inputNode;instance.dataSource.on('request', A.bind(button.set, button, ICON, ICON_LOADING));inputNode.on('blur', instance._onTextboxBlur, instance);inputNode.on('focus', instance._onTextboxFocus, instance);inputNode.on('keydown', instance._onTextboxKeyDown, instance);inputNode.on('keypress', instance._onTextboxKeyPress, instance);inputNode.on('keyup', instance._onTextboxKeyUp, instance);var overlayBoundingBox = instance.overlay.get(BOUNDING_BOX);overlayBoundingBox.on('click', instance._onContainerClick, instance);overlayBoundingBox.on('mouseout', instance._onContainerMouseout, instance);overlayBoundingBox.on('mouseover', instance._onContainerMouseover, instance);overlayBoundingBox.on('scroll', instance._onContainerScroll, instance);/** * Handles the containerCollapse event. Fired when the container is hidden. * * @event containerCollapse * @param {Event.Facade} event The containerCollapse event. */instance.publish('containerCollapse');/** * Handles the containerExpand event. Fired when the container is shown. * * @event containerExpand * @param {Event.Facade} event The containerExpand event. */instance.publish('containerExpand');/** * Handles the containerPopulate event. Fired when the container is populated. * * @event containerPopulate * @param {Event.Facade} event The containerPopulate event. */instance.publish('containerPopulate');/** * Handles the dataError event. Fired when there is an error accessing the data. * * @event dataError * @param {Event.Facade} event The dataError event. */instance.publish('dataError');/** * Handles the dataRequest event. Fired when ever a query is sent to the data source. * * @event dataRequest * @param {Event.Facade} event The dataRequest event. */instance.publish('dataRequest');/** * Handles the dataReturn event. Fired when data successfully comes back from the data request. * * @event dataReturn * @param {Event.Facade} event The dataReturn event. */instance.publish('dataReturn');/** * Handles the itemArrowFrom event. Fired when the user navigates via the keyboard away from * a selected item. * * @event itemArrowFrom * @param {Event.Facade} event The itemArrowFrom event. */instance.publish('itemArrowFrom');/** * Handles the itemArrowTo event. Fired when the user navigates via the keyboard to a selected item. * * @event itemArrowTo * @param {Event.Facade} event The itemArrowTo event. */instance.publish('itemArrowTo');/** * Handles the itemMouseOut event. Fired when the user mouses away from an item. * * @event itemMouseOut * @param {Event.Facade} event The itemMouseOut event. */instance.publish('itemMouseOut');/** * Handles the itemMouseOver event. Fired when the user mouses over an item. * * @event itemMouseOver * @param {Event.Facade} event The itemMouseOver event. */instance.publish('itemMouseOver');/** * Handles the itemSelect event. Fired when an item in the list is selected. * * @event itemSelect * @param {Event.Facade} event The itemSelect event. */instance.publish('itemSelect');/** * Handles the selectionEnforce event. Fired if <a href="Autocomplete.html#config_forceSelection">forceSelection</a> * is enabled and the users input element has been cleared because it did not match one of the results. * * @event selectionEnforce * @param {Event.Facade} event The selectionEnforce event. */instance.publish('selectionEnforce');/** * Handles the textboxBlur event. Fired when the user leaves the input element. * * @event textboxBlur * @param {Event.Facade} event The textboxBlur event. */instance.publish('textboxBlur');/** * Handles the textboxChange event. Fired when the value in the input element is changed. * * @event textboxChange * @param {Event.Facade} event The textboxChange event. */instance.publish('textboxChange');/** * Handles the textboxFocus event. Fired when user moves focus to the input element. * * @event textboxFocus * @param {Event.Facade} event The textboxFocus event. */instance.publish('textboxFocus');/** * Handles the textboxKey event. Fired when the input element receives key input. * * @event textboxKey * @param {Event.Facade} event The textboxKey event. */instance.publish('textboxKey');/** * Handles the typeAhead event. Fired when the input element has been pre-filled by the type-ahead feature. * * @event typeAhead * @param {Event.Facade} event The typeAhead event. */instance.publish('typeAhead');/** * Handles the unmatchedItemSelect event. Fired when a user selects something that does * not match any of the displayed results. * * @event unmatchedItemSelect * @param {Event.Facade} event The unmatchedItemSelect event. */instance.publish('unmatchedItemSelect');instance.overlay.after('visibleChange', instance._realignContainer, instance);},/** * Sync the AutoComplete UI. Lifecycle. * * @method syncUI * @protected */syncUI: function() {var instance = this;instance.inputNode.setAttribute('autocomplete', 'off');},/** * An overridable method that is executed before the result container is shown. * The method can return false to prevent the container from being shown. * * @method doBeforeExpandContainer * @param {String} query The query that was submitted to the data source * @param {Object} allResults The parsed results * @return {Boolean} */doBeforeExpandContainer: function() {return true;},/** * An overridable method that is executed before the result overlay is loaded with results. * * @method doBeforeLoadData * @param {EventFacade} event * @return {Boolean} */doBeforeLoadData: function(event) {return true;},/** * Executed by the data source as a mechanism to do simple client-side * filtering of the results. * * @method filterResults * @param {EventFacade} event * @return {Object} Filtered response object */filterResults: function(event) {var instance = this;var callback = event.callback;var query = event.request;var response = event.response;if (callback && callback.argument && callback.argument.query) {query = callback.argument.query;}if (query) {var dataSource = instance.dataSource;var allResults = response.results;var filteredResults = [];var matchFound = false;var matchKey = instance.get('matchKey');var matchCase = instance.get('queryMatchCase');var matchContains = instance.get('queryMatchContains');var showAll = (query == '*');var fields = instance.get('schema.resultFields');for (var i = allResults.length - 1; i >= 0; i--){var result = allResults[i];var strResult = null;if (isString(result)) {strResult = result;}else if (isArray(result)) {strResult = result[0];}else if (fields) {strResult = result[matchKey || fields[0]];}if (isString(strResult)) {var keyIndex = -1;if (matchCase) {keyIndex = strResult.indexOf(decodeURIComponent(query));}else {keyIndex = strResult.toLowerCase().indexOf(decodeURIComponent(query).toLowerCase());}if ((showAll) ||(!matchContains && (keyIndex === 0)) ||(matchContains && (keyIndex > -1))) {filteredResults.unshift(result);}}}response.results = filteredResults;}return response;},/** * An overridable method for formatting the result of the query before it's displayed in the overlay. * * @method formatResult * @param {Object} result The result data object * @param {String} request The current query string * @param {String} resultMatch The string from the results that matches the query * @return {String} */formatResult: function(result, request, resultMatch) {return resultMatch || '';},/** * An overridable method that creates an object to be passed to the sendRequest * method of the data source object. Useful to overwrite if you wish to create * a custom request object before it's sent. * * @method generateRequest * @param {String} query The string currently being entered * @return {Object} */generateRequest: function(query) {return {request: query};},/** * Handles the response for the display of the results. This is a callback method * that is fired by the sendRequest method so that results are ready to be accessed. * * @method handleResponse * @param {EventFacade} event */handleResponse: function(event) {var instance = this;instance._populateList(event);var iconClass = ICON_DEFAULT;if (event.error) {iconClass = ICON_ERROR;}instance.button.set(ICON, iconClass);},/** * Sends a query request to the data source object. * * @method sendQuery * @param {String} query Query string */sendQuery: function(query) {var instance = this;instance.set('focused', null);var newQuery = query;if (instance.get('delimChar')) {query = instance.inputNode.get('value') + query;}instance._sendQuery(newQuery);},/** * Clears the query interval * * @method _clearInterval * @private */_clearInterval: function() {var instance = this;if (instance._queryIntervalId) {clearInterval(instance._queryIntervalId);instance._queryIntervalId = null;}},/** * When <a href="Autocomplete.html#config_forceSelection">forceSelection</a> is true and * the user tries to leave the input element without selecting an item from the results, * the user selection is cleared. * * @method _clearSelection * @protected */_clearSelection: function() {var instance = this;var delimChar = instance.get('delimChar');var extraction = {previous: '',query: instance.inputNode.get('value')};if (delimChar) {extraction = instance._extractQuery(instance.inputNode.get('value'));}instance.fire('selectionEnforce', extraction.query);},/** * Creates the data source object using the passed in <a href="Autocomplete.html#config_dataSource">dataSource</a>, * and if it is a string, will use the <a href="Autocomplete.html#config_dataSourceType">dataSourceType</a> to * create a new <a href="module_datasource.html">DataSource</a> object. * * @method _createDataSource * @protected * @return {String} */_createDataSource: function() {var instance = this;instance._queryTask = new A.DelayedTask(instance.sendQuery, instance);var dataSource = instance.get('dataSource');var data = dataSource;var dataSourceType = instance.get('dataSourceType');if (!(dataSource instanceof A.DataSource.Local)) {if (!dataSourceType) {dataSourceType = 'Local';if (isFunction(data)) {dataSourceType = 'Function';}else if (isString(data)) {dataSourceType = 'IO';}}dataSource = new A.DataSource[dataSourceType]({source: data});}dataSource.on('error', instance.handleResponse, instance);dataSource.after('response', instance.handleResponse, instance);dataSourceType = dataSource.name;if (dataSourceType == 'dataSourceLocal') {instance.set('applyLocalFilter', true);}instance.set('dataSource', dataSource);instance.set('dataSource', dataSourceType);instance.dataSource = dataSource;var schema = instance.get('schema');if (schema) {if (schema.fn) {instance.dataSource.plug(schema);}else {var schemaType = instance.get('schemaType');var schemaTypes = {array: A.Plugin.DataSourceArraySchema,json: A.Plugin.DataSourceJSONSchema,text: A.Plugin.DataSourceTextSchema,xml: A.Plugin.DataSourceXMLSchema};schemaType = schemaType.toLowerCase() || 'array';instance.dataSource.plug({fn: schemaTypes[schemaType],cfg: {schema: schema}});}}instance.set('schema', schema);},/** * Enables query interval detection for IME support. * * @method _enableIntervalDetection * @protected */_enableIntervalDetection: function() {var instance = this;var queryInterval = instance.get('queryInterval');if (!instance._queryIntervalId && queryInterval) {instance._queryInterval = setInterval(A.bind(instance._onInterval, instance), queryInterval);}},/** * Extracts the right most query from the delimited string in the input. * * @method _extractQuery * @param {String} query String to parse * @protected * @return {String} */_extractQuery: function(query) {var instance = this;var delimChar = instance.get('delimChar');var delimIndex = -1;var i = delimChar.length - 1;var newIndex, queryStart, previous;for (; i >= 0; i--) {newIndex = query.lastIndexOf(delimChar[i]);if (newIndex > delimIndex) {delimIndex = newIndex;}}if (delimChar[i] == ' ') {for (var j = delimChar.length - 1; j >= 0; j--){if (query[delimIndex - 1] == delimChar[j]) {delimIndex--;break;}}}if (delimIndex > -1) {queryStart = delimIndex + 1;while (query.charAt(queryStart) == ' ') {queryStart += 1;}previous = query.substring(0, queryStart);query = query.substring(queryStart);}else {previous = '';}return {previous: previous,query: query};},/** * Focuses the input element. * * @method _focus * @protected */_focus: function() {var instance = this;setTimeout(function() {instance.inputNode.focus();},0);},/** * Whether or not the pressed key triggers some functionality or if it should * be ignored. * * @method _isIgnoreKey * @param {keyCode} Number The numeric code of the key pressed * @protected * @return {String} */_isIgnoreKey: function(keyCode) {var instance = this;if ((keyCode == KEY_TAB) ||(keyCode == KEY_ENTER) ||(keyCode == KEY_SHIFT) ||(keyCode == KEY_CTRL) ||(keyCode >= KEY_ALT && keyCode <= KEY_CAPS_LOCK) ||(keyCode == KEY_ESC) ||(keyCode >= KEY_PAGEUP && keyCode <= KEY_END) ||(keyCode >= KEY_HOME && keyCode <= KEY_DOWN) ||(keyCode >= KEY_PRINT_SCREEN && keyCode <= KEY_INSERT) ||(keyCode == KEY_KOREAN_IME)) {return true;}return false;},/** * If there is a currently selected item, the right arrow key will select * that item and jump to the end of the input element, otherwise the container is closed. * * @method _jumpSelection * @protected */_jumpSelection: function() {var instance = this;if (instance._elCurListItem) {instance._selectItem(instance._elCurListItem);}else {instance._toggleContainer(false);}},/** * Triggered by the up and down arrow keys, changes the currently selected list element item, and scrolls the * container if necessary. * * @method _moveSelection * @param {Number} keyCode The numeric code of the key pressed * @protected */_moveSelection: function(keyCode) {var instance = this;if (instance.overlay.get('visible')) {var elCurListItem = instance._elCurListItem;var curItemIndex = -1;if (elCurListItem) {curItemIndex = Number(elCurListItem.getAttribute('data-listItemIndex'));}var newItemIndex = curItemIndex - 1;if (keyCode == KEY_DOWN) {newItemIndex = curItemIndex + 1;}if (newItemIndex == -1) {newItemIndex = instance._displayedItems - 1;}if (newItemIndex >= instance._displayedItems) {newItemIndex = 0;}if (newItemIndex < -2) {return;}if (elCurListItem) {instance._toggleHighlight(elCurListItem, 'from');instance.fire('itemArrowFrom', elCurListItem);}if (newItemIndex == -1) {if (instance.get('delimChar')) {instance.inputNode.set('value', instance._pastSelections + instance._currentQuery);}else {instance.inputNode.set('value', instance._currentQuery);}return;}if (newItemIndex == -2) {instance._toggleContainer(false);return;}var elNewListItem = instance.resultList.get('childNodes').item(newItemIndex);var elContent = instance.overlay.get(CONTENT_BOX);var contentOverflow = elContent.getStyle('overflow');var contentOverflowY = elContent.getStyle('overflowY');var scrollOn = (contentOverflow == 'auto') || (contentOverflow == 'scroll') || (contentOverflowY == 'auto') || (contentOverflowY == 'scroll');if (scrollOn &&(newItemIndex > -1) &&(newItemIndex < instance._displayedItems)) {var newScrollTop = -1;var liTop = elNewListItem.get('offsetTop');var liBottom = liTop + elNewListItem.get('offsetHeight');var contentHeight = elContent.get('offsetHeight');var contentScrollTop = elContent.get('scrollTop');var contentBottom = contentHeight + contentScrollTop;if (keyCode == KEY_DOWN) {if (liBottom > contentBottom) {newScrollTop = (liBottom - contentHeight);}else if (liBottom < contentScrollTop) {newScrollTop = liTop;}}else {if (liTop < contentHeight) {newScrollTop = liTop;}else if (liTop > contentBottom) {newScrollTop = (liBottom - contentHeight);}}if (newScrollTop > -1) {elContent.set('scrollTop', newScrollTop);}}instance._toggleHighlight(elNewListItem, 'to');instance.fire('itemArrowTo', elNewListItem);if (instance.get('typeAhead')) {instance._updateValue(elNewListItem);}}},/** * Called when the user mouses down on the button element in the combobox. * * @method _onButtonMouseDown * @param {EventFacade} event * @protected */_onButtonMouseDown: function(event) {var instance = this;event.halt();instance._focus();instance._sendQuery(instance.inputNode.get('value') + '*');},/** * Handles when a user clicks on the container. * * @method _onContainerClick * @param {EventFacade} event * @protected */_onContainerClick: function(event) {var instance = this;var target = event.target;var tagName = target.get('nodeName').toLowerCase();event.halt();while (target && (tagName != 'table')) {switch (tagName) {case 'body':return;case 'li':instance._toggleHighlight(target, 'to');instance._selectItem(target);return;default:break;}target = target.get('parentNode');if (target) {tagName.get('nodeName').toLowerCase();}}},/** * Handles when a user mouses out of the container. * * @method _onContainerMouseout * @param {EventFacade} event * @protected */_onContainerMouseout: function(event) {var instance = this;var target = event.target;var tagName = target.get('nodeName').toLowerCase();while (target && (tagName != 'table')) {switch (tagName) {case 'body':return;case 'li':instance._toggleHighlight(target, 'from');instance.fire('itemMouseOut', target);break;case 'ul':instance._toggleHighlight(instance._elCurListItem, 'to');break;case 'div':if (target.hasClass(CSS_RESULTS_OVERLAY)) {instance._overContainer = false;return;}break;default:break;}target = target.get('parentNode');if (target) {tagName = target.get('nodeName').toLowerCase();}}},/** * Handles when a user mouses over the container. * * @method _onContainerMouseover * @param {EventFacade} event * @protected */_onContainerMouseover: function(event) {var instance = this;var target = event.target;var tagName = target.get('nodeName').toLowerCase();while (target && (tagName != 'table')) {switch (tagName) {case 'body':return;case 'li':instance._toggleHighlight(target, 'to');instance.fire('itemMouseOut', target);break;case 'div':if (target.hasClass(CSS_RESULTS_OVERLAY)) {instance._overContainer = true;return;}break;default:break;}target = target.get('parentNode');if (target) {tagName = target.get('nodeName').toLowerCase();}}},/** * Handles the container scroll events. * * @method _onContainerScroll * @param {EventFacade} event * @protected */_onContainerScroll: function(event) {var instance = this;instance._focus();},/** * Enables the query to be triggered based on detecting text input via intervals instead of via * key events. * * @method _onInterval * @protected */_onInterval: function() {var instance = this;var curValue = instance.inputNode.get('value');var lastValue = instance._lastValue;if (curValue != lastValue) {instance._lastValue = curValue;instance._sendQuery(curValue);}},/** * Handles the input element losing focus. * * @method _onTextboxBlur * @param {EventFacade} event * @protected */_onTextboxBlur: function(event) {var instance = this;if (!instance._overContainer || (instance._keyCode == KEY_TAB)) {if (!instance._itemSelected) {var elMatchListItem = instance._textMatchesOption();var overlayVisible = instance.overlay.get('visible');if (!overlayVisible || (overlayVisible && isNull(elMatchListItem))) {if (instance.get('forceSelection')) {instance._clearSelection();}else {instance.fire('unmatchedItemSelect', instance._currentQuery);}}else {if (instance.get('forceSelection')) {instance._selectItem(elMatchListItem);}}}instance._clearInterval();instance.blur();if (instance._initInputValue !== instance.inputNode.get('value')) {instance.fire('textboxChange');}instance.fire('textboxBlur');instance._toggleContainer(false);}else {instance._focus();}},/** * Handles the input element gaining focus. * * @method _onTextboxFocus * @param {EventFacade} event * @protected */_onTextboxFocus: function(event) {var instance = this;if (!instance.get('focused')) {instance.inputNode.setAttribute('autocomplete', 'off');instance.focus();instance._initInputValue = instance.inputNode.get('value');instance.fire('textboxFocus');}},/** * Handles the keydown events on the input element for functional keys. * * @method _onTextboxKeyDown * @param {EventFacade} event * @protected */_onTextboxKeyDown: function(event) {var instance = this;var keyCode = event.keyCode;if (instance._typeAheadDelayId != -1) {clearTimeout(instance._typeAheadDelayId);}switch (keyCode) {case KEY_TAB:if (instance._elCurListItem) {if (instance.get('delimChar') && instance._keyCode != keyCode) {if (instance.overlay.get('visible')) {event.halt();}}instance._selectItem(instance._elCurListItem);}else {instance._toggleContainer(false);}break;case KEY_ENTER:if (instance._elCurListItem) {if (instance._keyCode != keyCode) {if (instance.overlay.get('visible')) {event.halt();}}instance._selectItem(instance._elCurListItem);}else {instance._toggleContainer(false);}break;case KEY_ESC:instance._toggleContainer(false);return;case KEY_UP:if (instance.overlay.get('visible')) {event.halt();instance._moveSelection(keyCode);}break;case KEY_RIGHT:instance._jumpSelection();break;case KEY_DOWN:if (instance.overlay.get('visible')) {event.halt();instance._moveSelection(keyCode);}break;default:instance._itemSelected = false;instance._toggleHighlight(instance._elCurListItem, 'from');instance.fire('textboxKey', keyCode);break;}if (keyCode == KEY_ALT) {instance._enableIntervalDetection();}instance._keyCode = keyCode;},/** * Handles the key press events of the input element. * * @method _onTextboxKeyPress * @param {EventFacade} event * @protected */_onTextboxKeyPress: function(event) {var instance = this;var keyCode = event.keyCode;switch (keyCode) {case KEY_TAB:if (instance.overlay.get('visible')) {if (instance.get('delimChar')) {event.halt();}if (instance._elCurListItem) {instance._selectItem(instance._elCurListItem);}else {instance._toggleContainer(false);}}break;case 13:if (instance.overlay.get('visible')) {event.halt();if (instance._elCurListItem) {instance._selectItem(instance._elCurListItem);}else {instance._toggleContainer(false);}}break;default:break;}if (keyCode == KEY_KOREAN_IME) {instance._enableIntervalDetection();}},/** * Handles the keyup events of the input element. * * @method _onTextboxKeyUp * @param {EventFacade} event * @protected */_onTextboxKeyUp: function(event) {var instance = this;var input = instance.inputNode;var value = input.get('value');var keyCode = event.keyCode;if (instance._isIgnoreKey(keyCode)) {return;}instance._queryTask.delay(instance.get('queryDelay'), null, null, [value]);},/** * Populates the container with list items of the query results. * * @method _populateList * @param {EventFacade} event * @protected */_populateList: function(event) {var instance = this;if (instance._typeAheadDelayId != -1) {clearTimeout(instance._typeAheadDelayId);}var query = event.request;var response = event.response;var callback = event.callback;var showAll = (query == '*');if (callback && callback.argument && callback.argument.query) {event.request = query = callback.argument.query;}var ok = instance.doBeforeLoadData(event);if (ok && !event.error) {instance.fire('dataReturn', event);var focused = instance.get('focused');if (showAll || focused || focused === null) {var currentQuery = decodeURIComponent(query);instance._currentQuery = currentQuery;instance._itemSelected = false;var allResults = event.response.results;var itemsToShow = Math.min(allResults.length, instance.get('maxResultsDisplayed'));var fields = instance.get('schema.resultFields');var matchKey = instance.get('matchKey');if (!matchKey && fields) {matchKey = fields[0];}else {matchKey = matchKey || 0;}if (itemsToShow > 0) {var allListItemEls = instance.resultList.get('childNodes');allListItemEls.each(function(node, i, nodeList) {if (i < itemsToShow) {var result = allResults[i];var resultMatch = '';if (isString(result)) {resultMatch = result;}else if (isArray(result)) {resultMatch = result[0];}else {resultMatch = result[matchKey];}node._resultMatch = resultMatch;node._resultData = result;node.html(instance.formatResult(result, currentQuery, resultMatch));node.removeClass(CSS_HIDDEN);}else {node.addClass(CSS_HIDDEN);}});instance._displayedItems = itemsToShow;instance.fire('containerPopulate', query, allResults);if (query != '*' && instance.get('autoHighlight')) {var elFirstListItem = instance.resultList.get('firstChild');instance._toggleHighlight(elFirstListItem, 'to');instance.fire('itemArrowTo', elFirstListItem);instance._typeAhead(elFirstListItem, query);}else {instance._toggleHighlight(instance._elCurListItem, 'from');}ok = instance.doBeforeExpandContainer(query, allResults);instance._toggleContainer(ok);}else {instance._toggleContainer(false);}return;}}else {instance.fire('dataError', query);}},/** * Realigns the container to the input element. * * @method _realignContainer * @param {EventFacade} event * @protected */_realignContainer: function(event) {var instance = this;var overlayAlign = instance._overlayAlign;if (event.newVal) {instance.overlay._uiSetAlign(overlayAlign.node, overlayAlign.points);}},/** * Handles the rendering of the input element. * * @method _renderInput * @protected */_renderInput: function() {var instance = this;var contentBox = instance.get(CONTENT_BOX);var input = instance.get('input');var comboConfig = {field: {labelText: false},icons: [{icon: 'circle-triangle-b',id: 'trigger',handler: {fn: instance._onButtonMouseDown,context: instance}}]};var inputReference = null;var inputParent = null;if (input) {input = A.one(input);comboConfig.field.node = input;inputReference = input.next();inputParent = input.get('parentNode');}var comboBox = new A.Combobox(comboConfig).render(contentBox);if (inputParent) {var comboBoundingBox = comboBox.get('boundingBox');inputParent.insertBefore(comboBoundingBox, inputReference);}instance.inputNode = comboBox.get('node');instance.button = comboBox.icons.item('trigger');instance.set('uniqueName', A.stamp(instance.inputNode));},/** * Pre-populates the container with the  * <a href="Autocomplete.html#config_maxResultsDisplayed">maxResultsDisplayed</a> * number of list items. * * @method _renderListElements * @protected */_renderListElements: function() {var instance = this;var maxResultsDisplayed = instance.get('maxResultsDisplayed');var resultList = instance.resultList;var listItems = [];while (maxResultsDisplayed--) {listItems[maxResultsDisplayed] = '<li data-listItemIndex="' + maxResultsDisplayed + '"></li>';}resultList.html(listItems.join(''));},/** * Handles the creation of the overlay where the result list will be displayed. * * @method _renderOverlay * @protected */_renderOverlay: function() {var instance = this;var overlayAlign = instance._overlayAlign;overlayAlign.node = instance.inputNode;var overlay = new A.OverlayBase({align: overlayAlign,bodyContent: '<ul></ul>',visible: false,width: instance.inputNode.get('offsetWidth')});var contentBox = overlay.get(CONTENT_BOX);overlay.get(BOUNDING_BOX).addClass(CSS_RESULTS_OVERLAY);contentBox.addClass(CSS_RESULTS_OVERLAY_CONTENT);overlay.render(document.body);overlay.addTarget(instance);instance.overlay = overlay;instance.resultList = contentBox.one('ul');instance.resultList.addClass(CSS_RESULTS_LIST);instance._renderListElements();},/** * Selects a list item from the query results. * * @method _selectItem * @param {Node} elListItem The list item to select * @protected */_selectItem: function(elListItem) {var instance = this;instance._itemSelected = true;instance._updateValue(elListItem);instance._pastSelections = instance.inputNode.get('value');instance._clearInterval();instance.fire('itemSelect', elListItem, elListItem._resultData);instance._toggleContainer(false);},/** * Makes a query request to the data source. * * @method _sendQuery * @param {String} query The query string * @protected */_sendQuery: function(query) {var instance = this;if (instance.get('disabled')) {instance._toggleContainer(false);return;}var delimChar = instance.get('delimChar');var minQueryLength = instance.get('minQueryLength');if (delimChar) {var extraction = instance._extractQuery(query);query = extraction.query;instance._pastSelections = extraction.previous;}if ((query && (query.length < minQueryLength)) || (!query && minQueryLength > 0)) {instance._queryTask.cancel();instance._toggleContainer(false);return;}query = encodeURIComponent(query);if (instance.get('applyLocalFilter')) {instance.dataSource.on('response', instance.filterResults, instance);}var request = instance.generateRequest(query);instance.fire('dataRequest', query, request);instance.dataSource.sendRequest(request);},/** * Checks to see if the value typed by the user matches any of the * query results. * * @method _textMatchesOption * @protected */_textMatchesOption: function() {var instance = this;var elMatch = null;var displayedItems = instance._displayedItems;var listItems = instance.resultList.get('childNodes');for (var i=0; i < displayedItems.length; i++) {var elListItem = listItems.item(i);var match = ('' + elListItem._resultMatch).toLowerCase();if (match == instance._currentQuery.toLowerCase()) {elMatch = elListItem;break;}}return elMatch;},/** * Toggles the display of the results container. * * @method _toggleContainer * @param {Boolean} show Flag to force the showing or hiding of the container * @protected */_toggleContainer: function(show) {var instance = this;var overlay = instance.overlay;if (instance.get('alwaysShowContainer') && overlay.get('visible')) {return;}if (!show) {instance._toggleHighlight(instance._elCurListItem, 'from');instance._displayedItems = 0;instance._currentQuery = null;}if (show) {overlay.show();instance.fire('containerExpand');}else {overlay.hide();instance.fire('containerCollapse');}},/** * Toggles the highlighting of a list item, and removes the highlighting from the previous item * * @method _toggleHighlight * @param {Node} elNewListItem The item to be highlighted * @param {String} action Whether we are moving to or from an item. Valid values are "to" or "from". * @protected */_toggleHighlight: function(elNewListItem, action) {var instance = this;if (elNewListItem) {if (instance._elCurListItem) {instance._elCurListItem.removeClass(CSS_HIGLIGHT);instance._elCurListItem = null;}if (action == 'to') {elNewListItem.addClass(CSS_HIGLIGHT);instance._elCurListItem = elNewListItem;}}},/** * Updates in the input element with the first result as the user types, * selecting the text the user has not typed yet. * * @method _typeAhead * @param {Node} elListItem The selected list item * @param {String} query The query string * @protected */_typeAhead: function(elListItem, query) {var instance = this;if (!instance.get('typeAhead') || instance._keyCode == KEY_BACKSPACE) {return;}var inputEl = A.Node.getDOMNode(instance.inputNode);if (inputEl.setSelectionRange || inputEl.createTextRange) {instance._typeAheadDelayId = setTimeout(function() {var value = inputEl.value;var start = value.length;instance._updateValue(elListItem);var end = inputEl.value.length;instance.inputNode.selectText(start, end);var prefill = inputEl.value.substr(start, end);instance.fire('typeAhead', query, prefill);},instance.get('typeAheadDelay'));}},/** * Updates the input element with the selected query result. If * <a href="Autocomplete.html#config_delimChar">delimChar</a> has been set, * then the value gets appended with the delimiter. * * @method _updateValue * @param {Node} elListItem The selected list item * @protected */_updateValue: function(elListItem) {var instance = this;if (!instance.get('suppressInputUpdate')) {var input = instance.inputNode;var resultMatch = elListItem._resultMatch;var delimChar = instance.get('delimChar');delimChar = (delimChar && delimChar[0]) || delimChar;var newValue = '';if (delimChar) {newValue = instance._pastSelections;newValue += resultMatch + delimChar;if (delimChar != ' ') {newValue += ' ';}}else {newValue = resultMatch;}input.set('value', newValue);if (input.get('type') == 'textarea') {input.set('scrollTop', input.get('scrollHeight'));}var end = newValue.length;input.selectText(end, end);instance._elCurListItem = elListItem;}},_currentQuery: null,_displayedItems: 0,_elCurListItem: null,_initInputValue: null,_itemSelected: false,_keyCode: null,_lastValue: null,_overContainer: false,_pastSelections: '',_typeAheadDelayId: -1}});A.AutoComplete = AutoComplete;}, '1.0.1' ,{skinnable:true, requires:['aui-base','aui-overlay-base','datasource','dataschema','aui-form-combobox']});

热点排行