arale-autocomplete
Advanced tools
Comparing version 1.2.6 to 1.4.0
@@ -7,3 +7,3 @@ # 数据源 dataSource | ||
数据源是 autocomplete 的基础,数据可以是静态的也可以是动态的。 | ||
数据源是 autocomplete 的基础,数据可以是静态的也可以是动态的,可通过 this.dataSource 获取该实例。 | ||
@@ -10,0 +10,0 @@ 只提供 `.getData` 的方法获取数据,通过 data 事件异步返回。 |
@@ -7,110 +7,100 @@ # 过滤器 Filter | ||
过滤器接受数据源和输入的值,最后输出给模板进行渲染 | ||
过滤器用于筛选 dataSource,最后输出给模板进行渲染。 | ||
## 使用方式 | ||
直接指定使用 | ||
实例化的时候 filter 参数支持三种方式: | ||
``` | ||
filter: 'startsWith' | ||
``` | ||
- 不传参或值为 null、false | ||
## 内置的过滤器 | ||
这种请看默认使用 `startsWith`。 | ||
### default | ||
**注意:如果 `dataSource` 为 url,使用 default,一般异步场景不需要 filter** | ||
按原样输出,指定 `filter = ''` 会调用这个过滤器。 | ||
- 字符串 | ||
### startsWith | ||
可指定[内置的 filter](#内置的过滤器),如找不到则用 default。 | ||
从头开始全匹配输入的值 | ||
支持参数: | ||
- 函数 | ||
- key: 如果是对象数组,key 为需要过滤的那个字段,默认值为 `value`。 | ||
通过自定义函数去筛选 dataSource,要注意数据转换。 | ||
示例:提供 dataSource | ||
如:筛选出包含输入值的数据 | ||
``` | ||
[ | ||
{title: 'abc'}, | ||
{title: 'abd'}, | ||
{title: 'bcd'} | ||
] | ||
``` | ||
输入 `a` 后显示 abc 和 abd,进行如下设置 | ||
``` | ||
filter: { | ||
name: 'startsWith', | ||
options: { | ||
key: 'title' | ||
``` | ||
filter: function(data, query) { | ||
var result = []; | ||
$.each(data, function(index, item) { | ||
if (item.value.indexOf(query) > -1) { | ||
result.push({matchKey: value}); | ||
} | ||
}); | ||
return result; | ||
} | ||
} | ||
``` | ||
``` | ||
### stringMatch | ||
## 内置的过滤器 | ||
全局匹配字符串,只要有匹配到就会返回 | ||
### default | ||
支持参数: | ||
不做任何处理,按原样输出 | ||
- key: 如果是对象数组,key 为需要过滤的那个字段,默认值为 `value`。 | ||
### startsWith | ||
示例:提供 dataSource | ||
从头开始全匹配输入的值,示例 | ||
``` | ||
[ | ||
{title: 'abc'}, | ||
{title: 'abd'}, | ||
{title: 'bcd'} | ||
'abc', | ||
'abd', | ||
'bcd' | ||
] | ||
``` | ||
输入 `c` 后显示 abc 和 bcd,进行如下设置 | ||
输入 a 匹配 abc abd;输入 abc 匹配 abc。 | ||
### stringMatch | ||
全局匹配字符串,只要有匹配到就会返回,示例 | ||
``` | ||
filter: { | ||
name: 'stringMatch', | ||
options: { | ||
key: 'title' | ||
} | ||
} | ||
[ | ||
'abc', | ||
'abd', | ||
'bcd' | ||
] | ||
``` | ||
## 自定义 | ||
输入 b 全部匹配;输入 c 匹配 abc bcd。 | ||
提供三个参数 | ||
## 自定义过滤器 | ||
每个过滤器接受两个参数,最后返回一个数组 | ||
1. data: dataSource 的返回值 | ||
2. query: input 过滤后的返回值 | ||
3. options: 自定义属性 | ||
2. query: 输入后的值 | ||
返回结果也要是一个数组,但结构有变化。 | ||
### filter 中的数据转换 | ||
过滤器的输入(也就是数据源的输出)支持两个类型:字符串数组和对象数组,最终 filter 输出的仅为对象数组。 | ||
过滤器处理的数据类型为数组,也就是说输入和输出都为数组。 | ||
输出的对象需要提供: | ||
输入的数组是符合 [dataSource 数据格式](http://aralejs.org/autocomplete/#datasource-array-object-string-function)的。 | ||
- matchKey | ||
输出的对象也该保持一样的数据格式,如果想使用 highlightItem 的话,需要提供 highlightIndex。 | ||
被匹配的值,最后显示的内容。如果是字符串则取本身,如果是对象通过 options/key 来指定那个属性为 matchKey。 | ||
这个属性是为了增加高亮被匹配值的配置,渲染模版时会增加 `item-hl` 样式。 | ||
- highlightIndex | ||
highlightIndex 为一个二位数组,确定被匹配字符的位置。描述了多个高亮元素的索引值,aba 中第一个 a 的索引值为 [0, 1],第二个 a 的索引值为 [2, 3]。 | ||
这个属性是为了增加高亮被匹配值的配置,渲染模版时会增加 `item-hl` 样式。 | ||
``` | ||
{value: 'aba', highlightIndex:[[0, 1], [2, 3]]} | ||
``` | ||
highlightIndex 为一个二位数组,确定被匹配字符的位置。描述了多个高亮元素的索引值,aba 中第一个 a 的索引值为 [0, 1],第二个 a 的索引值为 [2, 3]。 | ||
在 DOM 上的表现 | ||
``` | ||
{matchKey: 'aba', highlightIndex:[[0, 1], [2, 3]]} | ||
``` | ||
``` | ||
<span class="item-hl">a<span>b<span class="item-hl">c<span> | ||
``` | ||
``` | ||
<span class="item-hl">a<span>b<span class="item-hl">c<span> | ||
``` | ||
示例: | ||
@@ -120,10 +110,14 @@ | ||
// 输入 | ||
['aba', 'abb', 'abc'] | ||
[ | ||
{value: 'aba'}, | ||
{value: 'abb'}, | ||
{value: 'abc'} | ||
] | ||
// 输出 | ||
[ | ||
{matchKey: 'aba', highlightIndex:[[0, 1], [2, 3]]}, | ||
{matchKey: 'abb', highlightIndex:[[0, 1]]}, | ||
{matchKey: 'abc', highlightIndex:[[0, 1]]}, | ||
{value: 'aba', highlightIndex:[[0, 1], [2, 3]]}, | ||
{value: 'abb', highlightIndex:[[0, 1]]}, | ||
{value: 'abc', highlightIndex:[[0, 1]]} | ||
] | ||
``` |
@@ -8,3 +8,3 @@ # 数据源 | ||
<script> | ||
seajs.use('../src/autocomplete.css'); | ||
seajs.use('alice-select'); | ||
</script> | ||
@@ -28,3 +28,4 @@ | ||
data: ['abc', 'abd', 'abe', 'acd'] | ||
} | ||
}, | ||
width: 150 | ||
}).render(); | ||
@@ -44,3 +45,4 @@ }); | ||
trigger: '#acTrigger2', | ||
dataSource: './data.json?q={{query}}' | ||
dataSource: './data.json?q={{query}}', | ||
width: 150 | ||
}).render(); | ||
@@ -57,3 +59,3 @@ }); | ||
````javascript | ||
seajs.use(['autocomplete', '$'], function(AutoComplete, $) { | ||
seajs.use(['autocomplete', 'jquery'], function(AutoComplete, $) { | ||
var local = ['ade', 'adf']; | ||
@@ -73,3 +75,4 @@ new AutoComplete({ | ||
}); | ||
} | ||
}, | ||
width: 150 | ||
}).render(); | ||
@@ -79,3 +82,3 @@ }); | ||
## 处理复杂数据 | ||
## 处理嵌套结构 | ||
@@ -87,3 +90,3 @@ 如果数据结构很复杂,你可以通过 `locator` 找到你要的数据 | ||
````javascript | ||
seajs.use(['autocomplete', '$'], function(AutoComplete, $) { | ||
seajs.use(['autocomplete', 'jquery'], function(AutoComplete, $) { | ||
new AutoComplete({ | ||
@@ -105,3 +108,4 @@ trigger: '#acTrigger4', | ||
} | ||
} | ||
}, | ||
width: 150 | ||
}).render(); | ||
@@ -111,3 +115,3 @@ }); | ||
## 处理复杂数据的数据结构 | ||
## 处理复杂数据结构 | ||
@@ -119,19 +123,14 @@ 数据不是字符串而是复杂结构 | ||
````javascript | ||
seajs.use(['autocomplete', '$'], function(AutoComplete, $) { | ||
seajs.use(['autocomplete', 'jquery'], function(AutoComplete, $) { | ||
new AutoComplete({ | ||
trigger: '#acTrigger5', | ||
filter: { | ||
name: 'startsWith', | ||
options: { | ||
key: 'title' | ||
} | ||
}, | ||
dataSource: [ | ||
{title: 'abc', myprop: '123'}, | ||
{title: 'abd', myprop: '124'}, | ||
{title: 'abe', myprop: '125'}, | ||
{title: 'acd', myprop: '134'} | ||
] | ||
{value: 'abc', myprop: '123'}, | ||
{value: 'abd', myprop: '124'}, | ||
{value: 'abe', myprop: '125'}, | ||
{value: 'acd', myprop: '134'} | ||
], | ||
width: 150 | ||
}).render(); | ||
}); | ||
```` |
@@ -8,3 +8,3 @@ # 过滤器 | ||
<script> | ||
seajs.use('../src/autocomplete.css'); | ||
seajs.use('alice-select'); | ||
</script> | ||
@@ -14,3 +14,3 @@ | ||
输出过滤将数据源的值通过一定规则过滤后输出,下面的规则为“只要包涵输入值” | ||
输出过滤将数据源的值通过一定规则过滤后输出,下面的规则为“匹配输入值在中间” | ||
@@ -20,3 +20,3 @@ <input id="acTrigger1" type="text" value="" /> | ||
````javascript | ||
seajs.use(['autocomplete', '$'], function(AutoComplete, $) { | ||
seajs.use(['autocomplete', 'jquery'], function(AutoComplete, $) { | ||
new AutoComplete({ | ||
@@ -27,9 +27,12 @@ trigger: '#acTrigger1', | ||
var result = []; | ||
$.each(data, function(index, value) { | ||
if (value.indexOf(query) > -1) { | ||
result.push({matchKey: value}); | ||
if (!query) return result; | ||
$.each(data, function(index, item) { | ||
var value = item.value; | ||
if (new RegExp('\\w+'+query+'\\w+').test(value)) { | ||
result.push(item); | ||
} | ||
}); | ||
return result; | ||
} | ||
}, | ||
width: 150 | ||
}).render(); | ||
@@ -39,27 +42,2 @@ }); | ||
## 输入过滤 | ||
输入过滤将输入框的值过滤,某些情况不想取整个输入框的值,只想去一部分。 | ||
如 email 只想通过 @ 前的值进行筛选 | ||
<input id="acTrigger2" type="text" value="a@gmail.com" /> | ||
````javascript | ||
seajs.use(['autocomplete', '$'], function(AutoComplete, $) { | ||
new AutoComplete({ | ||
trigger: '#acTrigger2', | ||
dataSource: [ | ||
'abc@gmail.com', | ||
'abd@gmail.com', | ||
'abe@gmail.com', | ||
'acd@gmail.com' | ||
], | ||
inputFilter: function(value) { | ||
return value.split('@')[0]; | ||
} | ||
}).render(); | ||
}); | ||
```` | ||
## 全字符匹配 | ||
@@ -70,9 +48,10 @@ | ||
````javascript | ||
seajs.use(['autocomplete', '$'], function(AutoComplete, $) { | ||
seajs.use(['autocomplete', 'jquery'], function(AutoComplete, $) { | ||
new AutoComplete({ | ||
trigger: '#acTrigger3', | ||
dataSource: ['abc abd', 'bcd tcd', 'cbdc abdc'], | ||
filter: 'stringMatch' | ||
filter: 'stringMatch', | ||
width: 150 | ||
}).render(); | ||
}); | ||
```` |
@@ -8,3 +8,3 @@ # 基本操作 | ||
<script> | ||
seajs.use('../src/autocomplete.css'); | ||
seajs.use('alice-select'); | ||
</script> | ||
@@ -20,3 +20,4 @@ | ||
trigger: '#acTrigger1', | ||
dataSource: ['abc', 'abd', 'abe', 'acd'] | ||
dataSource: ['abc', 'abd', 'abe', 'acd'], | ||
width: 150 | ||
}).render(); | ||
@@ -41,3 +42,4 @@ }); | ||
submitOnEnter: false, | ||
dataSource: ['abc', 'abd', 'abe', 'acd'] | ||
dataSource: ['abc', 'abd', 'abe', 'acd'], | ||
width: 150 | ||
}).render(); | ||
@@ -56,6 +58,7 @@ }); | ||
````javascript | ||
seajs.use(['autocomplete', '$'], function(AutoComplete, $) { | ||
seajs.use(['autocomplete', 'jquery'], function(AutoComplete, $) { | ||
var ac = new AutoComplete({ | ||
trigger: '#acTrigger3', | ||
dataSource: ['abc', 'abd', 'abe', 'acd'] | ||
dataSource: ['abc', 'abd', 'abe', 'acd'], | ||
width: 150 | ||
}).render(); | ||
@@ -72,6 +75,4 @@ | ||
## 默认选中第一个 | ||
## 可以默认选中第一个 | ||
<input id="acTrigger5" type="text" value="" /> | ||
@@ -84,5 +85,40 @@ | ||
selectFirst: true, | ||
dataSource: ['abc', 'abd', 'abe', 'acd'] | ||
dataSource: ['abc', 'abd', 'abe', 'acd'], | ||
width: 150 | ||
}).render(); | ||
}); | ||
```` | ||
## 高亮匹配值 | ||
<style> | ||
.ui-select-item-hl {background: yellow;} | ||
</style> | ||
<input id="acTrigger6" type="text" value="" /> | ||
````javascript | ||
seajs.use('autocomplete', function(AutoComplete) { | ||
new AutoComplete({ | ||
trigger: '#acTrigger6', | ||
html: '{{{highlightItem label}}}', | ||
dataSource: ['abc', 'abd', 'abe', 'acd'], | ||
width: 150 | ||
}).render(); | ||
}); | ||
```` | ||
## 下拉框高度固定, 出现滚动条时, 键盘上下选中项时跟随 | ||
<input id="scroll" type="text" value="" /> | ||
````javascript | ||
seajs.use('autocomplete', function(AutoComplete) { | ||
var ac = new AutoComplete({ | ||
trigger: '#scroll', | ||
dataSource: ['abc', 'abd', 'abe', 'acd', 'ace', 'acf', 'acg', 'ach', 'aci', 'acj', 'ack'], | ||
height: 120 | ||
}).render(); | ||
ac.element.children().css('overflow', 'scroll'); | ||
}); | ||
```` |
@@ -1,5 +0,38 @@ | ||
# 其他 | ||
# 其他示例 | ||
- order:5 | ||
--- | ||
<script> | ||
seajs.use('alice-select'); | ||
</script> | ||
## Email 自动补全 | ||
这个功能很常用,在输入账号的时候希望补全常用的邮箱后缀 | ||
通过 dataSource 实现,每次输入 dataSource 都根据输入自动生成,并设置 filter 为空 | ||
<input id="example" type="text" value="" /> | ||
````javascript | ||
seajs.use(['autocomplete', 'jquery'], function(AutoComplete, $) { | ||
var data = [ | ||
'163.com', | ||
'126.com', | ||
'gmail.com' | ||
]; | ||
new AutoComplete({ | ||
trigger: '#example', | ||
dataSource: function(query) { | ||
query = query.replace(/^(.*)@.*$/,'$1'); | ||
return $.map(data, function(v) { | ||
return query + '@' + v; | ||
}); | ||
} | ||
}).render(); | ||
}); | ||
```` | ||
## 选中后新开窗口 | ||
@@ -25,18 +58,1 @@ | ||
```` | ||
## 下拉框高度固定, 出现滚动条时, 键盘上下选中项时跟随 | ||
<input id="scroll" type="text" value="" /> | ||
````javascript | ||
seajs.use('autocomplete', function(AutoComplete) { | ||
new AutoComplete({ | ||
trigger: '#scroll', | ||
dataSource: ['abc', 'abd', 'abe', 'acd', 'ace', 'acf', 'acg', 'ach', 'aci', 'acj', 'ack'], | ||
style: { | ||
'overflow': 'scroll' | ||
}, | ||
height: 120 | ||
}).render(); | ||
}); | ||
```` |
# 自定义模板 | ||
- order:6 | ||
- order:4 | ||
@@ -8,24 +8,58 @@ ---- | ||
<script> | ||
seajs.use('../src/autocomplete.css'); | ||
seajs.use('alice-select'); | ||
</script> | ||
## 自定义模板 | ||
## 使用参数来自定义模板 | ||
默认的模板可以查看 `src/autocomplete.tpl`,如果有修改模板的操作可如下自己定义 | ||
<input id="acTrigger1" type="text" value="" /> | ||
<style> | ||
.ui-select-item a {padding: 7px 10px 7px 0;} | ||
.ui-select-item a span {float: right; color: #ccc;} | ||
.ui-select-header, .ui-select-footer {padding: 3px 10px; font-size: 12px;} | ||
.ui-select-footer {text-align: right;} | ||
</style> | ||
````javascript | ||
seajs.use(['autocomplete', 'jquery'], function(AutoComplete, $) { | ||
var ac = new AutoComplete({ | ||
trigger: '#acTrigger1', | ||
header: '<div class="{{classPrefix}}-header">筛选省市:</div>', | ||
footer: '<div class="{{classPrefix}}-footer">搜索 {{query}} 的{{length}}个结果</div>', | ||
html: '<strong>{{city}}</strong><span>{{prov}}</span>', | ||
dataSource: [ | ||
{city: '上海', prov: '上海', label: '上海', value: 'shanghai', alias: ['上海']}, | ||
{city: '苏州', prov: '江苏', label: '苏州', value: 'suzhou', alias: ['苏州']}, | ||
{city: '深圳', prov: '广州', label: '深圳', value: 'shenzhen', alias: ['深圳']}, | ||
{city: '沈阳', prov: '辽宁', label: '沈阳', value: 'shenyang', alias: ['沈阳']} | ||
], | ||
width: 150 | ||
}).render(); | ||
ac.element.on('click', '#xxx', function() { | ||
//alert(1); | ||
}) | ||
}); | ||
```` | ||
## 自定义整个模板 | ||
默认的模板可以查看 `src/autocomplete.handlebars`,如果有修改模板的操作可如下自己定义 | ||
````html | ||
<script id="acTrigger4-template" type="text/x-handlebars-template"> | ||
<div class="{{classPrefix}}"> | ||
<input type="text" value="" class="{{classPrefix}}-input" style="margin:5px;"> | ||
<ul class="{{classPrefix}}-ctn" data-role="items"> | ||
{{#if items}} | ||
<div class="{{classPrefix}}"> | ||
{{#if items}} | ||
<ul class="{{classPrefix}}-content" data-role="items"> | ||
{{#each items}} | ||
<li data-role="item" class="{{../classPrefix}}-item" data-value="{{matchKey}}">{{matchKey}}</li> | ||
<li data-role="item" class="{{../classPrefix}}-item"><a href="javascript:''">{{> html}}</a></li> | ||
{{/each}} | ||
{{/if}} | ||
{{#unless items}} | ||
<li class="{{classPrefix}}-item">不存在</li> | ||
{{/unless}} | ||
</ul> | ||
{{else}} | ||
<ul class="{{classPrefix}}-content"> | ||
<li class="{{../classPrefix}}-item"> | ||
没有匹配任何数据 | ||
</li> | ||
</ul> | ||
{{/if}} | ||
</div> | ||
@@ -35,20 +69,24 @@ </script> | ||
需要注意的: | ||
当未匹配的时候会有提示 | ||
1. `data-role` 必须指定 | ||
2. `highlightItem` 提供高亮功能,可不用 | ||
3. `classPrefix` 可根据需求指定,可自定义命名空间 | ||
使用该模板 | ||
<input id="acTrigger4" type="text" value="" /> | ||
````javascript | ||
seajs.use(['autocomplete', '$'], function(AutoComplete, $) { | ||
var ac = new AutoComplete({ | ||
seajs.use(['autocomplete', 'jquery'], function(AutoComplete, $) { | ||
var AutoCompleteX = AutoComplete.extend({ | ||
_isEmpty: function() { | ||
return false; | ||
} | ||
}); | ||
var ac = new AutoCompleteX({ | ||
trigger: '#acTrigger4', | ||
template: $('#acTrigger4-template').html(), | ||
dataSource: ['abc', 'abd', 'abe', 'acd'] | ||
dataSource: ['abc', 'abd', 'abe', 'acd'], | ||
width: 150 | ||
}).render(); | ||
ac.input.on('focus', function() { | ||
ac.show(); | ||
}); | ||
}); | ||
```` | ||
```` |
@@ -5,10 +5,14 @@ # History | ||
## 1.2.5 | ||
## 1.4.0 | ||
`tag:fixed` 不想说什么了,1.2.4 build 内容是错的 | ||
迁移 spm@3.x | ||
## 1.2.4 | ||
## 1.3.1 | ||
`tag:fixed` #91 data 不存在的时候按上下键报错 | ||
`tag:fixed` 修复 for/in 数组的 bug | ||
## 1.3.0 | ||
`tag:changed` 大范围的重构,很多细节有变化,可以按照新文档开发。 | ||
## 1.2.3 | ||
@@ -90,3 +94,3 @@ | ||
`tag:new` [#13](https://github.com/aralejs/autocomplete/issues/13) dataSource 支持 ajax。 | ||
`tag:new` [#15](https://github.com/aralejs/autocomplete/issues/15) 提供 selectFirst 参数。 | ||
@@ -93,0 +97,0 @@ |
{ | ||
"name": "arale-autocomplete", | ||
"version": "1.2.6", | ||
"version": "1.4.0", | ||
"description": "自动补全组件", | ||
@@ -9,3 +9,3 @@ "keywords": [ | ||
"homepage": "http://aralejs.org/autocomplete/", | ||
"author": "贯高 <sakura9515@gmail.com>", | ||
"author": "popomore <sakura9515@gmail.com>", | ||
"maintainers": [ | ||
@@ -37,4 +37,4 @@ "贯高 <sakura9515@gmail.com>", | ||
"spm-jquery": "1.7.2", | ||
"arale-templatable": "0.10.0", | ||
"arale-base": "1.2.0", | ||
"arale-templatable": "0.10.0", | ||
"arale-overlay": "1.2.0", | ||
@@ -44,2 +44,3 @@ "spm-handlebars-runtime": "1.3.0" | ||
"devDependencies": { | ||
"alice-select": "1.1.0", | ||
"spm-expect.js": "0.3.1", | ||
@@ -46,0 +47,0 @@ "spm-sinon": "1.6.0", |
195
README.md
# AutoComplete | ||
- order: 1 | ||
--- | ||
[![Build Status](https://secure.travis-ci.org/aralejs/autocomplete.png)](https://travis-ci.org/aralejs/autocomplete) | ||
[![spm package](http://spmjs.io/badge/arale-autocomplete)](http://spmjs.io/package/arale-autocomplete) | ||
[![Build Status](https://secure.travis-ci.org/aralejs/autocomplete.png?branch=master)](https://travis-ci.org/aralejs/autocomplete) | ||
[![Coverage Status](https://coveralls.io/repos/aralejs/autocomplete/badge.png?branch=master)](https://coveralls.io/r/aralejs/autocomplete) | ||
@@ -25,2 +28,13 @@ | ||
再稍微复杂一点可以通过数据控制显示,dataSource 可以是一个数据集,格式为 | ||
``` | ||
dataSource: [ | ||
{value: 'shanghai', label: '上海', alias: ['sh']}, | ||
{value: 'beijing', label: '北京', alias: ['bj']} | ||
] | ||
``` | ||
组件通过 value 和 alias 进行过滤,而最后显示的是 label 的数据,选中后输入框的也是 label 数据。这三个字段为保留字段,其他字段可以自己定义,还可以定义模板去修改显示,[查看演示](http://aralejs.org/autocomplete/examples/template.html#使用参数来自定义模板)。 | ||
在看 API 之前查看[更多演示](http://aralejs.org/autocomplete/examples/) | ||
@@ -30,54 +44,45 @@ | ||
### 属性 | ||
### Attribute | ||
#### trigger *selector* | ||
输入框 | ||
指定输入框 | ||
#### template *string* | ||
默认模板 | ||
默认模板请[查看源码](https://github.com/aralejs/autocomplete/blob/master/src/autocomplete.handlebars) | ||
```html | ||
<div class="{{classPrefix}}"> | ||
<ul class="{{classPrefix}}-ctn" data-role="items"> | ||
{{#each items}} | ||
<li data-role="item" class="{{../classPrefix}}-item" data-value="{{matchKey}}">{{highlightItem ../classPrefix matchKey}}</li> | ||
{{/each}} | ||
</ul> | ||
</div> | ||
``` | ||
**注意覆盖的时候不要缺少 `data-role`**,查看[如何自定义 template 的演示](http://aralejs.org/autocomplete/examples/template.html),简单的场景可以使用下面几项。 | ||
- 注意覆盖的时候不要缺少 `data-role` | ||
### header *string* | ||
- 模板重新渲染的时候只会渲染 `data-role="items"` 下面的 | ||
自定义模板头部,默认为空。 | ||
- highlightItem 会高亮匹配的值,接受两个参数 `classPrefix` 和 `需要高亮的值`,这个方法会通过对象的 highlightIndex 读取高亮的位置,具体格式看 [filter](http://aralejs.org/autocomplete/docs/filter.html) | ||
### html *string* | ||
自定义每项,默认是 {{label}}。 | ||
### footer *string* | ||
自定义模板尾部,默认为空。 | ||
#### classPrefix *string* | ||
样式前缀,默认为 `ui-autocomplete` | ||
样式前缀,默认为 `ui-select` | ||
#### dataSource *array | object | string | function* | ||
最终提供给 filter 的数据是一个数组,数组内的每项可为字符串或对象 | ||
最终提供给 filter 的数据是一个数组,数组内的每项为一个对象,value、label 和 alias 为保留字段。 | ||
``` | ||
[ | ||
{value: 'abc'}, | ||
{value: 'abd'} | ||
{value: '', label: '', alias: []}, | ||
... | ||
] | ||
``` | ||
或 | ||
如果传入的为字符串 ['a'],会自动转化成 [{value: 'a', label: 'a'}]。 | ||
``` | ||
[ | ||
'abc', | ||
'abd' | ||
] | ||
``` | ||
数据源支持4种形式 | ||
数据源通过转化变成这种形式,共支持 4 种方式 | ||
1. Array | ||
@@ -92,5 +97,5 @@ | ||
1. Object | ||
2. Object | ||
提供一个对象,里面要包含数组,可以通过 `locator` 找到 | ||
提供一个对象,里面要包含数组,可以通过 `locator: 'data'` 指定这个数组 | ||
@@ -104,11 +109,11 @@ ``` | ||
1. URL | ||
3. URL | ||
提供一个 URL,通过 ajax 返回数据,返回的数据也可以通过 `locator` 查找。 | ||
URL 提供模版参数 `./test.json?v={{query}}&t={{timestamp}}`,query 是输入的值(如果使用了 `inputFilter` 则是过滤后的值),timestamp 为时间戳。 | ||
URL 提供模版参数 `./test.json?v={{query}}&t={{timestamp}}`,query 是输入的值,timestamp 为时间戳。 | ||
如果 URL 为 http 或 https 开头,会用 jsonp 发送请求,否则为 ajax _(0.9.0+ 支持 ajax)_。 | ||
如果 URL 为 http 或 https 开头,会用 jsonp 发送请求,否则为 ajax。 | ||
1. Function | ||
4. Function | ||
@@ -120,8 +125,8 @@ 提供一个自定义函数,根据自己的业务逻辑返回数组,这个自定义程度很高,可实现上面 3 种方式。 | ||
``` | ||
dataSource: function(value) { | ||
return [ | ||
value + '@gmail.com', | ||
value + '@qq.com', | ||
value + '@163.com' | ||
]; | ||
dataSource: function(query) { | ||
return [ | ||
query + '@gmail.com', | ||
query + '@qq.com', | ||
query + '@163.com' | ||
]; | ||
} | ||
@@ -133,5 +138,5 @@ ``` | ||
``` | ||
dataSource: function(value, done) { | ||
dataSource: function(query, done) { | ||
var that = this; | ||
$.ajax('test.json?v=' + value, { | ||
$.ajax('test.json?v=' + query, { | ||
dataType: 'jsonp' | ||
@@ -147,3 +152,3 @@ }).success(function(data) { | ||
这个参数与 dataSource 相关,一般情况 dataSource 为一个数组,filter 可以直接处理。但如果返回的是 Object,那么就需要找到那个数组。 | ||
这个参数与 dataSource 相关,一般情况 dataSource 为一个数组,filter 可以直接处理。但如果返回的是 Object,那么就需要指定那个数组。 | ||
@@ -178,102 +183,72 @@ 这个参数可定位到需要的值,支持两种方式 | ||
输出值的过滤器,用于筛选 dataSource,默认方法为 `startsWith`。 | ||
过滤器用于筛选 dataSource,最后输出给模板进行渲染,默认方法为 `startsWith`。 | ||
**过滤器支持类型** | ||
使用方式可查看[过滤器章节](http://aralejs.org/autocomplete/docs/filter.html)。 | ||
- 字符串 | ||
#### submitOnEnter *boolean* | ||
可指定内置的 filter,查看[过滤器章节](http://aralejs.org/autocomplete/docs/filter.html)。 | ||
如不设置此属性会调用默认的 `startsWith` 方法,从头开始匹配 | ||
回车时是否提交表单,默认为 true,会提交表单,组件不做任何处理。 | ||
- 对象 _(0.9.0+)_ | ||
#### disabled *boolean* | ||
过滤器可支持参数,具体参数(options)由过滤器自身决定。 | ||
是否禁用,默认为 `false` | ||
``` | ||
filter: { | ||
name: 'startsWith', | ||
options: { | ||
param: 1 | ||
} | ||
} | ||
``` | ||
#### selectFirst *boolean* | ||
`filter: {name: 'startsWith'}` 等于 `filter: 'startsWith'` | ||
默认选中第一项 | ||
- 空字符串、null、false | ||
#### delay *number* | ||
什么都不处理,返回原来的 dataSource | ||
按键频率,每次按键的间隔,在这个时间范围内不会处理过滤流程。 | ||
_(0.9.0+ 支持)_ **注意:如果 `dataSource` 为 url,默认值为空,一般异步场景不需要 filter** | ||
### 方法 | ||
- 函数 | ||
#### selectItem | ||
通过自定义函数去筛选 dataSource,要注意数据转换。 | ||
选中某项 | ||
如:筛选出包含输入值的数据 | ||
``` | ||
.selectItem(0) | ||
``` | ||
``` | ||
filter: function(data, query) { | ||
var result = []; | ||
$.each(data, function(index, value) { | ||
if (value.indexOf(query) > -1) { | ||
result.push({matchKey: value}); | ||
} | ||
}); | ||
return result; | ||
} | ||
``` | ||
选中第一项,如果没有参数,选中当前 selectedIndex 那项。 | ||
想了解更多可查看[过滤器章节](http://aralejs.org/autocomplete/docs/filter.html)和[设计章节](http://aralejs.org/autocomplete/docs/design.html) | ||
#### setInputValue | ||
#### inputFilter *function* | ||
通过 api 模拟输入框输入,输入 a | ||
输入值的过滤器,支持 Function,默认不做处理。 | ||
这个参数可以过滤输入框中的值,过滤后的匹配值用于筛选数据。 | ||
如果用户输入 a@alipay,但想用 @ 前面的值去筛选可以如下处理 | ||
``` | ||
new AutoComplete({ | ||
inputFilter: function(value) { | ||
return value.split('@')[0]; | ||
} | ||
}) | ||
.setInputValue('a') | ||
``` | ||
#### submitOnEnter *boolean* | ||
### Properties | ||
回车时是否提交表单,默认为 true,会提交表单,组件不做任何处理。 | ||
#### input | ||
#### disabled *boolean* _(0.9.0+)_ | ||
输入框的实例,`this.input`,查看[文档](http://aralejs.org/autocomplete/docs/input.html) | ||
是否禁用,默认为 `false` | ||
#### dataSource | ||
#### selectFirst *boolean* _(0.9.0+)_ | ||
数据源的实例,`this.dataSource`,查看[文档](http://aralejs.org/autocomplete/docs/data-source.html) | ||
默认选中第一项 | ||
#### items | ||
#### delay *number* _(1.0.0+)_ | ||
下拉框的选项,`this.items`,等同于 `this.$('[data-role=items]').children()`。 | ||
按键频率,每次按键的间隔,在这个时间范围内不会处理过滤流程。 | ||
### 事件 | ||
#### itemSelect | ||
#### itemSelected | ||
当选中某项时触发 | ||
- data _(0.9.0 支持)_:选中项对应的数据源对象 | ||
- data:选中项对应的数据源对象 | ||
- item:选中项对应的 DOM | ||
**注意:**原来回调第一个参数为选中的内容,现在替换为一个对象,这样能获得整个 dataSource 的值。对象中也包括原来的内容 `value == data.matchKey`。 | ||
``` | ||
.on('itemSelect', function(data){ | ||
console.log(data.matchKey); | ||
.on('itemSelected', function(data, item){ | ||
console.log(data.label); | ||
}); | ||
``` | ||
#### indexChange | ||
#### indexChanged | ||
@@ -284,7 +259,7 @@ 当选项切换时触发,可能是鼠标或键盘。 | ||
- lastIndex _(0.9.0 支持)_: 切换前的索引值 | ||
- previousIndex: 切换前的索引值 | ||
``` | ||
.on('indexChange', function(currentIndex, lastIndex){ | ||
console.log(this.items[currentIndex]) | ||
.on('indexChanged', function(current, prev){ | ||
console.log(this.items[current]) | ||
}); | ||
@@ -291,0 +266,0 @@ ``` |
@@ -6,18 +6,7 @@ var $ = require('spm-jquery'); | ||
var Filter = require('./filter'); | ||
var Input = require('./input'); | ||
var IE678 = /\bMSIE [678]\.0\b/.test(navigator.userAgent); | ||
var template = require('./autocomplete.handlebars'); | ||
var isIE = (window.navigator.userAgent || "").toLowerCase().indexOf("msie") !== -1; | ||
// keyCode | ||
var KEY = { | ||
UP: 38, | ||
DOWN: 40, | ||
LEFT: 37, | ||
RIGHT: 39, | ||
ENTER: 13, | ||
ESC: 27, | ||
BACKSPACE: 8 | ||
}; | ||
var AutoComplete = Overlay.extend({ | ||
@@ -29,67 +18,51 @@ | ||
// 触发元素 | ||
trigger: { | ||
value: null, | ||
// required | ||
getter: function (val) { | ||
return $(val); | ||
} | ||
}, | ||
classPrefix: 'ui-autocomplete', | ||
trigger: null, | ||
classPrefix: 'ui-select', | ||
align: { | ||
baseXY: [0, '100%'] | ||
}, | ||
template: template, | ||
submitOnEnter: true, | ||
// 回车是否会提交表单 | ||
selectItem: true, | ||
// 选中时是否调用 selectItem 方法 | ||
dataSource: [], | ||
//数据源,支持 Array, URL, Object, Function | ||
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: undefined, | ||
// 输出过滤 | ||
inputFilter: function (v) { | ||
return v; | ||
}, | ||
// 输入过滤 | ||
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: undefined, | ||
inputValue: null, | ||
// 同步输入框的 value | ||
data: null | ||
selectedIndex: null, | ||
data: [] | ||
}, | ||
events: { | ||
// mousedown 先于 blur 触发,选中后再触发 blur 隐藏浮层 | ||
// see _blurEvent | ||
'mousedown [data-role=item]': function (e) { | ||
var i = this.items.index(e.currentTarget); | ||
this.set('selectedIndex', i); | ||
if (this.get('selectItem')) { | ||
this.selectItem(); | ||
this._firstMousedown = true; | ||
} | ||
}, | ||
'mousedown': function () { | ||
this._secondMousedown = true; | ||
}, | ||
'click [data-role=item]': function () { | ||
// 在非 selectItem 时隐藏浮层 | ||
if (!this.get('selectItem')) { | ||
this.hide(); | ||
} | ||
}, | ||
'mouseenter [data-role=item]': function (e) { | ||
var className = this.get('classPrefix') + '-item-hover'; | ||
if (this.currentItem) this.currentItem.removeClass(className); | ||
$(e.currentTarget).addClass(className); | ||
}, | ||
'mouseleave [data-role=item]': function (e) { | ||
var className = this.get('classPrefix') + '-item-hover'; | ||
$(e.currentTarget).removeClass(className); | ||
} | ||
'mousedown [data-role=items]': '_handleMouseDown', | ||
'click [data-role=item]': '_handleSelection', | ||
'mouseenter [data-role=item]': '_handleMouseMove', | ||
'mouseleave [data-role=item]': '_handleMouseMove' | ||
}, | ||
@@ -99,9 +72,11 @@ | ||
// 将匹配的高亮文字加上 hl 的样式 | ||
highlightItem: highlightItem | ||
highlightItem: highlightItem, | ||
include: include | ||
}, | ||
parseElement: function () { | ||
this.set("model", { | ||
classPrefix: this.get('classPrefix'), | ||
items: [] | ||
var that = this; | ||
this.templatePartials || (this.templatePartials = {}); | ||
$.each(['header', 'footer', 'html'], function (index, item) { | ||
that.templatePartials[item] = that.get(item); | ||
}); | ||
@@ -112,33 +87,29 @@ AutoComplete.superclass.parseElement.call(this); | ||
setup: function () { | ||
var trigger = this.get('trigger'), | ||
that = this; | ||
AutoComplete.superclass.setup.call(this); | ||
// 初始化数据源 | ||
this.dataSource = new DataSource({ | ||
source: this.get('dataSource') | ||
}).on('data', this._filterData, this); | ||
this._initFilter(); // 初始化 filter | ||
this._blurHide([trigger]); | ||
this._isOpen = false; | ||
this._initInput(); // 初始化输入框 | ||
this._initDataSource(); // 初始化数据源 | ||
this._initFilter(); // 初始化过滤器 | ||
this._bindHandle(); // 绑定事件 | ||
this._blurHide([$(this.get('trigger'))]); | ||
this._tweakAlignDefaultValue(); | ||
trigger.attr('autocomplete', 'off'); | ||
this.delegateEvents(trigger, 'blur.autocomplete', $.proxy(this._blurEvent, this)); | ||
this.delegateEvents(trigger, 'keydown.autocomplete', $.proxy(this._keydownEvent, this)); | ||
this.delegateEvents(trigger, 'keyup.autocomplete', function () { | ||
clearTimeout(that._timeout); | ||
that._timeout = setTimeout(function () { | ||
that._timeout = null; | ||
that._keyupEvent.call(that); | ||
}, that.get('delay')); | ||
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); | ||
}); | ||
}, | ||
destroy: function () { | ||
this._clear(); | ||
this.element.remove(); | ||
AutoComplete.superclass.destroy.call(this); | ||
show: function () { | ||
this._isOpen = true; | ||
// 无数据则不显示 | ||
if (this._isEmpty()) return; | ||
AutoComplete.superclass.show.call(this); | ||
}, | ||
@@ -150,22 +121,23 @@ | ||
this.dataSource.abort(); | ||
AutoComplete.superclass.hide.call(this); | ||
this._hide(); | ||
}, | ||
destroy: function () { | ||
this._clear(); | ||
if (this.input) { | ||
this.input.destroy(); | ||
this.input = null; | ||
} | ||
AutoComplete.superclass.destroy.call(this); | ||
}, | ||
// Public Methods | ||
// -------------- | ||
selectItem: function () { | ||
this.hide(); | ||
var item = this.currentItem, | ||
index = this.get('selectedIndex'), | ||
data = this.get('data')[index]; | ||
if (item) { | ||
var matchKey = item.attr('data-value'); | ||
this.get('trigger').val(matchKey); | ||
this.set('inputValue', matchKey, { | ||
silent: true | ||
}); | ||
this.trigger('itemSelect', data); | ||
this._clear(); | ||
selectItem: function (index) { | ||
if (this.items) { | ||
if (index && this.items.length > index && index >= -1) { | ||
this.set('selectedIndex', index); | ||
} | ||
this._handleSelection(); | ||
} | ||
@@ -175,12 +147,3 @@ }, | ||
setInputValue: function (val) { | ||
if (this.get('inputValue') !== val) { | ||
// 进入处理流程 | ||
this._start = true; | ||
this.set('inputValue', val); | ||
// 避免光标移动到尾部 #44 | ||
var trigger = this.get('trigger'); | ||
if (trigger.val() !== val) { | ||
trigger.val(val); | ||
} | ||
} | ||
this.input.setValue(val); | ||
}, | ||
@@ -190,23 +153,4 @@ | ||
// --------------- | ||
// 1. 判断输入值,调用数据源 | ||
_onRenderInputValue: function (val) { | ||
if (this._start && val) { | ||
var oldQueryValue = this.queryValue; | ||
this.queryValue = this.get('inputFilter').call(this, val); | ||
// 如果 query 为空或者相等则不处理 | ||
if (this.queryValue && this.queryValue !== oldQueryValue) { | ||
this.dataSource.abort(); | ||
this.dataSource.getData(this.queryValue); | ||
} | ||
} else { | ||
this.queryValue = ''; | ||
} | ||
if (val === '' || !this.queryValue) { | ||
this.set('data', []); | ||
this.hide(); | ||
} | ||
delete this._start; | ||
}, | ||
// 2. 数据源返回,过滤数据 | ||
// 数据源返回,过滤数据 | ||
_filterData: function (data) { | ||
@@ -220,3 +164,3 @@ var filter = this.get('filter'), | ||
// 进行过滤 | ||
data = filter.func.call(this, data, this.queryValue, filter.options); | ||
data = filter.call(this, normalize(data), this.input.get('query')); | ||
@@ -226,17 +170,17 @@ this.set('data', data); | ||
// 3. 通过数据渲染模板 | ||
// 通过数据渲染模板 | ||
_onRenderData: function (data) { | ||
// 清除状态 | ||
this._clear(); | ||
data || (data = []); | ||
// 渲染下拉 | ||
this.set("model", { | ||
items: data | ||
this.set('model', { | ||
items: data, | ||
query: this.input.get('query'), | ||
length: data.length | ||
}); | ||
this.renderPartial('[data-role=items]'); | ||
this.renderPartial(); | ||
// 初始化下拉的状态 | ||
this.items = this.$('[data-role=items]').children(); | ||
this.currentItem = null; | ||
@@ -247,8 +191,4 @@ if (this.get('selectFirst')) { | ||
// data-role=items 无内容才隐藏 | ||
if ($.trim(this.$('[data-role=items]').text())) { | ||
this.show(); | ||
} else { | ||
this.hide(); | ||
} | ||
// 选中后会修改 input 的值并触发下一次渲染,但第二次渲染的结果不应该显示出来。 | ||
this._isOpen && this.show(); | ||
}, | ||
@@ -258,20 +198,26 @@ | ||
_onRenderSelectedIndex: function (index) { | ||
var hoverClass = this.get('classPrefix') + '-item-hover'; | ||
this.items && this.items.removeClass(hoverClass); | ||
// -1 什么都不选 | ||
if (index === -1) return; | ||
var className = this.get('classPrefix') + '-item-hover'; | ||
if (this.currentItem) { | ||
this.currentItem.removeClass(className); | ||
} | ||
this.currentItem = this.items.eq(index).addClass(className); | ||
this.trigger('indexChange', index, this.lastIndex); | ||
this.items.eq(index).addClass(hoverClass); | ||
this.trigger('indexChanged', index, this.lastIndex); | ||
this.lastIndex = index; | ||
}, | ||
// scroll current item into view | ||
//this.currentItem.scrollIntoView(); | ||
var containerHeight = parseInt(this.get('height')); | ||
if (!containerHeight) return; | ||
// 初始化 | ||
// ------------ | ||
_initDataSource: function () { | ||
this.dataSource = new DataSource({ | ||
source: this.get('dataSource') | ||
}); | ||
}, | ||
var itemHeight = this.currentItem.parent().height() / this.items.length, | ||
itemTop = Math.max(0, itemHeight * (index + 1) - containerHeight); | ||
this.element.scrollTop(itemTop); | ||
_initInput: function () { | ||
this.input = new Input({ | ||
element: this.get('trigger'), | ||
delay: this.get('delay') | ||
}); | ||
}, | ||
@@ -281,155 +227,83 @@ | ||
var filter = this.get('filter'); | ||
// 设置 filter 的默认值 | ||
if (filter === undefined) { | ||
// 异步请求的时候一般不需要过滤器 | ||
if (this.dataSource.get('type') === 'url') { | ||
filter = null; | ||
} else { | ||
filter = { | ||
name: 'startsWith', | ||
func: Filter['startsWith'], | ||
options: { | ||
key: 'value' | ||
} | ||
}; | ||
} | ||
} else { | ||
// object 的情况 | ||
// { | ||
// name: '', | ||
// options: {} | ||
// } | ||
if ($.isPlainObject(filter)) { | ||
if (Filter[filter.name]) { | ||
filter = { | ||
name: filter.name, | ||
func: Filter[filter.name], | ||
options: filter.options | ||
}; | ||
} else { | ||
filter = null; | ||
} | ||
} else if ($.isFunction(filter)) { | ||
filter = { | ||
func: filter | ||
}; | ||
} else { | ||
// 从组件内置的 FILTER 获取 | ||
if (Filter[filter]) { | ||
filter = { | ||
name: filter, | ||
func: Filter[filter] | ||
}; | ||
} else { | ||
filter = null; | ||
} | ||
} | ||
} | ||
// filter 为 null,设置为 default | ||
if (!filter) { | ||
filter = { | ||
name: 'default', | ||
func: Filter['default'] | ||
}; | ||
} | ||
filter = initFilter(filter, this.dataSource); | ||
this.set('filter', filter); | ||
}, | ||
_blurEvent: function () { | ||
if (isIE) return; | ||
// 事件绑定 | ||
// ------------ | ||
_bindHandle: function () { | ||
this.dataSource.on('data', this._filterData, this); | ||
// https://github.com/aralejs/autocomplete/issues/26 | ||
if (!this._secondMousedown) { | ||
this.hide(); | ||
} else if (this._firstMousedown) { | ||
this.get('trigger').focus(); | ||
this.hide(); | ||
} | ||
delete this._firstMousedown; | ||
delete this._secondMousedown; | ||
}, | ||
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); | ||
_keyupEvent: function () { | ||
if (this.get('disabled')) return; | ||
this.after('hide', function () { | ||
this.set('selectedIndex', -1); | ||
}); | ||
if (this._keyupStart) { | ||
delete this._keyupStart; | ||
// 获取输入的值 | ||
var v = this.get('trigger').val(); | ||
this.setInputValue(v); | ||
} | ||
// 选中后隐藏浮层 | ||
this.on('itemSelected', function () { | ||
this._hide(); | ||
}); | ||
}, | ||
_keydownEvent: function (e) { | ||
if (this.get('disabled')) return; | ||
// 选中的处理器 | ||
// 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]; | ||
// 先清空状态 | ||
delete this._keyupStart; | ||
if (index >= 0 && item) { | ||
this.input.setValue(data.label); | ||
this.set('selectedIndex', index, { | ||
silent: true | ||
}); | ||
switch (e.which) { | ||
case KEY.ESC: | ||
this.hide(); | ||
break; | ||
// 是否阻止回车提交表单 | ||
if (e && !isMouse && !this.get('submitOnEnter')) e.preventDefault(); | ||
// top arrow | ||
case KEY.UP: | ||
this._keyUp(e); | ||
break; | ||
this.trigger('itemSelected', data, item); | ||
} | ||
}, | ||
// bottom arrow | ||
case KEY.DOWN: | ||
this._keyDown(e); | ||
break; | ||
_handleFocus: function () { | ||
this._isOpen = true; | ||
}, | ||
// left arrow | ||
case KEY.LEFT: | ||
// right arrow | ||
case KEY.RIGHT: | ||
break; | ||
// enter | ||
case KEY.ENTER: | ||
this._keyEnter(e); | ||
break; | ||
// default 继续执行 keyup | ||
default: | ||
this._keyupStart = 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); | ||
} | ||
}, | ||
_keyUp: function (e) { | ||
_handleMouseDown: function (e) { | ||
if (IE678) { | ||
var trigger = this.input.get('element')[0]; | ||
trigger.onbeforedeactivate = function () { | ||
window.event.returnValue = false; | ||
trigger.onbeforedeactivate = null; | ||
}; | ||
} | ||
e.preventDefault(); | ||
var data = this.get('data'); | ||
if (data && data.length) { | ||
if (!this.get('visible')) { | ||
this.show(); | ||
return; | ||
} | ||
this._step(-1); | ||
} | ||
}, | ||
_keyDown: function (e) { | ||
_handleStep: function (e) { | ||
e.preventDefault(); | ||
var data = this.get('data'); | ||
if (data && data.length) { | ||
if (!this.get('visible')) { | ||
this.show(); | ||
return; | ||
} | ||
this._step(1); | ||
} | ||
this.get('visible') && this._step(e.type === 'keyUp' ? -1 : 1); | ||
}, | ||
_keyEnter: function (e) { | ||
if (this.get('visible')) { | ||
this.selectItem(); | ||
_handleQueryChange: function (val, prev) { | ||
if (this.get('disabled')) return; | ||
// 是否阻止回车提交表单 | ||
if (!this.get('submitOnEnter')) { | ||
e.preventDefault(); | ||
} | ||
} | ||
this.dataSource.abort(); | ||
this.dataSource.getData(val); | ||
}, | ||
@@ -439,6 +313,5 @@ | ||
_step: function (direction) { | ||
if (!this.items) return; | ||
var currentIndex = this.get('selectedIndex'); | ||
if (direction === -1) { // 反向 | ||
if (currentIndex > 0) { | ||
if (currentIndex > -1) { | ||
this.set('selectedIndex', currentIndex - 1); | ||
@@ -452,3 +325,3 @@ } else { | ||
} else { | ||
this.set('selectedIndex', 0); | ||
this.set('selectedIndex', -1); | ||
} | ||
@@ -463,5 +336,14 @@ } | ||
delete this.lastIndex; | ||
delete this.currentItem; | ||
}, | ||
_hide: function () { | ||
this._isOpen = false; | ||
AutoComplete.superclass.hide.call(this); | ||
}, | ||
_isEmpty: function () { | ||
var data = this.get('data'); | ||
return !(data && data.length > 0); | ||
}, | ||
// 调整 align 属性的默认值 | ||
@@ -473,12 +355,6 @@ _tweakAlignDefaultValue: function () { | ||
} | ||
}); | ||
// 以便写测试用例 | ||
AutoComplete._filter = Filter; | ||
module.exports = AutoComplete; | ||
function isString(str) { | ||
@@ -488,2 +364,6 @@ return Object.prototype.toString.call(str) === '[object String]'; | ||
function isObject(obj) { | ||
return Object.prototype.toString.call(obj) === '[object Object]'; | ||
} | ||
// 通过 locator 找到 data 中的某个属性的值 | ||
@@ -500,20 +380,18 @@ // 1. locator 支持 function,函数返回值为结果 | ||
function locateResult(locator, data) { | ||
if (!locator) { | ||
return data; | ||
} | ||
if ($.isFunction(locator)) { | ||
return locator.call(this, data); | ||
} else if (isString(locator)) { | ||
var s = locator.split('.'), | ||
p = data; | ||
while (s.length) { | ||
var v = s.shift(); | ||
if (!p[v]) { | ||
break; | ||
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]; | ||
} | ||
p = p[v]; | ||
return p; | ||
} | ||
return p; | ||
} | ||
@@ -523,6 +401,75 @@ return data; | ||
function highlightItem(classPrefix, matchKey) { | ||
// 标准格式,不匹配则忽略 | ||
// | ||
// { | ||
// 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 = matchKey || this.matchKey || '', | ||
v = label || this.label || '', | ||
h = ''; | ||
@@ -545,3 +492,4 @@ if ($.isArray(index)) { | ||
if (start < v.length) { | ||
h += '<span class="' + classPrefix + '-item-hl">' + v.substr(start, length) + '</span>'; | ||
var className = classPrefix ? ('class="' + classPrefix + '-item-hl"') : ''; | ||
h += '<span ' + className + '>' + v.substr(start, length) + '</span>'; | ||
} | ||
@@ -548,0 +496,0 @@ cursor = start + length; |
@@ -53,3 +53,3 @@ var Base = require('arale-base'); | ||
}; | ||
var url = this.get('source').replace(/{{(.*?)}}/g, function (all, match) { | ||
var url = this.get('source').replace(/\{\{(.*?)\}\}/g, function (all, match) { | ||
return obj[match]; | ||
@@ -100,3 +100,2 @@ }); | ||
function done(data) { | ||
@@ -112,3 +111,2 @@ that._done(data); | ||
module.exports = DataSource; | ||
@@ -115,0 +113,0 @@ |
var $ = require('spm-jquery'); | ||
var Filter = { | ||
'default': function (data, query, options) { | ||
var result = []; | ||
$.each(data, function (index, item) { | ||
var o = {}, | ||
matchKey = getMatchKey(item, options); | ||
if ($.isPlainObject(item)) { | ||
o = $.extend({}, item); | ||
} | ||
o.matchKey = matchKey; | ||
result.push(o); | ||
}); | ||
return result; | ||
'default': function (data) { | ||
return data; | ||
}, | ||
// options: { | ||
// key: 'value' | ||
// } | ||
'startsWith': function (data, query, options) { | ||
'startsWith': function (data, query) { | ||
query = query || ''; | ||
var result = [], | ||
@@ -29,22 +17,16 @@ l = query.length, | ||
$.each(data, function (index, item) { | ||
var o = {}, | ||
matchKey = getMatchKey(item, options); | ||
var a, matchKeys = [item.value].concat(item.alias); | ||
if ($.isPlainObject(item)) { | ||
o = $.extend({}, item); | ||
} | ||
// 生成 item | ||
// { | ||
// ... // self property | ||
// matchKey: '', // 匹配的内容 | ||
// highlightIndex: [] // 高亮的索引 | ||
// } | ||
if (reg.test(matchKey)) { | ||
o.matchKey = matchKey; | ||
if (l > 0) { | ||
o.highlightIndex = [ | ||
[0, l] | ||
]; | ||
// 匹配 value 和 alias 中的 | ||
while (a = matchKeys.shift()) { | ||
if (reg.test(a)) { | ||
// 匹配和显示相同才有必要高亮 | ||
if (item.label === a) { | ||
item.highlightIndex = [ | ||
[0, l] | ||
]; | ||
} | ||
result.push(item); | ||
break; | ||
} | ||
result.push(o); | ||
} | ||
@@ -56,3 +38,3 @@ }); | ||
'stringMatch': function (data, query, options) { | ||
'stringMatch': function (data, query) { | ||
query = query || ''; | ||
@@ -65,14 +47,15 @@ var result = [], | ||
$.each(data, function (index, item) { | ||
var o = {}, | ||
matchKey = getMatchKey(item, options); | ||
var a, matchKeys = [item.value].concat(item.alias); | ||
if ($.isPlainObject(item)) { | ||
o = $.extend({}, item); | ||
// 匹配 value 和 alias 中的 | ||
while (a = matchKeys.shift()) { | ||
if (a.indexOf(query) > -1) { | ||
// 匹配和显示相同才有必要高亮 | ||
if (item.label === a) { | ||
item.highlightIndex = stringMatch(a, query); | ||
} | ||
result.push(item); | ||
break; | ||
} | ||
} | ||
if (matchKey.indexOf(query) > -1) { | ||
o.matchKey = matchKey; | ||
o.highlightIndex = stringMatch(matchKey, query); | ||
result.push(o); | ||
} | ||
}); | ||
@@ -85,10 +68,7 @@ return result; | ||
function getMatchKey(item, options) { | ||
if ($.isPlainObject(item)) { | ||
// 默认取对象的 value 属性 | ||
var key = (options && options.key) || 'value'; | ||
return item[key] || ''; | ||
} else { | ||
return item; | ||
} | ||
// 转义正则关键字 | ||
var keyword = /(\[|\[|\]|\^|\$|\||\(|\)|\{|\}|\+|\*|\?|\\)/g; | ||
function escapeKeyword(str) { | ||
return (str || '').replace(keyword, '\\$1'); | ||
} | ||
@@ -103,3 +83,3 @@ | ||
var v = a[i]; | ||
if (v == q[queryIndex]) { | ||
if (v === q[queryIndex]) { | ||
if (queryIndex === q.length - 1) { | ||
@@ -116,9 +96,2 @@ r.push([i - q.length + 1, i + 1]); | ||
return r; | ||
} | ||
// 转义正则关键字 | ||
var keyword = /(\[|\[|\]|\^|\$|\||\(|\)|\{|\}|\+|\*|\?|\\)/g; | ||
function escapeKeyword(str) { | ||
return (str || '').replace(keyword, '\\$1'); | ||
} |
var sinon = require('spm-sinon'); | ||
var expect = require('spm-expect.js'); | ||
var Filter = require('../src/filter'); | ||
var AutoComplete = require('../src/autocomplete'); | ||
var $ = require('spm-jquery'); | ||
var isIE = (window.navigator.userAgent || "").toLowerCase().indexOf("msie") !== -1; | ||
require('alice-select'); | ||
AutoComplete._filter.test = function () { | ||
Filter.test = function () { | ||
return []; | ||
@@ -35,3 +36,5 @@ }; | ||
expect(ac.get('data')).to.eql([{ | ||
matchKey: 'abc', | ||
label: 'abc', | ||
value: 'abc', | ||
alias: [], | ||
highlightIndex: [ | ||
@@ -42,3 +45,5 @@ [0, 1] | ||
{ | ||
matchKey: 'abd', | ||
label: 'abd', | ||
value: 'abd', | ||
alias: [], | ||
highlightIndex: [ | ||
@@ -61,23 +66,5 @@ [0, 1] | ||
expect(ac.get('visiable')).not.to.be.ok(); | ||
expect(ac.get('inputValue')).to.be(''); | ||
expect(ac.input.getValue()).to.be(''); | ||
}); | ||
it('should not call "getData" when empty', function () { | ||
var input = $('#test'); | ||
input.val('a'); | ||
ac = new AutoComplete({ | ||
trigger: '#test', | ||
dataSource: ['abc', 'abd', 'cbd'] | ||
}).render(); | ||
var getData = sinon.spy(ac.dataSource, 'getData'); | ||
ac.setInputValue(''); | ||
expect(getData.called).not.to.be(true); | ||
ac.setInputValue('a'); | ||
expect(getData.called).to.be(true); | ||
getData.restore(); | ||
}); | ||
it('render', function () { | ||
@@ -91,59 +78,9 @@ ac = new AutoComplete({ | ||
expect(ac.items.eq(0).data('value')).to.be('abc'); | ||
expect(ac.items.eq(0).text()).to.be('abc'); | ||
expect(ac.items.eq(0).find('.ui-autocomplete-item-hl').text()).to.be('a'); | ||
expect(ac.items.eq(1).data('value')).to.be('abd'); | ||
expect(ac.items.eq(1).text()).to.be('abd'); | ||
expect(ac.items.eq(1).find('.ui-autocomplete-item-hl').text()).to.be('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('inputValue', function () { | ||
it('should be called when value changed', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
trigger: '#test', | ||
dataSource: ['abc', 'abd', 'cbd'] | ||
}).render(); | ||
var spy = sinon.spy(ac, '_onRenderInputValue'); | ||
ac.setInputValue('a'); | ||
expect(spy.withArgs('a').calledOnce).to.be(true); | ||
ac._keyupEvent.call(ac); | ||
expect(spy.calledOnce).to.be(true); | ||
ac.setInputValue('ab'); | ||
expect(spy.withArgs('ab').calledOnce).to.be(true); | ||
ac.setInputValue('a'); | ||
expect(spy.withArgs('a').calledTwice).to.be(true); | ||
spy.restore(); | ||
}); | ||
it('should filter the input', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
trigger: '#test', | ||
inputFilter: function (val) { | ||
return 'filter-' + val; | ||
}, | ||
dataSource: ['abc', 'abd', 'cbd'] | ||
}).render(); | ||
var spy = sinon.spy(ac.dataSource, 'getData'); | ||
ac.setInputValue('a'); | ||
expect(spy.withArgs('filter-a').calledOnce).to.be(true); | ||
ac.setInputValue(''); | ||
expect(spy.calledOnce).to.be(true); | ||
spy.restore(); | ||
}); | ||
}); | ||
describe('data locator', function () { | ||
it('should support string', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
@@ -159,3 +96,5 @@ trigger: '#test', | ||
expect(ac.get('data')).to.eql([{ | ||
matchKey: 'abc', | ||
label: 'abc', | ||
value: 'abc', | ||
alias: [], | ||
highlightIndex: [ | ||
@@ -166,3 +105,5 @@ [0, 1] | ||
{ | ||
matchKey: 'abd', | ||
label: 'abd', | ||
value: 'abd', | ||
alias: [], | ||
highlightIndex: [ | ||
@@ -175,3 +116,2 @@ [0, 1] | ||
it('should support dot string', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
@@ -189,3 +129,5 @@ trigger: '#test', | ||
expect(ac.get('data')).to.eql([{ | ||
matchKey: 'abc', | ||
label: 'abc', | ||
value: 'abc', | ||
alias: [], | ||
highlightIndex: [ | ||
@@ -196,3 +138,5 @@ [0, 1] | ||
{ | ||
matchKey: 'abd', | ||
label: 'abd', | ||
value: 'abd', | ||
alias: [], | ||
highlightIndex: [ | ||
@@ -205,3 +149,2 @@ [0, 1] | ||
it('should support function', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
@@ -220,3 +163,5 @@ trigger: '#test', | ||
expect(ac.get('data')).to.eql([{ | ||
matchKey: 'abc', | ||
label: 'abc', | ||
value: 'abc', | ||
alias: [], | ||
highlightIndex: [ | ||
@@ -227,3 +172,5 @@ [0, 1] | ||
{ | ||
matchKey: 'abd', | ||
label: 'abd', | ||
value: 'abd', | ||
alias: [], | ||
highlightIndex: [ | ||
@@ -234,6 +181,19 @@ [0, 1] | ||
}); | ||
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([]); | ||
}); | ||
}); | ||
it('should be hide when trigger blur #26', function () { | ||
if (isIE) return; | ||
if ($.browser.msie) return; | ||
var input = $('#test'); | ||
@@ -251,3 +211,2 @@ ac = new AutoComplete({ | ||
it('should be hide when mousedown #26', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
@@ -258,3 +217,3 @@ trigger: '#test', | ||
ac.setInputValue('a'); | ||
ac.items.eq(0).mousedown(); | ||
ac.items.eq(0).click(); | ||
@@ -265,6 +224,5 @@ expect(ac.get('visible')).not.to.be.ok(); | ||
it('should not be hide when mousedown #26', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
trigger: '#test', | ||
template: '<div><p data-role="other">a</p><ul data-role="items">{{#each items}}<li data-role="item">{{matchKey}}</li>{{/each}}</ul></div>', | ||
template: '<div><p data-role="other">a</p><ul data-role="items">{{#each items}}<li data-role="item">{{label}}</li>{{/each}}</ul></div>', | ||
dataSource: ['abc'] | ||
@@ -281,3 +239,2 @@ }).render(); | ||
it('should be "startsWith" by default', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
@@ -287,10 +244,5 @@ trigger: '#test', | ||
}); | ||
var filter = ac.get('filter'); | ||
expect(filter.name).to.eql('startsWith'); | ||
expect(filter.options.key).to.eql("value"); | ||
expect(ac.get('filter')).to.eql(Filter['startsWith']); | ||
}); | ||
it('should be "default" when ajax by default', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
@@ -300,7 +252,5 @@ trigger: '#test', | ||
}); | ||
expect(ac.get('filter').name).to.eql('default'); | ||
expect(ac.get('filter')).to.eql(Filter['default']); | ||
}); | ||
it('should be "default" when "", null, false', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
@@ -312,6 +262,5 @@ trigger: '#test', | ||
expect(ac.get('filter').name).to.eql('default'); | ||
expect(ac.get('filter')).to.eql(Filter['default']); | ||
}); | ||
it('should support string', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
@@ -323,7 +272,6 @@ trigger: '#test', | ||
expect(ac.get('filter').name).to.eql('test'); | ||
expect(ac.get('filter')).to.eql(Filter.test); | ||
}); | ||
it('should support string but not exist', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
@@ -335,6 +283,5 @@ trigger: '#test', | ||
expect(ac.get('filter').name).to.eql('default'); | ||
expect(ac.get('filter')).to.eql(Filter['default']); | ||
}); | ||
it('should support function', function () { | ||
var input = $('#test'); | ||
var func = function () {}; | ||
@@ -347,45 +294,19 @@ ac = new AutoComplete({ | ||
expect(ac.get('filter')).to.eql({ | ||
func: func | ||
}); | ||
expect(ac.get('filter')).to.eql(func); | ||
}); | ||
it('should support object', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
trigger: '#test', | ||
filter: { | ||
name: 'startsWith', | ||
options: { | ||
key: 'title' | ||
} | ||
}, | ||
dataSource: [] | ||
}); | ||
expect(ac.get('filter').name).to.eql('startsWith'); | ||
}); | ||
it('should support object but not exist', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
trigger: '#test', | ||
filter: { | ||
name: 'notExist' | ||
}, | ||
filter: 'notExist', | ||
dataSource: [] | ||
}); | ||
expect(ac.get('filter').name).to.eql('default'); | ||
expect(ac.get('filter')).to.eql(Filter['default']); | ||
}); | ||
it('should be called with 3 param', function () { | ||
var input = $('#test'); | ||
var spy = sinon.spy(); | ||
AutoComplete._filter.filter = spy; | ||
Filter.filter = spy; | ||
ac = new AutoComplete({ | ||
trigger: '#test', | ||
filter: { | ||
name: 'filter', | ||
options: { | ||
key: 'value' | ||
} | ||
}, | ||
filter: 'filter', | ||
dataSource: ['abc'] | ||
@@ -395,5 +316,8 @@ }).render(); | ||
ac.setInputValue('a'); | ||
expect(spy.withArgs(['abc'], 'a', { | ||
key: 'value' | ||
}).called).to.be(true); | ||
var data = [{ | ||
label: 'abc', | ||
value: 'abc', | ||
alias: [] | ||
}]; | ||
expect(spy.withArgs(data, 'a').called).to.be.ok(); | ||
}); | ||
@@ -404,25 +328,29 @@ }); | ||
var input = $('#test'), | ||
beCalled = false; | ||
spy = sinon.spy(); | ||
ac = new AutoComplete({ | ||
trigger: '#test', | ||
dataSource: ['abc', 'abd', 'cbd'] | ||
}).on('itemSelect', function () { | ||
beCalled = true; | ||
}).render(); | ||
}).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.get('inputValue')).to.be('abc'); | ||
expect(beCalled).to.be.ok(); | ||
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(); | ||
}); | ||
it('highlight item', function () { | ||
var input = $('#test'), | ||
item; | ||
var item; | ||
ac = new AutoComplete({ | ||
trigger: '#test', | ||
html: '{{{highlightItem label}}}', | ||
dataSource: ['abc', 'abd', 'cbd'] | ||
@@ -432,3 +360,3 @@ }).render(); | ||
ac.set('data', [{ | ||
matchKey: 'abcdefg', | ||
label: 'abcdefg', | ||
highlightIndex: [ | ||
@@ -438,9 +366,8 @@ [0, 1] | ||
}]); | ||
item = ac.$('[data-role=item]').eq(0).find('.ui-autocomplete-item-hl'); | ||
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'); | ||
delete ac.oldInput; | ||
ac.set('data', [{ | ||
matchKey: 'abcdefg', | ||
label: 'abcdefg', | ||
highlightIndex: [ | ||
@@ -451,10 +378,9 @@ [1, 2], | ||
}]); | ||
item = ac.$('[data-role=item]').eq(0).find('.ui-autocomplete-item-hl'); | ||
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'); | ||
delete ac.oldInput; | ||
ac.set('data', [{ | ||
matchKey: 'abcdefg', | ||
label: 'abcdefg', | ||
highlightIndex: [ | ||
@@ -466,30 +392,33 @@ [0, 1], | ||
}]); | ||
item = ac.$('[data-role=item]').eq(0).find('.ui-autocomplete-item-hl'); | ||
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'); | ||
delete ac.oldInput; | ||
ac.set('data', [{ | ||
matchKey: 'abcdefg', | ||
label: 'abcdefg', | ||
highlightIndex: [1, 4] | ||
}]); | ||
item = ac.$('[data-role=item]').eq(0).find('.ui-autocomplete-item-hl'); | ||
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'); | ||
delete ac.oldInput; | ||
ac.set('data', [{ | ||
matchKey: 'abcdefg', | ||
label: 'abcdefg', | ||
highlightIndex: [6, 8] | ||
}]); | ||
item = ac.$('[data-role=item]').eq(0).find('.ui-autocomplete-item-hl'); | ||
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'); | ||
delete ac.oldInput; | ||
ac.set('data', [{ | ||
label: 'abcdefg', | ||
highlightIndex: 0 | ||
}]); | ||
item = ac.$('.ui-select-item-hl'); | ||
expect(item.length).to.be(0); | ||
}); | ||
it('clear', function () { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
@@ -511,3 +440,2 @@ trigger: '#test', | ||
it('do not show when async #14', function (done) { | ||
var input = $('#test'); | ||
ac = new AutoComplete({ | ||
@@ -525,7 +453,5 @@ trigger: '#test', | ||
}, | ||
error: function (callback) {} | ||
error: function () {} | ||
}); | ||
var t = ac.element.html(); | ||
ac.setInputValue('a'); | ||
@@ -571,2 +497,106 @@ | ||
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('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'] | ||
}]); | ||
}); | ||
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); | ||
triggerKeyEvent(input, 40); | ||
expect(ac.get('selectedIndex')).to.be(-1); | ||
triggerKeyEvent(input, 40); | ||
expect(ac.get('selectedIndex')).to.be(0); | ||
triggerKeyEvent(input, 38); | ||
expect(ac.get('selectedIndex')).to.be(-1); | ||
triggerKeyEvent(input, 38); | ||
expect(ac.get('selectedIndex')).to.be(1); | ||
}); | ||
it('input focus', function () { | ||
ac = new AutoComplete({ | ||
trigger: '#test', | ||
dataSource: ['abc', 'abd', 'cbd'] | ||
}).render(); | ||
input.focus(); | ||
expect(ac._isOpen).to.be.ok(); | ||
}); | ||
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(); | ||
}); | ||
it('should auto scroll #82', function () { | ||
@@ -576,7 +606,5 @@ ac = new AutoComplete({ | ||
dataSource: ['abc', 'abd', 'abe', 'acd', 'ace', 'acf', 'acg', 'ach', 'aci', 'acj', 'ack'], | ||
style: { | ||
'overflow': 'scroll' | ||
}, | ||
height: 120 | ||
height: 123 | ||
}).render(); | ||
ac.element.children().css('overflow', 'scroll'); | ||
@@ -586,13 +614,18 @@ ac.setInputValue('a'); | ||
var content = ac.element.children(); | ||
ac._step(1); | ||
expect(ac.element.scrollTop()).to.be(0); | ||
expect(content.scrollTop()).to.be(0); | ||
ac._step(1); | ||
expect(ac.element.scrollTop()).to.be(0); | ||
expect(content.scrollTop()).to.be(0); | ||
ac._step(1); | ||
expect(ac.element.scrollTop()).to.be(0); | ||
expect(content.scrollTop()).to.be(0); | ||
ac._step(1); | ||
expect(ac.element.scrollTop()).to.be(0); | ||
ac._step(1); | ||
expect(ac.element.scrollTop()).to.be(30); | ||
expect(content.scrollTop()).to.be(41); | ||
}); | ||
}); | ||
}); | ||
function triggerKeyEvent(el, keyCode) { | ||
var e = $.Event('keydown.autocomplete'); | ||
e.which = keyCode; | ||
el.trigger(e); | ||
} |
@@ -50,6 +50,6 @@ var sinon = require('spm-sinon'); | ||
var param = [1, 2, 3]; | ||
var source = new DataSource({ | ||
new DataSource({ | ||
source: param | ||
}).on('data', spy).getData(); | ||
expect(spy.withArgs(param).called).to.be(true); | ||
expect(spy.withArgs(param).called).to.be.ok(); | ||
}); | ||
@@ -62,6 +62,6 @@ | ||
}; | ||
var source = new DataSource({ | ||
new DataSource({ | ||
source: param | ||
}).on('data', spy).getData(); | ||
expect(spy.withArgs(param).called).to.be(true); | ||
expect(spy.withArgs(param).called).to.be.ok(); | ||
}); | ||
@@ -71,3 +71,3 @@ | ||
var spy = sinon.spy(); | ||
var source = new DataSource({ | ||
new DataSource({ | ||
source: function (q) { | ||
@@ -78,3 +78,3 @@ return [ | ||
}).on('data', spy).getData('a'); | ||
expect(spy.withArgs(['a@163.com']).called).to.be(true); | ||
expect(spy.withArgs(['a@163.com']).called).to.be.ok(); | ||
}); | ||
@@ -84,8 +84,8 @@ | ||
var spy = sinon.spy(); | ||
var source = new DataSource({ | ||
source: function (q) { | ||
new DataSource({ | ||
source: function () { | ||
return false; | ||
} | ||
}).on('data', spy).getData('a'); | ||
expect(spy.called).not.to.be(true); | ||
expect(spy.called).not.to.be.ok(); | ||
}); | ||
@@ -95,3 +95,3 @@ | ||
var spy = sinon.spy(); | ||
var source = new DataSource({ | ||
new DataSource({ | ||
source: function (q, done) { | ||
@@ -106,3 +106,3 @@ setTimeout(function () { | ||
setTimeout(function () { | ||
expect(spy.called).to.be(true); | ||
expect(spy.called).to.be.ok(); | ||
done(); | ||
@@ -119,10 +119,10 @@ }, 500); | ||
}, | ||
error: function (callback) {} | ||
error: function () {} | ||
}); | ||
var source = new DataSource({ | ||
new DataSource({ | ||
source: './test.json?q={{query}}' | ||
}).on('data', spy).getData('a'); | ||
//expect(stub.called).to.be(true); | ||
expect(spy.withArgs([1, 2, 3]).called).to.be(true); | ||
expect(stub.calledWithMatch('./test.json?q=a')).to.be.ok(); | ||
expect(spy.withArgs([1, 2, 3]).called).to.be.ok(); | ||
stub.restore(); | ||
@@ -134,3 +134,3 @@ }); | ||
var stub = sinon.stub($, 'ajax').returns({ | ||
success: function (callback) { | ||
success: function () { | ||
return this; | ||
@@ -144,6 +144,6 @@ }, | ||
var source = new DataSource({ | ||
new DataSource({ | ||
source: './test.json?q={{query}}' | ||
}).on('data', spy).getData('a'); | ||
expect(spy.withArgs({}).called).to.be(true); | ||
expect(spy.withArgs({}).called).to.be.ok(); | ||
stub.restore(); | ||
@@ -159,3 +159,3 @@ }); | ||
}, | ||
error: function (callback) { | ||
error: function () { | ||
return this; | ||
@@ -183,3 +183,3 @@ } | ||
setTimeout(function () { | ||
expect(spy.calledOnce).to.be(true); | ||
expect(spy.calledOnce).to.be.ok(); | ||
stub.restore(); | ||
@@ -186,0 +186,0 @@ done(); |
@@ -5,77 +5,82 @@ var expect = require('spm-expect.js'); | ||
describe('Filter', function () { | ||
var data; | ||
beforeEach(function () { | ||
data = ['about', | ||
{ | ||
value: 'abuse abbess' | ||
}, | ||
{ | ||
title: 'absolute abbey' | ||
}, 'but', 'buffer']; | ||
}); | ||
afterEach(function () { | ||
data = null; | ||
}); | ||
describe('default', function () { | ||
it('return all', function () { | ||
var result = Filter['default'](data); | ||
expect(result).to.eql([{ | ||
matchKey: 'about' | ||
describe('startsWith', function () { | ||
var data; | ||
beforeEach(function () { | ||
data = [{ | ||
label: 'aa1', | ||
value: 'aa2', | ||
alias: [] | ||
}, | ||
{ | ||
matchKey: 'abuse abbess', | ||
value: 'abuse abbess' | ||
label: 'ba1', | ||
value: 'ba2', | ||
alias: [] | ||
}, | ||
{ | ||
matchKey: '', | ||
title: 'absolute abbey' | ||
label: 'ac1', | ||
value: 'ac2', | ||
alias: [] | ||
}, | ||
{ | ||
matchKey: 'but' | ||
label: 'bc1', | ||
value: 'bc2', | ||
alias: [], | ||
other: 'bc2' | ||
}, | ||
{ | ||
matchKey: 'buffer' | ||
}]); | ||
label: 'ad1', | ||
value: 'ad2', | ||
alias: [] | ||
}, | ||
{ | ||
label: 'ae1', | ||
value: 'ae2', | ||
alias: ['be'] | ||
}]; | ||
}); | ||
afterEach(function () { | ||
data = null; | ||
}); | ||
it('return all when set option key', function () { | ||
var result = Filter['default'](data, 'a', { | ||
key: 'title' | ||
}); | ||
it('start width a', function () { | ||
var result = Filter.startsWith(data, 'a'); | ||
expect(result).to.eql([{ | ||
matchKey: 'about' | ||
label: 'aa1', | ||
value: 'aa2', | ||
alias: [] | ||
}, | ||
{ | ||
matchKey: '', | ||
value: 'abuse abbess' | ||
label: 'ac1', | ||
value: 'ac2', | ||
alias: [] | ||
}, | ||
{ | ||
matchKey: 'absolute abbey', | ||
title: 'absolute abbey' | ||
label: 'ad1', | ||
value: 'ad2', | ||
alias: [] | ||
}, | ||
{ | ||
matchKey: 'but' | ||
}, | ||
{ | ||
matchKey: 'buffer' | ||
label: 'ae1', | ||
value: 'ae2', | ||
alias: ['be'] | ||
}]); | ||
}); | ||
}); | ||
describe('startsWith', function () { | ||
it('start width a', function () { | ||
var result = Filter.startsWith(data, 'a'); | ||
it('start width b', function () { | ||
var result = Filter.startsWith(data, 'b'); | ||
expect(result).to.eql([{ | ||
matchKey: 'about', | ||
highlightIndex: [ | ||
[0, 1] | ||
] | ||
label: 'ba1', | ||
value: 'ba2', | ||
alias: [] | ||
}, | ||
{ | ||
matchKey: 'abuse abbess', | ||
value: 'abuse abbess', | ||
highlightIndex: [ | ||
[0, 1] | ||
] | ||
label: 'bc1', | ||
value: 'bc2', | ||
alias: [], | ||
other: 'bc2' | ||
}, | ||
{ | ||
label: 'ae1', | ||
value: 'ae2', | ||
alias: ['be'] | ||
}]); | ||
@@ -93,41 +98,76 @@ }); | ||
}); | ||
}); | ||
it('option key is title', function () { | ||
var result = Filter.startsWith(data, 'a', { | ||
key: 'title' | ||
}); | ||
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([{ | ||
matchKey: 'about', | ||
highlightIndex: [ | ||
[0, 1] | ||
] | ||
label: 'abc1', | ||
value: 'abc', | ||
alias: [] | ||
}, | ||
{ | ||
matchKey: 'absolute abbey', | ||
title: 'absolute abbey', | ||
highlightIndex: [ | ||
[0, 1] | ||
] | ||
label: 'bcd1', | ||
value: 'bcd', | ||
alias: [] | ||
}, | ||
{ | ||
label: 'dce1', | ||
value: 'dce', | ||
alias: ['bcd'] | ||
}]); | ||
}); | ||
}); | ||
describe('stringMatch', function () { | ||
it('match a', function () { | ||
var result = Filter.stringMatch(data, 'ab', { | ||
key: 'title' | ||
}); | ||
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([{ | ||
matchKey: 'about', | ||
label: 'abc', | ||
value: 'abc', | ||
alias: [], | ||
highlightIndex: [ | ||
[0, 2] | ||
[1, 3] | ||
] | ||
}, | ||
{ | ||
matchKey: 'absolute abbey', | ||
title: 'absolute abbey', | ||
label: 'bcd', | ||
value: 'bcd', | ||
alias: [], | ||
highlightIndex: [ | ||
[0, 2], | ||
[9, 11] | ||
[0, 2] | ||
] | ||
}, | ||
{ | ||
label: 'dce', | ||
value: 'dce', | ||
alias: ['bcd'] | ||
}]); | ||
@@ -134,0 +174,0 @@ }); |
var expect = require('spm-expect.js'); | ||
var $ = require('spm-jquery'); | ||
var AutoComplete = require('../src/autocomplete'); | ||
var Filter = require('../src/filter'); | ||
describe('Issue', function () { | ||
it('#56 start with (', function () { | ||
var data = ['about', | ||
{ | ||
value: 'abuse' | ||
var data = [{ | ||
label: 'about1', | ||
value: 'about', | ||
alias: [] | ||
}, | ||
{ | ||
title: 'absolute' | ||
}, 'but', 'buffer', '(abc']; | ||
var result = AutoComplete._filter.startsWith(data, '(a'); | ||
label: '(abc1', | ||
value: '(abc', | ||
alias: [] | ||
}]; | ||
var result = Filter.startsWith(data, '(a'); | ||
expect(result).to.eql([{ | ||
matchKey: '(abc', | ||
highlightIndex: [ | ||
[0, 2] | ||
] | ||
label: '(abc1', | ||
value: '(abc', | ||
alias: [] | ||
}]); | ||
}); | ||
it('#55 dont\'t change selectedIndex when hover', function () { | ||
xit('#55 dont\'t change selectedIndex when hover', function () { | ||
var ac, input = $('<input id="test" type="text" value="" />').appendTo(document.body); | ||
@@ -25,0 +28,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1172742
152
26444
4
1
262
8
4