mirror of https://github.com/abpframework/eventhub
62 changed files with 4322 additions and 2 deletions
@ -0,0 +1,14 @@ |
|||
engines: |
|||
eslint: |
|||
enabled: true |
|||
fixme: |
|||
enabled: true |
|||
ratings: |
|||
paths: |
|||
- "**.js" |
|||
exclude_paths: |
|||
- "**.min.js" |
|||
- karma*.conf.js |
|||
- prism/**/* |
|||
- test/**/* |
|||
- .* |
|||
@ -0,0 +1,254 @@ |
|||
ecmaFeatures: {} |
|||
rules: |
|||
no-alert: 0 |
|||
no-array-constructor: 0 |
|||
no-bitwise: 0 |
|||
no-caller: 0 |
|||
no-catch-shadow: 0 |
|||
no-class-assign: 0 |
|||
no-cond-assign: 2 |
|||
no-console: 2 |
|||
no-const-assign: 0 |
|||
no-constant-condition: 2 |
|||
no-continue: 0 |
|||
no-control-regex: 2 |
|||
no-debugger: 2 |
|||
no-delete-var: 2 |
|||
no-div-regex: 0 |
|||
no-dupe-keys: 2 |
|||
no-dupe-args: 2 |
|||
no-duplicate-case: 2 |
|||
no-else-return: 0 |
|||
no-empty: 2 |
|||
no-empty-character-class: 2 |
|||
no-empty-label: 0 |
|||
no-eq-null: 0 |
|||
no-eval: 0 |
|||
no-ex-assign: 2 |
|||
no-extend-native: 0 |
|||
no-extra-bind: 0 |
|||
no-extra-boolean-cast: 2 |
|||
no-extra-parens: 0 |
|||
no-extra-semi: 2 |
|||
no-fallthrough: 2 |
|||
no-floating-decimal: 0 |
|||
no-func-assign: 2 |
|||
no-implicit-coercion: 0 |
|||
no-implied-eval: 0 |
|||
no-inline-comments: 0 |
|||
no-inner-declarations: |
|||
- 2 |
|||
- functions |
|||
no-invalid-regexp: 2 |
|||
no-invalid-this: 0 |
|||
no-irregular-whitespace: 2 |
|||
no-iterator: 0 |
|||
no-label-var: 0 |
|||
no-labels: 0 |
|||
no-lone-blocks: 0 |
|||
no-lonely-if: 0 |
|||
no-loop-func: 0 |
|||
no-mixed-requires: |
|||
- 0 |
|||
- false |
|||
no-mixed-spaces-and-tabs: |
|||
- 2 |
|||
- smart-tabs |
|||
linebreak-style: |
|||
- 0 |
|||
- unix |
|||
no-multi-spaces: 0 |
|||
no-multi-str: 0 |
|||
no-multiple-empty-lines: |
|||
- 0 |
|||
- max: 2 |
|||
no-native-reassign: 0 |
|||
no-negated-in-lhs: 2 |
|||
no-nested-ternary: 0 |
|||
no-new: 0 |
|||
no-new-func: 0 |
|||
no-new-object: 0 |
|||
no-new-require: 0 |
|||
no-new-wrappers: 0 |
|||
no-obj-calls: 2 |
|||
no-octal: 2 |
|||
no-octal-escape: 0 |
|||
no-param-reassign: 0 |
|||
no-path-concat: 0 |
|||
no-plusplus: 0 |
|||
no-process-env: 0 |
|||
no-process-exit: 0 |
|||
no-proto: 0 |
|||
no-redeclare: 2 |
|||
no-regex-spaces: 2 |
|||
no-reserved-keys: 0 |
|||
no-restricted-modules: 0 |
|||
no-return-assign: 0 |
|||
no-script-url: 0 |
|||
no-self-compare: 0 |
|||
no-sequences: 0 |
|||
no-shadow: 0 |
|||
no-shadow-restricted-names: 0 |
|||
no-spaced-func: 0 |
|||
no-sparse-arrays: 2 |
|||
no-sync: 0 |
|||
no-ternary: 0 |
|||
no-trailing-spaces: 0 |
|||
no-this-before-super: 0 |
|||
no-throw-literal: 0 |
|||
no-undef: 2 |
|||
no-undef-init: 0 |
|||
no-undefined: 0 |
|||
no-unexpected-multiline: 0 |
|||
no-underscore-dangle: 0 |
|||
no-unneeded-ternary: 0 |
|||
no-unreachable: 2 |
|||
no-unused-expressions: 0 |
|||
no-unused-vars: |
|||
- 2 |
|||
- vars: all |
|||
args: after-used |
|||
no-use-before-define: 0 |
|||
no-useless-call: 0 |
|||
no-void: 0 |
|||
no-var: 0 |
|||
no-warning-comments: |
|||
- 0 |
|||
- terms: |
|||
- todo |
|||
- fixme |
|||
- xxx |
|||
location: start |
|||
no-with: 0 |
|||
array-bracket-spacing: |
|||
- 0 |
|||
- never |
|||
arrow-parens: 0 |
|||
arrow-spacing: 0 |
|||
accessor-pairs: 0 |
|||
block-scoped-var: 0 |
|||
brace-style: |
|||
- 0 |
|||
- 1tbs |
|||
callback-return: 0 |
|||
camelcase: 0 |
|||
comma-dangle: |
|||
- 2 |
|||
- never |
|||
comma-spacing: 0 |
|||
comma-style: 0 |
|||
complexity: |
|||
- 2 |
|||
- 11 |
|||
computed-property-spacing: |
|||
- 0 |
|||
- never |
|||
consistent-return: 0 |
|||
consistent-this: |
|||
- 0 |
|||
- that |
|||
constructor-super: 0 |
|||
curly: |
|||
- 0 |
|||
- all |
|||
default-case: 0 |
|||
dot-location: 0 |
|||
dot-notation: |
|||
- 0 |
|||
- allowKeywords: true |
|||
eol-last: 0 |
|||
eqeqeq: 0 |
|||
func-names: 0 |
|||
func-style: |
|||
- 0 |
|||
- declaration |
|||
generator-star-spacing: 0 |
|||
guard-for-in: 0 |
|||
handle-callback-err: 0 |
|||
indent: 0 |
|||
init-declarations: 0 |
|||
key-spacing: |
|||
- 0 |
|||
- beforeColon: false |
|||
afterColon: true |
|||
lines-around-comment: 0 |
|||
max-depth: |
|||
- 0 |
|||
- 4 |
|||
max-len: |
|||
- 0 |
|||
- 80 |
|||
- 4 |
|||
max-nested-callbacks: |
|||
- 0 |
|||
- 2 |
|||
max-params: |
|||
- 0 |
|||
- 3 |
|||
max-statements: |
|||
- 0 |
|||
- 10 |
|||
new-cap: 0 |
|||
new-parens: 0 |
|||
newline-after-var: 0 |
|||
object-curly-spacing: |
|||
- 0 |
|||
- never |
|||
object-shorthand: 0 |
|||
one-var: 0 |
|||
operator-assignment: |
|||
- 0 |
|||
- always |
|||
operator-linebreak: 0 |
|||
padded-blocks: 0 |
|||
prefer-const: 0 |
|||
prefer-spread: 0 |
|||
prefer-reflect: 0 |
|||
quote-props: 0 |
|||
quotes: |
|||
- 2 |
|||
- double |
|||
- avoid-escape |
|||
radix: 0 |
|||
require-yield: 0 |
|||
semi: 0 |
|||
semi-spacing: |
|||
- 0 |
|||
- before: false |
|||
after: true |
|||
sort-vars: 0 |
|||
space-after-keywords: |
|||
- 0 |
|||
- always |
|||
space-before-blocks: |
|||
- 0 |
|||
- always |
|||
space-before-function-paren: |
|||
- 0 |
|||
- always |
|||
space-in-parens: |
|||
- 0 |
|||
- never |
|||
space-infix-ops: 0 |
|||
space-return-throw-case: 0 |
|||
space-unary-ops: |
|||
- 0 |
|||
- words: true |
|||
nonwords: false |
|||
spaced-comment: 0 |
|||
strict: 0 |
|||
use-isnan: 2 |
|||
valid-jsdoc: 0 |
|||
valid-typeof: 2 |
|||
vars-on-top: 0 |
|||
wrap-iife: 0 |
|||
wrap-regex: 0 |
|||
yoda: |
|||
- 0 |
|||
- never |
|||
env: |
|||
browser: true |
|||
node: true |
|||
jquery: true |
|||
amd: true |
|||
commonjs: true |
|||
@ -0,0 +1,28 @@ |
|||
language: node_js |
|||
|
|||
node_js: |
|||
- "0.10" |
|||
|
|||
branches: |
|||
only: |
|||
- gh-pages |
|||
- /v\d+\.\d+/ |
|||
|
|||
install: |
|||
- npm install -g codeclimate-test-reporter |
|||
- npm install |
|||
|
|||
before_script: |
|||
- export CHROME_BIN=chromium-browser |
|||
- export DISPLAY=:99.0 |
|||
- sh -e /etc/init.d/xvfb start |
|||
|
|||
after_script: |
|||
- codeclimate-test-reporter < ./coverage/lcov.info |
|||
|
|||
deploy: |
|||
provider: npm |
|||
email: $NPM_EMAIL |
|||
api_key: $NPM_TOKEN |
|||
on: |
|||
tags: true |
|||
@ -0,0 +1,22 @@ |
|||
# 1.1.1 (2016-06-25) |
|||
|
|||
## Fixes |
|||
|
|||
* Improved docs |
|||
* Emit close reason with `awesomplete-close` events ([8c0ff62](https://github.com/LeaVerou/awesomplete/commit/8c0ff6225c96af2f5f3b7312d7ba7b69f71be575)). See [Events](http://leaverou.github.io/awesomplete/#events). |
|||
* Fire `awesomplete-close` event only if open ([2cef2c2](https://github.com/LeaVerou/awesomplete/commit/2cef2c28a6f74ee5c0b294d2c3c7d2bad72bd466)) |
|||
|
|||
# 1.1.0 (2016-03-16) |
|||
|
|||
## Features |
|||
|
|||
* Separate label/value for each suggestion on the list |
|||
* New `suggestions` property with suggestion items only (to be rendered in completer) |
|||
* New `data` method to convert/change suggestion item data and corresponding `Awesomplete.DATA` |
|||
* The `item` and `replace` methods have a corresponding `Awesomplete.ITEM` and `Awesomplete.REPLACE` defaults now |
|||
* Ajax and Combobox examples |
|||
* Nearly 100% test coverage with Travis CI and Code Climate |
|||
|
|||
# 1.0.0 (2015-04-23) |
|||
|
|||
## First release |
|||
@ -0,0 +1,59 @@ |
|||
##Contributing |
|||
|
|||
**Prerequisites** |
|||
|
|||
Install [Node.js](https://nodejs.org/) and [npm](https://www.npmjs.com/). On OSX with [Homebrew](http://brew.sh/) installed it is as easy as: |
|||
``` |
|||
brew install node |
|||
``` |
|||
|
|||
Install dependencies: |
|||
``` |
|||
npm install |
|||
``` |
|||
|
|||
**Running tests** |
|||
|
|||
Run tests once and exit: |
|||
``` |
|||
npm test |
|||
``` |
|||
|
|||
Continuous mode. Whenever any source or test file changes, tests will run automatically: |
|||
``` |
|||
karma start |
|||
``` |
|||
|
|||
Chrome starts automatically and stops on ```Ctrl+C```. You can also open ```http://localhost:9876/``` in any other browser and it will run the tests as long as the tab is open. |
|||
|
|||
**Adding a test** |
|||
|
|||
[Jasmine](http://jasmine.github.io/) is the testing framework used by Awesomplete. |
|||
|
|||
To write a test (or suite of tests) start by adding a `describe` function which receives a string describing what is being tested and a function containing what you expect the test to do. Inside the function use the `it` block to arrange and assert a functionality. |
|||
|
|||
A test would look like this: |
|||
|
|||
```javascript |
|||
describe("A fact", function(){ |
|||
it("is always true",function(){ |
|||
var fact = true; |
|||
expect(fact).toBe(true); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
See existing tests in ```test``` directory as an example. More expectations and examples on how to use Jasmine can be found on the official [documentation](http://jasmine.github.io/2.2/introduction.html). |
|||
|
|||
**Build** |
|||
|
|||
Run the build with the following command: |
|||
|
|||
``` |
|||
gulp |
|||
``` |
|||
|
|||
The build will: |
|||
|
|||
1. Minify `awesomplete.js` and generate `awesomplete.min.js`. |
|||
2. Merge `awesomplete.base.css` and `awesomplete.theme.css` and generate `awesomplete.css`. |
|||
@ -0,0 +1,22 @@ |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2015 Lea Verou |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
|||
|
|||
@ -0,0 +1,98 @@ |
|||
# Awesomplete |
|||
[](https://www.npmjs.com/package/awesomplete) |
|||
[](https://travis-ci.org/LeaVerou/awesomplete) |
|||
[](https://codeclimate.com/github/LeaVerou/awesomplete) |
|||
[](https://codeclimate.com/github/LeaVerou/awesomplete/coverage) |
|||
|
|||
https://leaverou.github.io/awesomplete/ |
|||
|
|||
Awesomplete is an ultra lightweight, customizable, simple autocomplete widget with zero dependencies, built with modern standards for modern browsers. |
|||
|
|||
## Installation |
|||
There are a few ways to obtain the needed files. |
|||
Here are 2 of them: |
|||
1. CDN server |
|||
|
|||
```sh |
|||
https://cdnjs.com/libraries/awesomplete |
|||
``` |
|||
|
|||
2. Another way to get up and running is by using `yarn` or `npm`: |
|||
|
|||
```sh |
|||
yarn add awesomplete |
|||
``` |
|||
|
|||
```sh |
|||
npm install awesomplete --save |
|||
``` |
|||
|
|||
More information about the npm package can be found [here](https://www.npmjs.com/package/awesomplete). |
|||
|
|||
## Basic Usage |
|||
|
|||
Before you try anything, you need to include awesomplete.css and awesomplete.js in your page, via the usual tags: |
|||
|
|||
```html |
|||
<link rel="stylesheet" href="awesomplete.css" /> |
|||
<script src="awesomplete.js" async></script> |
|||
``` |
|||
|
|||
Then you can add an Awesomplete widget by adding the following input tag: |
|||
|
|||
```html |
|||
<input class="awesomplete" |
|||
data-list="Ada, Java, JavaScript, Brainfuck, LOLCODE, Node.js, Ruby on Rails" /> |
|||
``` |
|||
|
|||
Add `class="awesomplete"` for it to be automatically processed (you can still specify many options via HTML attributes) |
|||
Otherwise you can instantiate with a few lines of JS code, which allow for more customization. |
|||
|
|||
There are many ways to link an input to a list of suggestions. |
|||
The simple example above could have also been made with the following markup, which provides a nice native fallback in case the script doesn’t load: |
|||
|
|||
```html |
|||
<input class="awesomplete" list="mylist" /> |
|||
<datalist id="mylist"> |
|||
<option>Ada</option> |
|||
<option>Java</option> |
|||
<option>JavaScript</option> |
|||
<option>Brainfuck</option> |
|||
<option>LOLCODE</option> |
|||
<option>Node.js</option> |
|||
<option>Ruby on Rails</option> |
|||
</datalist> |
|||
``` |
|||
|
|||
Or the following, if you don’t want to use a `<datalist>`, or if you don’t want to use IDs (since any selector will work in data-list): |
|||
|
|||
```html |
|||
<input class="awesomplete" data-list="#mylist" /> |
|||
<ul id="mylist"> |
|||
<li>Ada</li> |
|||
<li>Java</li> |
|||
<li>JavaScript</li> |
|||
<li>Brainfuck</li> |
|||
<li>LOLCODE</li> |
|||
<li>Node.js</li> |
|||
<li>Ruby on Rails</li> |
|||
</ul> |
|||
``` |
|||
|
|||
There are multiple customizations and properties able to be instantiated within the JS. Libraries and definitions of the properties are available in the Links below. |
|||
|
|||
## License |
|||
|
|||
Awesomplete is released under the MIT License. See [LICENSE][1] file for |
|||
details. |
|||
|
|||
## Links |
|||
|
|||
The official site for the library is at <https://leaverou.github.io/awesomplete/>. |
|||
|
|||
Documentation for the API and other topics is at |
|||
<https://leaverou.github.io/awesomplete/#api>. |
|||
|
|||
Created by Lea Verou and other fantastic contributors. |
|||
|
|||
[1]: https://github.com/LeaVerou/awesomplete/blob/gh-pages/LICENSE |
|||
@ -0,0 +1,33 @@ |
|||
.awesomplete [hidden] { |
|||
display: none; |
|||
} |
|||
|
|||
.awesomplete .visually-hidden { |
|||
position: absolute; |
|||
clip: rect(0, 0, 0, 0); |
|||
} |
|||
|
|||
.awesomplete { |
|||
display: inline-block; |
|||
position: relative; |
|||
} |
|||
|
|||
.awesomplete > input { |
|||
display: block; |
|||
} |
|||
|
|||
.awesomplete > ul { |
|||
position: absolute; |
|||
left: 0; |
|||
z-index: 1; |
|||
min-width: 100%; |
|||
box-sizing: border-box; |
|||
list-style: none; |
|||
padding: 0; |
|||
margin: 0; |
|||
background: #fff; |
|||
} |
|||
|
|||
.awesomplete > ul:empty { |
|||
display: none; |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
.awesomplete [hidden] { |
|||
display: none; |
|||
} |
|||
|
|||
.awesomplete .visually-hidden { |
|||
position: absolute; |
|||
clip: rect(0, 0, 0, 0); |
|||
} |
|||
|
|||
.awesomplete { |
|||
display: inline-block; |
|||
position: relative; |
|||
} |
|||
|
|||
.awesomplete > input { |
|||
display: block; |
|||
} |
|||
|
|||
.awesomplete > ul { |
|||
position: absolute; |
|||
left: 0; |
|||
z-index: 1; |
|||
min-width: 100%; |
|||
box-sizing: border-box; |
|||
list-style: none; |
|||
padding: 0; |
|||
margin: 0; |
|||
background: #fff; |
|||
} |
|||
|
|||
.awesomplete > ul:empty { |
|||
display: none; |
|||
} |
|||
|
|||
.awesomplete > ul { |
|||
border-radius: .3em; |
|||
margin: .2em 0 0; |
|||
background: hsla(0,0%,100%,.9); |
|||
background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8)); |
|||
border: 1px solid rgba(0,0,0,.3); |
|||
box-shadow: .05em .2em .6em rgba(0,0,0,.2); |
|||
text-shadow: none; |
|||
} |
|||
|
|||
@supports (transform: scale(0)) { |
|||
.awesomplete > ul { |
|||
transition: .3s cubic-bezier(.4,.2,.5,1.4); |
|||
transform-origin: 1.43em -.43em; |
|||
} |
|||
|
|||
.awesomplete > ul[hidden], |
|||
.awesomplete > ul:empty { |
|||
opacity: 0; |
|||
transform: scale(0); |
|||
display: block; |
|||
transition-timing-function: ease; |
|||
} |
|||
} |
|||
|
|||
/* Pointer */ |
|||
.awesomplete > ul:before { |
|||
content: ""; |
|||
position: absolute; |
|||
top: -.43em; |
|||
left: 1em; |
|||
width: 0; height: 0; |
|||
padding: .4em; |
|||
background: white; |
|||
border: inherit; |
|||
border-right: 0; |
|||
border-bottom: 0; |
|||
-webkit-transform: rotate(45deg); |
|||
transform: rotate(45deg); |
|||
} |
|||
|
|||
.awesomplete > ul > li { |
|||
position: relative; |
|||
padding: .2em .5em; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.awesomplete > ul > li:hover { |
|||
background: hsl(200, 40%, 80%); |
|||
color: black; |
|||
} |
|||
|
|||
.awesomplete > ul > li[aria-selected="true"] { |
|||
background: hsl(205, 40%, 40%); |
|||
color: white; |
|||
} |
|||
|
|||
.awesomplete mark { |
|||
background: hsl(65, 100%, 50%); |
|||
} |
|||
|
|||
.awesomplete li:hover mark { |
|||
background: hsl(68, 100%, 41%); |
|||
} |
|||
|
|||
.awesomplete li[aria-selected="true"] mark { |
|||
background: hsl(86, 100%, 21%); |
|||
color: inherit; |
|||
} |
|||
/*# sourceMappingURL=awesomplete.css.map */ |
|||
@ -0,0 +1 @@ |
|||
{"version":3,"sources":["awesomplete.base.css","awesomplete.theme.css"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACjCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"awesomplete.css","sourcesContent":[".awesomplete [hidden] {\n display: none;\n}\n\n.awesomplete .visually-hidden {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n}\n\n.awesomplete {\n display: inline-block;\n position: relative;\n}\n\n.awesomplete > input {\n display: block;\n}\n\n.awesomplete > ul {\n position: absolute;\n left: 0;\n z-index: 1;\n min-width: 100%;\n box-sizing: border-box;\n list-style: none;\n padding: 0;\n margin: 0;\n background: #fff;\n}\n\n.awesomplete > ul:empty {\n display: none;\n}\n",".awesomplete > ul {\n\tborder-radius: .3em;\n\tmargin: .2em 0 0;\n\tbackground: hsla(0,0%,100%,.9);\n\tbackground: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8));\n\tborder: 1px solid rgba(0,0,0,.3);\n\tbox-shadow: .05em .2em .6em rgba(0,0,0,.2);\n\ttext-shadow: none;\n}\n\n@supports (transform: scale(0)) {\n\t.awesomplete > ul {\n\t\ttransition: .3s cubic-bezier(.4,.2,.5,1.4);\n\t\ttransform-origin: 1.43em -.43em;\n\t}\n\t\n\t.awesomplete > ul[hidden],\n\t.awesomplete > ul:empty {\n\t\topacity: 0;\n\t\ttransform: scale(0);\n\t\tdisplay: block;\n\t\ttransition-timing-function: ease;\n\t}\n}\n\n\t/* Pointer */\n\t.awesomplete > ul:before {\n\t\tcontent: \"\";\n\t\tposition: absolute;\n\t\ttop: -.43em;\n\t\tleft: 1em;\n\t\twidth: 0; height: 0;\n\t\tpadding: .4em;\n\t\tbackground: white;\n\t\tborder: inherit;\n\t\tborder-right: 0;\n\t\tborder-bottom: 0;\n\t\t-webkit-transform: rotate(45deg);\n\t\ttransform: rotate(45deg);\n\t}\n\n\t.awesomplete > ul > li {\n\t\tposition: relative;\n\t\tpadding: .2em .5em;\n\t\tcursor: pointer;\n\t}\n\t\n\t.awesomplete > ul > li:hover {\n\t\tbackground: hsl(200, 40%, 80%);\n\t\tcolor: black;\n\t}\n\t\n\t.awesomplete > ul > li[aria-selected=\"true\"] {\n\t\tbackground: hsl(205, 40%, 40%);\n\t\tcolor: white;\n\t}\n\t\n\t\t.awesomplete mark {\n\t\t\tbackground: hsl(65, 100%, 50%);\n\t\t}\n\t\t\n\t\t.awesomplete li:hover mark {\n\t\t\tbackground: hsl(68, 100%, 41%);\n\t\t}\n\t\t\n\t\t.awesomplete li[aria-selected=\"true\"] mark {\n\t\t\tbackground: hsl(86, 100%, 21%);\n\t\t\tcolor: inherit;\n\t\t}"]} |
|||
@ -0,0 +1,552 @@ |
|||
/** |
|||
* Simple, lightweight, usable local autocomplete library for modern browsers |
|||
* Because there weren’t enough autocomplete scripts in the world? Because I’m completely insane and have NIH syndrome? Probably both. :P |
|||
* @author Lea Verou http://leaverou.github.io/awesomplete
|
|||
* MIT license |
|||
*/ |
|||
|
|||
(function () { |
|||
|
|||
var _ = function (input, o) { |
|||
var me = this; |
|||
|
|||
// Keep track of number of instances for unique IDs
|
|||
_.count = (_.count || 0) + 1; |
|||
this.count = _.count; |
|||
|
|||
// Setup
|
|||
|
|||
this.isOpened = false; |
|||
|
|||
this.input = $(input); |
|||
this.input.setAttribute("autocomplete", "off"); |
|||
this.input.setAttribute("aria-expanded", "false"); |
|||
this.input.setAttribute("aria-owns", "awesomplete_list_" + this.count); |
|||
this.input.setAttribute("role", "combobox"); |
|||
|
|||
// store constructor options in case we need to distinguish
|
|||
// between default and customized behavior later on
|
|||
this.options = o = o || {}; |
|||
|
|||
configure(this, { |
|||
minChars: 2, |
|||
maxItems: 10, |
|||
autoFirst: false, |
|||
data: _.DATA, |
|||
filter: _.FILTER_CONTAINS, |
|||
sort: o.sort === false ? false : _.SORT_BYLENGTH, |
|||
container: _.CONTAINER, |
|||
item: _.ITEM, |
|||
replace: _.REPLACE, |
|||
tabSelect: false |
|||
}, o); |
|||
|
|||
this.index = -1; |
|||
|
|||
// Create necessary elements
|
|||
|
|||
this.container = this.container(input); |
|||
|
|||
this.ul = $.create("ul", { |
|||
hidden: "hidden", |
|||
role: "listbox", |
|||
id: "awesomplete_list_" + this.count, |
|||
inside: this.container |
|||
}); |
|||
|
|||
this.status = $.create("span", { |
|||
className: "visually-hidden", |
|||
role: "status", |
|||
"aria-live": "assertive", |
|||
"aria-atomic": true, |
|||
inside: this.container, |
|||
textContent: this.minChars != 0 ? ("Type " + this.minChars + " or more characters for results.") : "Begin typing for results." |
|||
}); |
|||
|
|||
// Bind events
|
|||
|
|||
this._events = { |
|||
input: { |
|||
"input": this.evaluate.bind(this), |
|||
"blur": this.close.bind(this, { reason: "blur" }), |
|||
"keydown": function(evt) { |
|||
var c = evt.keyCode; |
|||
|
|||
// If the dropdown `ul` is in view, then act on keydown for the following keys:
|
|||
// Enter / Esc / Up / Down
|
|||
if(me.opened) { |
|||
if (c === 13 && me.selected) { // Enter
|
|||
evt.preventDefault(); |
|||
me.select(undefined, undefined, evt); |
|||
} |
|||
else if (c === 9 && me.selected && me.tabSelect) { |
|||
me.select(undefined, undefined, evt); |
|||
} |
|||
else if (c === 27) { // Esc
|
|||
me.close({ reason: "esc" }); |
|||
} |
|||
else if (c === 38 || c === 40) { // Down/Up arrow
|
|||
evt.preventDefault(); |
|||
me[c === 38? "previous" : "next"](); |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
form: { |
|||
"submit": this.close.bind(this, { reason: "submit" }) |
|||
}, |
|||
ul: { |
|||
// Prevent the default mousedowm, which ensures the input is not blurred.
|
|||
// The actual selection will happen on click. This also ensures dragging the
|
|||
// cursor away from the list item will cancel the selection
|
|||
"mousedown": function(evt) { |
|||
evt.preventDefault(); |
|||
}, |
|||
// The click event is fired even if the corresponding mousedown event has called preventDefault
|
|||
"click": function(evt) { |
|||
var li = evt.target; |
|||
|
|||
if (li !== this) { |
|||
|
|||
while (li && !/li/i.test(li.nodeName)) { |
|||
li = li.parentNode; |
|||
} |
|||
|
|||
if (li && evt.button === 0) { // Only select on left click
|
|||
evt.preventDefault(); |
|||
me.select(li, evt.target, evt); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
$.bind(this.input, this._events.input); |
|||
$.bind(this.input.form, this._events.form); |
|||
$.bind(this.ul, this._events.ul); |
|||
|
|||
if (this.input.hasAttribute("list")) { |
|||
this.list = "#" + this.input.getAttribute("list"); |
|||
this.input.removeAttribute("list"); |
|||
} |
|||
else { |
|||
this.list = this.input.getAttribute("data-list") || o.list || []; |
|||
} |
|||
|
|||
_.all.push(this); |
|||
}; |
|||
|
|||
_.prototype = { |
|||
set list(list) { |
|||
if (Array.isArray(list)) { |
|||
this._list = list; |
|||
} |
|||
else if (typeof list === "string" && list.indexOf(",") > -1) { |
|||
this._list = list.split(/\s*,\s*/); |
|||
} |
|||
else { // Element or CSS selector
|
|||
list = $(list); |
|||
|
|||
if (list && list.children) { |
|||
var items = []; |
|||
slice.apply(list.children).forEach(function (el) { |
|||
if (!el.disabled) { |
|||
var text = el.textContent.trim(); |
|||
var value = el.value || text; |
|||
var label = el.label || text; |
|||
if (value !== "") { |
|||
items.push({ label: label, value: value }); |
|||
} |
|||
} |
|||
}); |
|||
this._list = items; |
|||
} |
|||
} |
|||
|
|||
if (document.activeElement === this.input) { |
|||
this.evaluate(); |
|||
} |
|||
}, |
|||
|
|||
get selected() { |
|||
return this.index > -1; |
|||
}, |
|||
|
|||
get opened() { |
|||
return this.isOpened; |
|||
}, |
|||
|
|||
close: function (o) { |
|||
if (!this.opened) { |
|||
return; |
|||
} |
|||
|
|||
this.input.setAttribute("aria-expanded", "false"); |
|||
this.ul.setAttribute("hidden", ""); |
|||
this.isOpened = false; |
|||
this.index = -1; |
|||
|
|||
this.status.setAttribute("hidden", ""); |
|||
|
|||
$.fire(this.input, "awesomplete-close", o || {}); |
|||
}, |
|||
|
|||
open: function () { |
|||
this.input.setAttribute("aria-expanded", "true"); |
|||
this.ul.removeAttribute("hidden"); |
|||
this.isOpened = true; |
|||
|
|||
this.status.removeAttribute("hidden"); |
|||
|
|||
if (this.autoFirst && this.index === -1) { |
|||
this.goto(0); |
|||
} |
|||
|
|||
$.fire(this.input, "awesomplete-open"); |
|||
}, |
|||
|
|||
destroy: function() { |
|||
//remove events from the input and its form
|
|||
$.unbind(this.input, this._events.input); |
|||
$.unbind(this.input.form, this._events.form); |
|||
|
|||
// cleanup container if it was created by Awesomplete but leave it alone otherwise
|
|||
if (!this.options.container) { |
|||
//move the input out of the awesomplete container and remove the container and its children
|
|||
var parentNode = this.container.parentNode; |
|||
|
|||
parentNode.insertBefore(this.input, this.container); |
|||
parentNode.removeChild(this.container); |
|||
} |
|||
|
|||
//remove autocomplete and aria-autocomplete attributes
|
|||
this.input.removeAttribute("autocomplete"); |
|||
this.input.removeAttribute("aria-autocomplete"); |
|||
|
|||
//remove this awesomeplete instance from the global array of instances
|
|||
var indexOfAwesomplete = _.all.indexOf(this); |
|||
|
|||
if (indexOfAwesomplete !== -1) { |
|||
_.all.splice(indexOfAwesomplete, 1); |
|||
} |
|||
}, |
|||
|
|||
next: function () { |
|||
var count = this.ul.children.length; |
|||
this.goto(this.index < count - 1 ? this.index + 1 : (count ? 0 : -1) ); |
|||
}, |
|||
|
|||
previous: function () { |
|||
var count = this.ul.children.length; |
|||
var pos = this.index - 1; |
|||
|
|||
this.goto(this.selected && pos !== -1 ? pos : count - 1); |
|||
}, |
|||
|
|||
// Should not be used, highlights specific item without any checks!
|
|||
goto: function (i) { |
|||
var lis = this.ul.children; |
|||
|
|||
if (this.selected) { |
|||
lis[this.index].setAttribute("aria-selected", "false"); |
|||
} |
|||
|
|||
this.index = i; |
|||
|
|||
if (i > -1 && lis.length > 0) { |
|||
lis[i].setAttribute("aria-selected", "true"); |
|||
|
|||
this.status.textContent = lis[i].textContent + ", list item " + (i + 1) + " of " + lis.length; |
|||
|
|||
this.input.setAttribute("aria-activedescendant", this.ul.id + "_item_" + this.index); |
|||
|
|||
// scroll to highlighted element in case parent's height is fixed
|
|||
this.ul.scrollTop = lis[i].offsetTop - this.ul.clientHeight + lis[i].clientHeight; |
|||
|
|||
$.fire(this.input, "awesomplete-highlight", { |
|||
text: this.suggestions[this.index] |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
select: function (selected, origin, originalEvent) { |
|||
if (selected) { |
|||
this.index = $.siblingIndex(selected); |
|||
} else { |
|||
selected = this.ul.children[this.index]; |
|||
} |
|||
|
|||
if (selected) { |
|||
var suggestion = this.suggestions[this.index]; |
|||
|
|||
var allowed = $.fire(this.input, "awesomplete-select", { |
|||
text: suggestion, |
|||
origin: origin || selected, |
|||
originalEvent: originalEvent |
|||
}); |
|||
|
|||
if (allowed) { |
|||
this.replace(suggestion); |
|||
this.close({ reason: "select" }); |
|||
$.fire(this.input, "awesomplete-selectcomplete", { |
|||
text: suggestion, |
|||
originalEvent: originalEvent |
|||
}); |
|||
} |
|||
} |
|||
}, |
|||
|
|||
evaluate: function() { |
|||
var me = this; |
|||
var value = this.input.value; |
|||
|
|||
if (value.length >= this.minChars && this._list && this._list.length > 0) { |
|||
this.index = -1; |
|||
// Populate list with options that match
|
|||
this.ul.innerHTML = ""; |
|||
|
|||
this.suggestions = this._list |
|||
.map(function(item) { |
|||
return new Suggestion(me.data(item, value)); |
|||
}) |
|||
.filter(function(item) { |
|||
return me.filter(item, value); |
|||
}); |
|||
|
|||
if (this.sort !== false) { |
|||
this.suggestions = this.suggestions.sort(this.sort); |
|||
} |
|||
|
|||
this.suggestions = this.suggestions.slice(0, this.maxItems); |
|||
|
|||
this.suggestions.forEach(function(text, index) { |
|||
me.ul.appendChild(me.item(text, value, index)); |
|||
}); |
|||
|
|||
if (this.ul.children.length === 0) { |
|||
|
|||
this.status.textContent = "No results found"; |
|||
|
|||
this.close({ reason: "nomatches" }); |
|||
|
|||
} else { |
|||
this.open(); |
|||
|
|||
this.status.textContent = this.ul.children.length + " results found"; |
|||
} |
|||
} |
|||
else { |
|||
this.close({ reason: "nomatches" }); |
|||
|
|||
this.status.textContent = "No results found"; |
|||
} |
|||
} |
|||
}; |
|||
|
|||
// Static methods/properties
|
|||
|
|||
_.all = []; |
|||
|
|||
_.FILTER_CONTAINS = function (text, input) { |
|||
return RegExp($.regExpEscape(input.trim()), "i").test(text); |
|||
}; |
|||
|
|||
_.FILTER_STARTSWITH = function (text, input) { |
|||
return RegExp("^" + $.regExpEscape(input.trim()), "i").test(text); |
|||
}; |
|||
|
|||
_.SORT_BYLENGTH = function (a, b) { |
|||
if (a.length !== b.length) { |
|||
return a.length - b.length; |
|||
} |
|||
|
|||
return a < b? -1 : 1; |
|||
}; |
|||
|
|||
_.CONTAINER = function (input) { |
|||
return $.create("div", { |
|||
className: "awesomplete", |
|||
around: input |
|||
}); |
|||
} |
|||
|
|||
_.ITEM = function (text, input, item_id) { |
|||
var html = input.trim() === "" ? text : text.replace(RegExp($.regExpEscape(input.trim()), "gi"), "<mark>$&</mark>"); |
|||
return $.create("li", { |
|||
innerHTML: html, |
|||
"role": "option", |
|||
"aria-selected": "false", |
|||
"id": "awesomplete_list_" + this.count + "_item_" + item_id |
|||
}); |
|||
}; |
|||
|
|||
_.REPLACE = function (text) { |
|||
this.input.value = text.value; |
|||
}; |
|||
|
|||
_.DATA = function (item/*, input*/) { return item; }; |
|||
|
|||
// Private functions
|
|||
|
|||
function Suggestion(data) { |
|||
var o = Array.isArray(data) |
|||
? { label: data[0], value: data[1] } |
|||
: typeof data === "object" && "label" in data && "value" in data ? data : { label: data, value: data }; |
|||
|
|||
this.label = o.label || o.value; |
|||
this.value = o.value; |
|||
} |
|||
Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", { |
|||
get: function() { return this.label.length; } |
|||
}); |
|||
Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () { |
|||
return "" + this.label; |
|||
}; |
|||
|
|||
function configure(instance, properties, o) { |
|||
for (var i in properties) { |
|||
var initial = properties[i], |
|||
attrValue = instance.input.getAttribute("data-" + i.toLowerCase()); |
|||
|
|||
if (typeof initial === "number") { |
|||
instance[i] = parseInt(attrValue); |
|||
} |
|||
else if (initial === false) { // Boolean options must be false by default anyway
|
|||
instance[i] = attrValue !== null; |
|||
} |
|||
else if (initial instanceof Function) { |
|||
instance[i] = null; |
|||
} |
|||
else { |
|||
instance[i] = attrValue; |
|||
} |
|||
|
|||
if (!instance[i] && instance[i] !== 0) { |
|||
instance[i] = (i in o)? o[i] : initial; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Helpers
|
|||
|
|||
var slice = Array.prototype.slice; |
|||
|
|||
function $(expr, con) { |
|||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; |
|||
} |
|||
|
|||
function $$(expr, con) { |
|||
return slice.call((con || document).querySelectorAll(expr)); |
|||
} |
|||
|
|||
$.create = function(tag, o) { |
|||
var element = document.createElement(tag); |
|||
|
|||
for (var i in o) { |
|||
var val = o[i]; |
|||
|
|||
if (i === "inside") { |
|||
$(val).appendChild(element); |
|||
} |
|||
else if (i === "around") { |
|||
var ref = $(val); |
|||
ref.parentNode.insertBefore(element, ref); |
|||
element.appendChild(ref); |
|||
|
|||
if (ref.getAttribute("autofocus") != null) { |
|||
ref.focus(); |
|||
} |
|||
} |
|||
else if (i in element) { |
|||
element[i] = val; |
|||
} |
|||
else { |
|||
element.setAttribute(i, val); |
|||
} |
|||
} |
|||
|
|||
return element; |
|||
}; |
|||
|
|||
$.bind = function(element, o) { |
|||
if (element) { |
|||
for (var event in o) { |
|||
var callback = o[event]; |
|||
|
|||
event.split(/\s+/).forEach(function (event) { |
|||
element.addEventListener(event, callback); |
|||
}); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
$.unbind = function(element, o) { |
|||
if (element) { |
|||
for (var event in o) { |
|||
var callback = o[event]; |
|||
|
|||
event.split(/\s+/).forEach(function(event) { |
|||
element.removeEventListener(event, callback); |
|||
}); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
$.fire = function(target, type, properties) { |
|||
var evt = document.createEvent("HTMLEvents"); |
|||
|
|||
evt.initEvent(type, true, true ); |
|||
|
|||
for (var j in properties) { |
|||
evt[j] = properties[j]; |
|||
} |
|||
|
|||
return target.dispatchEvent(evt); |
|||
}; |
|||
|
|||
$.regExpEscape = function (s) { |
|||
return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); |
|||
}; |
|||
|
|||
$.siblingIndex = function (el) { |
|||
/* eslint-disable no-cond-assign */ |
|||
for (var i = 0; el = el.previousElementSibling; i++); |
|||
return i; |
|||
}; |
|||
|
|||
// Initialization
|
|||
|
|||
function init() { |
|||
$$("input.awesomplete").forEach(function (input) { |
|||
new _(input); |
|||
}); |
|||
} |
|||
|
|||
// Make sure to export Awesomplete on self when in a browser
|
|||
if (typeof self !== "undefined") { |
|||
self.Awesomplete = _; |
|||
} |
|||
|
|||
// Are we in a browser? Check for Document constructor
|
|||
if (typeof Document !== "undefined") { |
|||
// DOM already loaded?
|
|||
if (document.readyState !== "loading") { |
|||
init(); |
|||
} |
|||
else { |
|||
// Wait for it
|
|||
document.addEventListener("DOMContentLoaded", init); |
|||
} |
|||
} |
|||
|
|||
_.$ = $; |
|||
_.$$ = $$; |
|||
|
|||
// Expose Awesomplete as a CJS module
|
|||
if (typeof module === "object" && module.exports) { |
|||
module.exports = _; |
|||
} |
|||
|
|||
return _; |
|||
|
|||
}()); |
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,69 @@ |
|||
.awesomplete > ul { |
|||
border-radius: .3em; |
|||
margin: .2em 0 0; |
|||
background: hsla(0,0%,100%,.9); |
|||
background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8)); |
|||
border: 1px solid rgba(0,0,0,.3); |
|||
box-shadow: .05em .2em .6em rgba(0,0,0,.2); |
|||
text-shadow: none; |
|||
} |
|||
|
|||
@supports (transform: scale(0)) { |
|||
.awesomplete > ul { |
|||
transition: .3s cubic-bezier(.4,.2,.5,1.4); |
|||
transform-origin: 1.43em -.43em; |
|||
} |
|||
|
|||
.awesomplete > ul[hidden], |
|||
.awesomplete > ul:empty { |
|||
opacity: 0; |
|||
transform: scale(0); |
|||
display: block; |
|||
transition-timing-function: ease; |
|||
} |
|||
} |
|||
|
|||
/* Pointer */ |
|||
.awesomplete > ul:before { |
|||
content: ""; |
|||
position: absolute; |
|||
top: -.43em; |
|||
left: 1em; |
|||
width: 0; height: 0; |
|||
padding: .4em; |
|||
background: white; |
|||
border: inherit; |
|||
border-right: 0; |
|||
border-bottom: 0; |
|||
-webkit-transform: rotate(45deg); |
|||
transform: rotate(45deg); |
|||
} |
|||
|
|||
.awesomplete > ul > li { |
|||
position: relative; |
|||
padding: .2em .5em; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.awesomplete > ul > li:hover { |
|||
background: hsl(200, 40%, 80%); |
|||
color: black; |
|||
} |
|||
|
|||
.awesomplete > ul > li[aria-selected="true"] { |
|||
background: hsl(205, 40%, 40%); |
|||
color: white; |
|||
} |
|||
|
|||
.awesomplete mark { |
|||
background: hsl(65, 100%, 50%); |
|||
} |
|||
|
|||
.awesomplete li:hover mark { |
|||
background: hsl(68, 100%, 41%); |
|||
} |
|||
|
|||
.awesomplete li[aria-selected="true"] mark { |
|||
background: hsl(86, 100%, 21%); |
|||
color: inherit; |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
{ |
|||
"name": "awesomplete", |
|||
"main": [ |
|||
"./awesomplete.css", |
|||
"./awesomplete.js" |
|||
], |
|||
"homepage": "https://github.com/LeaVerou/awesomplete", |
|||
"authors": [ |
|||
"Lea Verou <>" |
|||
], |
|||
"description": "Ultra lightweight, usable autocomplete with zero dependencies.", |
|||
"keywords": [ |
|||
"autocomplete", |
|||
"lightweight" |
|||
], |
|||
"license": "MIT" |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
var gulp = require('gulp'); |
|||
var uglify = require('gulp-uglify'); |
|||
var rename = require('gulp-rename'); |
|||
var header = require('gulp-header'); |
|||
var concat = require('gulp-concat'); |
|||
var sourcemaps = require('gulp-sourcemaps'); |
|||
|
|||
var banner = "// Awesomplete - Lea Verou - MIT license\n"; |
|||
|
|||
gulp.task('minify', function() { |
|||
return gulp.src(['awesomplete.js']) |
|||
.pipe(sourcemaps.init()) |
|||
.pipe(uglify()) |
|||
.pipe(rename({ suffix: '.min' })) |
|||
.pipe(header(banner)) |
|||
.pipe(sourcemaps.write('.')) |
|||
.pipe(gulp.dest('.')); |
|||
}); |
|||
|
|||
gulp.task('concat', function() { |
|||
|
|||
return gulp.src(['awesomplete.base.css', 'awesomplete.theme.css']) |
|||
.pipe(sourcemaps.init()) |
|||
.pipe(concat('awesomplete.css')) |
|||
.pipe(sourcemaps.write('.')) |
|||
.pipe(gulp.dest('.')); |
|||
|
|||
|
|||
}); |
|||
|
|||
gulp.task('default', ['minify', 'concat']); |
|||
@ -0,0 +1,501 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
|
|||
<meta charset="utf-8" /> |
|||
<title>Awesomplete: Ultra lightweight, highly customizable, simple autocomplete, by Lea Verou</title> |
|||
<link rel="stylesheet" href="prism/prism.css" /> |
|||
<link rel="stylesheet" href="awesomplete.css" /> |
|||
<link rel="stylesheet" href="style.css" /> |
|||
|
|||
<script src="awesomplete.js"></script> |
|||
<script src="index.js"></script> |
|||
</head> |
|||
<body class="language-markup"> |
|||
|
|||
<header> |
|||
|
|||
<h1>Awesomplete</h1> |
|||
|
|||
<a href="#download" class="size"><strong>2KB</strong> minified <span class="amp">&</span> gzipped!</a> |
|||
|
|||
<p>Ultra lightweight, customizable, simple autocomplete widget with zero dependencies, built with modern standards for <abbr title="Verified to work in: IE9 (sorta), IE10+, Chrome, Firefox, Safari 5+, Mobile Safari">modern browsers</abbr>. <a href="http://lea.verou.me/2015/02/awesomplete-2kb-autocomplete-with-zero-dependencies">Because <code><datalist></code> still doesn’t cut it.</a></p> |
|||
|
|||
<nav></nav> |
|||
</header> |
|||
|
|||
<section> |
|||
|
|||
<h1>Demo (no JS, minimal options)</h1> |
|||
<label> |
|||
Pick a programming language:<br /> |
|||
<input autofocus class="awesomplete" data-list="Ada, Java, JavaScript, Brainfuck, LOLCODE, Node.js, Ruby on Rails" /> |
|||
</label> |
|||
|
|||
<p>Note that by default you need to type <strong>at least 2 characters</strong> for the popup to show up, though that’s <a href="#customization">super easy to customize</a>. With Awesomplete, making something like this can be as simple as:</p> |
|||
|
|||
<pre class="language-markup"><code><input class="awesomplete" |
|||
data-list="Ada, Java, JavaScript, Brainfuck, LOLCODE, Node.js, Ruby on Rails" /></code></pre> |
|||
|
|||
<pre class="language-javascript"><code>// No extra JS needed for basic usage!</code></pre> |
|||
|
|||
</section> |
|||
|
|||
<section id="basic-usage"> |
|||
|
|||
<h1>Basic usage</h1> |
|||
<p>Before you try anything, you need to include <code>awesomplete.css</code> and <code>awesomplete.js</code> in your page, via the usual <code><link rel="stylesheet" href="awesomplete.css" /></code> and <code><script src="awesomplete.js" async></script></code> tags.</p> |
|||
|
|||
<p>For the autocomplete, you just need an <code><input></code> text field (might work on <code><textarea></code> and elements with <code>contentEditable</code>, but that hasn’t been tested). Add <code>class="awesomplete"</code> for it to be <strong>automatically processed</strong> (you can still specify many options via HTML attributes), otherwise you can instantiate with a few lines of JS code, which allow for more customization.</p> |
|||
<p>There are many ways <strong>to link an input to a list of suggestions</strong>. The simple example above could have also been made with the following markup, which provides a nice native fallback in case the script doesn’t load:</p> |
|||
|
|||
<pre class="language-markup"><code><input class="awesomplete" list="mylist" /> |
|||
<datalist id="mylist"> |
|||
<option>Ada</option> |
|||
<option>Java</option> |
|||
<option>JavaScript</option> |
|||
<option>Brainfuck</option> |
|||
<option>LOLCODE</option> |
|||
<option>Node.js</option> |
|||
<option>Ruby on Rails</option> |
|||
</datalist></code></pre> |
|||
|
|||
<pre class="language-javascript"><code>// None!</code></pre> |
|||
|
|||
<p>Or the following, if you don’t want to use a <code><datalist></code>, or if you don’t want to use IDs (since any selector will work in <code>data-list</code>):</p> |
|||
|
|||
<pre class="language-markup"><code><input class="awesomplete" data-list="#mylist" /> |
|||
<ul id="mylist"> |
|||
<li>Ada</li> |
|||
<li>Java</li> |
|||
<li>JavaScript</li> |
|||
<li>Brainfuck</li> |
|||
<li>LOLCODE</li> |
|||
<li>Node.js</li> |
|||
<li>Ruby on Rails</li> |
|||
</ul></code></pre> |
|||
|
|||
<pre class="language-javascript"><code>// None!</code></pre> |
|||
|
|||
<p>Or the following, if we want to instantiate in JS:</p> |
|||
|
|||
<pre class="language-markup"><code><input id="myinput" /> |
|||
<ul id="mylist"> |
|||
<li>Ada</li> |
|||
<li>Java</li> |
|||
<li>JavaScript</li> |
|||
<li>Brainfuck</li> |
|||
<li>LOLCODE</li> |
|||
<li>Node.js</li> |
|||
<li>Ruby on Rails</li> |
|||
</ul></code></pre> |
|||
<pre class="language-javascript"><code>var input = document.getElementById("myinput"); |
|||
new Awesomplete(input, {list: "#mylist"});</code></pre> |
|||
|
|||
<p>We can use an <strong>element reference</strong> for the list instead of a selector:</p> |
|||
|
|||
<pre class="language-markup"><code><input id="myinput" /> |
|||
<ul id="mylist"> |
|||
<li>Ada</li> |
|||
<li>Java</li> |
|||
<li>JavaScript</li> |
|||
<li>Brainfuck</li> |
|||
<li>LOLCODE</li> |
|||
<li>Node.js</li> |
|||
<li>Ruby on Rails</li> |
|||
</ul></code></pre> |
|||
<pre class="language-javascript"><code>var input = document.getElementById("myinput"); |
|||
new Awesomplete(input, {list: document.querySelector("#mylist")});</code></pre> |
|||
|
|||
<p>We can also directly use an <strong>array of strings</strong>:</p> |
|||
|
|||
<pre class="language-markup"><code><input id="myinput" /></code></pre> |
|||
<pre class="language-javascript"><code>var input = document.getElementById("myinput"); |
|||
new Awesomplete(input, { |
|||
list: ["Ada", "Java", "JavaScript", "Brainfuck", "LOLCODE", "Node.js", "Ruby on Rails"] |
|||
});</code></pre> |
|||
|
|||
<p>We can even set it (or override it) later and it will just work:</p> |
|||
|
|||
<pre class="language-markup"><code><input id="myinput" /></code></pre> |
|||
<pre class="language-javascript"><code>var input = document.getElementById("myinput"); |
|||
var awesomplete = new Awesomplete(input); |
|||
|
|||
/* ...more code... */ |
|||
|
|||
awesomplete.list = ["Ada", "Java", "JavaScript", "Brainfuck", "LOLCODE", "Node.js", "Ruby on Rails"];</code></pre> |
|||
|
|||
<p>Suggestions with different <strong>label</strong> and <strong>value</strong> are supported too. The label will be shown in autocompleter and the <strong>value</strong> will be inserted into the input. If you want to insert the label into the input you can provide your own <strong>replace</strong> function</p> |
|||
|
|||
<pre class="language-markup"><code><input id="myinput" /></code></pre> |
|||
<pre class="language-javascript"><code>var input = document.getElementById("myinput"); |
|||
|
|||
// Show label but insert value into the input: |
|||
new Awesomplete(input, { |
|||
list: [ |
|||
{ label: "Belarus", value: "BY" }, |
|||
{ label: "China", value: "CN" }, |
|||
{ label: "United States", value: "US" } |
|||
] |
|||
}); |
|||
|
|||
// Same with arrays: |
|||
new Awesomplete(input, { |
|||
list: [ |
|||
[ "Belarus", "BY" ], |
|||
[ "China", "CN" ], |
|||
[ "United States", "US" ] |
|||
] |
|||
}); |
|||
|
|||
// Show label and insert label into the input: |
|||
new Awesomplete(input, { |
|||
list: [ |
|||
{ label: "Belarus", value: "BY" }, |
|||
{ label: "China", value: "CN" }, |
|||
{ label: "United States", value: "US" } |
|||
], |
|||
// insert label instead of value into the input. |
|||
replace: function(suggestion) { |
|||
this.input.value = suggestion.label; |
|||
} |
|||
}); |
|||
</code></pre> |
|||
|
|||
</section> |
|||
|
|||
<section id="customization"> |
|||
<h1>Customize</h1> |
|||
|
|||
<p>All settings discussed in this section are settable via either a <code>data-</code> attribute on the <code><input></code> element or a JS property on the second argument of the <code>Awesomplete</code> constructor, like so:</p> |
|||
|
|||
<pre class="language-javascript"><code>new Awesomplete(inputReference, { |
|||
minChars: 3, |
|||
maxItems: 15, |
|||
... |
|||
});</code></pre> |
|||
<p>You can of course combine both HTML attributes and JS properties. <strong>In case of conflict</strong> (e.g. you’ve specified both a <code>data-minchars</code> on the text field and a <code>minChars</code> JS property, <strong>the HTML attribute wins</strong>. You can also use the JS properties to change a parameter <strong>after</strong> the object has been created, in which case the change will apply even if there is a conflicting HTML attribute.</p> |
|||
|
|||
<table> |
|||
<thead> |
|||
<tr> |
|||
<th>JS property</th> |
|||
<th>HTML attribute</th> |
|||
<th>Description</th> |
|||
<th>Value</th> |
|||
<th>Default</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td><code>list</code></td> |
|||
<td><code>data-list</code></td> |
|||
<td>Where to find the list of suggestions. Described in more detail in the “<a href="#basic-usage">Basic usage</a>” section above.</td> |
|||
<td><ul> |
|||
<li>Array of strings</li> |
|||
<li>HTML element</li> |
|||
<li>CSS selector (no groups, i.e. no commas)</li> |
|||
<li>String containing a comma-separated list of items</li> |
|||
</ul></td> |
|||
<td>N/A</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>minChars</code></td> |
|||
<td><code>data-minchars</code></td> |
|||
<td>Minimum characters the user has to type before the autocomplete popup shows up.</td> |
|||
<td>Number</td> |
|||
<td>2</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>maxItems</code></td> |
|||
<td><code>data-maxitems</code></td> |
|||
<td>Maximum number of suggestions to display.</td> |
|||
<td>Number</td> |
|||
<td>10</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>autoFirst</code></td> |
|||
<td><code>data-autofirst</code></td> |
|||
<td>Should the first element be automatically selected? Demo: <input class="awesomplete" data-autofirst data-list="Ada, Java, JavaScript, Brainfuck, LOLCODE, Node.js, Ruby on Rails" xautofocus /></td> |
|||
<td>Boolean</td> |
|||
<td>false</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
|
|||
</section> |
|||
|
|||
<section id="extensibility"> |
|||
<h1>Extend</h1> |
|||
<p>The following JS properties do not have equivalent HTML attributes, because their values are functions. They allow you to completely change the way Awesomplete works:</p> |
|||
|
|||
<table> |
|||
<thead> |
|||
<tr> |
|||
<th>Property</th> |
|||
<th>Description</th> |
|||
<th>Value</th> |
|||
<th>Default</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td><code>filter</code></td> |
|||
<td>Controls how entries get matched. By default, the input can match anywhere in the string and it’s a case insensitive match.</td> |
|||
<td>Function that takes two parameters, the first one being the current suggestion text that’s being tested and the second a string with the user’s input it’s matched against. Returns <code>true</code> if the match is successful and <code>false</code> if it is not. For example, to only match strings that <strong>start with the user’s input</strong>, <strong>case sensitive</strong>, we can do this: |
|||
<pre class="language-javascript"><code>filter: function (text, input) { |
|||
return text.indexOf(input) === 0; |
|||
}</code></pre> For case-<strong>in</strong>sensitive matching from the start of the word, there is a predefined filter that you can use, <code class="language-javascript">Awesomplete.FILTER_STARTSWITH</code> |
|||
</td> |
|||
<td><code class="language-javascript">Awesomplete.FILTER_CONTAINS</code>: Text can match anywhere, case insensitive.</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>sort</code></td> |
|||
<td>Controls how list items are ordered.</td> |
|||
<td>Sort function (will be passed directly to <code>Array.prototype.sort()</code>) to sort the items after they have been filtered and before they are truncated and converted to HTML elements. If value is <code>false</code>, sorting will be disabled.</td> |
|||
<td>Sorted by length first, order second.</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>container</code></td> |
|||
<td>Controls how list container element is generated.</td> |
|||
<td>Function that takes one parameter, the user’s input and returns an element.</td> |
|||
<td>Generates <code><div></code> with class <code>awesomplete</code></td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>item</code></td> |
|||
<td>Controls how list items are generated.</td> |
|||
<td>Function that takes two parameters, the first one being the suggestion text and the second one the user’s input and returns a list item.</td> |
|||
<td>Generates list items with the user’s input highlighted via <code><mark></code>.</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>replace</code></td> |
|||
<td>Controls how the user’s selection replaces the user’s input. For example, this is useful if you want the selection to only partially replace the user’s input.</td> |
|||
<td>Function that takes one parameter, the text of the selected option, and is responsible for replacing the current input value with it. |
|||
</td> |
|||
<td><pre class="language-javascript"><code>function (text) { |
|||
this.input.value = text; |
|||
}</code></pre></td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>data</code></td> |
|||
<td>Controls suggestions' <code>label</code> and <code>value</code>. This is useful if you have list items in custom format, or want to change list items based on user's input.</td> |
|||
<td>Function that takes two parameters, the first one being the original list item and the second a string with the user’s input and returns a list item in one of supported by default formats: |
|||
<ul> |
|||
<li><code>"JavaScript"</code></li> |
|||
<li><code>{ label: "JavaScript", value: "JS" }</code></li> |
|||
<li><code>[ "JavaScript", "JS" ]</code></li> |
|||
</ul> |
|||
To <strong>use objects without <code>label</code> or <code>value</code> properties</strong>, e.g. <code>name</code> and <code>id</code> instead, you can do this: |
|||
<pre class="language-javascript"><code>data: function (item, input) { |
|||
return { label: item.name, value: item.id }; |
|||
}</code></pre> |
|||
You can <strong>use any object for <code>label</code> and <code>value</code></strong> and it will be converted to String where necessary: |
|||
<pre class="language-javascript"><code>list: [ new Date("2015-01-01"), ... ] </code></pre> |
|||
Original list items as Date objects will be accessible in <code>filter</code>, <code>sort</code>, <code>item</code> and <code>replace</code> functions, but by default we'll just see Date objects converted to strings in autocompleter and the same value will be inserted to the input. |
|||
<br /> |
|||
We can also <strong>generate list items based on user's input</strong>. See E-mail autocomplete example in <a href="#advanced-examples">Advanced Examples</a> section. |
|||
</td> |
|||
<td><code class="language-javascript">Awesomplete.DATA</code>: Identity function which just returns the original list item.</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</section> |
|||
|
|||
<section id="events"> |
|||
<h1>Events</h1> |
|||
|
|||
<p>Custom events are thrown in several places and are often cancellable. To avoid conflicts, all custom events are prefixed with <code>awesomplete-</code>.</p> |
|||
|
|||
<table> |
|||
<thead> |
|||
<tr> |
|||
<th>Name</th> |
|||
<th>Description</th> |
|||
<th>event.preventDefault()?</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td><code>awesomplete-select</code></td> |
|||
<td>The user has made a selection (either via pressing enter or clicking on an item), but it has not been applied yet. Callback will be passed an object with <code>text</code> (selected suggestion), <code>origin</code> (DOM element) properties and <code>originalEvent</code> the original triggering DOM event.</td> |
|||
<td>Yes. The selection will not be applied and the popup will not close.</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>awesomplete-selectcomplete</code></td> |
|||
<td>The user has made a selection (either via pressing enter or clicking on an item), and it has been applied. Callback will be passed an object with a <code>text</code> property containing the selected suggestion and <code>originalEvent</code> the original triggering DOM event.</td> |
|||
<td>No</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>awesomplete-open</code></td> |
|||
<td>The popup just appeared.</td> |
|||
<td>No</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>awesomplete-close</code></td> |
|||
<td>The popup just closed. Callback will be passed an object with a <code>reason</code> property that indicates why the event was fired. Reasons include <code>"blur"</code>, <code>"esc"</code>, <code>"submit"</code>, <code>"select"</code>, and <code>"nomatches"</code>.</td> |
|||
<td>No</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>awesomplete-highlight</code></td> |
|||
<td>The highlighted item just changed (in response to pressing an arrow key or via an API call). Callback will be passed an object with a <code>text</code> property containing the highlighted suggestion.</td> |
|||
<td>No</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</section> |
|||
|
|||
<section id="api"> |
|||
<h1>API</h1> |
|||
|
|||
<p>There are several methods on every Awesomplete instance that you can call to customize behavior:</p> |
|||
|
|||
<table> |
|||
<thead> |
|||
<tr> |
|||
<th>Method</th> |
|||
<th>Description</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr> |
|||
<td><code>open()</code></td> |
|||
<td>Opens the popup.</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>close()</code></td> |
|||
<td>Closes the popup.</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>next()</code></td> |
|||
<td>Highlights the next item in the popup.</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>previous()</code></td> |
|||
<td>Highlights the previous item in the popup.</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>goto(i)</code></td> |
|||
<td>Highlights the item with index <code>i</code> in the popup (<code>-1</code> to deselect all). Avoid using this directly and try to use <code>next()</code> or <code>previous()</code> instead when possible.</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>select()</code></td> |
|||
<td>Selects the currently highlighted item, replaces the text field’s value with it and closes the popup.</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>evaluate()</code></td> |
|||
<td>Evaluates the current state of the widget and regenerates the list of suggestions or closes the popup if none are available. You need to call it if you dynamically set <code>list</code> while the popup is open.</td> |
|||
</tr> |
|||
<tr> |
|||
<td><code>destroy()</code></td> |
|||
<td>Clean up and remove the instance from the input.</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</section> |
|||
|
|||
<section id="advanced-examples"> |
|||
<h1>Advanced Examples</h1> |
|||
<p>These examples show how powerful Awesomplete’s minimal API can be.</p> |
|||
|
|||
<section id="email"> |
|||
<h2>E-mail autocomplete</h2> |
|||
<label>Type an email: <input type="email"></label> |
|||
<pre class="language-markup"><code><input type="email" /></code></pre> |
|||
<pre class="language-javascript"><code><script>new Awesomplete('input[type="email"]', { |
|||
list: ["aol.com", "att.net", "comcast.net", "facebook.com", "gmail.com", "gmx.com", "googlemail.com", "google.com", "hotmail.com", "hotmail.co.uk", "mac.com", "me.com", "mail.com", "msn.com", "live.com", "sbcglobal.net", "verizon.net", "yahoo.com", "yahoo.co.uk"], |
|||
data: function (text, input) { |
|||
return input.slice(0, input.indexOf("@")) + "@" + text; |
|||
}, |
|||
filter: Awesomplete.FILTER_STARTSWITH |
|||
});</script></code></pre> |
|||
</section> |
|||
|
|||
<section id="multiple-values"> |
|||
<h2>Multiple values</h2> |
|||
<label>Tags (comma separated): <input data-list="CSS, JavaScript, HTML, SVG, ARIA, MathML" data-multiple data-minchars="1" /></label> |
|||
<pre class="language-markup"><code><input data-list="CSS, JavaScript, HTML, SVG, ARIA, MathML" data-multiple /></code></pre> |
|||
<pre class="language-javascript"><code><script>new Awesomplete('input[data-multiple]', { |
|||
filter: function(text, input) { |
|||
return Awesomplete.FILTER_CONTAINS(text, input.match(/[^,]*$/)[0]); |
|||
}, |
|||
|
|||
item: function(text, input) { |
|||
return Awesomplete.ITEM(text, input.match(/[^,]*$/)[0]); |
|||
}, |
|||
|
|||
replace: function(text) { |
|||
var before = this.input.value.match(/^.+,\s*|/)[0]; |
|||
this.input.value = before + text + ", "; |
|||
} |
|||
});</script></code></pre> |
|||
</section> |
|||
|
|||
<section id="ajax-example"> |
|||
<h2>Ajax example <small>(restcountries.eu api)</small></h2> |
|||
<label class="">Select French speaking country <input type="text"></label> |
|||
<pre class="language-javascript"><code><script>var ajax = new XMLHttpRequest(); |
|||
ajax.open("GET", "https://restcountries.eu/rest/v1/lang/fr", true); |
|||
ajax.onload = function() { |
|||
var list = JSON.parse(ajax.responseText).map(function(i) { return i.name; }); |
|||
new Awesomplete(document.querySelector("#ajax-example input"),{ list: list }); |
|||
}; |
|||
ajax.send();</script></code></pre> |
|||
</section> |
|||
|
|||
<section id="combobox"> |
|||
<h2>Combobox dropdown</h2> |
|||
<label>Type or click dropdown: <input data-list="CSS, JavaScript, HTML, SVG, ARIA, MathML" class="dropdown-input" /><button class="dropdown-btn" type="button"><span class="caret"></span></button></label> |
|||
<pre class="language-markup"><code><input data-list="CSS, JavaScript, HTML, SVG, ARIA, MathML" class="dropdown-input" /></code></pre> |
|||
<pre class="language-javascript"><code><script>var comboplete = new Awesomplete('input.dropdown-input', { |
|||
minChars: 0, |
|||
}); |
|||
Awesomplete.$('.dropdown-btn').addEventListener("click", function() { |
|||
if (comboplete.ul.childNodes.length === 0) { |
|||
comboplete.minChars = 0; |
|||
comboplete.evaluate(); |
|||
} |
|||
else if (comboplete.ul.hasAttribute('hidden')) { |
|||
comboplete.open(); |
|||
} |
|||
else { |
|||
comboplete.close(); |
|||
} |
|||
});</script></code></pre> |
|||
</section> |
|||
|
|||
<section id="download"> |
|||
<h1>Download!</h1> |
|||
|
|||
<ul> |
|||
<li><strong><a href="https://github.com/LeaVerou/awesomplete/archive/gh-pages.zip">Download everything as a zip file</a></strong></li> |
|||
<li><a href="https://github.com/LeaVerou/awesomplete">Fork me on Github</a></li> |
|||
<li><a href="https://github.com/LeaVerou/awesomplete/issues/new">File a bug</a> </li> |
|||
<li><a href="https://github.com/LeaVerou/awesomplete/issues/new">Suggest a feature</a> </li> |
|||
<li><a href="https://github.com/LeaVerou/awesomplete/blob/gh-pages/LICENSE">License: MIT license</a></li> |
|||
</ul> |
|||
|
|||
<p><strong>Pull requests are very welcome</strong>, as long as you maintain the code style and ask before adding tons of LOCs to the codebase!</p> |
|||
</section> |
|||
|
|||
<footer>Made with ♥ by <a href="http://lea.verou.me">Lea Verou</a> • <a href="http://lea.verou.me/2015/02/awesomplete-2kb-autocomplete-with-zero-dependencies">Read the blog post</a> • <a href="http://shop.oreilly.com/product/0636920031123.do">Buy my book!</a> </footer> |
|||
|
|||
|
|||
<script src="prism/prism.js" defer></script> |
|||
|
|||
<iframe src="https://ghbtns.com/github-btn.html?user=leaverou&repo=awesomplete&type=watch&count=true&size=large" height="30" width="170" frameborder="0" scrolling="0" style="width:170px; height: 30px;" allowTransparency="true" class="github-star"></iframe> |
|||
<a href="https://twitter.com/share" class="twitter-share-button" data-via="LeaVerou" data-size="large">Tweet</a> |
|||
<script async src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=leaveroume" id="_carbonads_js"></script> |
|||
|
|||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script> |
|||
<script> |
|||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
|||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
|||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
|||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); |
|||
|
|||
ga('create', 'UA-25106441-4', 'auto'); |
|||
ga('send', 'pageview'); |
|||
|
|||
</script> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,16 @@ |
|||
/* global Awesomplete, $:true, $$:true */ |
|||
$ = Awesomplete.$; |
|||
$$ = Awesomplete.$$; |
|||
|
|||
document.addEventListener("DOMContentLoaded", function() { |
|||
var nav = $("nav") |
|||
$$("section > h1").forEach(function (h1) { |
|||
if (h1.parentNode.id) { |
|||
$.create("a", { |
|||
href: "#" + h1.parentNode.id, |
|||
textContent: h1.textContent.replace(/\(.+?\)/g, ""), |
|||
inside: nav |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
@ -0,0 +1,70 @@ |
|||
module.exports = function(config) { |
|||
config.set({ |
|||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
|||
basePath: '', |
|||
|
|||
// frameworks to use
|
|||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
|||
frameworks: ['jasmine', 'jasmine-def', 'fixture'], |
|||
|
|||
// list of files / patterns to load in the browser
|
|||
files: [ |
|||
'awesomplete.js', |
|||
'test/specHelper.js', |
|||
{ |
|||
pattern: 'test/fixtures/**/*.html', |
|||
watched: true, included: true, served: true |
|||
}, |
|||
'test/**/*Spec.js' |
|||
], |
|||
|
|||
// list of files to exclude
|
|||
exclude: [ |
|||
'**/*.swp' |
|||
], |
|||
|
|||
// preprocess matching files before serving them to the browser
|
|||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
|||
preprocessors: { |
|||
'awesomplete.js': ['coverage'], |
|||
'**/*.html' : ['html2js'] |
|||
}, |
|||
|
|||
// test results reporter to use
|
|||
// possible values: 'dots', 'progress'
|
|||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
|||
reporters: ['dots', 'coverage'], |
|||
coverageReporter: { |
|||
type: 'lcov', |
|||
subdir: '.' |
|||
}, |
|||
// web server port
|
|||
port: 9876, |
|||
|
|||
// enable / disable colors in the output (reporters and logs)
|
|||
colors: true, |
|||
|
|||
// level of logging
|
|||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
|||
logLevel: config.LOG_INFO, |
|||
|
|||
// enable / disable watching file and executing tests whenever any file changes
|
|||
autoWatch: true, |
|||
|
|||
// start these browsers
|
|||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
|||
browsers: process.env.TRAVIS ? ['ChromeTravisCI'] : ['Chrome'], |
|||
|
|||
// need this to run Chrome on Travis CI
|
|||
customLaunchers: { |
|||
ChromeTravisCI: { |
|||
base: 'Chrome', |
|||
flags: ['--no-sandbox'] |
|||
} |
|||
}, |
|||
|
|||
// Continuous Integration mode
|
|||
// if true, Karma captures browsers, runs the tests and exits
|
|||
singleRun: false |
|||
}); |
|||
}; |
|||
@ -0,0 +1,32 @@ |
|||
{ |
|||
"name": "awesomplete", |
|||
"version": "1.1.5", |
|||
"description": "http://leaverou.github.io/awesomplete/", |
|||
"main": "awesomplete.js", |
|||
"scripts": { |
|||
"test": "karma start --single-run" |
|||
}, |
|||
"repository": "LeaVerou/awesomplete", |
|||
"author": "Lea Verou", |
|||
"license": "MIT", |
|||
"bugs": { |
|||
"url": "https://github.com/LeaVerou/awesomplete/issues" |
|||
}, |
|||
"homepage": "https://leaverou.github.io/awesomplete/", |
|||
"devDependencies": { |
|||
"gulp": "^3.9.0", |
|||
"gulp-concat": "^2.6.0", |
|||
"gulp-header": "^1.7.1", |
|||
"gulp-rename": "^1.2.2", |
|||
"gulp-sourcemaps": "^1.6.0", |
|||
"gulp-uglify": "^2.0.0", |
|||
"jasmine-core": "^2.4.1", |
|||
"karma": "^0.13.19", |
|||
"karma-chrome-launcher": "^0.2.2", |
|||
"karma-coverage": "^0.5.3", |
|||
"karma-fixture": "^0.2.5", |
|||
"karma-html2js-preprocessor": "^0.1.0", |
|||
"karma-jasmine": "^0.3.6", |
|||
"karma-jasmine-def": "^0.1.0" |
|||
} |
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+json */ |
|||
/** |
|||
* prism.js default theme for JavaScript, CSS and HTML |
|||
* Based on dabblet (http://dabblet.com) |
|||
* @author Lea Verou |
|||
*/ |
|||
|
|||
code[class*="language-"], |
|||
pre[class*="language-"] { |
|||
color: black; |
|||
text-shadow: 0 1px white; |
|||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; |
|||
direction: ltr; |
|||
text-align: left; |
|||
white-space: pre; |
|||
word-spacing: normal; |
|||
word-break: normal; |
|||
word-wrap: normal; |
|||
line-height: 1.5; |
|||
|
|||
-moz-tab-size: 4; |
|||
-o-tab-size: 4; |
|||
tab-size: 4; |
|||
|
|||
-webkit-hyphens: none; |
|||
-moz-hyphens: none; |
|||
-ms-hyphens: none; |
|||
hyphens: none; |
|||
} |
|||
|
|||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, |
|||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { |
|||
text-shadow: none; |
|||
background: #b3d4fc; |
|||
} |
|||
|
|||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection, |
|||
code[class*="language-"]::selection, code[class*="language-"] ::selection { |
|||
text-shadow: none; |
|||
background: #b3d4fc; |
|||
} |
|||
|
|||
@media print { |
|||
code[class*="language-"], |
|||
pre[class*="language-"] { |
|||
text-shadow: none; |
|||
} |
|||
} |
|||
|
|||
/* Code blocks */ |
|||
pre[class*="language-"] { |
|||
padding: 1em; |
|||
margin: .5em 0; |
|||
overflow: auto; |
|||
} |
|||
|
|||
:not(pre) > code[class*="language-"], |
|||
pre[class*="language-"] { |
|||
background: #f5f2f0; |
|||
} |
|||
|
|||
/* Inline code */ |
|||
:not(pre) > code[class*="language-"] { |
|||
padding: .1em; |
|||
border-radius: .3em; |
|||
white-space: normal; |
|||
} |
|||
|
|||
.token.comment, |
|||
.token.prolog, |
|||
.token.doctype, |
|||
.token.cdata { |
|||
color: slategray; |
|||
} |
|||
|
|||
.token.punctuation { |
|||
color: #999; |
|||
} |
|||
|
|||
.namespace { |
|||
opacity: .7; |
|||
} |
|||
|
|||
.token.property, |
|||
.token.tag, |
|||
.token.boolean, |
|||
.token.number, |
|||
.token.constant, |
|||
.token.symbol, |
|||
.token.deleted { |
|||
color: #905; |
|||
} |
|||
|
|||
.token.selector, |
|||
.token.attr-name, |
|||
.token.string, |
|||
.token.char, |
|||
.token.builtin, |
|||
.token.inserted { |
|||
color: #690; |
|||
} |
|||
|
|||
.token.operator, |
|||
.token.entity, |
|||
.token.url, |
|||
.language-css .token.string, |
|||
.style .token.string { |
|||
color: #a67f59; |
|||
background: hsla(0, 0%, 100%, .5); |
|||
} |
|||
|
|||
.token.atrule, |
|||
.token.attr-value, |
|||
.token.keyword { |
|||
color: #07a; |
|||
} |
|||
|
|||
.token.function { |
|||
color: #DD4A68; |
|||
} |
|||
|
|||
.token.regex, |
|||
.token.important, |
|||
.token.variable { |
|||
color: #e90; |
|||
} |
|||
|
|||
.token.important, |
|||
.token.bold { |
|||
font-weight: bold; |
|||
} |
|||
.token.italic { |
|||
font-style: italic; |
|||
} |
|||
|
|||
.token.entity { |
|||
cursor: help; |
|||
} |
|||
|
|||
@ -0,0 +1,7 @@ |
|||
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+json */ |
|||
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=t.util.clone(e[r]));return a;case"Array":return e.map&&e.map(function(e){return t.util.clone(e)})}return e}},languages:{extend:function(e,n){var a=t.util.clone(t.languages[e]);for(var r in n)a[r]=n[r];return a},insertBefore:function(e,n,a,r){r=r||t.languages;var l=r[e];if(2==arguments.length){a=arguments[1];for(var i in a)a.hasOwnProperty(i)&&(l[i]=a[i]);return l}var o={};for(var s in l)if(l.hasOwnProperty(s)){if(s==n)for(var i in a)a.hasOwnProperty(i)&&(o[i]=a[i]);o[s]=l[s]}return t.languages.DFS(t.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,n,a,r){r=r||{};for(var l in e)e.hasOwnProperty(l)&&(n.call(e,l,e[l],a||l),"Object"!==t.util.type(e[l])||r[e[l]]?"Array"!==t.util.type(e[l])||r[e[l]]||(r[e[l]]=!0,t.languages.DFS(e[l],n,l,r)):(r[e[l]]=!0,t.languages.DFS(e[l],n,null,r)))}},plugins:{},highlightAll:function(e,n){for(var a,r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'),l=0;a=r[l++];)t.highlightElement(a,e===!0,n)},highlightElement:function(n,a,r){for(var l,i,o=n;o&&!e.test(o.className);)o=o.parentNode;o&&(l=(o.className.match(e)||[,""])[1],i=t.languages[l]),n.className=n.className.replace(e,"").replace(/\s+/g," ")+" language-"+l,o=n.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,"").replace(/\s+/g," ")+" language-"+l);var s=n.textContent,u={element:n,language:l,grammar:i,code:s};if(!s||!i)return t.hooks.run("complete",u),void 0;if(t.hooks.run("before-highlight",u),a&&_self.Worker){var g=new Worker(t.filename);g.onmessage=function(e){u.highlightedCode=e.data,t.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(u.element),t.hooks.run("after-highlight",u),t.hooks.run("complete",u)},g.postMessage(JSON.stringify({language:u.language,code:u.code,immediateClose:!0}))}else u.highlightedCode=t.highlight(u.code,u.grammar,u.language),t.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(n),t.hooks.run("after-highlight",u),t.hooks.run("complete",u)},highlight:function(e,a,r){var l=t.tokenize(e,a);return n.stringify(t.util.encode(l),r)},tokenize:function(e,n){var a=t.Token,r=[e],l=n.rest;if(l){for(var i in l)n[i]=l[i];delete n.rest}e:for(var i in n)if(n.hasOwnProperty(i)&&n[i]){var o=n[i];o="Array"===t.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var u=o[s],g=u.inside,c=!!u.lookbehind,f=0,h=u.alias;u=u.pattern||u;for(var p=0;p<r.length;p++){var d=r[p];if(r.length>e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),P=[p,1];b&&P.push(b);var A=new a(i,g?t.tokenize(m,g):m,h);P.push(A),w&&P.push(w),Array.prototype.splice.apply(r,P)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var l={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==l.type&&(l.attributes.spellcheck="true"),e.alias){var i="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}t.hooks.run("wrap",l);var o="";for(var s in l.attributes)o+=(o?" ":"")+s+'="'+(l.attributes[s]||"")+'"';return"<"+l.tag+' class="'+l.classes.join(" ")+'" '+o+">"+l.content+"</"+l.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code,l=n.immediateClose;_self.postMessage(t.highlight(r,t.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); |
|||
Prism.languages.markup={comment:/<!--[\w\W]*?-->/,prolog:/<\?[\w\W]+?\?>/,doctype:/<!DOCTYPE[\w\W]+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?(?!\d)[^\s>\/=.$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; |
|||
Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<style[\w\W]*?>)[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); |
|||
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; |
|||
Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.insertBefore("javascript","class-name",{"template-string":{pattern:/`(?:\\`|\\?[^`])*`/,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<script[\w\W]*?>)[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript; |
|||
Prism.languages.json={property:/".*?"(?=\s*:)/gi,string:/"(?!:)(\\?[^"])*?"(?!:)/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,punctuation:/[{}[\]);,]/g,operator:/:/g,"boolean":/\b(true|false)\b/gi,"null":/\bnull\b/gi},Prism.languages.jsonp=Prism.languages.json; |
|||
@ -0,0 +1,292 @@ |
|||
body { |
|||
max-width: 50rem; |
|||
padding: 1rem; |
|||
margin: auto; |
|||
background: hsl(35, 80%, 94%); |
|||
color: hsl(35, 50%, 20%); |
|||
text-shadow: 0 1px 1px white; |
|||
font: 120%/1.6 Baskerville, Palatino Linotype, Palatino, serif; |
|||
} |
|||
|
|||
a { |
|||
color: #58a; |
|||
} |
|||
|
|||
h1 { |
|||
text-align: center; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
section > h1 { |
|||
margin-top: 2em; |
|||
} |
|||
|
|||
h2 { |
|||
color: hsl(35, 50%, 40%); |
|||
font-weight: 600; |
|||
font-size: 120%; |
|||
} |
|||
|
|||
ul { |
|||
margin: 0; |
|||
padding-left: .8em; |
|||
} |
|||
|
|||
strong { |
|||
font-weight: 600; |
|||
} |
|||
|
|||
abbr { |
|||
border-bottom: .1em dotted hsl(40, 80%, 40%); |
|||
cursor: help; |
|||
} |
|||
|
|||
input, button { |
|||
font: inherit; |
|||
text-shadow: inherit; |
|||
} |
|||
|
|||
button { |
|||
padding: .1em .5em; |
|||
border-radius: .3em; |
|||
background: hsl(80, 80%, 80%); |
|||
background: linear-gradient(hsl(40, 70%, 80%), hsl(40, 70%, 70%)); |
|||
border: 1px solid rgba(0,0,0,.3); |
|||
box-shadow: 0 1px white inset, 0 .3em .3em -.3em rgba(0,0,0,.3); |
|||
} |
|||
|
|||
input { |
|||
width: 12em; |
|||
padding: .1em .3em; |
|||
border: 0; |
|||
border: 1px solid hsl(35, 80%, 60%); |
|||
background: hsla(0,0%,100%,.2); |
|||
border-radius: .3em; |
|||
box-shadow: .05em .1em .3em rgba(0,0,0,.3) inset; |
|||
} |
|||
|
|||
@keyframes pulsate { |
|||
to { |
|||
box-shadow: .05em .1em .3em rgba(0,0,0,.3) inset, 0 0 .3em .1em #58a; |
|||
} |
|||
} |
|||
|
|||
input:focus { |
|||
outline: none; |
|||
border: 1px solid #58a; |
|||
animation: pulsate 2s infinite alternate linear; |
|||
background: hsla(0,0%,100%,.5); |
|||
} |
|||
|
|||
header { |
|||
max-width: 37rem; |
|||
margin: 0 auto 2em; |
|||
text-align: center; |
|||
font-size: 150%; |
|||
font-style: italic; |
|||
} |
|||
|
|||
header h1 { |
|||
margin: .1em 0; |
|||
text-align: center; |
|||
color: hsl(35, 50%, 40%); |
|||
font-size: 400%; |
|||
line-height: 1; |
|||
font-style: italic; |
|||
font-weight: normal; |
|||
} |
|||
|
|||
header p { |
|||
margin: 0; |
|||
} |
|||
|
|||
header p a { |
|||
text-decoration: none; |
|||
color: inherit; |
|||
} |
|||
|
|||
.size { |
|||
position: absolute; |
|||
top: 1em; |
|||
right: 1em; |
|||
width: 4em; |
|||
padding: .8em .6em 1em; |
|||
background: hsl(40, 80%, 40%); |
|||
color: hsl(35, 80%, 94%); |
|||
text-shadow: none; |
|||
text-align: center; |
|||
line-height: 1.1; |
|||
text-indent: -.1em; |
|||
outline: .1em dotted; |
|||
outline-offset: -.3em; |
|||
text-decoration: none; |
|||
border-radius: .15em; |
|||
} |
|||
|
|||
.size:hover { |
|||
background: #58a; |
|||
} |
|||
|
|||
.size strong { |
|||
display: block; |
|||
margin-bottom: .1em; |
|||
font-size: 150%; |
|||
line-height: 1; |
|||
font-weight: 900; |
|||
font-style: normal; |
|||
} |
|||
|
|||
.size .amp { |
|||
position: absolute; |
|||
left: 0; right: 0; |
|||
bottom: 0; |
|||
color: hsla(35, 80%, 94%,.3); |
|||
font-size: 350%; |
|||
} |
|||
|
|||
nav { |
|||
margin-top: .5em; |
|||
} |
|||
|
|||
nav a { |
|||
position: relative; |
|||
display: inline-block; |
|||
padding: 0 .4em; |
|||
margin: 0 .1em; |
|||
|
|||
color: hsl(35, 80%, 94%); |
|||
text-shadow: none; |
|||
text-decoration: none; |
|||
font-size: 80%; |
|||
} |
|||
|
|||
nav a::before { |
|||
content: ""; |
|||
position: absolute; |
|||
top: 0; left: 0; right: 0; bottom: 0; |
|||
z-index: -1; |
|||
border-radius: .2em; |
|||
background: hsl(40, 80%, 40%); |
|||
-webkit-transform: skew(-16deg); |
|||
transform: skew(-16deg); |
|||
} |
|||
|
|||
nav a[href="#download"] { |
|||
font-weight: 600; |
|||
} |
|||
|
|||
nav a[href="#download"]::before { |
|||
background: hsl(65, 85%, 35%); |
|||
} |
|||
|
|||
nav a:hover::before { |
|||
background: #58a; |
|||
} |
|||
|
|||
|
|||
:not(pre) > code[class*="language-"], |
|||
pre[class*="language-"] { |
|||
background: hsl(35, 70%, 88%); |
|||
font-size: 80%; |
|||
border-radius: .3em; |
|||
} |
|||
|
|||
footer { |
|||
padding: .6em; |
|||
border-top: 1px solid rgba(0,0,0,.3); |
|||
margin-top: 1em; |
|||
text-align: center; |
|||
} |
|||
|
|||
pre { |
|||
position: relative; |
|||
} |
|||
|
|||
pre::before { |
|||
position: absolute; |
|||
top: 0; |
|||
right: .5em; |
|||
padding: .3em .4em; |
|||
border-radius: 0 0 .3em .3em; |
|||
background: hsl(35, 50%, 60%); |
|||
color: hsl(35, 80%, 90%); |
|||
text-shadow: none; |
|||
font: bold 100%/1 Baskerville, Palatino Linotype, Palatino, serif; |
|||
} |
|||
|
|||
pre.language-markup::before { |
|||
content: "HTML"; |
|||
} |
|||
|
|||
pre.language-javascript::before { |
|||
content: "JS"; |
|||
} |
|||
|
|||
table { |
|||
table-layout: fixed; |
|||
border-spacing: 0; |
|||
border-collapse: collapse; |
|||
} |
|||
|
|||
th { |
|||
font-size: 75%; |
|||
} |
|||
|
|||
td { |
|||
vertical-align: top; |
|||
padding: .5em; |
|||
border: 1px solid rgba(0,0,0,.1); |
|||
} |
|||
|
|||
:not(pre) > code[class*="language-"] { |
|||
background: none; |
|||
} |
|||
|
|||
.github-star { |
|||
position: absolute; |
|||
top: 1em; |
|||
left: 1em; |
|||
} |
|||
|
|||
#carbonads { |
|||
position: absolute; |
|||
top: 100%; |
|||
top: calc(100vh - 15em); |
|||
right: 1em; |
|||
max-width: 10em; |
|||
max-width: -webkit-min-content; |
|||
max-width: min-content; |
|||
font-size: 75%; |
|||
} |
|||
|
|||
#carbonads a { |
|||
text-decoration: none; |
|||
color: inherit; |
|||
} |
|||
|
|||
#carbonads img { |
|||
display: block; |
|||
} |
|||
|
|||
#combobox .dropdown-input { |
|||
border-top-right-radius: 0; |
|||
border-bottom-right-radius: 0; |
|||
} |
|||
#combobox .dropdown-btn { |
|||
vertical-align: top; |
|||
height: 36.5px; |
|||
border-top-left-radius: 0; |
|||
border-bottom-left-radius: 0; |
|||
} |
|||
|
|||
#combobox .caret { |
|||
display: inline-block; |
|||
width: 0; |
|||
height: 0; |
|||
margin-left: 2px; |
|||
vertical-align: middle; |
|||
border-top: 4px dashed; |
|||
border-top: 4px solid; |
|||
border-right: 4px solid transparent; |
|||
border-left: 4px solid transparent; |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
describe("awesomplete.close", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }) }); |
|||
|
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "ite"); |
|||
this.subject.open(); |
|||
this.subject.next(); |
|||
}); |
|||
|
|||
it("closes completer", function () { |
|||
this.subject.close(); |
|||
expect(this.subject.ul.hasAttribute("hidden")).toBe(true); |
|||
}); |
|||
|
|||
it("makes no item selected", function () { |
|||
this.subject.close(); |
|||
expect(this.subject.index).toBe(-1); |
|||
}); |
|||
|
|||
it("fires awesomplete-close event", function () { |
|||
var handler = $.spyOnEvent(this.subject.input, "awesomplete-close"); |
|||
this.subject.close(); |
|||
|
|||
expect(handler).toHaveBeenCalledWith( |
|||
jasmine.any(document.createEvent("HTMLEvents").constructor) |
|||
); |
|||
}); |
|||
|
|||
it("returns early if already closed", function () { |
|||
var handler = $.spyOnEvent(this.subject.input, "awesomplete-close"); |
|||
this.subject.close(); |
|||
this.subject.close(); |
|||
|
|||
expect(handler.calls.count()).toBe(1); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,96 @@ |
|||
describe("awesomplete.evaluate", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }); |
|||
}); |
|||
|
|||
describe("with too short input value", function () { |
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "i"); |
|||
}); |
|||
|
|||
it("closes completer", function () { |
|||
spyOn(this.subject, "close"); |
|||
this.subject.evaluate(); |
|||
|
|||
expect(this.subject.close).toHaveBeenCalledWith({ |
|||
reason: "nomatches" |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe("with no items found", function () { |
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "nosuchitem"); |
|||
}); |
|||
|
|||
it("closes completer", function () { |
|||
spyOn(this.subject, "close"); |
|||
this.subject.evaluate(); |
|||
|
|||
expect(this.subject.close).toHaveBeenCalledWith({ |
|||
reason: "nomatches" |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe("with some items found", function () { |
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "ite"); |
|||
}); |
|||
|
|||
it("opens completer", function () { |
|||
spyOn(this.subject, "open"); |
|||
this.subject.evaluate(); |
|||
|
|||
expect(this.subject.open).toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it("fills completer with found items", function () { |
|||
this.subject.evaluate(); |
|||
expect(this.subject.ul.children.length).toBe(3); |
|||
}); |
|||
|
|||
it("shows no more than maxItems", function () { |
|||
this.subject.maxItems = 2; |
|||
this.subject.evaluate(); |
|||
expect(this.subject.ul.children.length).toBe(2); |
|||
}); |
|||
|
|||
it("makes no item selected", function () { |
|||
this.subject.evaluate(); |
|||
expect(this.subject.index).toBe(-1); |
|||
}); |
|||
}); |
|||
|
|||
describe("with minChars: 0", function () { |
|||
beforeEach(function () { |
|||
this.subject.minChars = 0; |
|||
}); |
|||
|
|||
it("opens completer", function () { |
|||
spyOn(this.subject, "open"); |
|||
this.subject.evaluate(); |
|||
|
|||
expect(this.subject.open).toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it("fills completer with all items", function () { |
|||
this.subject.evaluate(); |
|||
expect(this.subject.ul.children.length).toBe(3); |
|||
}); |
|||
|
|||
it("shows no more than maxItems", function () { |
|||
this.subject.maxItems = 2; |
|||
this.subject.evaluate(); |
|||
expect(this.subject.ul.children.length).toBe(2); |
|||
}); |
|||
|
|||
it("makes no item selected", function () { |
|||
this.subject.evaluate(); |
|||
expect(this.subject.index).toBe(-1); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,67 @@ |
|||
describe("awesomplete.goto", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }); |
|||
}); |
|||
|
|||
def("lastIndex", function () { return this.subject.ul.children.length - 1 }); |
|||
|
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "ite"); |
|||
}); |
|||
|
|||
it("clears previous aria-selected", function () { |
|||
this.subject.goto(0); |
|||
this.subject.goto(this.lastIndex); |
|||
|
|||
expect(this.subject.ul.children[0].getAttribute("aria-selected")).toBe("false"); |
|||
}); |
|||
|
|||
it("goes to first item", function () { |
|||
this.subject.goto(0); |
|||
expect(this.subject.index).toBe(0); |
|||
}); |
|||
|
|||
it("goes to last item", function () { |
|||
this.subject.goto(this.lastIndex); |
|||
expect(this.subject.index).toBe(this.lastIndex); |
|||
}); |
|||
|
|||
it("fires awesomplete-highlight event", function () { |
|||
var handler = $.spyOnEvent(this.subject.input, "awesomplete-highlight"); |
|||
this.subject.goto(1); |
|||
|
|||
expect(handler).toHaveBeenCalledWith( |
|||
jasmine.objectContaining({ |
|||
text: jasmine.objectContaining({ label: "item2", value: "item2" }) |
|||
}) |
|||
); |
|||
}); |
|||
|
|||
describe("with item index > -1", function () { |
|||
beforeEach(function () { |
|||
this.subject.goto(0); |
|||
}); |
|||
|
|||
it("sets aria-selected", function () { |
|||
expect(this.subject.ul.children[0].getAttribute("aria-selected")).toBe("true"); |
|||
}); |
|||
|
|||
it("updates status", function () { |
|||
expect(this.subject.status.textContent).toBe("item1, list item 1 of 3"); |
|||
}); |
|||
}); |
|||
|
|||
describe("with item index = -1", function () { |
|||
beforeEach(function () { |
|||
this.subject.goto(0); |
|||
this.subject.goto(-1); |
|||
}); |
|||
|
|||
it("does not update status", function () { |
|||
expect(this.subject.status.textContent).toBe("item1, list item 1 of 3"); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,56 @@ |
|||
describe("awesomplete.next", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }); |
|||
}); |
|||
|
|||
def("lastIndex", function () { return this.subject.ul.children.length - 1 }); |
|||
|
|||
describe("without any items found", function () { |
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "nosuchitem"); |
|||
this.subject.open(); |
|||
}); |
|||
|
|||
it("does not select any item", function () { |
|||
this.subject.next(); |
|||
expect(this.subject.index).toBe(-1); |
|||
}); |
|||
}); |
|||
|
|||
describe("with some items found", function () { |
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "ite"); |
|||
this.subject.open(); |
|||
}); |
|||
|
|||
describe("and no item was already selected", function () { |
|||
it("selects the first item ", function () { |
|||
this.subject.next(); |
|||
expect(this.subject.index).toBe(0); |
|||
}); |
|||
}); |
|||
|
|||
describe("and some item was already selected", function () { |
|||
it("selects the second item", function () { |
|||
this.subject.goto(0); |
|||
this.subject.next(); |
|||
expect(this.subject.index).toBe(1); |
|||
}); |
|||
|
|||
it("selects the last item", function () { |
|||
this.subject.goto(this.lastIndex - 1); |
|||
this.subject.next(); |
|||
expect(this.subject.index).toBe(this.lastIndex); |
|||
}); |
|||
|
|||
it("selects the first item after reaching the end", function () { |
|||
this.subject.goto(this.lastIndex); |
|||
this.subject.next(); |
|||
expect(this.subject.index).toBe(0); |
|||
}); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,59 @@ |
|||
describe("awesomplete.open", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }) }); |
|||
|
|||
// Exposes this bug https://github.com/LeaVerou/awesomplete/pull/16740
|
|||
// FIXME better fix is probably required as discussed in PR above
|
|||
xit("fills in the list on creation", function () { |
|||
$("#plain").value = "ite"; |
|||
this.subject.open(); |
|||
|
|||
expect(this.subject.ul.children.length).toBe(3); |
|||
}); |
|||
|
|||
it("opens completer", function () { |
|||
this.subject.open(); |
|||
expect(this.subject.ul.hasAttribute("hidden")).toBe(false); |
|||
}); |
|||
|
|||
describe("with autoFirst: true", function () { |
|||
beforeEach(function () { |
|||
this.subject.autoFirst = true; |
|||
spyOn(this.subject, "goto"); |
|||
}); |
|||
|
|||
it("selects first item if wasn't seleted before", function () { |
|||
this.subject.open(); |
|||
|
|||
expect(this.subject.goto).toHaveBeenCalledWith(0); |
|||
}); |
|||
|
|||
it("does not select any item if was seleted before", function () { |
|||
this.subject.index = 0; |
|||
this.subject.open(); |
|||
|
|||
expect(this.subject.goto).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
|
|||
describe("with autoFirst: false", function () { |
|||
it("does not select any item", function () { |
|||
this.subject.autoFirst = false; |
|||
spyOn(this.subject, "goto"); |
|||
|
|||
this.subject.open(); |
|||
|
|||
expect(this.subject.goto).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
|
|||
it("fires awesomplete-open event", function () { |
|||
var handler = $.spyOnEvent(this.subject.input, "awesomplete-open"); |
|||
|
|||
this.subject.open(); |
|||
|
|||
expect(handler).toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,26 @@ |
|||
describe("awesomplete.opened", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { return new Awesomplete("#plain") }); |
|||
|
|||
describe("with newly created completer", function () { |
|||
it("is false", function () { |
|||
expect(this.subject.opened).toBe(false); |
|||
}); |
|||
}); |
|||
|
|||
describe("with opened completer", function () { |
|||
it("is true", function () { |
|||
this.subject.open(); |
|||
expect(this.subject.opened).toBe(true); |
|||
}); |
|||
}); |
|||
|
|||
describe("with closed completer", function () { |
|||
it("is false", function () { |
|||
this.subject.close(); |
|||
expect(this.subject.opened).toBe(false); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,56 @@ |
|||
describe("awesomplete.previous", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }); |
|||
}); |
|||
|
|||
def("lastIndex", function () { return this.subject.ul.children.length - 1 }); |
|||
|
|||
describe("without any items found", function () { |
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "nosuchitem"); |
|||
this.subject.open(); |
|||
}); |
|||
|
|||
it("does not select any item", function () { |
|||
this.subject.previous(); |
|||
expect(this.subject.index).toBe(-1); |
|||
}); |
|||
}); |
|||
|
|||
describe("with some items found", function () { |
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "ite"); |
|||
this.subject.open(); |
|||
}); |
|||
|
|||
describe("and no item was already selected", function () { |
|||
it("selects the last item ", function () { |
|||
this.subject.previous(); |
|||
expect(this.subject.index).toBe(this.lastIndex); |
|||
}); |
|||
}); |
|||
|
|||
describe("and some item was already selected", function () { |
|||
it("selects the second item from the end", function () { |
|||
this.subject.goto(this.lastIndex); |
|||
this.subject.previous(); |
|||
expect(this.subject.index).toBe(this.lastIndex - 1); |
|||
}); |
|||
|
|||
it("selects the first item", function () { |
|||
this.subject.goto(1); |
|||
this.subject.previous(); |
|||
expect(this.subject.index).toBe(0); |
|||
}); |
|||
|
|||
it("selects the last item after reaching the start", function () { |
|||
this.subject.goto(0); |
|||
this.subject.previous(); |
|||
expect(this.subject.index).toBe(this.lastIndex); |
|||
}); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,55 @@ |
|||
describe("awesomplete.destroy", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }) }); |
|||
|
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "ite"); |
|||
this.subject.open(); |
|||
this.subject.next(); |
|||
}); |
|||
|
|||
it("removes its elements from the DOM", function () { |
|||
var instance = this.subject; |
|||
var inputParentBefore = this.subject.input.parentNode; |
|||
|
|||
instance.destroy(); |
|||
|
|||
expect(instance.input.parentNode).not.toBe(inputParentBefore); |
|||
expect(document.body.contains(instance.container)).toBeFalsy(); |
|||
expect(document.body.contains(instance.ul)).toBeFalsy() |
|||
}); |
|||
|
|||
it("removes the autocomplete and aria-autocomplete attributes", function() { |
|||
var instance = this.subject; |
|||
|
|||
instance.destroy(); |
|||
|
|||
expect(instance.input.getAttribute("autocomplete")).toBeNull(); |
|||
expect(instance.input.getAttribute("aria-autocomplete")).toBeNull(); |
|||
}); |
|||
|
|||
it("removes event listeners from form and input", function () { |
|||
var instance = this.subject; |
|||
var events = instance._events; |
|||
|
|||
spyOn(events.input, "blur"); |
|||
|
|||
instance.destroy(); |
|||
|
|||
Awesomplete.$.fire(instance.input, "blur"); |
|||
|
|||
expect(events.input.blur).not.toHaveBeenCalledTimes(1); |
|||
}); |
|||
|
|||
it("removes itself from the global list of instances", function() { |
|||
var countBefore = Awesomplete.all.length; |
|||
|
|||
this.subject.destroy(); |
|||
|
|||
var countAfter = Awesomplete.all.length; |
|||
|
|||
expect(countAfter).toBeLessThan(countBefore); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,132 @@ |
|||
describe("awesomplete.select", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }); |
|||
}); |
|||
|
|||
def("lastIndex", function () { return this.subject.ul.children.length - 1 }); |
|||
def("lastLi", function () { return this.subject.ul.children[this.lastIndex] }); |
|||
|
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "ite"); |
|||
}); |
|||
|
|||
describe("with closed completer", itDoesNotSelectAnyItem); |
|||
|
|||
describe("with opened completer", function () { |
|||
beforeEach(function () { |
|||
this.subject.open(); |
|||
}); |
|||
|
|||
describe("and no current item", itDoesNotSelectAnyItem); |
|||
|
|||
describe("and current item", function () { |
|||
beforeEach(function () { |
|||
this.subject.goto(0); |
|||
}); |
|||
|
|||
itSelects("item1"); |
|||
}); |
|||
|
|||
describe("and item specified as argument", function () { |
|||
def("selectArgument", function () { return this.lastLi }); |
|||
|
|||
itSelects("item3"); |
|||
}); |
|||
}); |
|||
|
|||
// Shared behaviors
|
|||
|
|||
function itSelects(expectedTxt) { |
|||
it("fires awesomplete-select event", function () { |
|||
var handler = $.spyOnEvent(this.subject.input, "awesomplete-select"); |
|||
this.subject.select(this.selectArgument); |
|||
|
|||
expect(handler).toHaveBeenCalledWith( |
|||
jasmine.objectContaining({ |
|||
text: jasmine.objectContaining({ label: expectedTxt, value: expectedTxt }), |
|||
origin: this.selectArgument || this.subject.ul.children[0] |
|||
}) |
|||
); |
|||
}); |
|||
|
|||
describe("and awesomplete-select event was not prevented", function () { |
|||
beforeEach(function () { |
|||
$.on(this.subject.input, "awesomplete-select", $.noop); |
|||
}); |
|||
|
|||
it("changes the input value", function () { |
|||
this.subject.select(this.selectArgument); |
|||
expect(this.subject.input.value).toBe(expectedTxt); |
|||
}); |
|||
|
|||
it("closes completer", function () { |
|||
spyOn(this.subject, "close"); |
|||
this.subject.select(this.selectArgument); |
|||
|
|||
expect(this.subject.close).toHaveBeenCalledWith({ |
|||
reason: "select" |
|||
}); |
|||
}); |
|||
|
|||
it("fires awesomplete-selectcomplete event", function () { |
|||
var handler = $.spyOnEvent(this.subject.input, "awesomplete-selectcomplete"); |
|||
this.subject.select(this.selectArgument); |
|||
|
|||
expect(handler).toHaveBeenCalledWith( |
|||
jasmine.objectContaining({ |
|||
text: jasmine.objectContaining({ label: expectedTxt, value: expectedTxt }) |
|||
}) |
|||
); |
|||
}); |
|||
}); |
|||
|
|||
describe("and awesomplete-select event was prevented", function () { |
|||
beforeEach(function () { |
|||
$.on(this.subject.input, "awesomplete-select", function (evt) { evt.preventDefault() }); |
|||
}); |
|||
|
|||
it("does not change the input value", function () { |
|||
this.subject.select(this.selectArgument); |
|||
expect(this.subject.input.value).toBe("ite"); |
|||
}); |
|||
|
|||
it("does not close completer", function () { |
|||
spyOn(this.subject, "close"); |
|||
this.subject.select(this.selectArgument); |
|||
|
|||
expect(this.subject.close).not.toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it("does not fire awesomplete-selectcomplete event", function () { |
|||
var handler = $.spyOnEvent(this.subject.input, "awesomplete-selectcomplete"); |
|||
this.subject.select(this.selectArgument); |
|||
|
|||
expect(handler).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function itDoesNotSelectAnyItem() { |
|||
it("does not change the input value", function () { |
|||
this.subject.select(); |
|||
expect(this.subject.input.value).toBe("ite"); |
|||
}); |
|||
|
|||
it("does not fire awesomplete-select event", function () { |
|||
var handler = $.spyOnEvent(this.subject.input, "awesomplete-select"); |
|||
this.subject.select(); |
|||
|
|||
expect(handler).not.toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it("does not fire awesomplete-selectcomplete event", function () { |
|||
var handler = $.spyOnEvent(this.subject.input, "awesomplete-selectcomplete"); |
|||
this.subject.select(); |
|||
|
|||
expect(handler).not.toHaveBeenCalled(); |
|||
}); |
|||
} |
|||
}); |
|||
@ -0,0 +1,41 @@ |
|||
describe("awesomplete.selected", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }); |
|||
}); |
|||
|
|||
describe("with newly created completer", function () { |
|||
it("is false", function () { |
|||
expect(this.subject.selected).toBe(false); |
|||
}); |
|||
}); |
|||
|
|||
describe("with opened completer", function () { |
|||
beforeEach(function () { |
|||
this.subject.open(); |
|||
$.type(this.subject.input, "ite"); |
|||
}); |
|||
|
|||
describe("and no item selected", function () { |
|||
it("is false", function () { |
|||
expect(this.subject.selected).toBe(false); |
|||
}); |
|||
}); |
|||
|
|||
describe("and some item selected", function () { |
|||
it("is true", function () { |
|||
this.subject.next(); |
|||
expect(this.subject.selected).toBe(true); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe("with closed completer", function () { |
|||
it("is false", function () { |
|||
this.subject.close(); |
|||
expect(this.subject.selected).toBe(false); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,15 @@ |
|||
describe("blur event", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { return new Awesomplete("#plain") }); |
|||
|
|||
it("closes completer", function () { |
|||
spyOn(Awesomplete.prototype, "close"); |
|||
$.fire(this.subject.input, "blur"); |
|||
expect(Awesomplete.prototype.close).toHaveBeenCalledWith( |
|||
{ reason: "blur" }, |
|||
jasmine.any(document.createEvent("HTMLEvents").constructor) |
|||
); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,61 @@ |
|||
describe("click event", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }); |
|||
}); |
|||
|
|||
def("li", function () { return this.subject.ul.children[1] }); |
|||
|
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "ite"); |
|||
spyOn(this.subject, "select"); |
|||
}); |
|||
|
|||
describe("with ul target", function () { |
|||
def("target", function () { return this.subject.ul }); |
|||
|
|||
it("does not select item", function () { |
|||
$.fire(this.target, "click", { button: 0 }); |
|||
expect(this.subject.select).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
|
|||
describe("with li target", function () { |
|||
def("target", function () { return this.li }); |
|||
|
|||
describe("on left click", function () { |
|||
it("selects item", function () { |
|||
var event = $.fire(this.target, "click", { button: 0 }); |
|||
expect(this.subject.select).toHaveBeenCalledWith(this.li, this.target, event); |
|||
}); |
|||
}); |
|||
|
|||
describe("on right click", function () { |
|||
it("does not select item", function () { |
|||
$.fire(this.target, "click", { button: 2 }); |
|||
expect(this.subject.select).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe("with child of li target", function () { |
|||
def("target", function () { return $("mark", this.li) }); |
|||
|
|||
describe("on left click", function () { |
|||
it("selects item", function () { |
|||
var event = $.fire(this.target, "click", { button: 0 }); |
|||
expect(this.subject.select).toHaveBeenCalledWith(this.li, this.target, event); |
|||
expect(event.defaultPrevented).toBe(true); |
|||
}); |
|||
}); |
|||
|
|||
describe("on right click", function () { |
|||
it("does not select item", function () { |
|||
$.fire(this.target, "click", { button: 2 }); |
|||
expect(this.subject.select).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,12 @@ |
|||
describe("input event", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { return new Awesomplete("#plain") }); |
|||
|
|||
it("rebuilds the list", function () { |
|||
spyOn(Awesomplete.prototype, "evaluate"); |
|||
$.type(this.subject.input, "ite"); |
|||
expect(Awesomplete.prototype.evaluate).toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,96 @@ |
|||
describe("keydown event", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }); |
|||
}); |
|||
|
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "ite"); |
|||
}); |
|||
|
|||
it("supports enter", function () { |
|||
this.subject.next(); |
|||
|
|||
spyOn(this.subject, "select"); |
|||
$.keydown(this.subject.input, $.k.ENTER); |
|||
|
|||
expect(this.subject.select).toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it("supports escape", function () { |
|||
spyOn(this.subject, "close"); |
|||
$.keydown(this.subject.input, $.k.ESC); |
|||
|
|||
expect(this.subject.close).toHaveBeenCalledWith({ |
|||
reason: "esc" |
|||
}); |
|||
}); |
|||
|
|||
it("supports down arrow", function () { |
|||
spyOn(this.subject, "next"); |
|||
$.keydown(this.subject.input, $.k.DOWN); |
|||
|
|||
expect(this.subject.next).toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it("supports up arrow", function () { |
|||
spyOn(this.subject, "previous"); |
|||
$.keydown(this.subject.input, $.k.UP); |
|||
|
|||
expect(this.subject.previous).toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it("ignores other keys", function() { |
|||
spyOn(this.subject, "select"); |
|||
spyOn(this.subject, "close"); |
|||
spyOn(this.subject, "next"); |
|||
spyOn(this.subject, "previous"); |
|||
|
|||
$.keydown(this.subject.input, 111); |
|||
|
|||
expect(this.subject.select).not.toHaveBeenCalled(); |
|||
expect(this.subject.close).not.toHaveBeenCalled(); |
|||
expect(this.subject.next).not.toHaveBeenCalled(); |
|||
expect(this.subject.previous).not.toHaveBeenCalled(); |
|||
}); |
|||
|
|||
it("does nothing if not opened", function () { |
|||
this.subject.close(); |
|||
|
|||
spyOn(this.subject, "next"); |
|||
$.keydown(this.subject.input, $.k.DOWN); |
|||
|
|||
expect(this.subject.next).not.toHaveBeenCalled(); |
|||
}); |
|||
it("does not select on tab", function () { |
|||
this.subject.next(); |
|||
|
|||
spyOn(this.subject, "select"); |
|||
$.keydown(this.subject.input, $.k.TAB); |
|||
|
|||
expect(this.subject.select).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
|
|||
describe("tabSelect option true ", function () { |
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return new Awesomplete("#plain", { list: ["item1", "item2", "item3"], tabSelect: true }); |
|||
}); |
|||
|
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "ite"); |
|||
}); |
|||
|
|||
it("selects on tab", function () { |
|||
this.subject.next(); |
|||
|
|||
spyOn(this.subject, "select"); |
|||
$.keydown(this.subject.input, $.k.TAB); |
|||
|
|||
expect(this.subject.select).toHaveBeenCalled(); |
|||
}); |
|||
}) |
|||
@ -0,0 +1,69 @@ |
|||
describe("mousedown event", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return new Awesomplete("#plain", { list: ["item1", "item2", "item3"] }); |
|||
}); |
|||
|
|||
def("li", function () { return this.subject.ul.children[1] }); |
|||
|
|||
beforeEach(function () { |
|||
$.type(this.subject.input, "ite"); |
|||
spyOn(this.subject, "select"); |
|||
}); |
|||
|
|||
describe("with ul target", function () { |
|||
def("target", function () { return this.subject.ul }); |
|||
|
|||
it("does not select item and keeps the input focussed", function () { |
|||
$.fire(this.target, "mousedown", { button: 0 }); |
|||
expect(this.subject.select).not.toHaveBeenCalled(); |
|||
expect(this.subject.input).toBe(document.activeElement); |
|||
}); |
|||
}); |
|||
|
|||
describe("with li target", function () { |
|||
def("target", function () { return this.li }); |
|||
|
|||
describe("on left click", function () { |
|||
it("does not select item and keeps the input focussed", function () { |
|||
var event = $.fire(this.target, "mousedown", { button: 0 }); |
|||
expect(this.subject.select).not.toHaveBeenCalled(); |
|||
expect(event.defaultPrevented).toBe(true); |
|||
expect(this.subject.input).toBe(document.activeElement); |
|||
}); |
|||
}); |
|||
|
|||
describe("on right click", function () { |
|||
it("does not select item and keeps the input focussed", function () { |
|||
var event = $.fire(this.target, "mousedown", { button: 2 }); |
|||
expect(this.subject.select).not.toHaveBeenCalled(); |
|||
expect(event.defaultPrevented).toBe(true); |
|||
expect(this.subject.input).toBe(document.activeElement); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe("with child of li target", function () { |
|||
def("target", function () { return $("mark", this.li) }); |
|||
|
|||
describe("on left click", function () { |
|||
it("does not select item and keeps the input focussed", function () { |
|||
var event = $.fire(this.target, "mousedown", { button: 0 }); |
|||
expect(this.subject.select).not.toHaveBeenCalled(); |
|||
expect(event.defaultPrevented).toBe(true); |
|||
expect(this.subject.input).toBe(document.activeElement); |
|||
}); |
|||
}); |
|||
|
|||
describe("on right click", function () { |
|||
it("does not select item and keeps the input focussed", function () { |
|||
var event = $.fire(this.target, "mousedown", { button: 2 }); |
|||
expect(this.subject.select).not.toHaveBeenCalled(); |
|||
expect(event.defaultPrevented).toBe(true); |
|||
expect(this.subject.input).toBe(document.activeElement); |
|||
}); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,17 @@ |
|||
describe("form submit event", function () { |
|||
|
|||
$.fixture("options"); |
|||
|
|||
subject(function () { return new Awesomplete("#inside-form") }); |
|||
|
|||
it("closes completer", function () { |
|||
spyOn(Awesomplete.prototype, "close"); |
|||
// prevent full page reload in Firefox, which causes tests to stop running
|
|||
$.on(this.subject.input.form, "submit", function (evt) { evt.preventDefault() }); |
|||
$.fire(this.subject.input.form, "submit"); |
|||
expect(Awesomplete.prototype.close).toHaveBeenCalledWith( |
|||
{ reason: "submit" }, |
|||
jasmine.any(document.createEvent("HTMLEvents").constructor) |
|||
); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,22 @@ |
|||
<input id="no-options" class="simple-input" /> |
|||
|
|||
<input id="with-custom-options" data-minchars="4" data-maxitems="8" data-autofirst="true" /> |
|||
|
|||
<input id="with-data-list-inline" data-list="With, Data, List, Inline" /> |
|||
|
|||
<input id="with-data-list" data-list="#data-list" /> |
|||
<ul id="data-list"> |
|||
<li>With</li> |
|||
<li>Data</li> |
|||
<li>List</li> |
|||
</ul> |
|||
|
|||
<input id="with-list" list="list" /> |
|||
<datalist id="list"> |
|||
<option>With</option> |
|||
<option>List</option> |
|||
</datalist> |
|||
|
|||
<form> |
|||
<input id="inside-form" /> |
|||
</form> |
|||
@ -0,0 +1 @@ |
|||
<input id="plain" /> |
|||
@ -0,0 +1,59 @@ |
|||
describe("Awesomplete.$.bind", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return function () { Awesomplete.$.bind(this.element, this.events) }; |
|||
}); |
|||
|
|||
describe("with invalid element", function () { |
|||
it("does nothing if element is undefined", function () { |
|||
this.element = undefined; |
|||
expect(this.subject).not.toThrow(); |
|||
}); |
|||
|
|||
it("does nothing if element is null", function () { |
|||
this.element = null; |
|||
expect(this.subject).not.toThrow(); |
|||
}); |
|||
|
|||
it("does nothing if element is false", function () { |
|||
this.element = false; |
|||
expect(this.subject).not.toThrow(); |
|||
}); |
|||
|
|||
it("does nothing if element is 0", function () { |
|||
this.element = 0; |
|||
expect(this.subject).not.toThrow(); |
|||
}); |
|||
|
|||
it("does nothing if element is empty string", function () { |
|||
this.element = ""; |
|||
expect(this.subject).not.toThrow(); |
|||
}); |
|||
}); |
|||
|
|||
describe("with valid element", function () { |
|||
def("element", function () { return $("#plain") }); |
|||
|
|||
beforeEach(function () { |
|||
spyOn(this.element, "addEventListener"); |
|||
}); |
|||
|
|||
it("adds event listeners for all events", function () { |
|||
this.events = { click: $.noop, input: $.noop }; |
|||
this.subject(); |
|||
|
|||
expect(this.element.addEventListener).toHaveBeenCalledWith("click", this.events.click); |
|||
expect(this.element.addEventListener).toHaveBeenCalledWith("input", this.events.input); |
|||
}); |
|||
|
|||
it("adds single event listener for multiple events", function () { |
|||
this.events = { "click input": $.noop }; |
|||
this.subject(); |
|||
|
|||
expect(this.element.addEventListener).toHaveBeenCalledWith("click", this.events["click input"]); |
|||
expect(this.element.addEventListener).toHaveBeenCalledWith("input", this.events["click input"]); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,102 @@ |
|||
describe("Awesomplete.$.create", function () { |
|||
|
|||
$.fixture("options"); |
|||
|
|||
subject(function () { return Awesomplete.$.create(this.tag, this.options || {}) }); |
|||
|
|||
def("tag", "div"); |
|||
|
|||
it("creates DOM element", function () { |
|||
expect(this.subject instanceof HTMLElement).toBe(true); |
|||
}); |
|||
|
|||
describe("with various tag names", function () { |
|||
it("creates <ul> element", function () { |
|||
this.tag = "ul"; |
|||
expect(this.subject.tagName).toEqual("UL"); |
|||
}); |
|||
|
|||
it("creates <li> element", function () { |
|||
this.tag = "li"; |
|||
expect(this.subject.tagName).toEqual("LI"); |
|||
}); |
|||
}); |
|||
|
|||
describe("without options", function () { |
|||
it("creates element without any attributes", function () { |
|||
expect(this.subject.attributes.length).toEqual(0); |
|||
}); |
|||
}); |
|||
|
|||
describe("with simple options", function () { |
|||
it("assigns properties", function () { |
|||
this.options = { id: "id1", className: "class-name" }; |
|||
|
|||
expect(this.subject.id).toEqual("id1"); |
|||
expect(this.subject.className).toEqual("class-name"); |
|||
}); |
|||
|
|||
it("assigns attributes", function () { |
|||
this.options = { attr1: "val1", attr2: "val2" }; |
|||
|
|||
expect(this.subject.getAttribute("attr1")).toEqual("val1"); |
|||
expect(this.subject.getAttribute("attr2")).toEqual("val2"); |
|||
}); |
|||
}); |
|||
|
|||
describe("with option for boolean attribute/property", function () { |
|||
it("assigns from true value", function () { |
|||
this.options = { hidden: true }; |
|||
expect(this.subject.hasAttribute("hidden")).toBe(true); |
|||
}); |
|||
|
|||
it("assigns from truthy value", function () { |
|||
this.options = { hidden: "hidden" }; |
|||
expect(this.subject.hasAttribute("hidden")).toBe(true); |
|||
}); |
|||
|
|||
it("assigns from false value", function () { |
|||
this.options = { hidden: false }; |
|||
expect(this.subject.hasAttribute("hidden")).toBe(false); |
|||
}); |
|||
|
|||
it("assigns from falsy value", function () { |
|||
this.options = { hidden: "" }; |
|||
expect(this.subject.hasAttribute("hidden")).toBe(false); |
|||
}); |
|||
}); |
|||
|
|||
describe("with inside: option", function () { |
|||
it("appends to container by element", function () { |
|||
this.options = { inside: $("#data-list") }; |
|||
|
|||
expect(this.subject).toEqual(this.options.inside.lastChild); |
|||
}); |
|||
|
|||
it("appends to container by selector", function () { |
|||
this.options = { inside: "#data-list" }; |
|||
|
|||
expect(this.subject).toEqual($(this.options.inside).lastChild); |
|||
}); |
|||
}); |
|||
|
|||
describe("with around: option", function () { |
|||
it("wraps specified element", function () { |
|||
this.options = { around: $("#no-options") }; |
|||
|
|||
var originalParent = this.options.around.parentNode; |
|||
expect(this.subject.parentNode).toEqual(originalParent); |
|||
|
|||
expect(this.subject.firstChild).toEqual(this.options.around); |
|||
}); |
|||
|
|||
it("wraps element specified by selector", function () { |
|||
this.options = { around: "#no-options" }; |
|||
|
|||
var originalParent = $(this.options.around).parentNode; |
|||
expect(this.subject.parentNode).toEqual(originalParent); |
|||
|
|||
expect(this.subject.firstChild).toEqual($(this.options.around)); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,62 @@ |
|||
describe("Awesomplete.$", function () { |
|||
|
|||
$.fixture("options"); |
|||
|
|||
subject(function () { return Awesomplete.$(this.expression, this.context) }); |
|||
|
|||
describe("with default context", itFindsElement); |
|||
|
|||
describe("with custom context", function () { |
|||
def("context", function () { return fixture.el }); |
|||
|
|||
itFindsElement(); |
|||
}); |
|||
|
|||
describe("with truthy non string expression", function () { |
|||
it("returns the expression back", function () { |
|||
this.expression = $("#no-options"); |
|||
expect(this.subject).toBe(this.expression); |
|||
}); |
|||
}); |
|||
|
|||
describe("with falsy non string expression", function () { |
|||
it("returns null if expression is undefined", function () { |
|||
this.expression = undefined; |
|||
expect(this.subject).toBeNull(); |
|||
}); |
|||
|
|||
it("returns null if expression is null", function () { |
|||
this.expression = null; |
|||
expect(this.subject).toBeNull(); |
|||
}); |
|||
|
|||
it("returns null if expression is false", function () { |
|||
this.expression = false; |
|||
expect(this.subject).toBeNull(); |
|||
}); |
|||
}); |
|||
|
|||
// Shared behaviors
|
|||
|
|||
function itFindsElement() { |
|||
it("returns DOM element", function () { |
|||
this.expression = "#no-options"; |
|||
expect(this.subject instanceof HTMLElement).toBe(true); |
|||
}); |
|||
|
|||
it("finds by id", function () { |
|||
this.expression = "#no-options"; |
|||
expect(this.subject.id).toEqual("no-options"); |
|||
}); |
|||
|
|||
it("finds by class name", function () { |
|||
this.expression = ".simple-input"; |
|||
expect(this.subject.id).toEqual("no-options"); |
|||
}); |
|||
|
|||
it("finds by tag name", function () { |
|||
this.expression = "datalist"; |
|||
expect(this.subject.id).toEqual("list"); |
|||
}); |
|||
} |
|||
}); |
|||
@ -0,0 +1,49 @@ |
|||
describe("Awesomplete.$$", function () { |
|||
|
|||
$.fixture("options"); |
|||
|
|||
subject(function () { return Awesomplete.$$(this.expression, this.context) }); |
|||
|
|||
describe("with default context", itFindsAllElements); |
|||
|
|||
describe("with custom context", function () { |
|||
def("context", function () { return fixture.el }); |
|||
|
|||
itFindsAllElements(); |
|||
}); |
|||
|
|||
// Shared behaviors
|
|||
|
|||
function itFindsAllElements() { |
|||
it("returns an array of DOM elements", function () { |
|||
this.expression = "#no-options"; |
|||
expect(this.subject).toEqual(jasmine.any(Array)); |
|||
expect(this.subject[0] instanceof HTMLElement).toBe(true); |
|||
}); |
|||
|
|||
it("finds all elements", function () { |
|||
this.expression = "input"; |
|||
expect(this.subject.length).toEqual($$("input").length); |
|||
}); |
|||
|
|||
it("finds DOM element", function () { |
|||
this.expression = "#no-options"; |
|||
expect(this.subject[0] instanceof HTMLElement).toBe(true); |
|||
}); |
|||
|
|||
it("finds by id", function () { |
|||
this.expression = "#no-options"; |
|||
expect(this.subject[0].id).toEqual("no-options"); |
|||
}); |
|||
|
|||
it("finds by class name", function () { |
|||
this.expression = ".simple-input"; |
|||
expect(this.subject[0].id).toEqual("no-options"); |
|||
}); |
|||
|
|||
it("finds by tag name", function () { |
|||
this.expression = "datalist"; |
|||
expect(this.subject[0].id).toEqual("list"); |
|||
}); |
|||
} |
|||
}); |
|||
@ -0,0 +1,60 @@ |
|||
describe("Awesomplete.$.fire", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return Awesomplete.$.fire.bind(Awesomplete.$, this.element); |
|||
}); |
|||
|
|||
def("element", function () { return $("#plain") }); |
|||
|
|||
beforeEach(function () { |
|||
spyOn(this.element, "dispatchEvent"); |
|||
}); |
|||
|
|||
it("fires event once", function () { |
|||
this.subject("click"); |
|||
expect(this.element.dispatchEvent.calls.count()).toEqual(1); |
|||
}); |
|||
|
|||
describe("fires different event types", function () { |
|||
it("fires click event", function () { |
|||
this.subject("click"); |
|||
expect(this.element.dispatchEvent).toHaveBeenCalledWith(jasmine.objectContaining({ type: "click" })); |
|||
}); |
|||
|
|||
it("fires input event", function () { |
|||
this.subject("input"); |
|||
expect(this.element.dispatchEvent).toHaveBeenCalledWith(jasmine.objectContaining({ type: "input" })); |
|||
}); |
|||
}); |
|||
|
|||
describe("sets event properties", function () { |
|||
it("makes cancelable event", function () { |
|||
this.subject("click"); |
|||
expect(this.element.dispatchEvent).toHaveBeenCalledWith(jasmine.objectContaining({ cancelable: true })); |
|||
}); |
|||
|
|||
it("can't make non cancelable event", function () { |
|||
this.subject("click", { cancelable: false }); |
|||
expect(this.element.dispatchEvent).toHaveBeenCalledWith(jasmine.objectContaining({ cancelable: true })); |
|||
}); |
|||
|
|||
it("makes event that bubbles", function () { |
|||
this.subject("click"); |
|||
expect(this.element.dispatchEvent).toHaveBeenCalledWith(jasmine.objectContaining({ bubbles: true })); |
|||
}); |
|||
|
|||
it("can't make event that does not bubble", function () { |
|||
this.subject("click", { bubbles: false }); |
|||
expect(this.element.dispatchEvent).toHaveBeenCalledWith(jasmine.objectContaining({ bubbles: true })); |
|||
}); |
|||
|
|||
it("sets properties on the event", function () { |
|||
var properties = { text: "hello", preventDefault: $.noop }; |
|||
|
|||
this.subject("click", properties); |
|||
expect(this.element.dispatchEvent).toHaveBeenCalledWith(jasmine.objectContaining(properties)); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,38 @@ |
|||
describe("Awesomplete.$.regExpEscape", function () { |
|||
|
|||
subject(function () { return Awesomplete.$.regExpEscape(this.str) }); |
|||
|
|||
describe("with regular expression special characters", function () { |
|||
it("escapes backslashes", function () { |
|||
this.str = "\\"; |
|||
expect(this.subject).toBe("\\\\"); |
|||
}); |
|||
|
|||
it("escapes brackets, braces and parentheses", function () { |
|||
this.str = "[]{}()"; |
|||
expect(this.subject).toBe("\\[\\]\\{\\}\\(\\)"); |
|||
}); |
|||
|
|||
it("escapes other special characters", function () { |
|||
this.str = "-^$*+?.|"; |
|||
expect(this.subject).toBe("\\-\\^\\$\\*\\+\\?\\.\\|"); |
|||
}); |
|||
|
|||
it("escapes the whole string", function () { |
|||
this.str = "**"; |
|||
expect(this.subject).toBe("\\*\\*"); |
|||
}); |
|||
}); |
|||
|
|||
describe("with plain characters", function () { |
|||
it("does not escape letters", function () { |
|||
this.str = "abcdefjhijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ"; |
|||
expect(this.subject).toBe(this.str); |
|||
}); |
|||
|
|||
it("does not escape numbers", function () { |
|||
this.str = "0123456789"; |
|||
expect(this.subject).toBe(this.str); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,59 @@ |
|||
describe("Awesomplete.$.unbind", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { |
|||
return function () { Awesomplete.$.unbind(this.element, this.events) }; |
|||
}); |
|||
|
|||
describe("with invalid element", function () { |
|||
it("does nothing if element is undefined", function () { |
|||
this.element = undefined; |
|||
expect(this.subject).not.toThrow(); |
|||
}); |
|||
|
|||
it("does nothing if element is null", function () { |
|||
this.element = null; |
|||
expect(this.subject).not.toThrow(); |
|||
}); |
|||
|
|||
it("does nothing if element is false", function () { |
|||
this.element = false; |
|||
expect(this.subject).not.toThrow(); |
|||
}); |
|||
|
|||
it("does nothing if element is 0", function () { |
|||
this.element = 0; |
|||
expect(this.subject).not.toThrow(); |
|||
}); |
|||
|
|||
it("does nothing if element is empty string", function () { |
|||
this.element = ""; |
|||
expect(this.subject).not.toThrow(); |
|||
}); |
|||
}); |
|||
|
|||
describe("with valid element", function () { |
|||
def("element", function () { return $("#plain") }); |
|||
|
|||
beforeEach(function () { |
|||
spyOn(this.element, "removeEventListener"); |
|||
}); |
|||
|
|||
it("removes event listeners for all events", function () { |
|||
this.events = { click: $.noop, input: $.noop }; |
|||
this.subject(); |
|||
|
|||
expect(this.element.removeEventListener).toHaveBeenCalledWith("click", this.events.click); |
|||
expect(this.element.removeEventListener).toHaveBeenCalledWith("input", this.events.input); |
|||
}); |
|||
|
|||
it("removes single event listener for multiple events", function () { |
|||
this.events = { "click input": $.noop }; |
|||
this.subject(); |
|||
|
|||
expect(this.element.removeEventListener).toHaveBeenCalledWith("click", this.events["click input"]); |
|||
expect(this.element.removeEventListener).toHaveBeenCalledWith("input", this.events["click input"]); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,60 @@ |
|||
describe("Html modifications", function () { |
|||
|
|||
$.fixture("plain"); |
|||
|
|||
subject(function () { return new Awesomplete("#plain") }); |
|||
|
|||
it("binds to correct input", function () { |
|||
expect(this.subject.input instanceof HTMLElement).toBe(true); |
|||
expect(this.subject.input.id).toBe("plain"); |
|||
}); |
|||
|
|||
it("turns native autocompleter off", function () { |
|||
expect(this.subject.input.getAttribute("autocomplete")).toBe("off"); |
|||
}); |
|||
|
|||
describe("HTML tweaks", function () { |
|||
it("creates container", function () { |
|||
expect(this.subject.container instanceof HTMLElement).toBe(true); |
|||
expect(this.subject.container.className).toBe("awesomplete"); |
|||
}); |
|||
|
|||
it("places input inside container", function () { |
|||
expect(this.subject.input.parentNode).toBe(this.subject.container); |
|||
}); |
|||
|
|||
it("creates list", function () { |
|||
expect(this.subject.ul instanceof HTMLElement).toBe(true); |
|||
expect(this.subject.ul.tagName).toBe("UL"); |
|||
}); |
|||
|
|||
it("puts list inside container", function () { |
|||
expect(this.subject.ul.parentNode).toBe(this.subject.container); |
|||
}); |
|||
|
|||
it("hides list", function () { |
|||
expect(this.subject.ul.hasAttribute("hidden")).toBe(true); |
|||
}); |
|||
}); |
|||
|
|||
describe("ARIA support", function () { |
|||
it("makes input accessible", function () { |
|||
expect(this.subject.input.getAttribute("role")).toBe("combobox"); |
|||
expect(this.subject.input.getAttribute("aria-owns")).toMatch(/awesomplete_list_[0-9]+/); |
|||
}); |
|||
|
|||
it("creates status", function () { |
|||
expect(this.subject.status instanceof HTMLElement).toBe(true); |
|||
expect(this.subject.status.getAttribute("role")).toBe("status"); |
|||
expect(this.subject.status.getAttribute("aria-live")).toBe("assertive"); |
|||
}); |
|||
|
|||
it("puts status inside container", function () { |
|||
expect(this.subject.status.parentNode).toBe(this.subject.container); |
|||
}); |
|||
|
|||
it("hides status", function () { |
|||
expect(this.subject.status.className).toBe("visually-hidden"); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,121 @@ |
|||
describe("Awesomplete list", function () { |
|||
|
|||
$.fixture("options"); |
|||
|
|||
subject(function () { return new Awesomplete(this.element, this.options) }); |
|||
|
|||
def("element", "#no-options"); |
|||
|
|||
it("is empty if not provided", function () { |
|||
expect(this.subject._list).toEqual([]); |
|||
}); |
|||
|
|||
describe("setter", function () { |
|||
it("assigns from array", function () { |
|||
this.subject.list = [ "From", "Array" ]; |
|||
expect(this.subject._list).toEqual([ "From", "Array" ]); |
|||
}); |
|||
|
|||
it("assigns from comma separated list", function () { |
|||
this.subject.list = "From, Inline, List"; |
|||
expect(this.subject._list).toEqual([ "From", "Inline", "List" ]); |
|||
}); |
|||
|
|||
it("assigns from element specified by selector", function () { |
|||
this.subject.list = "#data-list"; |
|||
expect(this.subject._list).toEqual([ |
|||
{ label: "With", value: "With" }, |
|||
{ label: "Data", value: "Data" }, |
|||
{ label: "List", value: "List" } |
|||
]); |
|||
}); |
|||
|
|||
it("assigns from element", function () { |
|||
this.subject.list = $("#data-list"); |
|||
expect(this.subject._list).toEqual([ |
|||
{ label: "With", value: "With" }, |
|||
{ label: "Data", value: "Data" }, |
|||
{ label: "List", value: "List" } |
|||
]); |
|||
}); |
|||
|
|||
it("does not assigns from not found list", function () { |
|||
this.subject.list = "#nosuchlist"; |
|||
expect(this.subject._list).toEqual([]); |
|||
}); |
|||
|
|||
it("does not assigns from empty list", function () { |
|||
this.subject.list = "#empty-list"; |
|||
expect(this.subject._list).toEqual([]); |
|||
}); |
|||
|
|||
describe("with active input", function() { |
|||
beforeEach(function() { |
|||
this.subject.input.focus(); |
|||
}); |
|||
|
|||
it("evaluates completer", function() { |
|||
spyOn(this.subject, "evaluate"); |
|||
this.subject.list = "#data-list"; |
|||
|
|||
expect(this.subject.evaluate).toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
describe("constructor option", function () { |
|||
it("assigns from array", function () { |
|||
this.options = { list: [ "From", "Array" ] }; |
|||
expect(this.subject._list).toEqual([ "From", "Array" ]); |
|||
}); |
|||
|
|||
it("assigns from comma separated list", function () { |
|||
this.options = { list: "From, Inline, List" }; |
|||
expect(this.subject._list).toEqual([ "From", "Inline", "List" ]); |
|||
}); |
|||
|
|||
it("assigns from element specified by selector", function () { |
|||
this.options = { list: "#data-list" }; |
|||
expect(this.subject._list).toEqual([ |
|||
{ label: "With", value: "With" }, |
|||
{ label: "Data", value: "Data" }, |
|||
{ label: "List", value: "List" } |
|||
]); |
|||
}); |
|||
|
|||
it("assigns from list specified by element", function () { |
|||
this.options = { list: $("#data-list") }; |
|||
expect(this.subject._list).toEqual([ |
|||
{ label: "With", value: "With" }, |
|||
{ label: "Data", value: "Data" }, |
|||
{ label: "List", value: "List" } |
|||
]); |
|||
}); |
|||
}); |
|||
|
|||
describe("data-list html attribute", function () { |
|||
it("assigns from comma separated list", function () { |
|||
this.element = "#with-data-list-inline"; |
|||
expect(this.subject._list).toEqual(["With", "Data", "List", "Inline"]); |
|||
}); |
|||
|
|||
it("assigns from element referenced by selector", function () { |
|||
this.element = "#with-data-list"; |
|||
expect(this.subject._list).toEqual([ |
|||
{ label: "With", value: "With" }, |
|||
{ label: "Data", value: "Data" }, |
|||
{ label: "List", value: "List" } |
|||
]); |
|||
}); |
|||
}); |
|||
|
|||
describe("list html attribute", function () { |
|||
it("assigns from element referenced by id", function () { |
|||
this.element = "#with-list"; |
|||
expect(this.subject._list).toEqual([ |
|||
{ label: "With", value: "With" }, |
|||
{ label: "List", value: "List" } |
|||
]); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,80 @@ |
|||
describe("Constructor options", function () { |
|||
|
|||
$.fixture("options"); |
|||
|
|||
subject(function () { return new Awesomplete(this.element, this.options) }); |
|||
|
|||
describe("with default options", function () { |
|||
def("element", "#with-data-list"); |
|||
|
|||
it("requires minimum 2 chars to open completer", function () { |
|||
expect(this.subject.minChars).toBe(2); |
|||
}); |
|||
|
|||
it("shows 10 items in completer", function () { |
|||
expect(this.subject.maxItems).toBe(10); |
|||
}); |
|||
|
|||
it("does not select the first ocurrence automatically" , function () { |
|||
expect(this.subject.autoFirst).toBe(false); |
|||
}); |
|||
|
|||
it("modifies list item with DATA", function () { |
|||
expect(this.subject.data).toBe(Awesomplete.DATA); |
|||
}); |
|||
|
|||
it("filters with FILTER_CONTAINS", function () { |
|||
expect(this.subject.filter).toBe(Awesomplete.FILTER_CONTAINS); |
|||
}); |
|||
|
|||
it("orders with SORT_BYLENGTH", function () { |
|||
expect(this.subject.sort).toBe(Awesomplete.SORT_BYLENGTH); |
|||
}); |
|||
|
|||
it("creates item with ITEM", function () { |
|||
expect(this.subject.item).toEqual(Awesomplete.ITEM); |
|||
}); |
|||
|
|||
it("replaces input value with REPLACE", function () { |
|||
expect(this.subject.replace).toEqual(Awesomplete.REPLACE); |
|||
}); |
|||
}); |
|||
|
|||
describe("with custom options in constructor", function () { |
|||
def("element", "#with-data-list"); |
|||
def("options", function () { |
|||
return { |
|||
minChars: 3, |
|||
maxItems: 9, |
|||
autoFirst: true, |
|||
filter: $.noop, |
|||
sort: $.noop, |
|||
item: $.noop, |
|||
replace: $.noop |
|||
}; |
|||
}); |
|||
|
|||
it("overrides simple default options", function () { |
|||
expect(this.subject.minChars).toBe(3); |
|||
expect(this.subject.maxItems).toBe(9); |
|||
expect(this.subject.autoFirst).toBe(true); |
|||
}); |
|||
|
|||
it("overrides default functions", function () { |
|||
expect(this.subject.filter).toBe(this.options.filter); |
|||
expect(this.subject.sort).toBe(this.options.sort); |
|||
expect(this.subject.item).toBe(this.options.item); |
|||
expect(this.subject.replace).toBe(this.options.replace); |
|||
}); |
|||
}); |
|||
|
|||
describe("with custom options in data-* attributes", function () { |
|||
def("element", "#with-custom-options"); |
|||
|
|||
it("overrides simple default options", function () { |
|||
expect(this.subject.minChars).toBe(4); |
|||
expect(this.subject.maxItems).toBe(8); |
|||
expect(this.subject.autoFirst).toBe(true); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,70 @@ |
|||
fixture.setBase("test/fixtures"); |
|||
|
|||
// finds DOM elements in tests
|
|||
function $ (str, context) { |
|||
return (context || fixture.el).querySelector(str); |
|||
} |
|||
|
|||
function $$ (str, context) { |
|||
return (context || fixture.el).querySelectorAll(str); |
|||
} |
|||
|
|||
// bundled fixture load/cleanup
|
|||
$.fixture = function (fixtureName) { |
|||
beforeEach(function () { |
|||
// Awesomplete probably needs to cleanup this by itself
|
|||
try { Awesomplete.all = []; } catch(e) {}; |
|||
fixture.load(fixtureName + ".html"); |
|||
}); |
|||
|
|||
afterEach(function () { |
|||
fixture.cleanup(); |
|||
}); |
|||
}; |
|||
|
|||
// spy to check if event was fired or not
|
|||
$.spyOnEvent = function (target, type) { |
|||
var handler = jasmine.createSpy(type); |
|||
$.on(target, type, handler); |
|||
return handler; |
|||
}; |
|||
|
|||
$.on = function (target, type, callback) { |
|||
target.addEventListener(type, callback); |
|||
}; |
|||
|
|||
$.fire = function (target, type, properties) { |
|||
var evt = document.createEvent("HTMLEvents"); |
|||
evt.initEvent(type, true, true ); |
|||
for (var j in properties) { |
|||
evt[j] = properties[j]; |
|||
} |
|||
target.dispatchEvent(evt); |
|||
return evt; |
|||
}; |
|||
|
|||
// simulates text input (very simple, only "input" event is fired)
|
|||
$.type = function (input, text) { |
|||
input.focus(); |
|||
input.value = text; |
|||
return $.fire(input, "input"); |
|||
}; |
|||
|
|||
// simulates keydown events
|
|||
$.keydown = function (target, keyCode) { |
|||
return $.fire(target, "keydown", { keyCode: keyCode }); |
|||
}; |
|||
$.k = { |
|||
TAB: 9, |
|||
ENTER: 13, |
|||
ESC: 27, |
|||
DOWN: 40, |
|||
UP: 38 |
|||
}; |
|||
|
|||
// $.noop returns a new empty function each time it's being called
|
|||
Object.defineProperty($, "noop", { |
|||
get: function () { |
|||
return function noop () {} |
|||
} |
|||
}); |
|||
@ -0,0 +1,21 @@ |
|||
describe("Awesomplete.all", function () { |
|||
|
|||
$.fixture("options"); |
|||
|
|||
subject(function () { return Awesomplete.all }); |
|||
|
|||
it("is empty initially", function () { |
|||
expect(this.subject.length).toBe(0); |
|||
}); |
|||
|
|||
it("keeps a list of created instances", function () { |
|||
var first = new Awesomplete("#with-data-list-inline"); |
|||
expect(this.subject.length).toBe(1); |
|||
expect(this.subject).toContain(first); |
|||
|
|||
var second = new Awesomplete("#with-data-list"); |
|||
expect(this.subject.length).toBe(2); |
|||
expect(this.subject).toContain(first); |
|||
expect(this.subject).toContain(second); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,19 @@ |
|||
describe("Awesomplete.DATA", function () { |
|||
|
|||
subject(function () { return Awesomplete.DATA(this.item) }); |
|||
|
|||
it("returns original String", function () { |
|||
this.item = "JavaScript"; |
|||
expect(this.subject).toEqual("JavaScript"); |
|||
}); |
|||
|
|||
it("returns original Object", function () { |
|||
this.item = { label: "JavaScript", value: "JS" }; |
|||
expect(this.subject).toEqual({ label: "JavaScript", value: "JS" }); |
|||
}); |
|||
|
|||
it("returns original Array", function () { |
|||
this.item = [ "JavaScript", "JS" ]; |
|||
expect(this.subject).toEqual([ "JavaScript", "JS" ]); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,56 @@ |
|||
describe("Awesomplete.FILTER_CONTAINS", function () { |
|||
|
|||
subject(function () { return Awesomplete.FILTER_CONTAINS }); |
|||
|
|||
describe("search in a plain string", function () { |
|||
it("matches at the start", function () { |
|||
expect(this.subject("Hello world", "Hello")).toBe(true); |
|||
}); |
|||
|
|||
it("matches in the middle", function () { |
|||
expect(this.subject("Ticket to the moon", "to the")).toBe(true); |
|||
}); |
|||
|
|||
it("matches at the end", function () { |
|||
expect(this.subject("This is the end", "end")).toBe(true); |
|||
}); |
|||
|
|||
it("performs case insensitive match", function () { |
|||
expect(this.subject("Hey You", "HEY YOU")).toBe(true); |
|||
}); |
|||
|
|||
it("ignores whitespaces around the search value", function () { |
|||
expect(this.subject("Watch this", " Watch ")).toBe(true); |
|||
}); |
|||
|
|||
it("does not match if substring is not found", function () { |
|||
expect(this.subject("No", "way")).toBe(false); |
|||
}); |
|||
}); |
|||
|
|||
describe("search in string with special RegExp chars", function () { |
|||
it("matches at the start", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", "[^j(a)v?a-")).toBe(true); |
|||
}); |
|||
|
|||
it("matches in the middle", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", "sc|ri\\p+t*")).toBe(true); |
|||
}); |
|||
|
|||
it("matches at the end", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", "{.$}")).toBe(true); |
|||
}); |
|||
|
|||
it("performs case insensitive match", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", "[^J(A)V?A-SC|RI\\P+T*]{.$}")).toBe(true); |
|||
}); |
|||
|
|||
it("ignores whitespaces around the search value", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", " [^j(a)v?a- ")).toBe(true); |
|||
}); |
|||
|
|||
it("does not match if substring is not found", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", "no way")).toBe(false); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,56 @@ |
|||
describe("Awesomplete.FILTER_STARTSWITH", function () { |
|||
|
|||
subject(function () { return Awesomplete.FILTER_STARTSWITH }); |
|||
|
|||
describe("search in plain string", function () { |
|||
it("matches at the start", function () { |
|||
expect(this.subject("Hello world", "Hello")).toBe(true); |
|||
}); |
|||
|
|||
it("does not match in the middle", function () { |
|||
expect(this.subject("Ticket to the moon", "to the")).toBe(false); |
|||
}); |
|||
|
|||
it("does not match at the end", function () { |
|||
expect(this.subject("This is the end", "end")).toBe(false); |
|||
}); |
|||
|
|||
it("performs case insensitive match", function () { |
|||
expect(this.subject("Hey You", "HEY YOU")).toBe(true); |
|||
}); |
|||
|
|||
it("ignores whitespaces around the search value", function () { |
|||
expect(this.subject("Watch this", " Watch ")).toBe(true); |
|||
}); |
|||
|
|||
it("does not match if substring is not found", function () { |
|||
expect(this.subject("No", "way")).toBe(false); |
|||
}); |
|||
}); |
|||
|
|||
describe("search in string with special RegExp chars", function () { |
|||
it("matches at the start", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", "[^j(a)v?a-")).toBe(true); |
|||
}); |
|||
|
|||
it("does not match in the middle", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", "sc|ri\\p+t*")).toBe(false); |
|||
}); |
|||
|
|||
it("does not match at the end", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", "{.$}")).toBe(false); |
|||
}); |
|||
|
|||
it("performs case insensitive match", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", "[^J(A)V?A-SC|RI\\P+T*]{.$}")).toBe(true); |
|||
}); |
|||
|
|||
it("ignores whitespaces around the search value", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", " [^j(a)v?a- ")).toBe(true); |
|||
}); |
|||
|
|||
it("does not match if substring is not found", function () { |
|||
expect(this.subject("[^j(a)v?a-sc|ri\\p+t*]{.$}", "no way")).toBe(false); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,58 @@ |
|||
describe("Awesomplete.ITEM", function () { |
|||
|
|||
subject(function () { return Awesomplete.ITEM }); |
|||
|
|||
def("element", function () { return this.subject("JavaScript", this.input || "") }); |
|||
|
|||
it("creates DOM element", function () { |
|||
expect(this.element instanceof HTMLElement).toBe(true); |
|||
}); |
|||
|
|||
it("creates LI element", function () { |
|||
expect(this.element.tagName).toBe("LI"); |
|||
}); |
|||
|
|||
it("sets aria-selected to false", function () { |
|||
expect(this.element.getAttribute("aria-selected")).toBe("false"); |
|||
}); |
|||
|
|||
describe("with matched input", function () { |
|||
def("input", "jav"); |
|||
|
|||
it("marks the match", function () { |
|||
expect(this.element.innerHTML).toBe("<mark>Jav</mark>aScript"); |
|||
}); |
|||
}); |
|||
|
|||
describe("with multiple matches", function () { |
|||
def("input", "a"); |
|||
|
|||
it("marks all matches", function () { |
|||
expect(this.element.innerHTML).toBe("J<mark>a</mark>v<mark>a</mark>Script"); |
|||
}); |
|||
}); |
|||
|
|||
describe("with no match", function () { |
|||
def("input", "foo"); |
|||
|
|||
it("does not mark anything", function () { |
|||
expect(this.element.innerHTML).toBe("JavaScript"); |
|||
}); |
|||
}); |
|||
|
|||
describe("with empty input", function () { |
|||
def("input", ""); |
|||
|
|||
it("does not mark anything", function () { |
|||
expect(this.element.innerHTML).toBe("JavaScript"); |
|||
}); |
|||
}); |
|||
|
|||
describe("with space input", function () { |
|||
def("input", " "); |
|||
|
|||
it("does not mark anything", function () { |
|||
expect(this.element.innerHTML).toBe("JavaScript"); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,11 @@ |
|||
describe("Awesomplete.REPLACE", function () { |
|||
|
|||
subject(function () { return Awesomplete.REPLACE }); |
|||
|
|||
def("instance", function() { return { input: { value: "initial" } } }); |
|||
|
|||
it("replaces input value", function () { |
|||
this.subject.call(this.instance, { value: "JavaScript" }); |
|||
expect(this.instance.input.value).toBe("JavaScript"); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,47 @@ |
|||
describe("Awesomplete.SORT_BYLENGTH", function () { |
|||
|
|||
subject(function () { return Awesomplete.SORT_BYLENGTH }); |
|||
|
|||
describe("with strings of different length", function () { |
|||
it("returns negative number if the first string is shorter", function () { |
|||
expect(this.subject("a", "aa")).toBe(-1); |
|||
expect(this.subject("a", "bb")).toBe(-1); |
|||
expect(this.subject("b", "aa")).toBe(-1); |
|||
|
|||
expect(this.subject("a", "aaa")).toBe(-2); |
|||
expect(this.subject("a", "bbb")).toBe(-2); |
|||
expect(this.subject("b", "aaa")).toBe(-2); |
|||
}); |
|||
|
|||
it("returns positive number if the first string is longer", function () { |
|||
expect(this.subject("aa", "a")).toBe(1); |
|||
expect(this.subject("bb", "a")).toBe(1); |
|||
expect(this.subject("aa", "b")).toBe(1); |
|||
|
|||
expect(this.subject("aaa", "a")).toBe(2); |
|||
expect(this.subject("bbb", "a")).toBe(2); |
|||
expect(this.subject("aaa", "b")).toBe(2); |
|||
}); |
|||
}); |
|||
|
|||
describe("with strings of the same length", function () { |
|||
it("returns -1 if the first string < second string", function () { |
|||
expect(this.subject("a", "b")).toBe(-1); |
|||
expect(this.subject("aa", "bb")).toBe(-1); |
|||
expect(this.subject("aaa", "bbb")).toBe(-1); |
|||
}); |
|||
|
|||
it("returns 1 if the first string > second string", function () { |
|||
expect(this.subject("b", "a")).toBe(1); |
|||
expect(this.subject("bb", "aa")).toBe(1); |
|||
expect(this.subject("bbb", "aaa")).toBe(1); |
|||
}); |
|||
|
|||
// FIXME SORT_BYLENGTH should probably return 0 like classic string comparison
|
|||
it("returns 1 if the first string == second string", function () { |
|||
expect(this.subject("a", "a")).toBe(1); |
|||
expect(this.subject("aa", "aa")).toBe(1); |
|||
expect(this.subject("aaa", "aaa")).toBe(1); |
|||
}); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue