├── .gitignore
├── .eslintrc
├── demo
├── assets
│ ├── favicon.ico
│ └── style.css
├── main.js
├── index.html
└── Typeahead.vue
├── .npmignore
├── .babelrc
├── .editorconfig
├── LICENSE
├── webpack.config.js
├── package.json
├── src
└── main.js
├── README.md
└── dist
└── vue-typeahead.common.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "vue",
3 | "rules": {
4 | "no-duplicate-imports": 0
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/demo/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pespantelis/vue-typeahead/HEAD/demo/assets/favicon.ico
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .babelrc
3 | .editorconfig
4 | .npmignore
5 | demo/
6 | index.html
7 | webpack.config.js
8 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-2"],
3 | "plugins": ["transform-runtime"],
4 | "comments": false
5 | }
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/demo/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Axios from 'axios'
3 | import Typeahead from './Typeahead.vue'
4 |
5 | Vue.prototype.$http = Axios
6 |
7 | new Vue({
8 | el: '#demo',
9 | components: {
10 | Typeahead
11 | }
12 | })
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Pantelis Peslis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 |
3 | module.exports = {
4 | entry: './demo/main.js',
5 | output: {
6 | path: './demo/build',
7 | publicPath: '/build/',
8 | filename: 'build.js'
9 | },
10 | resolve: {
11 | alias: {
12 | 'vue': 'vue/dist/vue.common'
13 | }
14 | },
15 | module: {
16 | loaders: [
17 | {
18 | test: /\.vue$/,
19 | loader: 'vue'
20 | },
21 | {
22 | test: /\.js$/,
23 | loader: 'babel',
24 | exclude: /node_modules/
25 | }
26 | ]
27 | },
28 | devServer: {
29 | historyApiFallback: true,
30 | noInfo: true
31 | },
32 | devtool: '#eval-source-map'
33 | }
34 |
35 | if (process.env.NODE_ENV === 'production') {
36 | module.exports.devtool = '#source-map'
37 | module.exports.plugins = (module.exports.plugins || []).concat([
38 | new webpack.DefinePlugin({
39 | 'process.env': {
40 | NODE_ENV: '"production"'
41 | }
42 | }),
43 | new webpack.optimize.UglifyJsPlugin({
44 | compress: {
45 | warnings: false
46 | }
47 | }),
48 | new webpack.optimize.OccurenceOrderPlugin()
49 | ])
50 | }
51 |
--------------------------------------------------------------------------------
/demo/assets/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0 20px;
4 | font-family: 'Source Sans Pro', 'Helvetica Neue', sans-serif;
5 | border-top: 2px solid #4fc08d;
6 | }
7 |
8 | h1, h4 {
9 | cursor: default;
10 | }
11 |
12 | a {
13 | text-decoration: none;
14 | }
15 |
16 | h1 {
17 | font-family: 'Dosis', 'Source Sans Pro', 'Helvetica Neue', sans-serif;
18 | font-weight: 300;
19 | font-size: 4em;
20 | color: #2c3e50;
21 | }
22 |
23 | h4, a {
24 | font-weight: 400;
25 | font-size: 15px;
26 | color: #7f8c8d;
27 | }
28 |
29 | .container {
30 | width: 100%;
31 | max-width: 600px;
32 | margin: 0 auto;
33 | text-align: center;
34 | }
35 |
36 | .button {
37 | display: inline-block;
38 | width: 180px;
39 | margin: 0.5em;
40 | padding: 12px 14px;
41 | background-color: #4fc08d;
42 | font-weight: 700;
43 | color: #fff;
44 | border-bottom: 2px solid #3aa373;
45 | border-radius: 4px;
46 | -webkit-transition: all 0.15s ease;
47 | transition: all 0.15s ease;
48 | }
49 |
50 | .button:hover {
51 | background-color: #5dc596;
52 | -webkit-transform: translate(0, -2px);
53 | -ms-transform: translate(0, -2px);
54 | transform: translate(0, -2px);
55 | }
56 |
57 | #social {
58 | margin: 2em 0 3em;
59 | }
60 |
61 | #social iframe {
62 | margin: 0 4px;
63 | }
64 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Typeahead | Vue.js
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
Typeahead
15 |
Vue.js component
16 |
Source on GitHub
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-typeahead",
3 | "version": "2.3.2",
4 | "author": "Pantelis Peslis ",
5 | "license": "MIT",
6 | "description": "Typeahead component for Vue.js",
7 | "keywords": [
8 | "vue",
9 | "typeahead"
10 | ],
11 | "main": "dist/vue-typeahead.common.js",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/pespantelis/vue-typeahead.git"
15 | },
16 | "bugs": "https://github.com/pespantelis/vue-typeahead/issues",
17 | "homepage": "http://pespantelis.github.io/vue-typeahead/",
18 | "scripts": {
19 | "lint": "eslint src demo",
20 | "dev": "webpack-dev-server --content-base demo/ --inline --hot",
21 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
22 | "dist": "babel src/main.js --out-file dist/vue-typeahead.common.js"
23 | },
24 | "peerDependencies": {
25 | "vue": "^1.0.21 || ^2.x.x"
26 | },
27 | "dependencies": {
28 | "babel-runtime": "^6.0.0"
29 | },
30 | "devDependencies": {
31 | "axios": "^0.15.3",
32 | "babel-cli": "^6.23.0",
33 | "babel-core": "^6.0.0",
34 | "babel-loader": "^6.0.0",
35 | "babel-plugin-transform-runtime": "^6.0.0",
36 | "babel-preset-es2015": "^6.0.0",
37 | "babel-preset-stage-2": "^6.0.0",
38 | "cross-env": "^1.0.6",
39 | "css-loader": "^0.23.0",
40 | "eslint": "^2.13.1",
41 | "eslint-config-vue": "^1.0.3",
42 | "eslint-plugin-html": "^1.5.5",
43 | "vue-hot-reload-api": "^1.2.0",
44 | "vue-html-loader": "^1.0.0",
45 | "vue-loader": "^11.1.4",
46 | "vue-style-loader": "^1.0.0",
47 | "vue-template-compiler": "^2.2.1",
48 | "webpack": "^1.12.2",
49 | "webpack-dev-server": "^1.12.0"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { util } from 'vue'
2 |
3 | export default {
4 | data () {
5 | return {
6 | items: [],
7 | query: '',
8 | current: -1,
9 | loading: false,
10 | selectFirst: false,
11 | queryParamName: 'q'
12 | }
13 | },
14 |
15 | computed: {
16 | hasItems () {
17 | return this.items.length > 0
18 | },
19 |
20 | isEmpty () {
21 | return !this.query
22 | },
23 |
24 | isDirty () {
25 | return !!this.query
26 | }
27 | },
28 |
29 | methods: {
30 | update () {
31 | this.cancel()
32 |
33 | if (!this.query) {
34 | return this.reset()
35 | }
36 |
37 | if (this.minChars && this.query.length < this.minChars) {
38 | return
39 | }
40 |
41 | this.loading = true
42 |
43 | this.fetch().then((response) => {
44 | if (response && this.query) {
45 | let data = response.data
46 | data = this.prepareResponseData ? this.prepareResponseData(data) : data
47 | this.items = this.limit ? data.slice(0, this.limit) : data
48 | this.current = -1
49 | this.loading = false
50 |
51 | if (this.selectFirst) {
52 | this.down()
53 | }
54 | }
55 | })
56 | },
57 |
58 | fetch () {
59 | if (!this.$http) {
60 | return util.warn('You need to provide a HTTP client', this)
61 | }
62 |
63 | if (!this.src) {
64 | return util.warn('You need to set the `src` property', this)
65 | }
66 |
67 | const src = this.queryParamName
68 | ? this.src
69 | : this.src + this.query
70 |
71 | const params = this.queryParamName
72 | ? Object.assign({ [this.queryParamName]: this.query }, this.data)
73 | : this.data
74 |
75 | let cancel = new Promise((resolve) => this.cancel = resolve)
76 | let request = this.$http.get(src, { params })
77 |
78 | return Promise.race([cancel, request])
79 | },
80 |
81 | cancel () {
82 | // used to 'cancel' previous searches
83 | },
84 |
85 | reset () {
86 | this.items = []
87 | this.query = ''
88 | this.loading = false
89 | },
90 |
91 | setActive (index) {
92 | this.current = index
93 | },
94 |
95 | activeClass (index) {
96 | return {
97 | active: this.current === index
98 | }
99 | },
100 |
101 | hit () {
102 | if (this.current !== -1) {
103 | this.onHit(this.items[this.current])
104 | }
105 | },
106 |
107 | up () {
108 | if (this.current > 0) {
109 | this.current--
110 | } else if (this.current === -1) {
111 | this.current = this.items.length - 1
112 | } else {
113 | this.current = -1
114 | }
115 | },
116 |
117 | down () {
118 | if (this.current < this.items.length - 1) {
119 | this.current++
120 | } else {
121 | this.current = -1
122 | }
123 | },
124 |
125 | onHit () {
126 | util.warn('You need to implement the `onHit` method', this)
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/demo/Typeahead.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
20 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
53 |
54 |
55 |
56 |
146 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VueTypeahead
2 |
3 | See a live demo [here](http://pespantelis.github.io/vue-typeahead/).
4 |
5 | ## Install
6 |
7 | #### NPM
8 | Available through npm as `vue-typeahead`.
9 | ```
10 | npm install --save vue-typeahead
11 | ```
12 | > Also, you need to install a HTTP client like [`axios`](https://github.com/mzabriskie/axios).
13 |
14 | ## Usage
15 | If you are using `vue@1.0.22+`, you could use the new [`extends`](http://vuejs.org/api/#extends) property (see below).
16 |
17 | Otherwise, the `mixins` way also works.
18 |
19 | ```html
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
48 |
49 |
50 |
51 |
103 |
104 |
109 | ```
110 |
111 | ## Key Actions
112 | **Down Arrow:** Highlight the previous item.
113 |
114 | **Up Arrow:** Highlight the next item.
115 |
116 | **Enter:** Hit on highlighted item.
117 |
118 | **Escape:** Hide the list.
119 |
120 | ## States
121 | **loading:** Indicates that awaits the data.
122 |
123 | **isEmpty:** Indicates that the input is empty.
124 |
125 | **isDirty:** Indicates that the input is not empty.
126 | > Useful if you want to add icon indicators (see the demo)
127 |
128 | ## License
129 | VueTypeahead is released under the MIT License. See the bundled LICENSE file for details.
130 |
--------------------------------------------------------------------------------
/dist/vue-typeahead.common.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _promise = require('babel-runtime/core-js/promise');
8 |
9 | var _promise2 = _interopRequireDefault(_promise);
10 |
11 | var _defineProperty2 = require('babel-runtime/helpers/defineProperty');
12 |
13 | var _defineProperty3 = _interopRequireDefault(_defineProperty2);
14 |
15 | var _assign = require('babel-runtime/core-js/object/assign');
16 |
17 | var _assign2 = _interopRequireDefault(_assign);
18 |
19 | var _vue = require('vue');
20 |
21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
22 |
23 | exports.default = {
24 | data: function data() {
25 | return {
26 | items: [],
27 | query: '',
28 | current: -1,
29 | loading: false,
30 | selectFirst: false,
31 | queryParamName: 'q'
32 | };
33 | },
34 |
35 |
36 | computed: {
37 | hasItems: function hasItems() {
38 | return this.items.length > 0;
39 | },
40 | isEmpty: function isEmpty() {
41 | return !this.query;
42 | },
43 | isDirty: function isDirty() {
44 | return !!this.query;
45 | }
46 | },
47 |
48 | methods: {
49 | update: function update() {
50 | var _this = this;
51 |
52 | this.cancel();
53 |
54 | if (!this.query) {
55 | return this.reset();
56 | }
57 |
58 | if (this.minChars && this.query.length < this.minChars) {
59 | return;
60 | }
61 |
62 | this.loading = true;
63 |
64 | this.fetch().then(function (response) {
65 | if (response && _this.query) {
66 | var data = response.data;
67 | data = _this.prepareResponseData ? _this.prepareResponseData(data) : data;
68 | _this.items = _this.limit ? data.slice(0, _this.limit) : data;
69 | _this.current = -1;
70 | _this.loading = false;
71 |
72 | if (_this.selectFirst) {
73 | _this.down();
74 | }
75 | }
76 | });
77 | },
78 | fetch: function fetch() {
79 | var _this2 = this;
80 |
81 | if (!this.$http) {
82 | return _vue.util.warn('You need to provide a HTTP client', this);
83 | }
84 |
85 | if (!this.src) {
86 | return _vue.util.warn('You need to set the `src` property', this);
87 | }
88 |
89 | var src = this.queryParamName ? this.src : this.src + this.query;
90 |
91 | var params = this.queryParamName ? (0, _assign2.default)((0, _defineProperty3.default)({}, this.queryParamName, this.query), this.data) : this.data;
92 |
93 | var cancel = new _promise2.default(function (resolve) {
94 | return _this2.cancel = resolve;
95 | });
96 | var request = this.$http.get(src, { params: params });
97 |
98 | return _promise2.default.race([cancel, request]);
99 | },
100 | cancel: function cancel() {},
101 | reset: function reset() {
102 | this.items = [];
103 | this.query = '';
104 | this.loading = false;
105 | },
106 | setActive: function setActive(index) {
107 | this.current = index;
108 | },
109 | activeClass: function activeClass(index) {
110 | return {
111 | active: this.current === index
112 | };
113 | },
114 | hit: function hit() {
115 | if (this.current !== -1) {
116 | this.onHit(this.items[this.current]);
117 | }
118 | },
119 | up: function up() {
120 | if (this.current > 0) {
121 | this.current--;
122 | } else if (this.current === -1) {
123 | this.current = this.items.length - 1;
124 | } else {
125 | this.current = -1;
126 | }
127 | },
128 | down: function down() {
129 | if (this.current < this.items.length - 1) {
130 | this.current++;
131 | } else {
132 | this.current = -1;
133 | }
134 | },
135 | onHit: function onHit() {
136 | _vue.util.warn('You need to implement the `onHit` method', this);
137 | }
138 | }
139 | };
140 |
--------------------------------------------------------------------------------