├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── build
└── build.js
├── dist
├── style
│ └── vue2-autocomplete.css
└── vue2-autocomplete.js
├── index.html
├── package.json
├── src
├── img
│ └── demo.gif
└── js
│ ├── components
│ ├── app.vue
│ └── vue-autocomplete.vue
│ ├── main.js
│ └── plugin.js
├── webpack
├── webpack-bundle.config.js
├── webpack-prod.config.js
└── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["es2015", { "modules": false }], "stage-1"],
3 | "plugins": ["transform-object-rest-spread"]
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Naufal Rabbani
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue 2 Autocomplete
2 | Autocomplete Component For [Vue 2](http://vuejs.org). It's based on [vue-autocomplete](https://github.com/BosNaufal/vue-autocomplete). [LIVE DEMO HERE!](https://rawgit.com/BosNaufal/vue2-autocomplete/master/index.html)
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | ## Install
11 | You can import [vue2-autocomplete.vue](./src/js/components/vue-autocomplete.vue) to your vue component file like [this](./src/js/components/app.vue) and process it with your preprocessor.
12 |
13 | You can install it via NPM
14 | ```bash
15 | npm install vue2-autocomplete-js
16 | ```
17 |
18 | Or Just put it after Vue JS~
19 | ```html
20 |
21 |
22 |
30 | ```
31 |
32 | ## Import Style
33 | Don't forget to import vue 2 css. You can link it via html
34 | ```html
35 |
36 | ```
37 |
38 | Or You can import it using commonJS
39 |
40 | ```javascript
41 | require('vue2-autocomplete-js/dist/style/vue2-autocomplete.css')
42 | ```
43 |
44 | Its style is very customizable. You can put any CSS over it. And You can add custom class via its prop.
45 |
46 |
47 | ## Import Module
48 | ```javascript
49 | import Autocomplete from 'vue2-autocomplete-js'
50 | // Or
51 | var Autocomplete = require('vue2-autocomplete-js');
52 | ```
53 |
54 | ## Usage
55 | ```html
56 |
57 |
58 |
63 |
64 |
65 |
66 |
67 |
68 |
84 | ```
85 |
86 | Available Props
87 | ```html
88 |
89 |
90 |
125 |
126 |
127 |
128 | ```
129 |
130 | ## Props
131 | #### url* (String)
132 | the URL must be active (not from file). the component will fetch JSON from this URL and passing one params (default : `q`) query. like:
133 | ```
134 | http://some-url.com/API/list?q=
135 | ```
136 | There are no filter and limit action inside the component. So, do it in your API logic.
137 |
138 | #### param (String: "q")
139 | name of the search parameter to query in Ajax call. default is `q`
140 |
141 | #### min (Number: 0)
142 | Minimum input typed chars before performing the search query. default is `0`
143 |
144 | #### anchor* (String)
145 | It's a object property path that used for Anchor in suggestions list. Example `anchor="name"` will get the name property of your JSON object. Like ("Bambang", "Sukijan", "Bejo") in the demo above. Or you can reach the deep value of your object. Like `anchor="geometry.location.lat"`
146 |
147 |
148 | #### label (String)
149 | Same as anchor but it's used for subtitle or description of list
150 |
151 | #### options (Array)
152 | Manual pass an Array of list options to the autocomplete.
153 |
154 | #### filterByAnchor (Boolean: true)
155 | When you're using options props, you can have autocomplete to filter your data. Or you can just show your data directly without any filter from autocomplete. The options will be filtered by anchor and it according to the user input.
156 |
157 | #### encodeParams (Boolean: true)
158 | Autocomplete will ```encodeURIComponent``` all your params before ajax send, When this props sets to ```true```. Default is ```true``` [#35](https://github.com/BosNaufal/vue2-autocomplete/pull/35)
159 |
160 | #### debounce (Number)
161 | Delay time before do the ajax for the data
162 |
163 |
164 | #### required (Boolean)
165 | Required attribute for input
166 |
167 | #### placeholder (String)
168 | Placeholder for input
169 |
170 | #### className (String)
171 | Custom class name for autocomplete component
172 |
173 | #### classes (Object)
174 | Spesific custom class for each part. available: wrapper, input, list, and item
175 |
176 | #### id (String)
177 | Custom id name for autocomplete component
178 |
179 | #### debounce (number)
180 | Number of milliseconds the user should stop typing for before the request is sent. Default is 0, meaning all requests are sent immediately.
181 |
182 | #### process (Function)
183 | Function to process the API result with. Should return an array of entries or an object whose properties can be enumerated.
184 |
185 | #### template (Function)
186 | Function to process each result with. Takes the type of an API reply element and should return HTML data.
187 |
188 |
189 | ## Callback Events
190 | You can make a callback event via props.
191 |
192 | #### onInput (Function)
193 | On Input event in autocomplete
194 |
195 | #### onShow (Function)
196 | On Show event in autocomplete list
197 |
198 | #### onBlur (Function)
199 | When autocomplete is blured
200 |
201 | #### onHide (Function)
202 | When autocomplete list is hidden
203 |
204 | #### onFocus (Function)
205 | When autocomplete input in focus mode
206 |
207 | #### onSelect (Function)
208 | When user has selected one item in the list
209 |
210 | #### onBeforeAjax (Function)
211 | Before the ajax send
212 |
213 | #### onAjaxProgress (Function)
214 | While ajax is fetching the data
215 |
216 | #### onAjaxLoaded (Function)
217 | When ajax process is totally loaded
218 |
219 | #### onShouldGetData (Function)
220 | Manually Process the whole ajax process. If it's a Promise, it should resolve the options for the list of autocomplete. If it isn't a Promise, you can manually pass the options to the props of autocomplete
221 | ```html
222 |
227 |
228 | ```
229 | ```javascript
230 | methods: {
231 | promise(value) {
232 | return new Promise((resolve, reject) => {
233 | let ajax = new XMLHttpRequest();
234 | ajax.open('GET', `https://maps.googleapis.com/maps/api/geocode/json?address=${value}`, true);
235 | // On Done
236 | ajax.addEventListener('loadend', (e) => {
237 | const { responseText } = e.target
238 | let response = JSON.parse(responseText);
239 | // The options to pass in the autocomplete
240 | resolve(response.results)
241 | });
242 | ajax.send();
243 | })
244 | },
245 |
246 | nonPromise() {
247 | getData(value) {
248 | let ajax = new XMLHttpRequest();
249 | ajax.open('GET', `https://maps.googleapis.com/maps/api/geocode/json?address=${value}`, true);
250 | // On Done
251 | ajax.addEventListener('loadend', (e) => {
252 | const { responseText } = e.target
253 | let response = JSON.parse(responseText);
254 | // The options to pass in the autocomplete props
255 | this.options = response.results;
256 | });
257 | ajax.send();
258 | },
259 | }
260 | }
261 | ```
262 |
263 | #### process (Function)
264 | Process the result before retrieveng the result array. You can shape your data here before it's passed to the autocomplete
265 |
266 | #### onShouldRenderChild (Function)
267 | Wanna use custom template for the list? Now, you can do it!
268 | ```html
269 |
274 |
275 | ```
276 | ```javascript
277 | methods: {
278 | renderChild(data) {
279 | return `
280 |
281 | ${data.something}
282 | `
283 | },
284 | }
285 | ```
286 |
287 |
288 |
289 | ## Methods
290 | You can do some methods by accessing the component via javascript.
291 | ```javascript
292 | this.$refs.autocomplete.someMethod()
293 | ```
294 |
295 | #### setValue (String)
296 | To set the value of the autocomplete input
297 |
298 |
299 |
300 | ## Thank You for Making this useful~
301 |
302 | ## Let's talk about some projects with me
303 | Just Contact Me At:
304 | - Email: [bosnaufalemail@gmail.com](mailto:bosnaufalemail@gmail.com)
305 | - Skype Id: bosnaufal254
306 | - twitter: [@BosNaufal](https://twitter.com/BosNaufal)
307 |
308 | ## License
309 | [MIT](http://opensource.org/licenses/MIT)
310 | Copyright (c) 2016 - forever Naufal Rabbani
311 |
--------------------------------------------------------------------------------
/dist/style/vue2-autocomplete.css:
--------------------------------------------------------------------------------
1 |
2 | .transition, .autocomplete, .showAll-transition, .autocomplete ul, .autocomplete ul li a{
3 | transition:all 0.3s ease-out;
4 | -moz-transition:all 0.3s ease-out;
5 | -webkit-transition:all 0.3s ease-out;
6 | -o-transition:all 0.3s ease-out;
7 | }
8 |
9 | .autocomplete ul{
10 | font-family: sans-serif;
11 | position: absolute;
12 | list-style: none;
13 | background: #f8f8f8;
14 | padding: 10px 0;
15 | margin: 0;
16 | display: inline-block;
17 | min-width: 15%;
18 | margin-top: 10px;
19 | }
20 |
21 | .autocomplete ul:before{
22 | content: "";
23 | display: block;
24 | position: absolute;
25 | height: 0;
26 | width: 0;
27 | border: 10px solid transparent;
28 | border-bottom: 10px solid #f8f8f8;
29 | left: 46%;
30 | top: -20px
31 | }
32 |
33 | .autocomplete ul li a{
34 | text-decoration: none;
35 | display: block;
36 | background: #f8f8f8;
37 | color: #2b2b2b;
38 | padding: 5px;
39 | padding-left: 10px;
40 | }
41 |
42 | .autocomplete ul li a:hover, .autocomplete ul li.focus-list a{
43 | color: white;
44 | background: #2F9AF7;
45 | }
46 |
47 | .autocomplete ul li a span, /*backwards compat*/
48 | .autocomplete ul li a .autocomplete-anchor-label{
49 | display: block;
50 | margin-top: 3px;
51 | color: grey;
52 | font-size: 13px;
53 | }
54 |
55 | .autocomplete ul li a:hover .autocomplete-anchor-label,
56 | .autocomplete ul li.focus-list a span, /*backwards compat*/
57 | .autocomplete ul li a:hover .autocomplete-anchor-label,
58 | .autocomplete ul li.focus-list a span{ /*backwards compat*/
59 | color: white;
60 | }
61 |
62 | /*.showAll-transition{
63 | opacity: 1;
64 | height: 50px;
65 | overflow: hidden;
66 | }
67 |
68 | .showAll-enter{
69 | opacity: 0.3;
70 | height: 0;
71 | }
72 |
73 | .showAll-leave{
74 | display: none;
75 | }*/
76 |
--------------------------------------------------------------------------------
/dist/vue2-autocomplete.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Copyright (c) 2016 Naufal Rabbani (http://github.com/BosNaufal),
3 | * ,Licensed Under MIT (http://opensource.org/licenses/MIT),
4 | * ,
5 | * ,Vue 2 Autocomplete @ Version 0.2.1,
6 | *
7 | */
8 | (function webpackUniversalModuleDefinition(root, factory) {
9 | if(typeof exports === 'object' && typeof module === 'object')
10 | module.exports = factory();
11 | else if(typeof define === 'function' && define.amd)
12 | define([], factory);
13 | else if(typeof exports === 'object')
14 | exports["Vue2Autocomplete"] = factory();
15 | else
16 | root["Vue2Autocomplete"] = factory();
17 | })(this, function() {
18 | return /******/ (function(modules) { // webpackBootstrap
19 | /******/ // The module cache
20 | /******/ var installedModules = {};
21 | /******/
22 | /******/ // The require function
23 | /******/ function __webpack_require__(moduleId) {
24 | /******/
25 | /******/ // Check if module is in cache
26 | /******/ if(installedModules[moduleId]) {
27 | /******/ return installedModules[moduleId].exports;
28 | /******/ }
29 | /******/ // Create a new module (and put it into the cache)
30 | /******/ var module = installedModules[moduleId] = {
31 | /******/ i: moduleId,
32 | /******/ l: false,
33 | /******/ exports: {}
34 | /******/ };
35 | /******/
36 | /******/ // Execute the module function
37 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
38 | /******/
39 | /******/ // Flag the module as loaded
40 | /******/ module.l = true;
41 | /******/
42 | /******/ // Return the exports of the module
43 | /******/ return module.exports;
44 | /******/ }
45 | /******/
46 | /******/
47 | /******/ // expose the modules object (__webpack_modules__)
48 | /******/ __webpack_require__.m = modules;
49 | /******/
50 | /******/ // expose the module cache
51 | /******/ __webpack_require__.c = installedModules;
52 | /******/
53 | /******/ // identity function for calling harmony imports with the correct context
54 | /******/ __webpack_require__.i = function(value) { return value; };
55 | /******/
56 | /******/ // define getter function for harmony exports
57 | /******/ __webpack_require__.d = function(exports, name, getter) {
58 | /******/ if(!__webpack_require__.o(exports, name)) {
59 | /******/ Object.defineProperty(exports, name, {
60 | /******/ configurable: false,
61 | /******/ enumerable: true,
62 | /******/ get: getter
63 | /******/ });
64 | /******/ }
65 | /******/ };
66 | /******/
67 | /******/ // getDefaultExport function for compatibility with non-harmony modules
68 | /******/ __webpack_require__.n = function(module) {
69 | /******/ var getter = module && module.__esModule ?
70 | /******/ function getDefault() { return module['default']; } :
71 | /******/ function getModuleExports() { return module; };
72 | /******/ __webpack_require__.d(getter, 'a', getter);
73 | /******/ return getter;
74 | /******/ };
75 | /******/
76 | /******/ // Object.prototype.hasOwnProperty.call
77 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
78 | /******/
79 | /******/ // __webpack_public_path__
80 | /******/ __webpack_require__.p = "../dist/";
81 | /******/
82 | /******/ // Load entry module and return exports
83 | /******/ return __webpack_require__(__webpack_require__.s = 2);
84 | /******/ })
85 | /************************************************************************/
86 | /******/ ([
87 | /* 0 */
88 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
89 |
90 | "use strict";
91 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__babel_loader_node_modules_vue_loader_lib_selector_type_script_index_0_vue_autocomplete_vue__ = __webpack_require__(1);
92 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__node_modules_vue_loader_lib_template_compiler_index_id_data_v_e47ae2be_hasScoped_false_node_modules_vue_loader_lib_selector_type_template_index_0_vue_autocomplete_vue__ = __webpack_require__(4);
93 | var disposed = false
94 | var normalizeComponent = __webpack_require__(3)
95 | /* script */
96 |
97 | /* template */
98 |
99 | /* styles */
100 | var __vue_styles__ = null
101 | /* scopeId */
102 | var __vue_scopeId__ = null
103 | /* moduleIdentifier (server only) */
104 | var __vue_module_identifier__ = null
105 | var Component = normalizeComponent(
106 | __WEBPACK_IMPORTED_MODULE_0__babel_loader_node_modules_vue_loader_lib_selector_type_script_index_0_vue_autocomplete_vue__["a" /* default */],
107 | __WEBPACK_IMPORTED_MODULE_1__node_modules_vue_loader_lib_template_compiler_index_id_data_v_e47ae2be_hasScoped_false_node_modules_vue_loader_lib_selector_type_template_index_0_vue_autocomplete_vue__["a" /* default */],
108 | __vue_styles__,
109 | __vue_scopeId__,
110 | __vue_module_identifier__
111 | )
112 | Component.options.__file = "src/js/components/vue-autocomplete.vue"
113 | if (Component.esModule && Object.keys(Component.esModule).some(function (key) {return key !== "default" && key.substr(0, 2) !== "__"})) {console.error("named exports are not supported in *.vue files.")}
114 | if (Component.options.functional) {console.error("[vue-loader] vue-autocomplete.vue: functional components are not supported with templates, they should use render functions.")}
115 |
116 | /* hot reload */
117 | if (false) {(function () {
118 | var hotAPI = require("vue-hot-reload-api")
119 | hotAPI.install(require("vue"), false)
120 | if (!hotAPI.compatible) return
121 | module.hot.accept()
122 | if (!module.hot.data) {
123 | hotAPI.createRecord("data-v-e47ae2be", Component.options)
124 | } else {
125 | hotAPI.reload("data-v-e47ae2be", Component.options)
126 | }
127 | module.hot.dispose(function (data) {
128 | disposed = true
129 | })
130 | })()}
131 |
132 | /* harmony default export */ __webpack_exports__["a"] = (Component.exports);
133 |
134 |
135 | /***/ }),
136 | /* 1 */
137 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
138 |
139 | "use strict";
140 | //
141 | //
142 | //
143 | //
144 | //
145 | //
146 | //
147 | //
148 | //
149 | //
150 | //
151 | //
152 | //
153 | //
154 | //
155 | //
156 | //
157 | //
158 | //
159 | //
160 | //
161 | //
162 | //
163 | //
164 | //
165 | //
166 | //
167 | //
168 | //
169 | //
170 | //
171 | //
172 | //
173 | //
174 | //
175 | //
176 | //
177 | //
178 | //
179 | //
180 | //
181 | //
182 | //
183 | //
184 | //
185 | //
186 | //
187 | //
188 |
189 |
190 | /*! Copyright (c) 2016 Naufal Rabbani (http://github.com/BosNaufal)
191 | * Licensed Under MIT (http://opensource.org/licenses/MIT)
192 | *
193 | * Vue 2 Autocomplete @ Version 0.0.1
194 | *
195 | */
196 |
197 | /* harmony default export */ __webpack_exports__["a"] = ({
198 |
199 | props: {
200 | id: String,
201 | name: String,
202 | className: String,
203 | classes: {
204 | type: Object,
205 | default: function _default() {
206 | return {
207 | wrapper: false,
208 | input: false,
209 | list: false,
210 | item: false
211 | };
212 | }
213 | },
214 | placeholder: String,
215 | required: Boolean,
216 |
217 | // Intial Value
218 | initValue: {
219 | type: String,
220 | default: ""
221 | },
222 |
223 | // Manual List
224 | options: Array,
225 |
226 | // Filter After Get the data
227 | filterByAnchor: {
228 | type: Boolean,
229 | default: true
230 | },
231 |
232 | // Anchor of list
233 | anchor: {
234 | type: String,
235 | required: true
236 | },
237 |
238 | // Label of list
239 | label: String,
240 |
241 | // Debounce time
242 | debounce: Number,
243 |
244 | // ajax URL will be fetched
245 | url: {
246 | type: String,
247 | required: true
248 | },
249 |
250 | // query param
251 | param: {
252 | type: String,
253 | default: 'q'
254 | },
255 |
256 | encodeParams: {
257 | type: Boolean,
258 | default: true
259 | },
260 |
261 | // Custom Params
262 | customParams: Object,
263 |
264 | // Custom Headers
265 | customHeaders: Object,
266 |
267 | // minimum length
268 | min: {
269 | type: Number,
270 | default: 0
271 | },
272 |
273 | // Create a custom template from data.
274 | onShouldRenderChild: Function,
275 |
276 | // Process the result before retrieveng the result array.
277 | process: Function,
278 |
279 | // Callback
280 | onInput: Function,
281 | onShow: Function,
282 | onBlur: Function,
283 | onHide: Function,
284 | onFocus: Function,
285 | onSelect: Function,
286 | onBeforeAjax: Function,
287 | onAjaxProgress: Function,
288 | onAjaxLoaded: Function,
289 | onShouldGetData: Function
290 | },
291 |
292 | data: function data() {
293 | return {
294 | showList: false,
295 | type: "",
296 | json: [],
297 | focusList: "",
298 | debounceTask: undefined
299 | };
300 | },
301 |
302 |
303 | watch: {
304 | options: function options(newVal, oldVal) {
305 | if (this.filterByAnchor) {
306 | var type = this.type,
307 | anchor = this.anchor;
308 |
309 | var regex = new RegExp("" + type, 'i');
310 | var filtered = newVal.filter(function (item) {
311 | var found = item[anchor].search(regex) !== -1;
312 | return found;
313 | });
314 | this.json = filtered;
315 | } else {
316 | this.json = newVal;
317 | }
318 | }
319 | },
320 |
321 | methods: {
322 | getClassName: function getClassName(part) {
323 | var classes = this.classes,
324 | className = this.className;
325 |
326 | if (classes[part]) return "" + classes[part];
327 | return className ? className + "-" + part : '';
328 | },
329 |
330 |
331 | // Netralize Autocomplete
332 | clearInput: function clearInput() {
333 | this.showList = false;
334 | this.type = "";
335 | this.json = [];
336 | this.focusList = "";
337 | },
338 |
339 |
340 | // Get the original data
341 | cleanUp: function cleanUp(data) {
342 | return JSON.parse(JSON.stringify(data));
343 | },
344 |
345 |
346 | /*==============================
347 | INPUT EVENTS
348 | =============================*/
349 | handleInput: function handleInput(e) {
350 | var _this = this;
351 |
352 | var value = e.target.value;
353 |
354 | this.showList = true;
355 | // Callback Event
356 | if (this.onInput) this.onInput(value);
357 | // If Debounce
358 | if (this.debounce) {
359 | if (this.debounceTask !== undefined) clearTimeout(this.debounceTask);
360 | this.debounceTask = setTimeout(function () {
361 | return _this.getData(value);
362 | }, this.debounce);
363 | } else {
364 | return this.getData(value);
365 | }
366 | },
367 | handleKeyDown: function handleKeyDown(e) {
368 | var key = e.keyCode;
369 |
370 | // Disable when list isn't showing up
371 | if (!this.showList) return;
372 |
373 | // Key List
374 | var DOWN = 40;
375 | var UP = 38;
376 | var ENTER = 13;
377 | var ESC = 27;
378 |
379 | // Prevent Default for Prevent Cursor Move & Form Submit
380 | switch (key) {
381 | case DOWN:
382 | e.preventDefault();
383 | this.focusList++;
384 | break;
385 | case UP:
386 | e.preventDefault();
387 | this.focusList--;
388 | break;
389 | case ENTER:
390 | e.preventDefault();
391 | this.selectList(this.json[this.focusList]);
392 | this.showList = false;
393 | break;
394 | case ESC:
395 | this.showList = false;
396 | break;
397 | }
398 |
399 | var listLength = this.json.length - 1;
400 | var outOfRangeBottom = this.focusList > listLength;
401 | var outOfRangeTop = this.focusList < 0;
402 | var topItemIndex = 0;
403 | var bottomItemIndex = listLength;
404 |
405 | var nextFocusList = this.focusList;
406 | if (outOfRangeBottom) nextFocusList = topItemIndex;
407 | if (outOfRangeTop) nextFocusList = bottomItemIndex;
408 | this.focusList = nextFocusList;
409 | },
410 | setValue: function setValue(val) {
411 | this.type = val;
412 | },
413 |
414 |
415 | /*==============================
416 | LIST EVENTS
417 | =============================*/
418 |
419 | handleDoubleClick: function handleDoubleClick() {
420 | this.json = [];
421 | this.getData("");
422 | // Callback Event
423 | this.onShow ? this.onShow() : null;
424 | this.showList = true;
425 | },
426 | handleBlur: function handleBlur(e) {
427 | var _this2 = this;
428 |
429 | // Callback Event
430 | this.onBlur ? this.onBlur(e) : null;
431 | setTimeout(function () {
432 | // Callback Event
433 | _this2.onHide ? _this2.onHide() : null;
434 | _this2.showList = false;
435 | }, 250);
436 | },
437 | handleFocus: function handleFocus(e) {
438 | this.focusList = 0;
439 | // Callback Event
440 | this.onFocus ? this.onFocus(e) : null;
441 | },
442 | mousemove: function mousemove(i) {
443 | this.focusList = i;
444 | },
445 | activeClass: function activeClass(i) {
446 | var focusClass = i === this.focusList ? 'focus-list' : '';
447 | return "" + focusClass;
448 | },
449 | selectList: function selectList(data) {
450 | // Deep clone of the original object
451 | var clean = this.cleanUp(data);
452 | // Put the selected data to type (model)
453 | this.type = clean[this.anchor];
454 | // Hide List
455 | this.showList = false;
456 | // Callback Event
457 | this.onSelect ? this.onSelect(clean) : null;
458 | },
459 | deepValue: function deepValue(obj, path) {
460 | var arrayPath = path.split('.');
461 | for (var i = 0; i < arrayPath.length; i++) {
462 | obj = obj[arrayPath[i]];
463 | }
464 | return obj;
465 | },
466 |
467 |
468 | /*==============================
469 | AJAX EVENTS
470 | =============================*/
471 |
472 | composeParams: function composeParams(val) {
473 | var _this3 = this;
474 |
475 | var encode = function encode(val) {
476 | return _this3.encodeParams ? encodeURIComponent(val) : val;
477 | };
478 | var params = this.param + "=" + encode(val);
479 | if (this.customParams) {
480 | Object.keys(this.customParams).forEach(function (key) {
481 | params += "&" + key + "=" + encode(_this3.customParams[key]);
482 | });
483 | }
484 | return params;
485 | },
486 | composeHeader: function composeHeader(ajax) {
487 | var _this4 = this;
488 |
489 | if (this.customHeaders) {
490 | Object.keys(this.customHeaders).forEach(function (key) {
491 | ajax.setRequestHeader(key, _this4.customHeaders[key]);
492 | });
493 | }
494 | },
495 | doAjax: function doAjax(val) {
496 | var _this5 = this;
497 |
498 | // Callback Event
499 | this.onBeforeAjax ? this.onBeforeAjax(val) : null;
500 | // Compose Params
501 | var params = this.composeParams(val);
502 | // Init Ajax
503 | var ajax = new XMLHttpRequest();
504 | ajax.open('GET', this.url + "?" + params, true);
505 | this.composeHeader(ajax);
506 | // Callback Event
507 | ajax.addEventListener('progress', function (data) {
508 | if (data.lengthComputable && _this5.onAjaxProgress) _this5.onAjaxProgress(data);
509 | });
510 | // On Done
511 | ajax.addEventListener('loadend', function (e) {
512 | var responseText = e.target.responseText;
513 |
514 | var json = JSON.parse(responseText);
515 | // Callback Event
516 | _this5.onAjaxLoaded ? _this5.onAjaxLoaded(json) : null;
517 | _this5.json = _this5.process ? _this5.process(json) : json;
518 | });
519 | // Send Ajax
520 | ajax.send();
521 | },
522 | getData: function getData(value) {
523 | if (value.length < this.min || !this.url) return;
524 | if (this.onShouldGetData) this.manualGetData(value);else this.doAjax(value);
525 | },
526 |
527 |
528 | // Do Ajax Manually, so user can do whatever he want
529 | manualGetData: function manualGetData(val) {
530 | var _this6 = this;
531 |
532 | var task = this.onShouldGetData(val);
533 | if (task && task.then) {
534 | return task.then(function (options) {
535 | _this6.json = options;
536 | });
537 | }
538 | }
539 | },
540 |
541 | created: function created() {
542 | // Sync parent model with initValue Props
543 | this.type = this.initValue ? this.initValue : null;
544 | },
545 | mounted: function mounted() {
546 | if (this.required) this.$refs.input.setAttribute("required", this.required);
547 | }
548 | });
549 |
550 | /***/ }),
551 | /* 2 */
552 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
553 |
554 | "use strict";
555 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
556 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__components_vue_autocomplete_vue__ = __webpack_require__(0);
557 |
558 | /* harmony default export */ __webpack_exports__["default"] = (__WEBPACK_IMPORTED_MODULE_0__components_vue_autocomplete_vue__["a" /* default */]);
559 |
560 | /***/ }),
561 | /* 3 */
562 | /***/ (function(module, exports) {
563 |
564 | /* globals __VUE_SSR_CONTEXT__ */
565 |
566 | // this module is a runtime utility for cleaner component module output and will
567 | // be included in the final webpack user bundle
568 |
569 | module.exports = function normalizeComponent (
570 | rawScriptExports,
571 | compiledTemplate,
572 | injectStyles,
573 | scopeId,
574 | moduleIdentifier /* server only */
575 | ) {
576 | var esModule
577 | var scriptExports = rawScriptExports = rawScriptExports || {}
578 |
579 | // ES6 modules interop
580 | var type = typeof rawScriptExports.default
581 | if (type === 'object' || type === 'function') {
582 | esModule = rawScriptExports
583 | scriptExports = rawScriptExports.default
584 | }
585 |
586 | // Vue.extend constructor export interop
587 | var options = typeof scriptExports === 'function'
588 | ? scriptExports.options
589 | : scriptExports
590 |
591 | // render functions
592 | if (compiledTemplate) {
593 | options.render = compiledTemplate.render
594 | options.staticRenderFns = compiledTemplate.staticRenderFns
595 | }
596 |
597 | // scopedId
598 | if (scopeId) {
599 | options._scopeId = scopeId
600 | }
601 |
602 | var hook
603 | if (moduleIdentifier) { // server build
604 | hook = function (context) {
605 | // 2.3 injection
606 | context =
607 | context || // cached call
608 | (this.$vnode && this.$vnode.ssrContext) || // stateful
609 | (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
610 | // 2.2 with runInNewContext: true
611 | if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
612 | context = __VUE_SSR_CONTEXT__
613 | }
614 | // inject component styles
615 | if (injectStyles) {
616 | injectStyles.call(this, context)
617 | }
618 | // register component module identifier for async chunk inferrence
619 | if (context && context._registeredComponents) {
620 | context._registeredComponents.add(moduleIdentifier)
621 | }
622 | }
623 | // used by ssr in case component is cached and beforeCreate
624 | // never gets called
625 | options._ssrRegister = hook
626 | } else if (injectStyles) {
627 | hook = injectStyles
628 | }
629 |
630 | if (hook) {
631 | var functional = options.functional
632 | var existing = functional
633 | ? options.render
634 | : options.beforeCreate
635 | if (!functional) {
636 | // inject component registration as beforeCreate hook
637 | options.beforeCreate = existing
638 | ? [].concat(existing, hook)
639 | : [hook]
640 | } else {
641 | // register for functioal component in vue file
642 | options.render = function renderWithStyleInjection (h, context) {
643 | hook.call(context)
644 | return existing(h, context)
645 | }
646 | }
647 | }
648 |
649 | return {
650 | esModule: esModule,
651 | exports: scriptExports,
652 | options: options
653 | }
654 | }
655 |
656 |
657 | /***/ }),
658 | /* 4 */
659 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
660 |
661 | "use strict";
662 | var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
663 | return _c('div', {
664 | class: ((_vm.getClassName('wrapper')) + " autocomplete-wrapper")
665 | }, [_c('input', {
666 | directives: [{
667 | name: "model",
668 | rawName: "v-model",
669 | value: (_vm.type),
670 | expression: "type"
671 | }],
672 | ref: "input",
673 | class: ((_vm.getClassName('input')) + " autocomplete-input"),
674 | attrs: {
675 | "type": "text",
676 | "id": _vm.id,
677 | "placeholder": _vm.placeholder,
678 | "name": _vm.name,
679 | "autocomplete": "off"
680 | },
681 | domProps: {
682 | "value": (_vm.type)
683 | },
684 | on: {
685 | "input": [function($event) {
686 | if ($event.target.composing) { return; }
687 | _vm.type = $event.target.value
688 | }, _vm.handleInput],
689 | "dblclick": _vm.handleDoubleClick,
690 | "blur": _vm.handleBlur,
691 | "keydown": _vm.handleKeyDown,
692 | "focus": _vm.handleFocus
693 | }
694 | }), _vm._v(" "), _c('div', {
695 | directives: [{
696 | name: "show",
697 | rawName: "v-show",
698 | value: (_vm.showList && _vm.json.length),
699 | expression: "showList && json.length"
700 | }],
701 | class: ((_vm.getClassName('list')) + " autocomplete autocomplete-list")
702 | }, [_c('ul', _vm._l((_vm.json), function(data, i) {
703 | return _c('li', {
704 | class: _vm.activeClass(i)
705 | }, [_c('a', {
706 | attrs: {
707 | "href": "#"
708 | },
709 | on: {
710 | "click": function($event) {
711 | $event.preventDefault();
712 | _vm.selectList(data)
713 | },
714 | "mousemove": function($event) {
715 | _vm.mousemove(i)
716 | }
717 | }
718 | }, [(_vm.onShouldRenderChild) ? _c('div', {
719 | domProps: {
720 | "innerHTML": _vm._s(_vm.onShouldRenderChild(data))
721 | }
722 | }) : _vm._e(), _vm._v(" "), (!_vm.onShouldRenderChild) ? _c('div', [_c('b', {
723 | staticClass: "autocomplete-anchor-text"
724 | }, [_vm._v(_vm._s(_vm.deepValue(data, _vm.anchor)))]), _vm._v(" "), _c('span', {
725 | staticClass: "autocomplete-anchor-label"
726 | }, [_vm._v(_vm._s(_vm.deepValue(data, _vm.label)))])]) : _vm._e()])])
727 | }))])])
728 | }
729 | var staticRenderFns = []
730 | render._withStripped = true
731 | var esExports = { render: render, staticRenderFns: staticRenderFns }
732 | /* harmony default export */ __webpack_exports__["a"] = (esExports);
733 | if (false) {
734 | module.hot.accept()
735 | if (module.hot.data) {
736 | require("vue-hot-reload-api").rerender("data-v-e47ae2be", esExports)
737 | }
738 | }
739 |
740 | /***/ })
741 | /******/ ]);
742 | });
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue 2 Autocomplete
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue2-autocomplete-js",
3 | "version": "0.2.1",
4 | "description": "Autocomplete Component for Vue 2",
5 | "main": "./dist/vue2-autocomplete.js",
6 | "scripts": {
7 | "start": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.js --inline --hot",
8 | "build": "cross-env NODE_ENV=production webpack --config ./webpack/webpack-prod.config.js -p",
9 | "build:prod": "cross-env NODE_ENV=production webpack --config ./webpack/webpack-bundle.config.js -p",
10 | "build:dist": "cross-env NODE_ENV=development webpack --config ./webpack/webpack-bundle.config.js",
11 | "prepublish": "npm run build && npm run build:prod && npm run build:dist"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/BosNaufal/vue2-autocomplete.git"
16 | },
17 | "keywords": [
18 | "vue-js",
19 | "autocomplete",
20 | "remote-list",
21 | "form",
22 | "component",
23 | "vue2-autocomplete",
24 | "form-helper",
25 | "vue-2"
26 | ],
27 | "author": "Naufal Rabbani ",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/BosNaufal/vue2-autocomplete/issues"
31 | },
32 | "homepage": "https://github.com/BosNaufal/vue2-autocomplete#readme",
33 | "devDependencies": {
34 | "babel-core": "^6.24.1",
35 | "babel-loader": "^6.4.1",
36 | "babel-plugin-transform-object-rest-spread": "^6.23.0",
37 | "babel-preset-es2015": "^6.24.1",
38 | "babel-preset-stage-1": "^6.24.1",
39 | "cross-env": "^5.0.3",
40 | "css-loader": "^0.28.0",
41 | "es6-promise": "^4.1.0",
42 | "extract-text-webpack-plugin": "^2.1.2",
43 | "gulp": "^3.9.1",
44 | "gulp-autoprefixer": "^4.0.0",
45 | "gulp-cssmin": "^0.2.0",
46 | "gulp-cssnano": "^2.1.2",
47 | "gulp-group-css-media-queries": "^1.2.0",
48 | "gulp-rename": "^1.2.2",
49 | "gulp-sourcemaps": "^2.6.0",
50 | "gulp-string-replace": "^0.4.0",
51 | "jsx-control-statements": "^3.2.5",
52 | "postcss-loader": "^2.0.6",
53 | "style-loader": "^0.16.1",
54 | "vue-loader": "^13.0.2",
55 | "vue-template-compiler": "^2.4.2",
56 | "webpack": "^2.3.3",
57 | "webpack-dev-server": "^2.4.2"
58 | },
59 | "dependencies": {
60 | "vue": "^2.4.2"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/img/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BosNaufal/vue2-autocomplete/c4b81a931fb182490e6f4b1c8fc6f37c6c80868f/src/img/demo.gif
--------------------------------------------------------------------------------
/src/js/components/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
16 |
17 | Selected Data:
18 | {{ preContent }}
19 |
20 |
21 |
22 |
23 |
56 |
--------------------------------------------------------------------------------
/src/js/components/vue-autocomplete.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
46 |
47 |
48 |
49 |
406 |
--------------------------------------------------------------------------------
/src/js/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './components/app.vue';
3 |
4 | Vue.config.debug = true
5 | Vue.config.devtools = true
6 |
7 | new Vue(Vue.util.extend(App)).$mount('app')
8 |
--------------------------------------------------------------------------------
/src/js/plugin.js:
--------------------------------------------------------------------------------
1 | import plugin from './components/vue-autocomplete.vue'
2 | export default plugin;
3 |
--------------------------------------------------------------------------------
/webpack/webpack-bundle.config.js:
--------------------------------------------------------------------------------
1 |
2 | var webpack = require('webpack');
3 | require('es6-promise').polyfill();
4 |
5 | var npm = require("../package.json");
6 |
7 | module.exports = {
8 |
9 | entry: __dirname + '/../src/js/plugin.js',
10 |
11 | output: {
12 | path: __dirname + '/../dist/',
13 | publicPath: '../dist/',
14 | filename: 'vue2-autocomplete.js',
15 | libraryTarget: "umd",
16 | library: "Vue2Autocomplete"
17 | },
18 |
19 | externals: {
20 | "vue": "Vue"
21 | },
22 |
23 |
24 | module: {
25 |
26 | loaders: [
27 | {
28 | test: /\.vue$/,
29 | loader: 'vue-loader'
30 | },
31 |
32 | {
33 | test: /\.js$/,
34 | exclude: /(node_modules|bower_components)/,
35 | loader: 'babel-loader',
36 | },
37 |
38 | {
39 | test: /\.css$/,
40 | use: ['style-loader','css-loader']
41 | }
42 | ]
43 | },
44 |
45 | plugins: [
46 |
47 | new webpack.BannerPlugin((
48 | [
49 | "Copyright (c) 2016 Naufal Rabbani (http://github.com/BosNaufal)",
50 | "\n",
51 | "Licensed Under MIT (http://opensource.org/licenses/MIT)",
52 | "\n",
53 | "\n",
54 | "Vue 2 Autocomplete @ Version "+ npm.version,
55 | "\n"
56 | ])
57 | .join(",")),
58 |
59 | new webpack.DefinePlugin({
60 | 'process.env': {
61 | 'NODE_ENV': '"production"'
62 | }
63 | }),
64 |
65 | ]
66 |
67 | };
68 |
--------------------------------------------------------------------------------
/webpack/webpack-prod.config.js:
--------------------------------------------------------------------------------
1 |
2 | var webpack = require('webpack');
3 | require('es6-promise').polyfill();
4 |
5 | module.exports = {
6 |
7 | devtool: 'eval',
8 |
9 | entry: __dirname + '/../src/js/main.js',
10 |
11 | output: {
12 | path: __dirname + '/../build',
13 | publicPath: '/build/',
14 | filename: 'build.js',
15 | chunkFilename: '[name].js'
16 | },
17 |
18 |
19 | module: {
20 |
21 | loaders: [
22 | {
23 | test: /\.vue$/,
24 | loader: 'vue-loader'
25 | },
26 |
27 | {
28 | test: /\.js$/,
29 | exclude: /(node_modules|bower_components)/,
30 | loader: 'babel-loader',
31 | },
32 |
33 | {
34 | test: /\.css$/,
35 | use: ['style-loader','css-loader']
36 | }
37 | ]
38 | },
39 |
40 | plugins: [
41 | new webpack.DefinePlugin({
42 | 'process.env': {
43 | 'NODE_ENV': '"production"'
44 | }
45 | }),
46 | ]
47 |
48 | };
49 |
--------------------------------------------------------------------------------
/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 |
2 | require('es6-promise').polyfill();
3 |
4 | module.exports = {
5 |
6 | entry: __dirname + '/../src/js/main.js',
7 |
8 | output: {
9 | path: __dirname + '/../build',
10 | publicPath: '/build/',
11 | filename: 'build.js',
12 | chunkFilename: '[name].js'
13 | },
14 |
15 | module: {
16 | loaders: [
17 | {
18 | test: /\.vue$/,
19 | loader: 'vue-loader'
20 | },
21 | {
22 | test: /\.js$/,
23 | exclude: /(node_modules|bower_components)/,
24 | loader: 'babel-loader',
25 | },
26 | {
27 | test: /\.css$/,
28 | use: ['style-loader','css-loader']
29 | }
30 | ]
31 | }
32 |
33 | };
34 |
--------------------------------------------------------------------------------