Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

arale-autocomplete

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

arale-autocomplete - npm Package Compare versions

Comparing version 1.2.6 to 1.4.0

docs/input.md

2

docs/data-source.md

@@ -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",

# 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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc