diff --git a/.gitignore b/.gitignore index 8754ab6..3f3cc66 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ node_modules sea-modules tests/coverage.html .cache +dist/ diff --git a/.travis.yml b/.travis.yml index e5c2daf..bf8de4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,16 @@ language: node_js node_js: - - 0.10 + - "0.10" install: - - npm install mocha-browser nico + - npm install spm@ninja coveralls before_script: - - git clone git://github.com/aralejs/nico-arale.git _theme - - node_modules/.bin/nico build --theme _theme -C _theme/nico.js + - node_modules/spm/bin/spm-install script: - - node_modules/.bin/mocha-browser _site/tests/runner.html -S + - node_modules/spm/bin/spm-test after_success: - - npm install jscoverage coveralls - - node_modules/.bin/jscoverage --encoding=utf8 src _site/src-cov - - node_modules/.bin/mocha-browser _site/tests/runner.html?cov -S -R lcov | node_modules/.bin/coveralls + - node_modules/spm/bin/spm-test --coveralls | node_modules/.bin/coveralls diff --git a/HISTORY.md b/HISTORY.md index b7bbd2f..c713272 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,10 @@ --- +## 1.4.0 + +迁移 spm@3.x + ## 1.3.1 `tag:fixed` 修复 for/in 数组的 bug @@ -86,7 +90,7 @@ `tag:new` [#11](https://github.com/aralejs/autocomplete/issues/11) 控制按键频率。 `tag:new` [#13](https://github.com/aralejs/autocomplete/issues/13) dataSource 支持 ajax。 - + `tag:new` [#15](https://github.com/aralejs/autocomplete/issues/15) 提供 selectFirst 参数。 `tag:new` [#16](https://github.com/aralejs/autocomplete/issues/16) dataSource 支持复杂的结构。 diff --git a/Makefile b/Makefile deleted file mode 100644 index b5a4ee9..0000000 --- a/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -THEME = $(HOME)/.spm/themes/arale - -build-doc: - @nico build -C $(THEME)/nico.js - -publish-doc: clean build-doc - @spm publish --doc _site -s spmjs - -server: - @nico server -C $(THEME)/nico.js - -watch: - @nico server -C $(THEME)/nico.js --watch - -clean: - @rm -fr _site - - -runner = _site/tests/runner.html -test-src: - @mocha-browser ${runner} -S - -test-dist: - @mocha-browser ${runner}?dist -S - -test: test-src test-dist - -output = _site/coverage.html -coverage: build-doc - @rm -fr _site/src-cov - @jscoverage --encoding=utf8 src _site/src-cov - @mocha-browser ${runner}?cov -S -R html-cov > ${output} - @echo "Build coverage to ${output}" - - -.PHONY: build-doc publish-doc server clean test coverage - - diff --git a/autocomplete.js b/autocomplete.js new file mode 100644 index 0000000..e03a36d --- /dev/null +++ b/autocomplete.js @@ -0,0 +1 @@ +module.exports = require('./src/autocomplete'); diff --git a/dist/autocomplete-debug.js b/dist/autocomplete-debug.js deleted file mode 100644 index 744bc19..0000000 --- a/dist/autocomplete-debug.js +++ /dev/null @@ -1,784 +0,0 @@ -define("arale/autocomplete/1.3.0/autocomplete-debug", [ "$-debug", "arale/overlay/1.1.2/overlay-debug", "arale/position/1.0.1/position-debug", "arale/iframe-shim/1.0.2/iframe-shim-debug", "arale/widget/1.1.1/widget-debug", "arale/base/1.1.1/base-debug", "arale/class/1.1.0/class-debug", "arale/events/1.1.0/events-debug", "arale/templatable/0.9.2/templatable-debug", "gallery/handlebars/1.0.2/handlebars-debug", "./data-source-debug", "./filter-debug", "./input-debug", "./autocomplete-debug.handlebars" ], function(require, exports, module) { - var $ = require("$-debug"); - var Overlay = require("arale/overlay/1.1.2/overlay-debug"); - var Templatable = require("arale/templatable/0.9.2/templatable-debug"); - var DataSource = require("./data-source-debug"); - var Filter = require("./filter-debug"); - var Input = require("./input-debug"); - var IE678 = /\bMSIE [678]\.0\b/.test(navigator.userAgent); - var template = require("./autocomplete-debug.handlebars"); - var AutoComplete = Overlay.extend({ - Implements: Templatable, - attrs: { - // 触发元素 - trigger: null, - classPrefix: "ui-select", - align: { - baseXY: [ 0, "100%" ] - }, - submitOnEnter: true, - // 回车是否会提交表单 - dataSource: { - //数据源,支持 Array, URL, Object, Function - value: [], - getter: function(val) { - var that = this; - if ($.isFunction(val)) { - return function() { - val.apply(that, arguments); - }; - } - return val; - } - }, - locator: "data", - // 输出过滤 - filter: null, - disabled: false, - selectFirst: false, - delay: 100, - // 以下为模板相关 - model: { - value: { - items: [] - }, - getter: function(val) { - val.classPrefix || (val.classPrefix = this.get("classPrefix")); - return val; - } - }, - template: template, - footer: "", - header: "", - html: "{{{label}}}", - // 以下仅为组件使用 - selectedIndex: null, - data: [] - }, - events: { - "mousedown [data-role=items]": "_handleMouseDown", - "click [data-role=item]": "_handleSelection", - "mouseenter [data-role=item]": "_handleMouseMove", - "mouseleave [data-role=item]": "_handleMouseMove" - }, - templateHelpers: { - // 将匹配的高亮文字加上 hl 的样式 - highlightItem: highlightItem, - include: include - }, - parseElement: function() { - var t = [ "header", "footer", "html" ]; - for (var i in t) { - this.templatePartials || (this.templatePartials = {}); - this.templatePartials[t[i]] = this.get(t[i]); - } - AutoComplete.superclass.parseElement.call(this); - }, - setup: function() { - AutoComplete.superclass.setup.call(this); - this._isOpen = false; - this._initInput(); - // 初始化输入框 - this._initDataSource(); - // 初始化数据源 - this._initFilter(); - // 初始化过滤器 - this._bindHandle(); - // 绑定事件 - this._blurHide([ $(this.get("trigger")) ]); - this._tweakAlignDefaultValue(); - this.on("indexChanged", function(index) { - // scroll current item into view - //this.currentItem.scrollIntoView(); - var containerHeight = parseInt(this.get("height"), 10); - if (!containerHeight) return; - var itemHeight = this.items.parent().height() / this.items.length, itemTop = Math.max(0, itemHeight * (index + 1) - containerHeight); - this.element.children().scrollTop(itemTop); - }); - }, - show: function() { - this._isOpen = true; - // 无数据则不显示 - if (this._isEmpty()) return; - AutoComplete.superclass.show.call(this); - }, - hide: function() { - // 隐藏的时候取消请求或回调 - if (this._timeout) clearTimeout(this._timeout); - this.dataSource.abort(); - this._hide(); - }, - destroy: function() { - this._clear(); - if (this.input) { - this.input.destroy(); - this.input = null; - } - AutoComplete.superclass.destroy.call(this); - }, - // Public Methods - // -------------- - selectItem: function(index) { - if (this.items) { - if (index && this.items.length > index && index >= -1) { - this.set("selectedIndex", index); - } - this._handleSelection(); - } - }, - setInputValue: function(val) { - this.input.setValue(val); - }, - // Private Methods - // --------------- - // 数据源返回,过滤数据 - _filterData: function(data) { - var filter = this.get("filter"), locator = this.get("locator"); - // 获取目标数据 - data = locateResult(locator, data); - // 进行过滤 - data = filter.call(this, normalize(data), this.input.get("query")); - this.set("data", data); - }, - // 通过数据渲染模板 - _onRenderData: function(data) { - data || (data = []); - // 渲染下拉 - this.set("model", { - items: data, - query: this.input.get("query"), - length: data.length - }); - this.renderPartial(); - // 初始化下拉的状态 - this.items = this.$("[data-role=items]").children(); - if (this.get("selectFirst")) { - this.set("selectedIndex", 0); - } - // 选中后会修改 input 的值并触发下一次渲染,但第二次渲染的结果不应该显示出来。 - this._isOpen && this.show(); - }, - // 键盘控制上下移动 - _onRenderSelectedIndex: function(index) { - var hoverClass = this.get("classPrefix") + "-item-hover"; - this.items && this.items.removeClass(hoverClass); - // -1 什么都不选 - if (index === -1) return; - this.items.eq(index).addClass(hoverClass); - this.trigger("indexChanged", index, this.lastIndex); - this.lastIndex = index; - }, - // 初始化 - // ------------ - _initDataSource: function() { - this.dataSource = new DataSource({ - source: this.get("dataSource") - }); - }, - _initInput: function() { - this.input = new Input({ - element: this.get("trigger"), - delay: this.get("delay") - }); - }, - _initFilter: function() { - var filter = this.get("filter"); - filter = initFilter(filter, this.dataSource); - this.set("filter", filter); - }, - // 事件绑定 - // ------------ - _bindHandle: function() { - this.dataSource.on("data", this._filterData, this); - this.input.on("blur", this.hide, this).on("focus", this._handleFocus, this).on("keyEnter", this._handleSelection, this).on("keyEsc", this.hide, this).on("keyUp keyDown", this.show, this).on("keyUp keyDown", this._handleStep, this).on("queryChanged", this._clear, this).on("queryChanged", this._hide, this).on("queryChanged", this._handleQueryChange, this).on("queryChanged", this.show, this); - this.after("hide", function() { - this.set("selectedIndex", -1); - }); - // 选中后隐藏浮层 - this.on("itemSelected", function() { - this._hide(); - }); - }, - // 选中的处理器 - // 1. 鼠标点击触发 - // 2. 回车触发 - // 3. selectItem 触发 - _handleSelection: function(e) { - var isMouse = e ? e.type === "click" : false; - var index = isMouse ? this.items.index(e.currentTarget) : this.get("selectedIndex"); - var item = this.items.eq(index); - var data = this.get("data")[index]; - if (index >= 0 && item) { - this.input.setValue(data.label); - this.set("selectedIndex", index, { - silent: true - }); - // 是否阻止回车提交表单 - if (e && !isMouse && !this.get("submitOnEnter")) e.preventDefault(); - this.trigger("itemSelected", data, item); - } - }, - _handleFocus: function() { - this._isOpen = true; - }, - _handleMouseMove: function(e) { - var hoverClass = this.get("classPrefix") + "-item-hover"; - this.items.removeClass(hoverClass); - if (e.type === "mouseenter") { - var index = this.items.index(e.currentTarget); - this.set("selectedIndex", index, { - silent: true - }); - this.items.eq(index).addClass(hoverClass); - } - }, - _handleMouseDown: function(e) { - if (IE678) { - var trigger = this.input.get("element")[0]; - trigger.onbeforedeactivate = function() { - window.event.returnValue = false; - trigger.onbeforedeactivate = null; - }; - } - e.preventDefault(); - }, - _handleStep: function(e) { - e.preventDefault(); - this.get("visible") && this._step(e.type === "keyUp" ? -1 : 1); - }, - _handleQueryChange: function(val, prev) { - if (this.get("disabled")) return; - this.dataSource.abort(); - this.dataSource.getData(val); - }, - // 选项上下移动 - _step: function(direction) { - var currentIndex = this.get("selectedIndex"); - if (direction === -1) { - // 反向 - if (currentIndex > -1) { - this.set("selectedIndex", currentIndex - 1); - } else { - this.set("selectedIndex", this.items.length - 1); - } - } else if (direction === 1) { - // 正向 - if (currentIndex < this.items.length - 1) { - this.set("selectedIndex", currentIndex + 1); - } else { - this.set("selectedIndex", -1); - } - } - }, - _clear: function() { - this.$("[data-role=items]").empty(); - this.set("selectedIndex", -1); - delete this.items; - delete this.lastIndex; - }, - _hide: function() { - this._isOpen = false; - AutoComplete.superclass.hide.call(this); - }, - _isEmpty: function() { - var data = this.get("data"); - return !(data && data.length > 0); - }, - // 调整 align 属性的默认值 - _tweakAlignDefaultValue: function() { - var align = this.get("align"); - align.baseElement = this.get("trigger"); - this.set("align", align); - } - }); - module.exports = AutoComplete; - function isString(str) { - return Object.prototype.toString.call(str) === "[object String]"; - } - function isObject(obj) { - return Object.prototype.toString.call(obj) === "[object Object]"; - } - // 通过 locator 找到 data 中的某个属性的值 - // 1. locator 支持 function,函数返回值为结果 - // 2. locator 支持 string,而且支持点操作符寻址 - // data { - // a: { - // b: 'c' - // } - // } - // locator 'a.b' - // 最后的返回值为 c - function locateResult(locator, data) { - if (locator) { - if ($.isFunction(locator)) { - return locator.call(this, data); - } else if (!$.isArray(data) && isString(locator)) { - var s = locator.split("."), p = data; - while (s.length) { - var v = s.shift(); - if (!p[v]) { - break; - } - p = p[v]; - } - return p; - } - } - return data; - } - // 标准格式,不匹配则忽略 - // - // { - // label: '', 显示的字段 - // value: '', 匹配的字段 - // alias: [] 其他匹配的字段 - // } - function normalize(data) { - var result = []; - $.each(data, function(index, item) { - if (isString(item)) { - result.push({ - label: item, - value: item, - alias: [] - }); - } else if (isObject(item)) { - if (!item.value && !item.label) return; - item.value || (item.value = item.label); - item.label || (item.label = item.value); - item.alias || (item.alias = []); - result.push(item); - } - }); - return result; - } - // 初始化 filter - // 支持的格式 - // 1. null: 使用默认的 startsWith - // 2. string: 从 Filter 中找,如果不存在则用 default - // 3. function: 自定义 - function initFilter(filter, dataSource) { - // 字符串 - if (isString(filter)) { - // 从组件内置的 FILTER 获取 - if (Filter[filter]) { - filter = Filter[filter]; - } else { - filter = Filter["default"]; - } - } else if (!$.isFunction(filter)) { - // 异步请求的时候不需要过滤器 - if (dataSource.get("type") === "url") { - filter = Filter["default"]; - } else { - filter = Filter["startsWith"]; - } - } - return filter; - } - function include(options) { - var context = {}; - mergeContext(this); - mergeContext(options.hash); - return options.fn(context); - function mergeContext(obj) { - for (var k in obj) context[k] = obj[k]; - } - } - function highlightItem(label) { - var index = this.highlightIndex, classPrefix = this.parent ? this.parent.classPrefix : "", cursor = 0, v = label || this.label || "", h = ""; - if ($.isArray(index)) { - for (var i = 0, l = index.length; i < l; i++) { - var j = index[i], start, length; - if ($.isArray(j)) { - start = j[0]; - length = j[1] - j[0]; - } else { - start = j; - length = 1; - } - if (start > cursor) { - h += v.substring(cursor, start); - } - if (start < v.length) { - var className = classPrefix ? 'class="' + classPrefix + '-item-hl"' : ""; - h += "" + v.substr(start, length) + ""; - } - cursor = start + length; - if (cursor >= v.length) { - break; - } - } - if (v.length > cursor) { - h += v.substring(cursor, v.length); - } - return h; - } - return v; - } -}); - -define("arale/autocomplete/1.3.0/data-source-debug", [ "arale/base/1.1.1/base-debug", "arale/class/1.1.0/class-debug", "arale/events/1.1.0/events-debug", "$-debug" ], function(require, exports, module) { - var Base = require("arale/base/1.1.1/base-debug"); - var $ = require("$-debug"); - var DataSource = Base.extend({ - attrs: { - source: null, - type: "array" - }, - initialize: function(config) { - DataSource.superclass.initialize.call(this, config); - // 每次发送请求会将 id 记录到 callbacks 中,返回后会从中删除 - // 如果 abort 会清空 callbacks,之前的请求结果都不会执行 - this.id = 0; - this.callbacks = []; - var source = this.get("source"); - if (isString(source)) { - this.set("type", "url"); - } else if ($.isArray(source)) { - this.set("type", "array"); - } else if ($.isPlainObject(source)) { - this.set("type", "object"); - } else if ($.isFunction(source)) { - this.set("type", "function"); - } else { - throw new Error("Source Type Error"); - } - }, - getData: function(query) { - return this["_get" + capitalize(this.get("type") || "") + "Data"](query); - }, - abort: function() { - this.callbacks = []; - }, - // 完成数据请求,getData => done - _done: function(data) { - this.trigger("data", data); - }, - _getUrlData: function(query) { - var that = this, options; - var obj = { - query: query ? encodeURIComponent(query) : "", - timestamp: new Date().getTime() - }; - var url = this.get("source").replace(/\{\{(.*?)\}\}/g, function(all, match) { - return obj[match]; - }); - var callbackId = "callback_" + this.id++; - this.callbacks.push(callbackId); - if (/^(https?:\/\/)/.test(url)) { - options = { - dataType: "jsonp" - }; - } else { - options = { - dataType: "json" - }; - } - $.ajax(url, options).success(function(data) { - if ($.inArray(callbackId, that.callbacks) > -1) { - delete that.callbacks[callbackId]; - that._done(data); - } - }).error(function() { - if ($.inArray(callbackId, that.callbacks) > -1) { - delete that.callbacks[callbackId]; - that._done({}); - } - }); - }, - _getArrayData: function() { - var source = this.get("source"); - this._done(source); - return source; - }, - _getObjectData: function() { - var source = this.get("source"); - this._done(source); - return source; - }, - _getFunctionData: function(query) { - var that = this, func = this.get("source"); - // 如果返回 false 可阻止执行 - function done(data) { - that._done(data); - } - var data = func.call(this, query, done); - if (data) { - this._done(data); - } - } - }); - module.exports = DataSource; - function isString(str) { - return Object.prototype.toString.call(str) === "[object String]"; - } - function capitalize(str) { - return str.replace(/^([a-z])/, function(f, m) { - return m.toUpperCase(); - }); - } -}); - -define("arale/autocomplete/1.3.0/filter-debug", [ "$-debug" ], function(require, exports, module) { - var $ = require("$-debug"); - var Filter = { - "default": function(data) { - return data; - }, - startsWith: function(data, query) { - query = query || ""; - var result = [], l = query.length, reg = new RegExp("^" + escapeKeyword(query)); - if (!l) return []; - $.each(data, function(index, item) { - var a, matchKeys = [ item.value ].concat(item.alias); - // 匹配 value 和 alias 中的 - while (a = matchKeys.shift()) { - if (reg.test(a)) { - // 匹配和显示相同才有必要高亮 - if (item.label === a) { - item.highlightIndex = [ [ 0, l ] ]; - } - result.push(item); - break; - } - } - }); - return result; - }, - stringMatch: function(data, query) { - query = query || ""; - var result = [], l = query.length; - if (!l) return []; - $.each(data, function(index, item) { - var a, matchKeys = [ item.value ].concat(item.alias); - // 匹配 value 和 alias 中的 - while (a = matchKeys.shift()) { - if (a.indexOf(query) > -1) { - // 匹配和显示相同才有必要高亮 - if (item.label === a) { - item.highlightIndex = stringMatch(a, query); - } - result.push(item); - break; - } - } - }); - return result; - } - }; - module.exports = Filter; - // 转义正则关键字 - var keyword = /(\[|\[|\]|\^|\$|\||\(|\)|\{|\}|\+|\*|\?|\\)/g; - function escapeKeyword(str) { - return (str || "").replace(keyword, "\\$1"); - } - function stringMatch(matchKey, query) { - var r = [], a = matchKey.split(""); - var queryIndex = 0, q = query.split(""); - for (var i = 0, l = a.length; i < l; i++) { - var v = a[i]; - if (v === q[queryIndex]) { - if (queryIndex === q.length - 1) { - r.push([ i - q.length + 1, i + 1 ]); - queryIndex = 0; - continue; - } - queryIndex++; - } else { - queryIndex = 0; - } - } - return r; - } -}); - -define("arale/autocomplete/1.3.0/input-debug", [ "$-debug", "arale/base/1.1.1/base-debug", "arale/class/1.1.0/class-debug", "arale/events/1.1.0/events-debug" ], function(require, exports, module) { - var $ = require("$-debug"); - var Base = require("arale/base/1.1.1/base-debug"); - var lteIE9 = /\bMSIE [6789]\.0\b/.test(navigator.userAgent); - var specialKeyCodeMap = { - 9: "tab", - 27: "esc", - 37: "left", - 39: "right", - 13: "enter", - 38: "up", - 40: "down" - }; - var Input = Base.extend({ - attrs: { - element: { - value: null, - setter: function(val) { - return $(val); - } - }, - query: null, - delay: 100 - }, - initialize: function() { - Input.superclass.initialize.apply(this, arguments); - // bind events - this._bindEvents(); - // init query - this.set("query", this.getValue()); - }, - focus: function() { - this.get("element").focus(); - }, - getValue: function() { - return this.get("element").val(); - }, - setValue: function(val, silent) { - this.get("element").val(val); - !silent && this._change(); - }, - destroy: function() { - Input.superclass.destroy.call(this); - }, - _bindEvents: function() { - var timer, input = this.get("element"); - input.attr("autocomplete", "off").on("focus.autocomplete", wrapFn(this._handleFocus, this)).on("blur.autocomplete", wrapFn(this._handleBlur, this)).on("keydown.autocomplete", wrapFn(this._handleKeydown, this)); - // IE678 don't support input event - // IE 9 does not fire an input event when the user removes characters from input filled by keyboard, cut, or drag operations. - if (!lteIE9) { - input.on("input.autocomplete", wrapFn(this._change, this)); - } else { - var that = this, events = [ "keydown.autocomplete", "keypress.autocomplete", "cut.autocomplete", "paste.autocomplete" ].join(" "); - input.on(events, wrapFn(function(e) { - if (specialKeyCodeMap[e.which]) return; - clearTimeout(timer); - timer = setTimeout(function() { - that._change.call(that, e); - }, this.get("delay")); - }, this)); - } - }, - _change: function() { - var newVal = this.getValue(); - var oldVal = this.get("query"); - var isSame = compare(oldVal, newVal); - var isSameExpectWhitespace = isSame ? newVal.length !== oldVal.length : false; - if (isSameExpectWhitespace) { - this.trigger("whitespaceChanged", oldVal); - } - if (!isSame) { - this.set("query", newVal); - this.trigger("queryChanged", newVal, oldVal); - } - }, - _handleFocus: function(e) { - this.trigger("focus", e); - }, - _handleBlur: function(e) { - this.trigger("blur", e); - }, - _handleKeydown: function(e) { - var keyName = specialKeyCodeMap[e.which]; - if (keyName) { - var eventKey = "key" + ucFirst(keyName); - this.trigger(e.type = eventKey, e); - } - } - }); - module.exports = Input; - function wrapFn(fn, context) { - return function() { - fn.apply(context, arguments); - }; - } - function compare(a, b) { - a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - return a === b; - } - function ucFirst(str) { - return str.charAt(0).toUpperCase() + str.substring(1); - } -}); - -define("arale/autocomplete/1.3.0/autocomplete-debug.handlebars", [ "gallery/handlebars/1.0.2/runtime-debug" ], function(require, exports, module) { - var Handlebars = require("gallery/handlebars/1.0.2/runtime-debug"); - var template = Handlebars.template; - module.exports = template(function(Handlebars, depth0, helpers, partials, data) { - this.compilerInfo = [ 3, ">= 1.0.0-rc.4" ]; - helpers = helpers || {}; - for (var key in Handlebars.helpers) { - helpers[key] = helpers[key] || Handlebars.helpers[key]; - } - partials = partials || Handlebars.partials; - data = data || {}; - var buffer = "", stack1, self = this, functionType = "function", escapeExpression = this.escapeExpression, helperMissing = helpers.helperMissing; - function program1(depth0, data, depth1) { - var buffer = "", stack1, stack2, options; - buffer += '\n
  • \n \n '; - options = { - hash: { - parent: depth1 - }, - inverse: self.noop, - fn: self.program(2, program2, data), - data: data - }; - stack2 = (stack1 = helpers.include, stack1 ? stack1.call(depth0, options) : helperMissing.call(depth0, "include", options)); - if (stack2 || stack2 === 0) { - buffer += stack2; - } - buffer += "\n \n
  • \n "; - return buffer; - } - function program2(depth0, data) { - var stack1; - stack1 = self.invokePartial(partials.html, "html", depth0, helpers, partials, data); - if (stack1 || stack1 === 0) { - return stack1; - } else { - return ""; - } - } - buffer += '
    \n
    \n '; - stack1 = self.invokePartial(partials.header, "header", depth0, helpers, partials, data); - if (stack1 || stack1 === 0) { - buffer += stack1; - } - buffer += '\n \n "; - stack1 = self.invokePartial(partials.footer, "footer", depth0, helpers, partials, data); - if (stack1 || stack1 === 0) { - buffer += stack1; - } - buffer += "\n
    \n
    \n"; - return buffer; - }); -}); diff --git a/dist/autocomplete.js b/dist/autocomplete.js deleted file mode 100644 index 2ed1316..0000000 --- a/dist/autocomplete.js +++ /dev/null @@ -1 +0,0 @@ -define("arale/autocomplete/1.3.0/autocomplete",["$","arale/overlay/1.1.2/overlay","arale/position/1.0.1/position","arale/iframe-shim/1.0.2/iframe-shim","arale/widget/1.1.1/widget","arale/base/1.1.1/base","arale/class/1.1.0/class","arale/events/1.1.0/events","arale/templatable/0.9.2/templatable","gallery/handlebars/1.0.2/handlebars","./data-source","./filter","./input","./autocomplete.handlebars"],function(a,b,c){function d(a){return"[object String]"===Object.prototype.toString.call(a)}function e(a){return"[object Object]"===Object.prototype.toString.call(a)}function f(a,b){if(a){if(k.isFunction(a))return a.call(this,b);if(!k.isArray(b)&&d(a)){for(var c=a.split("."),e=b;c.length;){var f=c.shift();if(!e[f])break;e=e[f]}return e}}return b}function g(a){var b=[];return k.each(a,function(a,c){if(d(c))b.push({label:c,value:c,alias:[]});else if(e(c)){if(!c.value&&!c.label)return;c.value||(c.value=c.label),c.label||(c.label=c.value),c.alias||(c.alias=[]),b.push(c)}}),b}function h(a,b){return d(a)?a=o[a]?o[a]:o["default"]:k.isFunction(a)||(a="url"===b.get("type")?o["default"]:o.startsWith),a}function i(a){function b(a){for(var b in a)c[b]=a[b]}var c={};return b(this),b(a.hash),a.fn(c)}function j(a){var b=this.highlightIndex,c=this.parent?this.parent.classPrefix:"",d=0,e=a||this.label||"",f="";if(k.isArray(b)){for(var g=0,h=b.length;h>g;g++){var i,j,l=b[g];if(k.isArray(l)?(i=l[0],j=l[1]-l[0]):(i=l,j=1),i>d&&(f+=e.substring(d,i)),i"+e.substr(i,j)+""}if(d=i+j,d>=e.length)break}return e.length>d&&(f+=e.substring(d,e.length)),f}return e}var k=a("$"),l=a("arale/overlay/1.1.2/overlay"),m=a("arale/templatable/0.9.2/templatable"),n=a("./data-source"),o=a("./filter"),p=a("./input"),q=/\bMSIE [678]\.0\b/.test(navigator.userAgent),r=a("./autocomplete.handlebars"),s=l.extend({Implements:m,attrs:{trigger:null,classPrefix:"ui-select",align:{baseXY:[0,"100%"]},submitOnEnter:!0,dataSource:{value:[],getter:function(a){var b=this;return k.isFunction(a)?function(){a.apply(b,arguments)}:a}},locator:"data",filter:null,disabled:!1,selectFirst:!1,delay:100,model:{value:{items:[]},getter:function(a){return a.classPrefix||(a.classPrefix=this.get("classPrefix")),a}},template:r,footer:"",header:"",html:"{{{label}}}",selectedIndex:null,data:[]},events:{"mousedown [data-role=items]":"_handleMouseDown","click [data-role=item]":"_handleSelection","mouseenter [data-role=item]":"_handleMouseMove","mouseleave [data-role=item]":"_handleMouseMove"},templateHelpers:{highlightItem:j,include:i},parseElement:function(){var a=["header","footer","html"];for(var b in a)this.templatePartials||(this.templatePartials={}),this.templatePartials[a[b]]=this.get(a[b]);s.superclass.parseElement.call(this)},setup:function(){s.superclass.setup.call(this),this._isOpen=!1,this._initInput(),this._initDataSource(),this._initFilter(),this._bindHandle(),this._blurHide([k(this.get("trigger"))]),this._tweakAlignDefaultValue(),this.on("indexChanged",function(a){var b=parseInt(this.get("height"),10);if(b){var c=this.items.parent().height()/this.items.length,d=Math.max(0,c*(a+1)-b);this.element.children().scrollTop(d)}})},show:function(){this._isOpen=!0,this._isEmpty()||s.superclass.show.call(this)},hide:function(){this._timeout&&clearTimeout(this._timeout),this.dataSource.abort(),this._hide()},destroy:function(){this._clear(),this.input&&(this.input.destroy(),this.input=null),s.superclass.destroy.call(this)},selectItem:function(a){this.items&&(a&&this.items.length>a&&a>=-1&&this.set("selectedIndex",a),this._handleSelection())},setInputValue:function(a){this.input.setValue(a)},_filterData:function(a){var b=this.get("filter"),c=this.get("locator");a=f(c,a),a=b.call(this,g(a),this.input.get("query")),this.set("data",a)},_onRenderData:function(a){a||(a=[]),this.set("model",{items:a,query:this.input.get("query"),length:a.length}),this.renderPartial(),this.items=this.$("[data-role=items]").children(),this.get("selectFirst")&&this.set("selectedIndex",0),this._isOpen&&this.show()},_onRenderSelectedIndex:function(a){var b=this.get("classPrefix")+"-item-hover";this.items&&this.items.removeClass(b),-1!==a&&(this.items.eq(a).addClass(b),this.trigger("indexChanged",a,this.lastIndex),this.lastIndex=a)},_initDataSource:function(){this.dataSource=new n({source:this.get("dataSource")})},_initInput:function(){this.input=new p({element:this.get("trigger"),delay:this.get("delay")})},_initFilter:function(){var a=this.get("filter");a=h(a,this.dataSource),this.set("filter",a)},_bindHandle:function(){this.dataSource.on("data",this._filterData,this),this.input.on("blur",this.hide,this).on("focus",this._handleFocus,this).on("keyEnter",this._handleSelection,this).on("keyEsc",this.hide,this).on("keyUp keyDown",this.show,this).on("keyUp keyDown",this._handleStep,this).on("queryChanged",this._clear,this).on("queryChanged",this._hide,this).on("queryChanged",this._handleQueryChange,this).on("queryChanged",this.show,this),this.after("hide",function(){this.set("selectedIndex",-1)}),this.on("itemSelected",function(){this._hide()})},_handleSelection:function(a){var b=a?"click"===a.type:!1,c=b?this.items.index(a.currentTarget):this.get("selectedIndex"),d=this.items.eq(c),e=this.get("data")[c];c>=0&&d&&(this.input.setValue(e.label),this.set("selectedIndex",c,{silent:!0}),!a||b||this.get("submitOnEnter")||a.preventDefault(),this.trigger("itemSelected",e,d))},_handleFocus:function(){this._isOpen=!0},_handleMouseMove:function(a){var b=this.get("classPrefix")+"-item-hover";if(this.items.removeClass(b),"mouseenter"===a.type){var c=this.items.index(a.currentTarget);this.set("selectedIndex",c,{silent:!0}),this.items.eq(c).addClass(b)}},_handleMouseDown:function(a){if(q){var b=this.input.get("element")[0];b.onbeforedeactivate=function(){window.event.returnValue=!1,b.onbeforedeactivate=null}}a.preventDefault()},_handleStep:function(a){a.preventDefault(),this.get("visible")&&this._step("keyUp"===a.type?-1:1)},_handleQueryChange:function(a){this.get("disabled")||(this.dataSource.abort(),this.dataSource.getData(a))},_step:function(a){var b=this.get("selectedIndex");-1===a?b>-1?this.set("selectedIndex",b-1):this.set("selectedIndex",this.items.length-1):1===a&&(b0)},_tweakAlignDefaultValue:function(){var a=this.get("align");a.baseElement=this.get("trigger"),this.set("align",a)}});c.exports=s}),define("arale/autocomplete/1.3.0/data-source",["arale/base/1.1.1/base","arale/class/1.1.0/class","arale/events/1.1.0/events","$"],function(a,b,c){function d(a){return"[object String]"===Object.prototype.toString.call(a)}function e(a){return a.replace(/^([a-z])/,function(a,b){return b.toUpperCase()})}var f=a("arale/base/1.1.1/base"),g=a("$"),h=f.extend({attrs:{source:null,type:"array"},initialize:function(a){h.superclass.initialize.call(this,a),this.id=0,this.callbacks=[];var b=this.get("source");if(d(b))this.set("type","url");else if(g.isArray(b))this.set("type","array");else if(g.isPlainObject(b))this.set("type","object");else{if(!g.isFunction(b))throw new Error("Source Type Error");this.set("type","function")}},getData:function(a){return this["_get"+e(this.get("type")||"")+"Data"](a)},abort:function(){this.callbacks=[]},_done:function(a){this.trigger("data",a)},_getUrlData:function(a){var b,c=this,d={query:a?encodeURIComponent(a):"",timestamp:(new Date).getTime()},e=this.get("source").replace(/\{\{(.*?)\}\}/g,function(a,b){return d[b]}),f="callback_"+this.id++;this.callbacks.push(f),b=/^(https?:\/\/)/.test(e)?{dataType:"jsonp"}:{dataType:"json"},g.ajax(e,b).success(function(a){g.inArray(f,c.callbacks)>-1&&(delete c.callbacks[f],c._done(a))}).error(function(){g.inArray(f,c.callbacks)>-1&&(delete c.callbacks[f],c._done({}))})},_getArrayData:function(){var a=this.get("source");return this._done(a),a},_getObjectData:function(){var a=this.get("source");return this._done(a),a},_getFunctionData:function(a){function b(a){c._done(a)}var c=this,d=this.get("source"),e=d.call(this,a,b);e&&this._done(e)}});c.exports=h}),define("arale/autocomplete/1.3.0/filter",["$"],function(a,b,c){function d(a){return(a||"").replace(h,"\\$1")}function e(a,b){for(var c=[],d=a.split(""),e=0,f=b.split(""),g=0,h=d.length;h>g;g++){var i=d[g];if(i===f[e]){if(e===f.length-1){c.push([g-f.length+1,g+1]),e=0;continue}e++}else e=0}return c}var f=a("$"),g={"default":function(a){return a},startsWith:function(a,b){b=b||"";var c=[],e=b.length,g=new RegExp("^"+d(b));return e?(f.each(a,function(a,b){for(var d,f=[b.value].concat(b.alias);d=f.shift();)if(g.test(d)){b.label===d&&(b.highlightIndex=[[0,e]]),c.push(b);break}}),c):[]},stringMatch:function(a,b){b=b||"";var c=[],d=b.length;return d?(f.each(a,function(a,d){for(var f,g=[d.value].concat(d.alias);f=g.shift();)if(f.indexOf(b)>-1){d.label===f&&(d.highlightIndex=e(f,b)),c.push(d);break}}),c):[]}};c.exports=g;var h=/(\[|\[|\]|\^|\$|\||\(|\)|\{|\}|\+|\*|\?|\\)/g}),define("arale/autocomplete/1.3.0/input",["$","arale/base/1.1.1/base","arale/class/1.1.0/class","arale/events/1.1.0/events"],function(a,b,c){function d(a,b){return function(){a.apply(b,arguments)}}function e(a,b){return a=(a||"").replace(/^\s*/g,"").replace(/\s{2,}/g," "),b=(b||"").replace(/^\s*/g,"").replace(/\s{2,}/g," "),a===b}function f(a){return a.charAt(0).toUpperCase()+a.substring(1)}var g=a("$"),h=a("arale/base/1.1.1/base"),i=/\bMSIE [6789]\.0\b/.test(navigator.userAgent),j={9:"tab",27:"esc",37:"left",39:"right",13:"enter",38:"up",40:"down"},k=h.extend({attrs:{element:{value:null,setter:function(a){return g(a)}},query:null,delay:100},initialize:function(){k.superclass.initialize.apply(this,arguments),this._bindEvents(),this.set("query",this.getValue())},focus:function(){this.get("element").focus()},getValue:function(){return this.get("element").val()},setValue:function(a,b){this.get("element").val(a),!b&&this._change()},destroy:function(){k.superclass.destroy.call(this)},_bindEvents:function(){var a,b=this.get("element");if(b.attr("autocomplete","off").on("focus.autocomplete",d(this._handleFocus,this)).on("blur.autocomplete",d(this._handleBlur,this)).on("keydown.autocomplete",d(this._handleKeydown,this)),i){var c=this,e=["keydown.autocomplete","keypress.autocomplete","cut.autocomplete","paste.autocomplete"].join(" ");b.on(e,d(function(b){j[b.which]||(clearTimeout(a),a=setTimeout(function(){c._change.call(c,b)},this.get("delay")))},this))}else b.on("input.autocomplete",d(this._change,this))},_change:function(){var a=this.getValue(),b=this.get("query"),c=e(b,a),d=c?a.length!==b.length:!1;d&&this.trigger("whitespaceChanged",b),c||(this.set("query",a),this.trigger("queryChanged",a,b))},_handleFocus:function(a){this.trigger("focus",a)},_handleBlur:function(a){this.trigger("blur",a)},_handleKeydown:function(a){var b=j[a.which];if(b){var c="key"+f(b);this.trigger(a.type=c,a)}}});c.exports=k}),define("arale/autocomplete/1.3.0/autocomplete.handlebars",["gallery/handlebars/1.0.2/runtime"],function(a,b,c){var d=a("gallery/handlebars/1.0.2/runtime"),e=d.template;c.exports=e(function(a,b,c,d,e){function f(a,b,d){var e,f,h,i="";return i+='\n
  • \n \n ',h={hash:{parent:d},inverse:k.noop,fn:k.program(2,g,b),data:b},e=c.include,f=e?e.call(a,h):n.call(a,"include",h),(f||0===f)&&(i+=f),i+="\n \n
  • \n "}function g(a,b){var e;return e=k.invokePartial(d.html,"html",a,c,d,b),e||0===e?e:""}this.compilerInfo=[3,">= 1.0.0-rc.4"],c=c||{};for(var h in a.helpers)c[h]=c[h]||a.helpers[h];d=d||a.partials,e=e||{};var i,j="",k=this,l="function",m=this.escapeExpression,n=c.helperMissing;return j+='
    \n
    \n ',i=k.invokePartial(d.header,"header",b,c,d,e),(i||0===i)&&(j+=i),j+='\n
      \n ',i=c.each.call(b,b.items,{hash:{},inverse:k.noop,fn:k.programWithDepth(1,f,e,b),data:e}),(i||0===i)&&(j+=i),j+="\n
    \n ",i=k.invokePartial(d.footer,"footer",b,c,d,e),(i||0===i)&&(j+=i),j+="\n
    \n
    \n"})}); diff --git a/package.json b/package.json index fc64b6d..4e032d0 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { - "family": "arale", - "name": "autocomplete", - "version": "1.3.0", + "name": "arale-autocomplete", + "version": "1.4.0", "description": "自动补全组件", - "keywords": ["widget"], + "keywords": [ + "widget" + ], "homepage": "http://aralejs.org/autocomplete/", "author": "popomore ", "maintainers": [ @@ -19,18 +20,24 @@ }, "license": "MIT", "spm": { - "alias": { - "$": "$", - "templatable": "arale/templatable/0.9.2/templatable", - "base": "arale/base/1.1.1/base", - "overlay": "arale/overlay/1.1.2/overlay" + "main": "autocomplete.js", + "dependencies": { + "jquery": "1.7.2", + "arale-templatable": "0.10.0", + "arale-base": "1.2.0", + "arale-overlay": "1.2.0", + "handlebars-runtime": "1.3.0" + }, + "devDependencies": { + "alice-select": "1.1.0", + "expect.js": "0.3.1", + "sinon": "1.6.0" }, - "devAlias": { - "select.css": "alice/select/1.0.1/select.css" + "engines": { + "seajs": "2.2.1", + "seajs-text": "1.1.0" }, - "output": [ - "autocomplete.js" - ] + "buildArgs": "--ignore jquery" }, "scripts": { "test": "make test" diff --git a/src/autocomplete.js b/src/autocomplete.js index 28c0e96..c670085 100644 --- a/src/autocomplete.js +++ b/src/autocomplete.js @@ -1,505 +1,487 @@ -define(function(require, exports, module) { - var $ = require('$'); - var Overlay = require('overlay'); - var Templatable = require('templatable'); - var DataSource = require('./data-source'); - var Filter = require('./filter'); - var Input = require('./input'); - - var IE678 = /\bMSIE [678]\.0\b/.test(navigator.userAgent); - var template = require('./autocomplete.handlebars'); - - var AutoComplete = Overlay.extend({ - - Implements: Templatable, - - attrs: { - // 触发元素 - trigger: null, - classPrefix: 'ui-select', - align: { - baseXY: [0, '100%'] - }, - submitOnEnter: true, // 回车是否会提交表单 - dataSource: { //数据源,支持 Array, URL, Object, Function - value: [], - getter: function(val) { - var that = this; - if ($.isFunction(val)) { - return function() { - val.apply(that, arguments); - }; - } - return val; - } - }, - locator: 'data', - // 输出过滤 - filter: null, - disabled: false, - selectFirst: false, - delay: 100, - // 以下为模板相关 - model: { - value: { - items: [] - }, - getter: function(val) { - val.classPrefix || (val.classPrefix = this.get('classPrefix')); - return val; - } - }, - template: template, - footer: '', - header: '', - html: '{{{label}}}', - // 以下仅为组件使用 - selectedIndex: null, - data: [] - }, - - events: { - 'mousedown [data-role=items]': '_handleMouseDown', - 'click [data-role=item]': '_handleSelection', - 'mouseenter [data-role=item]': '_handleMouseMove', - 'mouseleave [data-role=item]': '_handleMouseMove' +var $ = require('jquery'); +var Overlay = require('arale-overlay'); +var Templatable = require('arale-templatable'); +var DataSource = require('./data-source'); +var Filter = require('./filter'); +var Input = require('./input'); + +var IE678 = /\bMSIE [678]\.0\b/.test(navigator.userAgent); +var template = require('./autocomplete.handlebars'); + +var AutoComplete = Overlay.extend({ + + Implements: Templatable, + + attrs: { + // 触发元素 + trigger: null, + classPrefix: 'ui-select', + align: { + baseXY: [0, '100%'] }, - - templateHelpers: { - // 将匹配的高亮文字加上 hl 的样式 - highlightItem: highlightItem, - include: include + submitOnEnter: true, + // 回车是否会提交表单 + dataSource: { //数据源,支持 Array, URL, Object, Function + value: [], + getter: function (val) { + var that = this; + if ($.isFunction(val)) { + return function () { + val.apply(that, arguments); + }; + } + return val; + } }, - - parseElement: function() { - var that = this; - this.templatePartials || (this.templatePartials = {}); - $.each(['header', 'footer', 'html'], function( index, item ) { - that.templatePartials[item] = that.get(item); - }); - AutoComplete.superclass.parseElement.call(this); + locator: 'data', + // 输出过滤 + filter: null, + disabled: false, + selectFirst: false, + delay: 100, + // 以下为模板相关 + model: { + value: { + items: [] + }, + getter: function (val) { + val.classPrefix || (val.classPrefix = this.get('classPrefix')); + return val; + } }, - - setup: function() { - AutoComplete.superclass.setup.call(this); - - this._isOpen = false; - this._initInput(); // 初始化输入框 - this._initDataSource(); // 初始化数据源 - this._initFilter(); // 初始化过滤器 - this._bindHandle(); // 绑定事件 - this._blurHide([$(this.get('trigger'))]); - this._tweakAlignDefaultValue(); - - this.on('indexChanged', function(index) { - // scroll current item into view - //this.currentItem.scrollIntoView(); - var containerHeight = parseInt(this.get('height'), 10); - if (!containerHeight) return; - - var itemHeight = this.items.parent().height() / this.items.length, + template: template, + footer: '', + header: '', + html: '{{{label}}}', + // 以下仅为组件使用 + selectedIndex: null, + data: [] + }, + + events: { + 'mousedown [data-role=items]': '_handleMouseDown', + 'click [data-role=item]': '_handleSelection', + 'mouseenter [data-role=item]': '_handleMouseMove', + 'mouseleave [data-role=item]': '_handleMouseMove' + }, + + templateHelpers: { + // 将匹配的高亮文字加上 hl 的样式 + highlightItem: highlightItem, + include: include + }, + + parseElement: function () { + var that = this; + this.templatePartials || (this.templatePartials = {}); + $.each(['header', 'footer', 'html'], function (index, item) { + that.templatePartials[item] = that.get(item); + }); + AutoComplete.superclass.parseElement.call(this); + }, + + setup: function () { + AutoComplete.superclass.setup.call(this); + + this._isOpen = false; + this._initInput(); // 初始化输入框 + this._initDataSource(); // 初始化数据源 + this._initFilter(); // 初始化过滤器 + this._bindHandle(); // 绑定事件 + this._blurHide([$(this.get('trigger'))]); + this._tweakAlignDefaultValue(); + + this.on('indexChanged', function (index) { + // scroll current item into view + //this.currentItem.scrollIntoView(); + var containerHeight = parseInt(this.get('height'), 10); + if (!containerHeight) return; + + var itemHeight = this.items.parent().height() / this.items.length, itemTop = Math.max(0, itemHeight * (index + 1) - containerHeight); - this.element.children().scrollTop(itemTop); - }); - }, - - show: function() { - this._isOpen = true; - // 无数据则不显示 - if (this._isEmpty()) return; - AutoComplete.superclass.show.call(this); - }, + this.element.children().scrollTop(itemTop); + }); + }, + + show: function () { + this._isOpen = true; + // 无数据则不显示 + if (this._isEmpty()) return; + AutoComplete.superclass.show.call(this); + }, + + hide: function () { + // 隐藏的时候取消请求或回调 + if (this._timeout) clearTimeout(this._timeout); + this.dataSource.abort(); + this._hide(); + }, + + destroy: function () { + this._clear(); + if (this.input) { + this.input.destroy(); + this.input = null; + } + AutoComplete.superclass.destroy.call(this); + }, - hide: function() { - // 隐藏的时候取消请求或回调 - if (this._timeout) clearTimeout(this._timeout); - this.dataSource.abort(); - this._hide(); - }, - destroy: function() { - this._clear(); - if (this.input) { - this.input.destroy(); - this.input = null; + // Public Methods + // -------------- + selectItem: function (index) { + if (this.items) { + if (index && this.items.length > index && index >= -1) { + this.set('selectedIndex', index); } - AutoComplete.superclass.destroy.call(this); - }, + this._handleSelection(); + } + }, + setInputValue: function (val) { + this.input.setValue(val); + }, - // Public Methods - // -------------- + // Private Methods + // --------------- - selectItem: function(index) { - if (this.items) { - if (index && - this.items.length > index && index >= -1) { - this.set('selectedIndex', index); - } - this._handleSelection(); - } - }, + // 数据源返回,过滤数据 + _filterData: function (data) { + var filter = this.get('filter'), + locator = this.get('locator'); - setInputValue: function(val) { - this.input.setValue(val); - }, + // 获取目标数据 + data = locateResult(locator, data); - // Private Methods - // --------------- + // 进行过滤 + data = filter.call(this, normalize(data), this.input.get('query')); + this.set('data', data); + }, - // 数据源返回,过滤数据 - _filterData: function(data) { - var filter = this.get('filter'), - locator = this.get('locator'); + // 通过数据渲染模板 + _onRenderData: function (data) { + data || (data = []); - // 获取目标数据 - data = locateResult(locator, data); + // 渲染下拉 + this.set('model', { + items: data, + query: this.input.get('query'), + length: data.length + }); - // 进行过滤 - data = filter.call(this, normalize(data), this.input.get('query')); + this.renderPartial(); - this.set('data', data); - }, + // 初始化下拉的状态 + this.items = this.$('[data-role=items]').children(); - // 通过数据渲染模板 - _onRenderData: function(data) { - data || (data = []); + if (this.get('selectFirst')) { + this.set('selectedIndex', 0); + } - // 渲染下拉 - this.set('model', { - items: data, - query: this.input.get('query'), - length: data.length - }); + // 选中后会修改 input 的值并触发下一次渲染,但第二次渲染的结果不应该显示出来。 + this._isOpen && this.show(); + }, - this.renderPartial(); + // 键盘控制上下移动 + _onRenderSelectedIndex: function (index) { + var hoverClass = this.get('classPrefix') + '-item-hover'; + this.items && this.items.removeClass(hoverClass); - // 初始化下拉的状态 - this.items = this.$('[data-role=items]').children(); + // -1 什么都不选 + if (index === -1) return; - if (this.get('selectFirst')) { - this.set('selectedIndex', 0); - } + this.items.eq(index).addClass(hoverClass); + this.trigger('indexChanged', index, this.lastIndex); + this.lastIndex = index; + }, - // 选中后会修改 input 的值并触发下一次渲染,但第二次渲染的结果不应该显示出来。 - this._isOpen && this.show(); - }, + // 初始化 + // ------------ + _initDataSource: function () { + this.dataSource = new DataSource({ + source: this.get('dataSource') + }); + }, - // 键盘控制上下移动 - _onRenderSelectedIndex: function(index) { - var hoverClass = this.get('classPrefix') + '-item-hover'; - this.items && this.items.removeClass(hoverClass); + _initInput: function () { + this.input = new Input({ + element: this.get('trigger'), + delay: this.get('delay') + }); + }, - // -1 什么都不选 - if (index === -1) return; + _initFilter: function () { + var filter = this.get('filter'); + filter = initFilter(filter, this.dataSource); + this.set('filter', filter); + }, - this.items.eq(index).addClass(hoverClass); - this.trigger('indexChanged', index, this.lastIndex); - this.lastIndex = index; - }, + // 事件绑定 + // ------------ + _bindHandle: function () { + this.dataSource.on('data', this._filterData, this); - // 初始化 - // ------------ + this.input.on('blur', this.hide, this).on('focus', this._handleFocus, this).on('keyEnter', this._handleSelection, this).on('keyEsc', this.hide, this).on('keyUp keyDown', this.show, this).on('keyUp keyDown', this._handleStep, this).on('queryChanged', this._clear, this).on('queryChanged', this._hide, this).on('queryChanged', this._handleQueryChange, this).on('queryChanged', this.show, this); - _initDataSource: function() { - this.dataSource = new DataSource({ - source: this.get('dataSource') - }); - }, + this.after('hide', function () { + this.set('selectedIndex', -1); + }); - _initInput: function() { - this.input = new Input({ - element: this.get('trigger'), - delay: this.get('delay') + // 选中后隐藏浮层 + this.on('itemSelected', function () { + this._hide(); + }); + }, + + // 选中的处理器 + // 1. 鼠标点击触发 + // 2. 回车触发 + // 3. selectItem 触发 + _handleSelection: function (e) { + var isMouse = e ? e.type === 'click' : false; + var index = isMouse ? this.items.index(e.currentTarget) : this.get('selectedIndex'); + var item = this.items.eq(index); + var data = this.get('data')[index]; + + if (index >= 0 && item) { + this.input.setValue(data.label); + this.set('selectedIndex', index, { + silent: true }); - }, - _initFilter: function() { - var filter = this.get('filter'); - filter = initFilter(filter, this.dataSource); - this.set('filter', filter); - }, - - // 事件绑定 - // ------------ - - _bindHandle: function() { - this.dataSource - .on('data', this._filterData, this); - - this.input - .on('blur', this.hide, this) - .on('focus', this._handleFocus, this) - .on('keyEnter', this._handleSelection, this) - .on('keyEsc', this.hide, this) - .on('keyUp keyDown', this.show, this) - .on('keyUp keyDown', this._handleStep, this) - .on('queryChanged', this._clear, this) - .on('queryChanged', this._hide, this) - .on('queryChanged', this._handleQueryChange, this) - .on('queryChanged', this.show, this); - - this.after('hide', function() { - this.set('selectedIndex', -1); - }); + // 是否阻止回车提交表单 + if (e && !isMouse && !this.get('submitOnEnter')) e.preventDefault(); - // 选中后隐藏浮层 - this.on('itemSelected', function() { - this._hide(); + this.trigger('itemSelected', data, item); + } + }, + + _handleFocus: function () { + this._isOpen = true; + }, + + _handleMouseMove: function (e) { + var hoverClass = this.get('classPrefix') + '-item-hover'; + this.items.removeClass(hoverClass); + if (e.type === 'mouseenter') { + var index = this.items.index(e.currentTarget); + this.set('selectedIndex', index, { + silent: true }); - }, - - // 选中的处理器 - // 1. 鼠标点击触发 - // 2. 回车触发 - // 3. selectItem 触发 - _handleSelection: function(e) { - var isMouse = e ? e.type === 'click' : false; - var index = isMouse ? this.items.index(e.currentTarget) : this.get('selectedIndex'); - var item = this.items.eq(index); - var data = this.get('data')[index]; - - if (index >= 0 && item) { - this.input.setValue(data.label); - this.set('selectedIndex', index, {silent: true}); - - // 是否阻止回车提交表单 - if (e && !isMouse && !this.get('submitOnEnter')) e.preventDefault(); - - this.trigger('itemSelected', data, item); - } - }, - - _handleFocus: function() { - this._isOpen = true; - }, - - _handleMouseMove: function(e) { - var hoverClass = this.get('classPrefix') + '-item-hover'; - this.items.removeClass(hoverClass); - if (e.type === 'mouseenter') { - var index = this.items.index(e.currentTarget); - this.set('selectedIndex', index, { - silent: true - }); - this.items.eq(index).addClass(hoverClass); - } - }, - - _handleMouseDown: function(e) { - if (IE678) { - var trigger = this.input.get('element')[0]; - trigger.onbeforedeactivate = function() { - window.event.returnValue = false; - trigger.onbeforedeactivate = null; - }; + this.items.eq(index).addClass(hoverClass); + } + }, + + _handleMouseDown: function (e) { + if (IE678) { + var trigger = this.input.get('element')[0]; + trigger.onbeforedeactivate = function () { + window.event.returnValue = false; + trigger.onbeforedeactivate = null; + }; + } + e.preventDefault(); + }, + + _handleStep: function (e) { + e.preventDefault(); + this.get('visible') && this._step(e.type === 'keyUp' ? -1 : 1); + }, + + _handleQueryChange: function (val, prev) { + if (this.get('disabled')) return; + + this.dataSource.abort(); + this.dataSource.getData(val); + }, + + // 选项上下移动 + _step: function (direction) { + var currentIndex = this.get('selectedIndex'); + if (direction === -1) { // 反向 + if (currentIndex > -1) { + this.set('selectedIndex', currentIndex - 1); + } else { + this.set('selectedIndex', this.items.length - 1); } - e.preventDefault(); - }, - - _handleStep: function(e) { - e.preventDefault(); - this.get('visible') && - this._step(e.type === 'keyUp' ? -1 : 1); - }, - - _handleQueryChange: function(val, prev) { - if (this.get('disabled')) return; - - this.dataSource.abort(); - this.dataSource.getData(val); - }, - - // 选项上下移动 - _step: function(direction) { - var currentIndex = this.get('selectedIndex'); - if (direction === -1) { // 反向 - if (currentIndex > -1) { - this.set('selectedIndex', currentIndex - 1); - } else { - this.set('selectedIndex', this.items.length - 1); - } - } else if (direction === 1) { // 正向 - if (currentIndex < this.items.length - 1) { - this.set('selectedIndex', currentIndex + 1); - } else { - this.set('selectedIndex', -1); - } + } else if (direction === 1) { // 正向 + if (currentIndex < this.items.length - 1) { + this.set('selectedIndex', currentIndex + 1); + } else { + this.set('selectedIndex', -1); } - }, - - _clear: function() { - this.$('[data-role=items]').empty(); - this.set('selectedIndex', -1); - delete this.items; - delete this.lastIndex; - }, - - _hide: function() { - this._isOpen = false; - AutoComplete.superclass.hide.call(this); - }, - - _isEmpty: function() { - var data = this.get('data'); - return !(data && data.length > 0); - }, - - // 调整 align 属性的默认值 - _tweakAlignDefaultValue: function() { - var align = this.get('align'); - align.baseElement = this.get('trigger'); - this.set('align', align); } - }); - - module.exports = AutoComplete; - - function isString(str) { - return Object.prototype.toString.call(str) === '[object String]'; - } - - function isObject(obj) { - return Object.prototype.toString.call(obj) === '[object Object]'; + }, + + _clear: function () { + this.$('[data-role=items]').empty(); + this.set('selectedIndex', -1); + delete this.items; + delete this.lastIndex; + }, + + _hide: function () { + this._isOpen = false; + AutoComplete.superclass.hide.call(this); + }, + + _isEmpty: function () { + var data = this.get('data'); + return !(data && data.length > 0); + }, + + // 调整 align 属性的默认值 + _tweakAlignDefaultValue: function () { + var align = this.get('align'); + align.baseElement = this.get('trigger'); + this.set('align', align); } +}); - // 通过 locator 找到 data 中的某个属性的值 - // 1. locator 支持 function,函数返回值为结果 - // 2. locator 支持 string,而且支持点操作符寻址 - // data { - // a: { - // b: 'c' - // } - // } - // locator 'a.b' - // 最后的返回值为 c - - function locateResult(locator, data) { - if (locator) { - if ($.isFunction(locator)) { - return locator.call(this, data); - } else if (!$.isArray(data) && isString(locator)) { - var s = locator.split('.'), +module.exports = AutoComplete; + +function isString(str) { + return Object.prototype.toString.call(str) === '[object String]'; +} + +function isObject(obj) { + return Object.prototype.toString.call(obj) === '[object Object]'; +} + +// 通过 locator 找到 data 中的某个属性的值 +// 1. locator 支持 function,函数返回值为结果 +// 2. locator 支持 string,而且支持点操作符寻址 +// data { +// a: { +// b: 'c' +// } +// } +// locator 'a.b' +// 最后的返回值为 c + +function locateResult(locator, data) { + if (locator) { + if ($.isFunction(locator)) { + return locator.call(this, data); + } else if (!$.isArray(data) && isString(locator)) { + var s = locator.split('.'), p = data; - while (s.length) { - var v = s.shift(); - if (!p[v]) { - break; - } - p = p[v]; + while (s.length) { + var v = s.shift(); + if (!p[v]) { + break; } - return p; + p = p[v]; } + return p; } - return data; - } - - // 标准格式,不匹配则忽略 - // - // { - // label: '', 显示的字段 - // value: '', 匹配的字段 - // alias: [] 其他匹配的字段 - // } - - function normalize(data) { - var result = []; - $.each(data, function(index, item) { - if (isString(item)) { - result.push({ - label: item, - value: item, - alias: [] - }); - } else if (isObject(item)) { - if (!item.value && !item.label) return; - item.value || (item.value = item.label); - item.label || (item.label = item.value); - item.alias || (item.alias = []); - result.push(item); - } - }); - return result; } - - // 初始化 filter - // 支持的格式 - // 1. null: 使用默认的 startsWith - // 2. string: 从 Filter 中找,如果不存在则用 default - // 3. function: 自定义 - - function initFilter(filter, dataSource) { - // 字符串 - if (isString(filter)) { - // 从组件内置的 FILTER 获取 - if (Filter[filter]) { - filter = Filter[filter]; - } else { - filter = Filter['default']; - } + return data; +} + +// 标准格式,不匹配则忽略 +// +// { +// label: '', 显示的字段 +// value: '', 匹配的字段 +// alias: [] 其他匹配的字段 +// } + +function normalize(data) { + var result = []; + $.each(data, function (index, item) { + if (isString(item)) { + result.push({ + label: item, + value: item, + alias: [] + }); + } else if (isObject(item)) { + if (!item.value && !item.label) return; + item.value || (item.value = item.label); + item.label || (item.label = item.value); + item.alias || (item.alias = []); + result.push(item); } - // 非函数为默认值 - else if (!$.isFunction(filter)) { - // 异步请求的时候不需要过滤器 - if (dataSource.get('type') === 'url') { - filter = Filter['default']; - } else { - filter = Filter['startsWith']; - } + }); + return result; +} + +// 初始化 filter +// 支持的格式 +// 1. null: 使用默认的 startsWith +// 2. string: 从 Filter 中找,如果不存在则用 default +// 3. function: 自定义 + +function initFilter(filter, dataSource) { + // 字符串 + if (isString(filter)) { + // 从组件内置的 FILTER 获取 + if (Filter[filter]) { + filter = Filter[filter]; + } else { + filter = Filter['default']; + } + } + // 非函数为默认值 + else if (!$.isFunction(filter)) { + // 异步请求的时候不需要过滤器 + if (dataSource.get('type') === 'url') { + filter = Filter['default']; + } else { + filter = Filter['startsWith']; } - return filter; } + return filter; +} - function include(options) { - var context = {}; - - mergeContext(this); - mergeContext(options.hash); - return options.fn(context); +function include(options) { + var context = {}; - function mergeContext(obj) { - for(var k in obj) context[k] = obj[k]; - } + mergeContext(this); + mergeContext(options.hash); + return options.fn(context); + + function mergeContext(obj) { + for (var k in obj) context[k] = obj[k]; } +} - function highlightItem(label) { - var index = this.highlightIndex, +function highlightItem(label) { + var index = this.highlightIndex, classPrefix = this.parent ? this.parent.classPrefix : '', cursor = 0, v = label || this.label || '', h = ''; - if ($.isArray(index)) { - for (var i = 0, l = index.length; i < l; i++) { - var j = index[i], + if ($.isArray(index)) { + for (var i = 0, l = index.length; i < l; i++) { + var j = index[i], start, length; - if ($.isArray(j)) { - start = j[0]; - length = j[1] - j[0]; - } else { - start = j; - length = 1; - } + if ($.isArray(j)) { + start = j[0]; + length = j[1] - j[0]; + } else { + start = j; + length = 1; + } - if (start > cursor) { - h += v.substring(cursor, start); - } - if (start < v.length) { - var className = classPrefix ? ('class="' + classPrefix + '-item-hl"') : ''; - h += '' + - v.substr(start, length) + - ''; - } - cursor = start + length; - if (cursor >= v.length) { - break; - } + if (start > cursor) { + h += v.substring(cursor, start); } - if (v.length > cursor) { - h += v.substring(cursor, v.length); + if (start < v.length) { + var className = classPrefix ? ('class="' + classPrefix + '-item-hl"') : ''; + h += '' + v.substr(start, length) + ''; } - return h; + cursor = start + length; + if (cursor >= v.length) { + break; + } + } + if (v.length > cursor) { + h += v.substring(cursor, v.length); } - return v; + return h; } -}); + return v; +} \ No newline at end of file diff --git a/src/data-source.js b/src/data-source.js index 5be98b1..c625539 100644 --- a/src/data-source.js +++ b/src/data-source.js @@ -1,127 +1,119 @@ -define(function(require, exports, module) { - var Base = require('base'); - var $ = require('$'); - - var DataSource = Base.extend({ - - attrs: { - source: null, - type: 'array' - }, - - initialize: function(config) { - DataSource.superclass.initialize.call(this, config); - - // 每次发送请求会将 id 记录到 callbacks 中,返回后会从中删除 - // 如果 abort 会清空 callbacks,之前的请求结果都不会执行 - this.id = 0; - this.callbacks = []; - - var source = this.get('source'); - if (isString(source)) { - this.set('type', 'url'); - } else if ($.isArray(source)) { - this.set('type', 'array'); - } else if ($.isPlainObject(source)) { - this.set('type', 'object'); - } else if ($.isFunction(source)) { - this.set('type', 'function'); - } else { - throw new Error('Source Type Error'); - } - }, +var Base = require('arale-base'); +var $ = require('jquery'); + +var DataSource = Base.extend({ + + attrs: { + source: null, + type: 'array' + }, + + initialize: function (config) { + DataSource.superclass.initialize.call(this, config); + + // 每次发送请求会将 id 记录到 callbacks 中,返回后会从中删除 + // 如果 abort 会清空 callbacks,之前的请求结果都不会执行 + this.id = 0; + this.callbacks = []; + + var source = this.get('source'); + if (isString(source)) { + this.set('type', 'url'); + } else if ($.isArray(source)) { + this.set('type', 'array'); + } else if ($.isPlainObject(source)) { + this.set('type', 'object'); + } else if ($.isFunction(source)) { + this.set('type', 'function'); + } else { + throw new Error('Source Type Error'); + } + }, - getData: function(query) { - return this['_get' + capitalize(this.get('type') || '') + 'Data'](query); - }, + getData: function (query) { + return this['_get' + capitalize(this.get('type') || '') + 'Data'](query); + }, - abort: function() { - this.callbacks = []; - }, + abort: function () { + this.callbacks = []; + }, - // 完成数据请求,getData => done - _done: function(data) { - this.trigger('data', data); - }, + // 完成数据请求,getData => done + _done: function (data) { + this.trigger('data', data); + }, - _getUrlData: function(query) { - var that = this, + _getUrlData: function (query) { + var that = this, options; - var obj = { - query: query ? encodeURIComponent(query) : '', - timestamp: new Date().getTime() + var obj = { + query: query ? encodeURIComponent(query) : '', + timestamp: new Date().getTime() + }; + var url = this.get('source').replace(/\{\{(.*?)\}\}/g, function (all, match) { + return obj[match]; + }); + + var callbackId = 'callback_' + this.id++; + this.callbacks.push(callbackId); + + if (/^(https?:\/\/)/.test(url)) { + options = { + dataType: 'jsonp' }; - var url = this.get('source') - .replace(/\{\{(.*?)\}\}/g, function(all, match) { - return obj[match]; - }); - - var callbackId = 'callback_' + this.id++; - this.callbacks.push(callbackId); - - if (/^(https?:\/\/)/.test(url)) { - options = { - dataType: 'jsonp' - }; - } else { - options = { - dataType: 'json' - }; - } - $.ajax(url, options) - .success(function(data) { - if ($.inArray(callbackId, that.callbacks) > -1) { - delete that.callbacks[callbackId]; - that._done(data); - } - }) - .error(function() { - if ($.inArray(callbackId, that.callbacks) > -1) { - delete that.callbacks[callbackId]; - that._done({}); - } - }); - }, - - _getArrayData: function() { - var source = this.get('source'); - this._done(source); - return source; - }, - - _getObjectData: function() { - var source = this.get('source'); - this._done(source); - return source; - }, - - _getFunctionData: function(query) { - var that = this, - func = this.get('source'); - // 如果返回 false 可阻止执行 - - function done(data) { + } else { + options = { + dataType: 'json' + }; + } + $.ajax(url, options).success(function (data) { + if ($.inArray(callbackId, that.callbacks) > -1) { + delete that.callbacks[callbackId]; that._done(data); } - var data = func.call(this, query, done); - if (data) { - this._done(data); + }).error(function () { + if ($.inArray(callbackId, that.callbacks) > -1) { + delete that.callbacks[callbackId]; + that._done({}); } + }); + }, + + _getArrayData: function () { + var source = this.get('source'); + this._done(source); + return source; + }, + + _getObjectData: function () { + var source = this.get('source'); + this._done(source); + return source; + }, + + _getFunctionData: function (query) { + var that = this, + func = this.get('source'); + // 如果返回 false 可阻止执行 + + function done(data) { + that._done(data); } - }); + var data = func.call(this, query, done); + if (data) { + this._done(data); + } + } +}); - module.exports = DataSource; +module.exports = DataSource; - function isString(str) { - return Object.prototype.toString.call(str) === '[object String]'; - } +function isString(str) { + return Object.prototype.toString.call(str) === '[object String]'; +} - function capitalize(str) { - return str.replace( - /^([a-z])/, - function(f, m) { - return m.toUpperCase(); - } - ); - } -}); +function capitalize(str) { + return str.replace(/^([a-z])/, function (f, m) { + return m.toUpperCase(); + }); +} \ No newline at end of file diff --git a/src/filter.js b/src/filter.js index 5130163..231849f 100644 --- a/src/filter.js +++ b/src/filter.js @@ -1,93 +1,91 @@ -define(function(require, exports, module) { - var $ = require('$'); +var $ = require('jquery'); - var Filter = { - 'default': function(data) { - return data; - }, +var Filter = { + 'default': function (data) { + return data; + }, - 'startsWith': function(data, query) { - query = query || ''; - var result = [], + 'startsWith': function (data, query) { + query = query || ''; + var result = [], l = query.length, reg = new RegExp('^' + escapeKeyword(query)); - if (!l) return []; + if (!l) return []; - $.each(data, function(index, item) { - var a, matchKeys = [item.value].concat(item.alias); + $.each(data, function (index, item) { + var a, matchKeys = [item.value].concat(item.alias); - // 匹配 value 和 alias 中的 - while (a = matchKeys.shift()) { - if (reg.test(a)) { - // 匹配和显示相同才有必要高亮 - if (item.label === a) { - item.highlightIndex = [ - [0, l] - ]; - } - result.push(item); - break; + // 匹配 value 和 alias 中的 + while (a = matchKeys.shift()) { + if (reg.test(a)) { + // 匹配和显示相同才有必要高亮 + if (item.label === a) { + item.highlightIndex = [ + [0, l] + ]; } + result.push(item); + break; } - }); - return result; - }, + } + }); + return result; + }, - 'stringMatch': function(data, query) { - query = query || ''; - var result = [], + 'stringMatch': function (data, query) { + query = query || ''; + var result = [], l = query.length; - if (!l) return []; + if (!l) return []; - $.each(data, function(index, item) { - var a, matchKeys = [item.value].concat(item.alias); + $.each(data, function (index, item) { + var a, matchKeys = [item.value].concat(item.alias); - // 匹配 value 和 alias 中的 - while (a = matchKeys.shift()) { - if (a.indexOf(query) > -1) { - // 匹配和显示相同才有必要高亮 - if (item.label === a) { - item.highlightIndex = stringMatch(a, query); - } - result.push(item); - break; + // 匹配 value 和 alias 中的 + while (a = matchKeys.shift()) { + if (a.indexOf(query) > -1) { + // 匹配和显示相同才有必要高亮 + if (item.label === a) { + item.highlightIndex = stringMatch(a, query); } + result.push(item); + break; } - }); - return result; - } - }; + } + }); + return result; + } +}; - module.exports = Filter; +module.exports = Filter; - // 转义正则关键字 - var keyword = /(\[|\[|\]|\^|\$|\||\(|\)|\{|\}|\+|\*|\?|\\)/g; +// 转义正则关键字 +var keyword = /(\[|\[|\]|\^|\$|\||\(|\)|\{|\}|\+|\*|\?|\\)/g; - function escapeKeyword(str) { - return (str || '').replace(keyword, '\\$1'); - } +function escapeKeyword(str) { + return (str || '').replace(keyword, '\\$1'); +} - function stringMatch(matchKey, query) { - var r = [], +function stringMatch(matchKey, query) { + var r = [], a = matchKey.split(''); - var queryIndex = 0, + var queryIndex = 0, q = query.split(''); - for (var i = 0, l = a.length; i < l; i++) { - var v = a[i]; - if (v === q[queryIndex]) { - if (queryIndex === q.length - 1) { - r.push([i - q.length + 1, i + 1]); - queryIndex = 0; - continue; - } - queryIndex++; - } else { + for (var i = 0, l = a.length; i < l; i++) { + var v = a[i]; + if (v === q[queryIndex]) { + if (queryIndex === q.length - 1) { + r.push([i - q.length + 1, i + 1]); queryIndex = 0; + continue; } + queryIndex++; + } else { + queryIndex = 0; } - return r; } -}); + return r; +} \ No newline at end of file diff --git a/src/input.js b/src/input.js index 44f1fbe..797b945 100644 --- a/src/input.js +++ b/src/input.js @@ -1,138 +1,127 @@ -define(function(require, exports, module) { - var $ = require('$'); - var Base = require('base'); - - var lteIE9 = /\bMSIE [6789]\.0\b/.test(navigator.userAgent); - var specialKeyCodeMap = { - 9: 'tab', - 27: 'esc', - 37: 'left', - 39: 'right', - 13: 'enter', - 38: 'up', - 40: 'down' - }; - - var Input = Base.extend({ - - attrs: { - element: { - value: null, - setter: function(val) { - return $(val); - } - }, - query: null, - delay: 100 - }, - - initialize: function() { - Input.superclass.initialize.apply(this, arguments); - - // bind events - this._bindEvents(); - - // init query - this.set('query', this.getValue()); - }, - - focus: function() { - this.get('element').focus(); - }, - - getValue: function() { - return this.get('element').val(); - }, - - setValue: function(val, silent) { - this.get('element').val(val); - !silent && this._change(); - }, - - destroy: function() { - Input.superclass.destroy.call(this); - }, - - _bindEvents: function() { - var timer, input = this.get('element'); - - input - .attr('autocomplete', 'off') - .on('focus.autocomplete', wrapFn(this._handleFocus, this)) - .on('blur.autocomplete', wrapFn(this._handleBlur, this)) - .on('keydown.autocomplete', wrapFn(this._handleKeydown, this)); - - // IE678 don't support input event - // IE 9 does not fire an input event when the user removes characters from input filled by keyboard, cut, or drag operations. - if (!lteIE9) { - input.on('input.autocomplete', wrapFn(this._change, this)); - } else { - var that = this, - events = [ - 'keydown.autocomplete', - 'keypress.autocomplete', - 'cut.autocomplete', - 'paste.autocomplete' - ].join(' '); - - input.on(events, wrapFn(function(e) { - if (specialKeyCodeMap[e.which]) return; - - clearTimeout(timer); - timer = setTimeout(function() { - that._change.call(that, e); - }, this.get('delay')); - }, this)); +var $ = require('jquery'); +var Base = require('arale-base'); + +var lteIE9 = /\bMSIE [6789]\.0\b/.test(navigator.userAgent); +var specialKeyCodeMap = { + 9: 'tab', + 27: 'esc', + 37: 'left', + 39: 'right', + 13: 'enter', + 38: 'up', + 40: 'down' +}; + +var Input = Base.extend({ + + attrs: { + element: { + value: null, + setter: function (val) { + return $(val); } }, + query: null, + delay: 100 + }, + + initialize: function () { + Input.superclass.initialize.apply(this, arguments); + + // bind events + this._bindEvents(); + + // init query + this.set('query', this.getValue()); + }, + + focus: function () { + this.get('element').focus(); + }, + + getValue: function () { + return this.get('element').val(); + }, + + setValue: function (val, silent) { + this.get('element').val(val); + !silent && this._change(); + }, + + destroy: function () { + Input.superclass.destroy.call(this); + }, + + _bindEvents: function () { + var timer, input = this.get('element'); + + input.attr('autocomplete', 'off').on('focus.autocomplete', wrapFn(this._handleFocus, this)).on('blur.autocomplete', wrapFn(this._handleBlur, this)).on('keydown.autocomplete', wrapFn(this._handleKeydown, this)); + + // IE678 don't support input event + // IE 9 does not fire an input event when the user removes characters from input filled by keyboard, cut, or drag operations. + if (!lteIE9) { + input.on('input.autocomplete', wrapFn(this._change, this)); + } else { + var that = this, + events = ['keydown.autocomplete', 'keypress.autocomplete', 'cut.autocomplete', 'paste.autocomplete'].join(' '); + + input.on(events, wrapFn(function (e) { + if (specialKeyCodeMap[e.which]) return; + + clearTimeout(timer); + timer = setTimeout(function () { + that._change.call(that, e); + }, this.get('delay')); + }, this)); + } + }, - _change: function() { - var newVal = this.getValue(); - var oldVal = this.get('query'); - var isSame = compare(oldVal, newVal); - var isSameExpectWhitespace = isSame ? (newVal.length !== oldVal.length) : false; + _change: function () { + var newVal = this.getValue(); + var oldVal = this.get('query'); + var isSame = compare(oldVal, newVal); + var isSameExpectWhitespace = isSame ? (newVal.length !== oldVal.length) : false; - if (isSameExpectWhitespace) { - this.trigger('whitespaceChanged', oldVal); - } - if (!isSame) { - this.set('query', newVal); - this.trigger('queryChanged', newVal, oldVal); - } - }, + if (isSameExpectWhitespace) { + this.trigger('whitespaceChanged', oldVal); + } + if (!isSame) { + this.set('query', newVal); + this.trigger('queryChanged', newVal, oldVal); + } + }, - _handleFocus: function(e) { - this.trigger('focus', e); - }, + _handleFocus: function (e) { + this.trigger('focus', e); + }, - _handleBlur: function(e) { - this.trigger('blur', e); - }, + _handleBlur: function (e) { + this.trigger('blur', e); + }, - _handleKeydown: function(e) { - var keyName = specialKeyCodeMap[e.which]; - if (keyName) { - var eventKey = 'key' + ucFirst(keyName); - this.trigger(e.type = eventKey, e); - } + _handleKeydown: function (e) { + var keyName = specialKeyCodeMap[e.which]; + if (keyName) { + var eventKey = 'key' + ucFirst(keyName); + this.trigger(e.type = eventKey, e); } - }); + } +}); - module.exports = Input; +module.exports = Input; - function wrapFn(fn, context) { - return function() { - fn.apply(context, arguments); - }; - } +function wrapFn(fn, context) { + return function () { + fn.apply(context, arguments); + }; +} - function compare(a, b) { - a = (a || '').replace(/^\s*/g, '').replace(/\s{2,}/g, ' '); - b = (b || '').replace(/^\s*/g, '').replace(/\s{2,}/g, ' '); - return a === b; - } +function compare(a, b) { + a = (a || '').replace(/^\s*/g, '').replace(/\s{2,}/g, ' '); + b = (b || '').replace(/^\s*/g, '').replace(/\s{2,}/g, ' '); + return a === b; +} - function ucFirst(str) { - return str.charAt(0).toUpperCase() + str.substring(1); - } -}); +function ucFirst(str) { + return str.charAt(0).toUpperCase() + str.substring(1); +} \ No newline at end of file diff --git a/tests/autocomplete-spec.js b/tests/autocomplete-spec.js index 720bb63..92997aa 100644 --- a/tests/autocomplete-spec.js +++ b/tests/autocomplete-spec.js @@ -1,39 +1,93 @@ -define(function(require) { - var sinon = require('sinon'); - var expect = require('expect'); - var Filter = require('filter'); - var AutoComplete = require('autocomplete'); - var $ = require('$'); - require('select.css'); +var sinon = require('sinon'); +var expect = require('expect.js'); +var Filter = require('../src/filter'); +var AutoComplete = require('../src/autocomplete'); +var $ = require('jquery'); +require('alice-select'); - Filter.test = function() { - return []; - }; +Filter.test = function () { + return []; +}; - describe('Autocomplete', function() { +describe('Autocomplete', function () { - var input, ac; + var input, ac; - beforeEach(function() { - input = $('') - .appendTo(document.body); - }); - afterEach(function() { - input.remove(); - if (ac) { - ac.destroy(); - ac = null; - } - }); + beforeEach(function () { + input = $('').appendTo(document.body); + }); + afterEach(function () { + input.remove(); + if (ac) { + ac.destroy(); + ac = null; + } + }); + + it('normal usage', function () { + ac = new AutoComplete({ + trigger: '#test', + dataSource: ['abc', 'abd', 'cbd'] + }).render(); + + ac.setInputValue('a'); + + expect(ac.get('data')).to.eql([{ + label: 'abc', + value: 'abc', + alias: [], + highlightIndex: [ + [0, 1] + ] + }, + { + label: 'abd', + value: 'abd', + alias: [], + highlightIndex: [ + [0, 1] + ] + }]); + }); + + it('should hide when empty', function () { + var input = $('#test'); + input.val('a'); + ac = new AutoComplete({ + trigger: '#test', + dataSource: ['abc', 'abd', 'cbd'] + }).render(); - it('normal usage', function() { + ac.setInputValue(''); + + expect(ac.get('visiable')).not.to.be.ok(); + expect(ac.input.getValue()).to.be(''); + }); + + it('render', function () { + ac = new AutoComplete({ + trigger: '#test', + dataSource: ['abc', 'abd', 'cbd'] + }).render(); + + ac.setInputValue('a'); + + expect(ac.items.length).to.be(2); + expect(ac.items.eq(0).text().replace(/\s/g, '')).to.be('abc'); + expect(ac.items.eq(1).text().replace(/\s/g, '')).to.be('abd'); + }); + + describe('data locator', function () { + it('should support string', function () { ac = new AutoComplete({ trigger: '#test', - dataSource: ['abc', 'abd', 'cbd'] + dataSource: { + test: ['abc', 'abd', 'cbd'] + }, + locator: 'test' }).render(); ac.setInputValue('a'); - expect(ac.get('data')).to.eql([{ label: 'abc', value: 'abc', @@ -41,7 +95,8 @@ define(function(require) { highlightIndex: [ [0, 1] ] - }, { + }, + { label: 'abd', value: 'abd', alias: [], @@ -51,549 +106,492 @@ define(function(require) { }]); }); - it('should hide when empty', function() { - var input = $('#test'); - input.val('a'); + it('should support dot string', function () { ac = new AutoComplete({ trigger: '#test', - dataSource: ['abc', 'abd', 'cbd'] + dataSource: { + test: { + more: ['abc', 'abd', 'cbd'] + } + }, + locator: 'test.more' }).render(); - ac.setInputValue(''); - - expect(ac.get('visiable')).not.to.be.ok(); - expect(ac.input.getValue()).to.be(''); + ac.setInputValue('a'); + expect(ac.get('data')).to.eql([{ + label: 'abc', + value: 'abc', + alias: [], + highlightIndex: [ + [0, 1] + ] + }, + { + label: 'abd', + value: 'abd', + alias: [], + highlightIndex: [ + [0, 1] + ] + }]); }); - it('render', function() { + it('should support function', function () { ac = new AutoComplete({ trigger: '#test', - dataSource: ['abc', 'abd', 'cbd'] + dataSource: { + test1: ['abc', 'cbd'], + test2: ['abd'] + }, + locator: function (data) { + return data.test1.concat(data.test2); + } }).render(); ac.setInputValue('a'); + expect(ac.get('data')).to.eql([{ + label: 'abc', + value: 'abc', + alias: [], + highlightIndex: [ + [0, 1] + ] + }, + { + label: 'abd', + value: 'abd', + alias: [], + highlightIndex: [ + [0, 1] + ] + }]); + }); - expect(ac.items.length).to.be(2); - expect(ac.items.eq(0).text().replace(/\s/g, '')).to.be('abc'); - expect(ac.items.eq(1).text().replace(/\s/g, '')).to.be('abd'); + it('wrong locator', function () { + ac = new AutoComplete({ + trigger: '#test', + dataSource: { + test: ['abc', 'abd', 'cbd'] + }, + locator: 'wrong' + }).render(); + + ac.setInputValue('a'); + expect(ac.get('data')).to.eql([]); }); + }); - describe('data locator', function() { - it('should support string', function() { - ac = new AutoComplete({ - trigger: '#test', - dataSource: { - test: ['abc', 'abd', 'cbd'] - }, - locator: 'test' - }).render(); - - ac.setInputValue('a'); - expect(ac.get('data')).to.eql([{ - label: 'abc', - value: 'abc', - alias: [], - highlightIndex: [ - [0, 1] - ] - }, { - label: 'abd', - value: 'abd', - alias: [], - highlightIndex: [ - [0, 1] - ] - }]); - }); + it('should be hide when trigger blur #26', function () { + if ($.browser.msie) return; + var input = $('#test'); + ac = new AutoComplete({ + trigger: '#test', + dataSource: ['abc'] + }).render(); + ac.setInputValue('a'); + input.blur(); + + expect(ac.get('visible')).not.to.be.ok(); + }); - it('should support dot string', function() { - ac = new AutoComplete({ - trigger: '#test', - dataSource: { - test: { - more: ['abc', 'abd', 'cbd'] - } - }, - locator: 'test.more' - }).render(); - - ac.setInputValue('a'); - expect(ac.get('data')).to.eql([{ - label: 'abc', - value: 'abc', - alias: [], - highlightIndex: [ - [0, 1] - ] - }, { - label: 'abd', - value: 'abd', - alias: [], - highlightIndex: [ - [0, 1] - ] - }]); - }); + it('should be hide when mousedown #26', function () { + ac = new AutoComplete({ + trigger: '#test', + dataSource: ['abc'] + }).render(); + ac.setInputValue('a'); + ac.items.eq(0).click(); - it('should support function', function() { - ac = new AutoComplete({ - trigger: '#test', - dataSource: { - test1: ['abc', 'cbd'], - test2: ['abd'] - }, - locator: function(data) { - return data.test1.concat(data.test2); - } - }).render(); - - ac.setInputValue('a'); - expect(ac.get('data')).to.eql([{ - label: 'abc', - value: 'abc', - alias: [], - highlightIndex: [ - [0, 1] - ] - }, { - label: 'abd', - value: 'abd', - alias: [], - highlightIndex: [ - [0, 1] - ] - }]); - }); + expect(ac.get('visible')).not.to.be.ok(); + }); + + it('should not be hide when mousedown #26', function () { + ac = new AutoComplete({ + trigger: '#test', + template: '

    a

      {{#each items}}
    • {{label}}
    • {{/each}}
    ', + dataSource: ['abc'] + }).render(); + ac.setInputValue('a'); + expect(ac.get('visible')).to.be.ok(); + ac.element.find('[data-role=other]').mousedown(); + + expect(ac.get('visible')).to.be.ok(); + }); - it('wrong locator', function() { - ac = new AutoComplete({ - trigger: '#test', - dataSource: { - test: ['abc', 'abd', 'cbd'] - }, - locator: 'wrong' - }).render(); - - ac.setInputValue('a'); - expect(ac.get('data')).to.eql([]); + describe('filter', function () { + it('should be "startsWith" by default', function () { + ac = new AutoComplete({ + trigger: '#test', + dataSource: [] }); + expect(ac.get('filter')).to.eql(Filter['startsWith']); }); - - it('should be hide when trigger blur #26', function() { - if ($.browser.msie) return; - var input = $('#test'); + it('should be "default" when ajax by default', function () { ac = new AutoComplete({ trigger: '#test', - dataSource: ['abc'] - }).render(); - ac.setInputValue('a'); - input.blur(); - - expect(ac.get('visible')).not.to.be.ok(); + dataSource: './data.json' + }); + expect(ac.get('filter')).to.eql(Filter['default']); }); - - it('should be hide when mousedown #26', function() { + it('should be "default" when "", null, false', function () { ac = new AutoComplete({ trigger: '#test', - dataSource: ['abc'] - }).render(); - ac.setInputValue('a'); - ac.items.eq(0).click(); + filter: '', + dataSource: [] + }); - expect(ac.get('visible')).not.to.be.ok(); + expect(ac.get('filter')).to.eql(Filter['default']); }); - - it('should not be hide when mousedown #26', function() { + it('should support string', function () { ac = new AutoComplete({ trigger: '#test', - template: '

    a

      {{#each items}}
    • {{label}}
    • {{/each}}
    ', - dataSource: ['abc'] - }).render(); - ac.setInputValue('a'); - expect(ac.get('visible')).to.be.ok(); - ac.element.find('[data-role=other]').mousedown(); + filter: 'test', + dataSource: [] + }); - expect(ac.get('visible')).to.be.ok(); + expect(ac.get('filter')).to.eql(Filter.test); }); - describe('filter', function() { - it('should be "startsWith" by default', function() { - ac = new AutoComplete({ - trigger: '#test', - dataSource: [] - }); - expect(ac.get('filter')).to.eql(Filter['startsWith']); - }); - it('should be "default" when ajax by default', function() { - ac = new AutoComplete({ - trigger: '#test', - dataSource: './data.json' - }); - expect(ac.get('filter')).to.eql(Filter['default']); - }); - it('should be "default" when "", null, false', function() { - ac = new AutoComplete({ - trigger: '#test', - filter: '', - dataSource: [] - }); - - expect(ac.get('filter')).to.eql(Filter['default']); - }); - it('should support string', function() { - ac = new AutoComplete({ - trigger: '#test', - filter: 'test', - dataSource: [] - }); - - expect(ac.get('filter')).to.eql(Filter.test); + it('should support string but not exist', function () { + ac = new AutoComplete({ + trigger: '#test', + filter: 'notExist', + dataSource: [] }); - it('should support string but not exist', function() { - ac = new AutoComplete({ - trigger: '#test', - filter: 'notExist', - dataSource: [] - }); - - expect(ac.get('filter')).to.eql(Filter['default']); - }); - it('should support function', function() { - var func = function() {}; - ac = new AutoComplete({ - trigger: '#test', - filter: func, - dataSource: [] - }); - - expect(ac.get('filter')).to.eql(func); - }); - it('should support object but not exist', function() { - ac = new AutoComplete({ - trigger: '#test', - filter: 'notExist', - dataSource: [] - }); - - expect(ac.get('filter')).to.eql(Filter['default']); - }); - it('should be called with 3 param', function() { - var spy = sinon.spy(); - Filter.filter = spy; - ac = new AutoComplete({ - trigger: '#test', - filter: 'filter', - dataSource: ['abc'] - }).render(); - - ac.setInputValue('a'); - var data = [{ - label: 'abc', - value: 'abc', - alias: [] - }]; - expect(spy.withArgs(data, 'a').called).to.be.ok(); - }); + expect(ac.get('filter')).to.eql(Filter['default']); }); - - it('select item', function() { - var input = $('#test'), - spy = sinon.spy(); + it('should support function', function () { + var func = function () {}; ac = new AutoComplete({ trigger: '#test', - dataSource: ['abc', 'abd', 'cbd'] - }).on('itemSelected', spy).render(); + filter: func, + dataSource: [] + }); - ac.setInputValue('a'); - ac.set('selectedIndex', 0); - ac.selectItem(); - expect(ac.get('visible')).to.be(false); - expect(input.val()).to.be('abc'); - expect(ac.input.getValue()).to.be('abc'); - expect(spy.called).to.be.ok(); - - ac.setInputValue('ab'); - ac.selectItem(1); - expect(ac.get('visible')).to.be(false); - expect(input.val()).to.be('abd'); - expect(ac.input.getValue()).to.be('abd'); - expect(spy.calledTwice).to.be.ok(); + expect(ac.get('filter')).to.eql(func); }); - - it('highlight item', function() { - var item; + it('should support object but not exist', function () { ac = new AutoComplete({ trigger: '#test', - html: '{{{highlightItem label}}}', - dataSource: ['abc', 'abd', 'cbd'] - }).render(); + filter: 'notExist', + dataSource: [] + }); - ac.set('data', [{ - label: 'abcdefg', - highlightIndex: [ - [0, 1] - ] - }]); - item = ac.$('[data-role=item]') - .eq(0) - .find('.ui-select-item-hl'); - expect(item.length).to.be(1); - expect(item.eq(0).text()).to.be('a'); - - ac.set('data', [{ - label: 'abcdefg', - highlightIndex: [ - [1, 2], - [3, 4] - ] - }]); - item = ac.$('[data-role=item]') - .eq(0) - .find('.ui-select-item-hl'); - expect(item.length).to.be(2); - expect(item.eq(0).text()).to.be('b'); - expect(item.eq(1).text()).to.be('d'); - - ac.set('data', [{ - label: 'abcdefg', - highlightIndex: [ - [0, 1], - [3, 7], - [8, 9] - ] - }]); - item = ac.$('[data-role=item]') - .eq(0) - .find('.ui-select-item-hl'); - expect(item.length).to.be(2); - expect(item.eq(0).text()).to.be('a'); - expect(item.eq(1).text()).to.be('defg'); - - ac.set('data', [{ - label: 'abcdefg', - highlightIndex: [1, 4] - }]); - item = ac.$('[data-role=item]') - .eq(0) - .find('.ui-select-item-hl'); - expect(item.length).to.be(2); - expect(item.eq(0).text()).to.be('b'); - expect(item.eq(1).text()).to.be('e'); - - ac.set('data', [{ - label: 'abcdefg', - highlightIndex: [6, 8] - }]); - item = ac.$('[data-role=item]') - .eq(0) - .find('.ui-select-item-hl'); - expect(item.length).to.be(1); - expect(item.eq(0).text()).to.be('g'); - - ac.set('data', [{ - label: 'abcdefg', - highlightIndex: 0 - }]); - item = ac.$('.ui-select-item-hl'); - expect(item.length).to.be(0); + expect(ac.get('filter')).to.eql(Filter['default']); }); - - it('clear', function() { + it('should be called with 3 param', function () { + var spy = sinon.spy(); + Filter.filter = spy; ac = new AutoComplete({ trigger: '#test', - dataSource: ['abc', 'abd', 'cbd'] + filter: 'filter', + dataSource: ['abc'] }).render(); ac.setInputValue('a'); - - ac._clear(); - expect(ac.$('[data-role=items]').html()).to.be(''); - expect(ac.items).to.be(undefined); - expect(ac.currentItem).to.be(undefined); - expect(ac.currentItem).to.be(undefined); - expect(ac.get('selectedIndex')).to.be(-1); + var data = [{ + label: 'abc', + value: 'abc', + alias: [] + }]; + expect(spy.withArgs(data, 'a').called).to.be.ok(); }); + }); - it('do not show when async #14', function(done) { - ac = new AutoComplete({ - trigger: '#test', - dataSource: 'http://baidu.com' - }).render(); + it('select item', function () { + var input = $('#test'), + spy = sinon.spy(); + ac = new AutoComplete({ + trigger: '#test', + dataSource: ['abc', 'abd', 'cbd'] + }).on('itemSelected', spy).render(); + + ac.setInputValue('a'); + ac.set('selectedIndex', 0); + ac.selectItem(); + expect(ac.get('visible')).to.be(false); + expect(input.val()).to.be('abc'); + expect(ac.input.getValue()).to.be('abc'); + expect(spy.called).to.be.ok(); + + ac.setInputValue('ab'); + ac.selectItem(1); + expect(ac.get('visible')).to.be(false); + expect(input.val()).to.be('abd'); + expect(ac.input.getValue()).to.be('abd'); + expect(spy.calledTwice).to.be.ok(); + }); - var spy = sinon.stub($, 'ajax').returns({ - success: function(callback) { - setTimeout(function() { - callback(['abc', 'abd', 'cbd']); - }, 50); - return this; - }, - error: function() {} - }); + it('highlight item', function () { + var item; + ac = new AutoComplete({ + trigger: '#test', + html: '{{{highlightItem label}}}', + dataSource: ['abc', 'abd', 'cbd'] + }).render(); + + ac.set('data', [{ + label: 'abcdefg', + highlightIndex: [ + [0, 1] + ] + }]); + item = ac.$('[data-role=item]').eq(0).find('.ui-select-item-hl'); + expect(item.length).to.be(1); + expect(item.eq(0).text()).to.be('a'); + + ac.set('data', [{ + label: 'abcdefg', + highlightIndex: [ + [1, 2], + [3, 4] + ] + }]); + item = ac.$('[data-role=item]').eq(0).find('.ui-select-item-hl'); + expect(item.length).to.be(2); + expect(item.eq(0).text()).to.be('b'); + expect(item.eq(1).text()).to.be('d'); + + ac.set('data', [{ + label: 'abcdefg', + highlightIndex: [ + [0, 1], + [3, 7], + [8, 9] + ] + }]); + item = ac.$('[data-role=item]').eq(0).find('.ui-select-item-hl'); + expect(item.length).to.be(2); + expect(item.eq(0).text()).to.be('a'); + expect(item.eq(1).text()).to.be('defg'); + + ac.set('data', [{ + label: 'abcdefg', + highlightIndex: [1, 4] + }]); + item = ac.$('[data-role=item]').eq(0).find('.ui-select-item-hl'); + expect(item.length).to.be(2); + expect(item.eq(0).text()).to.be('b'); + expect(item.eq(1).text()).to.be('e'); + + ac.set('data', [{ + label: 'abcdefg', + highlightIndex: [6, 8] + }]); + item = ac.$('[data-role=item]').eq(0).find('.ui-select-item-hl'); + expect(item.length).to.be(1); + expect(item.eq(0).text()).to.be('g'); + + ac.set('data', [{ + label: 'abcdefg', + highlightIndex: 0 + }]); + item = ac.$('.ui-select-item-hl'); + expect(item.length).to.be(0); + }); - ac.setInputValue('a'); + it('clear', function () { + ac = new AutoComplete({ + trigger: '#test', + dataSource: ['abc', 'abd', 'cbd'] + }).render(); - setTimeout(function() { - expect(ac.get('visible')).to.be.ok(); - spy.restore(); - done(); - }, 50); + ac.setInputValue('a'); + ac._clear(); + expect(ac.$('[data-role=items]').html()).to.be(''); + expect(ac.items).to.be(undefined); + expect(ac.currentItem).to.be(undefined); + expect(ac.currentItem).to.be(undefined); + expect(ac.get('selectedIndex')).to.be(-1); + }); + + it('do not show when async #14', function (done) { + ac = new AutoComplete({ + trigger: '#test', + dataSource: 'http://baidu.com' + }).render(); + + var spy = sinon.stub($, 'ajax').returns({ + success: function (callback) { + setTimeout(function () { + callback(['abc', 'abd', 'cbd']); + }, 50); + return this; + }, + error: function () {} }); - it('should support selectFirst', function() { - ac = new AutoComplete({ - trigger: '#test', - selectFirst: true, - dataSource: ['abc', 'abd', 'cbd'] - }).render(); + ac.setInputValue('a'); - ac.setInputValue('a'); - expect(ac.get('selectedIndex')).to.be(0); + setTimeout(function () { + expect(ac.get('visible')).to.be.ok(); + spy.restore(); + done(); + }, 50); + }); - }); + it('should support selectFirst', function () { + ac = new AutoComplete({ + trigger: '#test', + selectFirst: true, + dataSource: ['abc', 'abd', 'cbd'] + }).render(); - it('should show when same value', function() { - ac = new AutoComplete({ - trigger: '#test', - selectFirst: true, - dataSource: ['abc', 'abd', 'cbd'] - }).render(); + ac.setInputValue('a'); + expect(ac.get('selectedIndex')).to.be(0); - ac.setInputValue('a'); - expect(ac.get('visible')).to.be.ok(); - ac.setInputValue(''); - expect(ac.get('visible')).not.to.be.ok(); + }); + + it('should show when same value', function () { + ac = new AutoComplete({ + trigger: '#test', + selectFirst: true, + dataSource: ['abc', 'abd', 'cbd'] + }).render(); - ac.setInputValue('a'); - expect(ac.get('visible')).to.be.ok(); - }); + ac.setInputValue('a'); + expect(ac.get('visible')).to.be.ok(); - it('normalize', function() { - ac = new AutoComplete({ - trigger: '#test', - filter: 'default', - dataSource: [ - 'aa', - 'ba', { - title: 'ab' - }, { - value: 'ac' - }, { - label: 'bc', - other: 'bc' - }, { - label: 'ad', - value: 'ad' - }, { - label: 'ae', - value: 'ae', - alias: ['be'] - } - ] - }).render(); + ac.setInputValue(''); + expect(ac.get('visible')).not.to.be.ok(); - ac.setInputValue('a'); - expect(ac.get('data')).to.eql([{ - label: 'aa', - value: 'aa', - alias: [] - }, { - label: 'ba', - value: 'ba', - alias: [] - }, { - label: 'ac', - value: 'ac', - alias: [] - }, { + ac.setInputValue('a'); + expect(ac.get('visible')).to.be.ok(); + }); + + it('normalize', function () { + ac = new AutoComplete({ + trigger: '#test', + filter: 'default', + dataSource: ['aa', 'ba', + { + title: 'ab' + }, + { + value: 'ac' + }, + { label: 'bc', - value: 'bc', - alias: [], other: 'bc' - }, { + }, + { label: 'ad', - value: 'ad', - alias: [] - }, { + value: 'ad' + }, + { label: 'ae', value: 'ae', alias: ['be'] - }]); - }); - - it('should step', function() { - ac = new AutoComplete({ - trigger: '#test', - dataSource: ['abc', 'abd', 'cbd'] - }).render(); - - ac.setInputValue('a'); - - ac.items.eq(1).mouseenter(); - expect(ac.get('selectedIndex')).to.be(1); + }] + }).render(); + + ac.setInputValue('a'); + expect(ac.get('data')).to.eql([{ + label: 'aa', + value: 'aa', + alias: [] + }, + { + label: 'ba', + value: 'ba', + alias: [] + }, + { + label: 'ac', + value: 'ac', + alias: [] + }, + { + label: 'bc', + value: 'bc', + alias: [], + other: 'bc' + }, + { + label: 'ad', + value: 'ad', + alias: [] + }, + { + label: 'ae', + value: 'ae', + alias: ['be'] + }]); + }); - triggerKeyEvent(input, 40); - expect(ac.get('selectedIndex')).to.be(-1); + it('should step', function () { + ac = new AutoComplete({ + trigger: '#test', + dataSource: ['abc', 'abd', 'cbd'] + }).render(); - triggerKeyEvent(input, 40); - expect(ac.get('selectedIndex')).to.be(0); + ac.setInputValue('a'); - triggerKeyEvent(input, 38); - expect(ac.get('selectedIndex')).to.be(-1); + ac.items.eq(1).mouseenter(); + expect(ac.get('selectedIndex')).to.be(1); - triggerKeyEvent(input, 38); - expect(ac.get('selectedIndex')).to.be(1); - }); + triggerKeyEvent(input, 40); + expect(ac.get('selectedIndex')).to.be(-1); - it('input focus', function() { - ac = new AutoComplete({ - trigger: '#test', - dataSource: ['abc', 'abd', 'cbd'] - }).render(); + triggerKeyEvent(input, 40); + expect(ac.get('selectedIndex')).to.be(0); - input.focus(); - expect(ac._isOpen).to.be.ok(); - }); + triggerKeyEvent(input, 38); + expect(ac.get('selectedIndex')).to.be(-1); - it('dataSource should call on ac', function() { - var spy = sinon.spy(); - ac = new AutoComplete({ - trigger: '#test', - dataSource: spy - }).render(); - ac.setInputValue('a'); - expect(spy.calledOn(ac)).to.be.ok(); - }); + triggerKeyEvent(input, 38); + expect(ac.get('selectedIndex')).to.be(1); + }); - it('should auto scroll #82', function() { - ac = new AutoComplete({ - trigger: '#test', - dataSource: ['abc', 'abd', 'abe', 'acd', 'ace', 'acf', 'acg', 'ach', 'aci', 'acj', 'ack'], - height: 123 - }).render(); - ac.element.children().css('overflow', 'scroll'); + it('input focus', function () { + ac = new AutoComplete({ + trigger: '#test', + dataSource: ['abc', 'abd', 'cbd'] + }).render(); - ac.setInputValue('a'); - expect(ac.get('visible')).to.be.ok(); + input.focus(); + expect(ac._isOpen).to.be.ok(); + }); - var content = ac.element.children(); - ac._step(1); - expect(content.scrollTop()).to.be(0); - ac._step(1); - expect(content.scrollTop()).to.be(0); - ac._step(1); - expect(content.scrollTop()).to.be(0); - ac._step(1); - expect(content.scrollTop()).to.be(41); - }); + it('dataSource should call on ac', function () { + var spy = sinon.spy(); + ac = new AutoComplete({ + trigger: '#test', + dataSource: spy + }).render(); + ac.setInputValue('a'); + expect(spy.calledOn(ac)).to.be.ok(); }); - function triggerKeyEvent(el, keyCode) { - var e = jQuery.Event('keydown.autocomplete'); - e.which = keyCode; - el.trigger(e); - } + it('should auto scroll #82', function () { + ac = new AutoComplete({ + trigger: '#test', + dataSource: ['abc', 'abd', 'abe', 'acd', 'ace', 'acf', 'acg', 'ach', 'aci', 'acj', 'ack'], + height: 123 + }).render(); + ac.element.children().css('overflow', 'scroll'); + + ac.setInputValue('a'); + expect(ac.get('visible')).to.be.ok(); + + var content = ac.element.children(); + ac._step(1); + expect(content.scrollTop()).to.be(0); + ac._step(1); + expect(content.scrollTop()).to.be(0); + ac._step(1); + expect(content.scrollTop()).to.be(0); + ac._step(1); + expect(content.scrollTop()).to.be(41); + }); }); + +function triggerKeyEvent(el, keyCode) { + var e = $.Event('keydown.autocomplete'); + e.which = keyCode; + el.trigger(e); +} diff --git a/tests/data-source-spec.js b/tests/data-source-spec.js index 7e80abb..902dd7a 100644 --- a/tests/data-source-spec.js +++ b/tests/data-source-spec.js @@ -1,185 +1,177 @@ -define(function(require) { - var sinon = require('sinon'); - var expect = require('expect'); - var DataSource = require('data-source'); - var $ = require('$'); - - describe('DataSource', function() { - - var data; - - beforeEach(function() { - data = [ - 'about', - 'abuse', - 'but', - 'buffter' - ]; - }); +var sinon = require('sinon'); +var expect = require('expect.js'); +var DataSource = require('../src/data-source'); +var $ = require('jquery'); - afterEach(function() { - data = null; - }); +describe('DataSource', function () { - it('error type', function() { - // not Boolean - expect(function() { - new DataSource({ - source: true - }); - }).to.throwError(); - - // not Null - expect(function() { - new DataSource({ - source: null - }); - }).to.throwError(); - - // not Undefined - expect(function() { - new DataSource({ - source: undefined - }); - }).to.throwError(); - - // not DOM Element - expect(function() { - new DataSource({ - source: document.body - }); - }).to.throwError(); - }); - it('type is array', function() { - var spy = sinon.spy(); - var param = [1, 2, 3]; - new DataSource({ - source: param - }).on('data', spy).getData(); - expect(spy.withArgs(param).called).to.be.ok(); - }); + var data; - it('type is object', function() { - var spy = sinon.spy(); - var param = { - data: 1 - }; - new DataSource({ - source: param - }).on('data', spy).getData(); - expect(spy.withArgs(param).called).to.be.ok(); - }); + beforeEach(function () { + data = ['about', 'abuse', 'but', 'buffter']; + }); - it('type is function', function() { - var spy = sinon.spy(); - new DataSource({ - source: function(q) { - return [ - q + '@163.com' - ]; - } - }).on('data', spy).getData('a'); - expect(spy.withArgs(['a@163.com']).called).to.be.ok(); - }); + afterEach(function () { + data = null; + }); - it('type is function return false', function() { - var spy = sinon.spy(); + it('error type', function () { + // not Boolean + expect(function () { new DataSource({ - source: function() { - return false; - } - }).on('data', spy).getData('a'); - expect(spy.called).not.to.be.ok(); - }); + source: true + }); + }).to.throwError(); - it('type is function async', function(done) { - var spy = sinon.spy(); + // not Null + expect(function () { new DataSource({ - source: function(q, done) { - setTimeout(function() { - done(); - }, 10); - return false; - } - }).on('data', spy).getData('a'); - - setTimeout(function() { - expect(spy.called).to.be.ok(); - done(); - }, 500); - }); - - it('type is url', function() { - var spy = sinon.spy(); - var stub = sinon.stub($, 'ajax').returns({ - success: function(callback) { - callback([1, 2, 3]); - return this; - }, - error: function() {} + source: null }); + }).to.throwError(); + // not Undefined + expect(function () { new DataSource({ - source: './test.json?q={{query}}' - }).on('data', spy).getData('a'); - expect(stub.calledWithMatch('./test.json?q=a')).to.be.ok(); - expect(spy.withArgs([1, 2, 3]).called).to.be.ok(); - stub.restore(); - }); - - it('type is url when error', function() { - var spy = sinon.spy(); - var stub = sinon.stub($, 'ajax').returns({ - success: function() { - return this; - }, - error: function(callback) { - callback(); - return this; - } + source: undefined }); + }).to.throwError(); + // not DOM Element + expect(function () { new DataSource({ - source: './test.json?q={{query}}' - }).on('data', spy).getData('a'); - expect(spy.withArgs({}).called).to.be.ok(); - stub.restore(); - }); - it('abort', function(done) { - var stub = sinon.stub($, 'ajax').returns({ - success: function(callback) { - setTimeout(function() { - callback(['a']); - }, 10); - return this; - }, - error: function() { - return this; - } - }); - var source = new DataSource({ - source: './test.json?q={{query}}' + source: document.body }); + }).to.throwError(); + }); + it('type is array', function () { + var spy = sinon.spy(); + var param = [1, 2, 3]; + new DataSource({ + source: param + }).on('data', spy).getData(); + expect(spy.withArgs(param).called).to.be.ok(); + }); + + it('type is object', function () { + var spy = sinon.spy(); + var param = { + data: 1 + }; + new DataSource({ + source: param + }).on('data', spy).getData(); + expect(spy.withArgs(param).called).to.be.ok(); + }); + + it('type is function', function () { + var spy = sinon.spy(); + new DataSource({ + source: function (q) { + return [ + q + '@163.com']; + } + }).on('data', spy).getData('a'); + expect(spy.withArgs(['a@163.com']).called).to.be.ok(); + }); - var spy = sinon.spy(source, '_done'); + it('type is function return false', function () { + var spy = sinon.spy(); + new DataSource({ + source: function () { + return false; + } + }).on('data', spy).getData('a'); + expect(spy.called).not.to.be.ok(); + }); - source.getData('a'); - expect(source.callbacks.length).to.be(1); + it('type is function async', function (done) { + var spy = sinon.spy(); + new DataSource({ + source: function (q, done) { + setTimeout(function () { + done(); + }, 10); + return false; + } + }).on('data', spy).getData('a'); + + setTimeout(function () { + expect(spy.called).to.be.ok(); + done(); + }, 500); + }); - source.getData('a'); - expect(source.callbacks.length).to.be(2); + it('type is url', function () { + var spy = sinon.spy(); + var stub = sinon.stub($, 'ajax').returns({ + success: function (callback) { + callback([1, 2, 3]); + return this; + }, + error: function () {} + }); - source.abort(); - expect(source.callbacks.length).to.be(0); + new DataSource({ + source: './test.json?q={{query}}' + }).on('data', spy).getData('a'); + expect(stub.calledWithMatch('./test.json?q=a')).to.be.ok(); + expect(spy.withArgs([1, 2, 3]).called).to.be.ok(); + stub.restore(); + }); - source.getData('a'); - expect(source.callbacks.length).to.be(1); + it('type is url when error', function () { + var spy = sinon.spy(); + var stub = sinon.stub($, 'ajax').returns({ + success: function () { + return this; + }, + error: function (callback) { + callback(); + return this; + } + }); - setTimeout(function() { - expect(spy.calledOnce).to.be.ok(); - stub.restore(); - done(); - }, 500); + new DataSource({ + source: './test.json?q={{query}}' + }).on('data', spy).getData('a'); + expect(spy.withArgs({}).called).to.be.ok(); + stub.restore(); + }); + it('abort', function (done) { + var stub = sinon.stub($, 'ajax').returns({ + success: function (callback) { + setTimeout(function () { + callback(['a']); + }, 10); + return this; + }, + error: function () { + return this; + } + }); + var source = new DataSource({ + source: './test.json?q={{query}}' }); + + var spy = sinon.spy(source, '_done'); + + source.getData('a'); + expect(source.callbacks.length).to.be(1); + + source.getData('a'); + expect(source.callbacks.length).to.be(2); + + source.abort(); + expect(source.callbacks.length).to.be(0); + + source.getData('a'); + expect(source.callbacks.length).to.be(1); + + setTimeout(function () { + expect(spy.calledOnce).to.be.ok(); + stub.restore(); + done(); + }, 500); }); -}); +}); \ No newline at end of file diff --git a/tests/filter-spec.js b/tests/filter-spec.js index 0055718..141ef3d 100644 --- a/tests/filter-spec.js +++ b/tests/filter-spec.js @@ -1,158 +1,174 @@ -define(function(require) { - var expect = require('expect'); - var Filter = require('filter'); +var expect = require('expect.js'); +var Filter = require('../src/filter'); - describe('Filter', function() { - describe('startsWith', function() { - var data; - beforeEach(function() { - data = [{ - label: 'aa1', - value: 'aa2', - alias: [] - }, { - label: 'ba1', - value: 'ba2', - alias: [] - }, { - label: 'ac1', - value: 'ac2', - alias: [] - }, { - label: 'bc1', - value: 'bc2', - alias: [], - other: 'bc2' - }, { - label: 'ad1', - value: 'ad2', - alias: [] - }, { - label: 'ae1', - value: 'ae2', - alias: ['be'] - }]; - }); - afterEach(function() { - data = null; - }); +describe('Filter', function () { + describe('startsWith', function () { + var data; + beforeEach(function () { + data = [{ + label: 'aa1', + value: 'aa2', + alias: [] + }, + { + label: 'ba1', + value: 'ba2', + alias: [] + }, + { + label: 'ac1', + value: 'ac2', + alias: [] + }, + { + label: 'bc1', + value: 'bc2', + alias: [], + other: 'bc2' + }, + { + label: 'ad1', + value: 'ad2', + alias: [] + }, + { + label: 'ae1', + value: 'ae2', + alias: ['be'] + }]; + }); + afterEach(function () { + data = null; + }); - it('start width a', function() { - var result = Filter.startsWith(data, 'a'); - expect(result).to.eql([{ - label: 'aa1', - value: 'aa2', - alias: [] - }, { - label: 'ac1', - value: 'ac2', - alias: [] - }, { - label: 'ad1', - value: 'ad2', - alias: [] - }, { - label: 'ae1', - value: 'ae2', - alias: ['be'] - }]); - }); + it('start width a', function () { + var result = Filter.startsWith(data, 'a'); + expect(result).to.eql([{ + label: 'aa1', + value: 'aa2', + alias: [] + }, + { + label: 'ac1', + value: 'ac2', + alias: [] + }, + { + label: 'ad1', + value: 'ad2', + alias: [] + }, + { + label: 'ae1', + value: 'ae2', + alias: ['be'] + }]); + }); - it('start width b', function() { - var result = Filter.startsWith(data, 'b'); - expect(result).to.eql([{ - label: 'ba1', - value: 'ba2', - alias: [] - }, { - label: 'bc1', - value: 'bc2', - alias: [], - other: 'bc2' - }, { - label: 'ae1', - value: 'ae2', - alias: ['be'] - }]); - }); + it('start width b', function () { + var result = Filter.startsWith(data, 'b'); + expect(result).to.eql([{ + label: 'ba1', + value: 'ba2', + alias: [] + }, + { + label: 'bc1', + value: 'bc2', + alias: [], + other: 'bc2' + }, + { + label: 'ae1', + value: 'ae2', + alias: ['be'] + }]); + }); - it('start width none', function() { - var result = Filter.startsWith(data, ''); - expect(result).to.eql([]); - }); + it('start width none', function () { + var result = Filter.startsWith(data, ''); + expect(result).to.eql([]); + }); - it('start width more', function() { - var result = Filter.startsWith(data, 'abc'); - expect(result).to.eql([]); - }); + it('start width more', function () { + var result = Filter.startsWith(data, 'abc'); + expect(result).to.eql([]); }); + }); - describe('stringMatch', function() { - it('match a', function() { - var data = [{ - label: 'abc1', - value: 'abc', - alias: [] - }, { - label: 'bcd1', - value: 'bcd', - alias: [] - }, { - label: 'dce1', - value: 'dce', - alias: ['bcd'] - }]; - var result = Filter.stringMatch(data, 'bc'); - expect(result).to.eql([{ - label: 'abc1', - value: 'abc', - alias: [] - }, { - label: 'bcd1', - value: 'bcd', - alias: [] - }, { - label: 'dce1', - value: 'dce', - alias: ['bcd'] - }]); - }); + describe('stringMatch', function () { + it('match a', function () { + var data = [{ + label: 'abc1', + value: 'abc', + alias: [] + }, + { + label: 'bcd1', + value: 'bcd', + alias: [] + }, + { + label: 'dce1', + value: 'dce', + alias: ['bcd'] + }]; + var result = Filter.stringMatch(data, 'bc'); + expect(result).to.eql([{ + label: 'abc1', + value: 'abc', + alias: [] + }, + { + label: 'bcd1', + value: 'bcd', + alias: [] + }, + { + label: 'dce1', + value: 'dce', + alias: ['bcd'] + }]); + }); - it('highlight', function() { - var data = [{ - label: 'abc', - value: 'abc', - alias: [] - }, { - label: 'bcd', - value: 'bcd', - alias: [] - }, { - label: 'dce', - value: 'dce', - alias: ['bcd'] - }]; - var result = Filter.stringMatch(data, 'bc'); - expect(result).to.eql([{ - label: 'abc', - value: 'abc', - alias: [], - highlightIndex: [ - [1, 3] - ] - }, { - label: 'bcd', - value: 'bcd', - alias: [], - highlightIndex: [ - [0, 2] - ] - }, { - label: 'dce', - value: 'dce', - alias: ['bcd'] - }]); - }); + it('highlight', function () { + var data = [{ + label: 'abc', + value: 'abc', + alias: [] + }, + { + label: 'bcd', + value: 'bcd', + alias: [] + }, + { + label: 'dce', + value: 'dce', + alias: ['bcd'] + }]; + var result = Filter.stringMatch(data, 'bc'); + expect(result).to.eql([{ + label: 'abc', + value: 'abc', + alias: [], + highlightIndex: [ + [1, 3] + ] + }, + { + label: 'bcd', + value: 'bcd', + alias: [], + highlightIndex: [ + [0, 2] + ] + }, + { + label: 'dce', + value: 'dce', + alias: ['bcd'] + }]); }); }); -}); +}); \ No newline at end of file diff --git a/tests/input-spec.js b/tests/input-spec.js index c141c0f..d9154b7 100644 --- a/tests/input-spec.js +++ b/tests/input-spec.js @@ -1,88 +1,75 @@ -define(function(require) { - var $ = require('$'); - var expect = require('expect'); - var Input = require('input'); - var sinon = require('sinon'); +var $ = require('jquery'); +var expect = require('expect.js'); +var Input = require('../src/input'); +var sinon = require('sinon'); - describe('Input', function() { - it('should trigger events when query changed', function() { - var spy1 = sinon.spy(); - var spy2 = sinon.spy(); - var dom = $('').appendTo(document.body); - var input = new Input({ - element: dom - }) - .on('queryChanged', spy1) - .on('whitespaceChanged', spy2); +describe('Input', function () { + it('should trigger events when query changed', function () { + var spy1 = sinon.spy(); + var spy2 = sinon.spy(); + var dom = $('').appendTo(document.body); + var input = new Input({ + element: dom + }).on('queryChanged', spy1).on('whitespaceChanged', spy2); - input.setValue('a'); - expect(spy1.withArgs('a', '').called).to.be.ok(); - expect(spy2.called).not.to.be.ok(); + input.setValue('a'); + expect(spy1.withArgs('a', '').called).to.be.ok(); + expect(spy2.called).not.to.be.ok(); - input.setValue('a '); - expect(spy1.callCount).to.be(2); - expect(spy2.called).not.to.be.ok(); + input.setValue('a '); + expect(spy1.callCount).to.be(2); + expect(spy2.called).not.to.be.ok(); - input.setValue('a '); - expect(spy1.callCount).to.be(2); - expect(spy2.withArgs('a ').called).to.be.ok(); + input.setValue('a '); + expect(spy1.callCount).to.be(2); + expect(spy2.withArgs('a ').called).to.be.ok(); - input.destroy(); - dom.remove(); - }); + input.destroy(); + dom.remove(); + }); - it('should trigger any events', function() { - var spy1 = sinon.spy(); - var spy2 = sinon.spy(); - var spy3 = sinon.spy(); - var spy4 = sinon.spy(); - var spy5 = sinon.spy(); - var spy6 = sinon.spy(); - var spy7 = sinon.spy(); - var spy8 = sinon.spy(); - var spy9 = sinon.spy(); - var dom = $('').appendTo(document.body); - var input = new Input({ - element: dom - }) - .on('focus', spy1) - .on('blur', spy2) - .on('keyTab', spy3) - .on('keyEsc', spy4) - .on('keyUp', spy5) - .on('keyDown', spy6) - .on('keyLeft', spy7) - .on('keyRight', spy8) - .on('keyEnter', spy9); + it('should trigger any events', function () { + var spy1 = sinon.spy(); + var spy2 = sinon.spy(); + var spy3 = sinon.spy(); + var spy4 = sinon.spy(); + var spy5 = sinon.spy(); + var spy6 = sinon.spy(); + var spy7 = sinon.spy(); + var spy8 = sinon.spy(); + var spy9 = sinon.spy(); + var dom = $('').appendTo(document.body); + var input = new Input({ + element: dom + }).on('focus', spy1).on('blur', spy2).on('keyTab', spy3).on('keyEsc', spy4).on('keyUp', spy5).on('keyDown', spy6).on('keyLeft', spy7).on('keyRight', spy8).on('keyEnter', spy9); - input.focus(); - dom.blur(); - triggerKeyEvent(dom, 9); - triggerKeyEvent(dom, 13); - triggerKeyEvent(dom, 27); - triggerKeyEvent(dom, 37); - triggerKeyEvent(dom, 38); - triggerKeyEvent(dom, 39); - triggerKeyEvent(dom, 40); + input.focus(); + dom.blur(); + triggerKeyEvent(dom, 9); + triggerKeyEvent(dom, 13); + triggerKeyEvent(dom, 27); + triggerKeyEvent(dom, 37); + triggerKeyEvent(dom, 38); + triggerKeyEvent(dom, 39); + triggerKeyEvent(dom, 40); - expect(spy1.callCount).to.be(1); - expect(spy2.callCount).to.be(1); - expect(spy3.callCount).to.be(1); - expect(spy4.callCount).to.be(1); - expect(spy5.callCount).to.be(1); - expect(spy6.callCount).to.be(1); - expect(spy7.callCount).to.be(1); - expect(spy8.callCount).to.be(1); - expect(spy9.callCount).to.be(1); + expect(spy1.callCount).to.be(1); + expect(spy2.callCount).to.be(1); + expect(spy3.callCount).to.be(1); + expect(spy4.callCount).to.be(1); + expect(spy5.callCount).to.be(1); + expect(spy6.callCount).to.be(1); + expect(spy7.callCount).to.be(1); + expect(spy8.callCount).to.be(1); + expect(spy9.callCount).to.be(1); - input.destroy(); - dom.remove(); - }); + input.destroy(); + dom.remove(); }); - - function triggerKeyEvent(el, keyCode) { - var e = jQuery.Event('keydown.autocomplete'); - e.which = keyCode; - el.trigger(e); - } }); + +function triggerKeyEvent(el, keyCode) { + var e = $.Event('keydown.autocomplete'); + e.which = keyCode; + el.trigger(e); +} diff --git a/tests/issue-spec.js b/tests/issue-spec.js index c2b11d0..824970c 100644 --- a/tests/issue-spec.js +++ b/tests/issue-spec.js @@ -1,58 +1,62 @@ -define(function(require) { - var expect = require('expect'); - var $ = require('$'); - var AutoComplete = require('autocomplete'); - var Filter = require('filter'); - - describe('Issue', function() { - it('#56 start with (', function() { - var data = [ - {label: 'about1', value: 'about', alias: []}, - {label: '(abc1', value: '(abc', alias: []} - ]; - var result = Filter.startsWith(data, '(a'); - expect(result).to.eql([ - {label: '(abc1', value: '(abc', alias: []} - ]); - }); - - xit('#55 dont\'t change selectedIndex when hover', function() { - var ac, input = $('') - .appendTo(document.body); - - ac = new AutoComplete({ - trigger: '#test', - dataSource: ['abc', 'abd', 'cbd'] - }).render(); - - ac.setInputValue('a'); - var item = ac.$('li').eq(1); - item.mouseenter(); - expect(item.hasClass('ui-autocomplete-item-hover')).to.be.ok(); - item.mouseleave(); - expect(item.hasClass('ui-autocomplete-item-hover')).not.to.be.ok(); - - expect(ac.get('selectedIndex')).to.be(-1); - - input.remove(); - ac.destroy(); - }); - - it('#72 start with \\', function() { - var ac, input = $('') - .appendTo(document.body); - - ac = new AutoComplete({ - trigger: '#test', - dataSource: [] - }).render(); - - expect(function() { - ac.setInputValue('\\'); - }).not.to.throwError(); - - input.remove(); - ac.destroy(); - }); +var expect = require('expect.js'); +var $ = require('jquery'); +var AutoComplete = require('../src/autocomplete'); +var Filter = require('../src/filter'); + +describe('Issue', function () { + it('#56 start with (', function () { + var data = [{ + label: 'about1', + value: 'about', + alias: [] + }, + { + label: '(abc1', + value: '(abc', + alias: [] + }]; + var result = Filter.startsWith(data, '(a'); + expect(result).to.eql([{ + label: '(abc1', + value: '(abc', + alias: [] + }]); }); -}); + + xit('#55 dont\'t change selectedIndex when hover', function () { + var ac, input = $('').appendTo(document.body); + + ac = new AutoComplete({ + trigger: '#test', + dataSource: ['abc', 'abd', 'cbd'] + }).render(); + + ac.setInputValue('a'); + var item = ac.$('li').eq(1); + item.mouseenter(); + expect(item.hasClass('ui-autocomplete-item-hover')).to.be.ok(); + item.mouseleave(); + expect(item.hasClass('ui-autocomplete-item-hover')).not.to.be.ok(); + + expect(ac.get('selectedIndex')).to.be(-1); + + input.remove(); + ac.destroy(); + }); + + it('#72 start with \\', function () { + var ac, input = $('').appendTo(document.body); + + ac = new AutoComplete({ + trigger: '#test', + dataSource: [] + }).render(); + + expect(function () { + ac.setInputValue('\\'); + }).not.to.throwError(); + + input.remove(); + ac.destroy(); + }); +}); \ No newline at end of file