├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── example ├── App.vue ├── Character.vue ├── View.vue └── main.js ├── package.json ├── resources ├── ss1.png ├── ss2.png ├── ss3.png ├── vue-smart-route-swapi.gif ├── vue-smart-route.gif └── vue-smart-route.png ├── src └── vue-smart-route.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | sourceType: 'module' 5 | }, 6 | 7 | extends: 'standard', 8 | // required to lint *.vue files 9 | plugins: [ 10 | 'html' 11 | ] 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless/ 74 | 75 | # FuseBox cache 76 | .fusebox/ 77 | 78 | #DynamoDB Local files 79 | .dynamodb/ 80 | 81 | .DS_Store 82 | dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fatih Kadir Akın 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 |

2 | 3 |

4 |

Make your users dynamically navigate routes, make smart commands and queries with a single directive.

5 | 6 |

7 | 8 |

9 | 10 | Vue Smart Route allows you to create a **query** system based on your **routes**. You can simply create a command input that creates **smart** actions, both static routes and the **async ones**: 11 | 12 |

13 | 14 |

15 | 16 | ## Install 17 | 18 | ``` 19 | yarn add vue-smart-route 20 | ``` 21 | 22 | Then install it: 23 | 24 | ```js 25 | import Vue from 'vue' 26 | import VueRouter from 'vue-router' 27 | import VueSmartRoute from 'vue-smart-route' 28 | 29 | Vue.use(VueRouter) 30 | Vue.use(VueSmartRoute) 31 | ``` 32 | 33 | ## Overview 34 | 35 | This is a well known route in **VueRouter**: 36 | 37 | ```js 38 | routes: [ 39 | { 40 | name: 'about', 41 | path: '/about' 42 | } 43 | ] 44 | ``` 45 | 46 | To make it **smart route**, just add a `smart` key: 47 | 48 | ```js 49 | routes: [ 50 | { 51 | name: 'about', 52 | path: '/about', 53 | // Adding smart key with `matcher` and `handler` (optional) 54 | smart: { 55 | matcher: { 56 | search: [/about/], 57 | title: () => 'About us' 58 | } 59 | } 60 | } 61 | ] 62 | ``` 63 | 64 | Then, you need to use **`v-smart-routes`** directive to connect possible routes you asked with **`search`**: 65 | 66 | ```vue 67 | 70 | 71 | 81 | ``` 82 | 83 | Now, `routes` and `search` are connected each other and `routes` will be **smartly calculated** according to `search` property. 84 | 85 | > Following examples are styled. **`vue-smart-route` does not contain any style or component.** 86 | 87 | 88 | 89 | [▶︎ Try in Example](https://f.github.io/vue-smart-route) 90 | 91 | You can check `/example` to see a working example. 92 | 93 | ## Passing Parameters 94 | 95 | **vue-smart-route** is simple yet powerful. You can extend your logic to **make your route smarter.** 96 | 97 | Let's create a smart `/search` route: 98 | 99 | ```js 100 | { 101 | name: 'search', 102 | path: '/search', 103 | component: () => import('./Search.vue'), 104 | smart: { 105 | matcher: { 106 | // Named RegExp will be passed to the `title` function: 107 | search: [/^search\:?\s+(?.+)/i], 108 | title: ({ query }) => `Search about *${query}*` 109 | } 110 | } 111 | } 112 | ``` 113 | 114 | 115 | 116 | [▶︎ Try in Example](https://f.github.io/vue-smart-route) 117 | 118 | When you click to the link, it will be navigated to the `/search?query=how+to+be+smart`. 119 | 120 | Then you'll be able to access to the query using `$route.query.query` from your view. 121 | 122 | ## Passing Optional Parameters 123 | 124 | You can simply make your search smarter by adding more logic: 125 | 126 | ```js 127 | { 128 | name: 'mail', 129 | path: '/mail', 130 | component: () => import('./SendMail.vue'), 131 | smart: { 132 | matcher: { 133 | search: [ 134 | /(?[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)/i, 135 | /(?[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)\s+(?\w+)/i 136 | ], 137 | title({ email, subject }) { 138 | if (subject) { 139 | return `Send email to *${email}* with subject *${subject}*`; 140 | } 141 | return `Send email to *${email}*`; 142 | } 143 | } 144 | } 145 | } 146 | ``` 147 | 148 | - You can pass multiple `RegExp` for search, 149 | - `title` gets all the named matches and may include logic. 150 | 151 | 152 | 153 | [▶︎ Try in Example](https://f.github.io/vue-smart-route) 154 | 155 | It lists all the routes. 156 | 157 | ## The Directive 158 | 159 | vue-smart-route includes only a directive that makes all the magic. 160 | 161 | Directive requires to be bound an **input with a `v-model`**, and using `v-smart-routes` you will bind results to another property. 162 | 163 | E.g. if you bind `v-smart-routes` to `results` property, it will be an array of route objects. 164 | 165 | | key | Type | Description | 166 | | --- | ---- | ----------- | 167 | | `name` | `String` | Route name, e.g. `home` | 168 | | `path` | `String` | Route path, e.g. `/` | 169 | | `title` | `String` | Route title generated by `title` function of the smart route | 170 | | `handler` | `Function` | A function that triggers the navigation. It can be overriden. 171 | 172 | ## Customizing the `handler` behaviour 173 | 174 | `handler` navigates to page by default, but it can be changed. 175 | 176 | Let's make `email` example smarter by changing the navigation handler: 177 | 178 | ```js 179 | { 180 | name: 'mail', 181 | path: '/mail', 182 | component: () => import('./SendMail.vue'), 183 | smart: { 184 | matcher: { 185 | search: [ 186 | /(?[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)/i, 187 | /(?[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)\s+(?\w+)/i 188 | ], 189 | title({ email, subject }) { 190 | if (subject) { 191 | return `Send email to *${email}* with subject *${subject}*`; 192 | } 193 | return `Send email to *${email}*`; 194 | } 195 | }, 196 | 197 | // Customizing the handler 198 | handler(route, next) { 199 | if (route.query.subject) { 200 | location.href = `mailto:${route.query.email}?subject=${ 201 | route.query.subject 202 | }`; 203 | // Calling next will continue navigation by default, you can redirect or just stop here. 204 | next(route); 205 | return; 206 | } 207 | location.href = `mailto:${route.query.email}`; 208 | next(route); 209 | } 210 | } 211 | } 212 | ``` 213 | 214 | According to this example, you will be able to navigate your user to the mail application. 215 | 216 | ## Async Route Generation (Autocomplete-like) 217 | 218 | **vue-smart-route** supports `async routes` that you can generate routes on demand, on runtime. To do that, you should use `async routes` method to matcher: 219 | 220 | ```js 221 | smart: { 222 | matcher: { 223 | search: [/swapi\s(?.*)/], 224 | async routes({ query }) { 225 | const people = await fetch(`https://swapi.co/api/people/?search=${encodeURIComponent(query)}`).then(r => r.json()) 226 | return people.results.map(character => ({ 227 | name: 'character', 228 | title: `Go to character *${character.name}*`, 229 | params: { url: character.url } 230 | })) 231 | } 232 | } 233 | } 234 | ``` 235 | 236 | This will help you to generate new routes dynamically: 237 | 238 | 239 | 240 | ## i18n 241 | 242 | You can also use `i18n` features in `vue-smart-route`: 243 | 244 | `search`, `title` and `handler` takes `ctx` parameters which you can access to current component. 245 | 246 | ```js 247 | routes: [ 248 | { 249 | name: 'about', 250 | path: '/about', 251 | smart: { 252 | matcher: { 253 | search: (ctx) => { 254 | switch (ctx.$i18n.locale) { 255 | case 'tr': 256 | return [/hakkinda/] 257 | case 'en': 258 | default: 259 | return [/about/] 260 | } 261 | }, 262 | title: ({}, ctx) => ctx.$i18n.t('navigation.about_us') 263 | }, 264 | handler (route, next, ctx) { 265 | location.href = `https://${ctx.i18n.locale}.example.com/about` 266 | } 267 | } 268 | } 269 | ] 270 | ``` 271 | 272 | ## License 273 | 274 | MIT. 275 | -------------------------------------------------------------------------------- /example/App.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 31 | 32 | 97 | -------------------------------------------------------------------------------- /example/Character.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | 34 | 35 | 43 | -------------------------------------------------------------------------------- /example/View.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | 20 | 21 | 29 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import App from './App.vue'; 4 | import VueSmartRoute from '../src/vue-smart-route'; 5 | 6 | Vue.use(VueRouter); 7 | Vue.use(VueSmartRoute); 8 | 9 | const router = new VueRouter({ 10 | mode: 'hash', 11 | routes: [ 12 | { 13 | name: 'searchSw', 14 | path: '/search-sw', 15 | component: () => import('./View.vue'), 16 | smart: { 17 | matcher: { 18 | search: [/sw\s(?.*)/], 19 | async routes({ query }) { 20 | if (query.length < 3) return []; 21 | const data = await fetch( 22 | `https://swapi.co/api/people/?search=${encodeURIComponent(query)}` 23 | ).then(r => r.json()); 24 | return data.results.map(person => ({ 25 | name: 'character', 26 | title: `Go to character *${person.name}*`, 27 | params: { id: +person.url.match(/\/(\d+)\//, '$1')[1] } 28 | })); 29 | } 30 | } 31 | } 32 | }, 33 | { 34 | name: 'character', 35 | path: '/character/:id', 36 | component: () => import('./Character.vue') 37 | }, 38 | { 39 | name: 'users', 40 | path: '/users', 41 | component: () => import('./View.vue'), 42 | smart: { 43 | matcher: { 44 | search: [/user/], 45 | title: () => 'Go to users' 46 | } 47 | }, 48 | children: [ 49 | { 50 | name: 'newUser', 51 | path: 'new', 52 | component: () => import('./View.vue'), 53 | smart: { 54 | matcher: { 55 | search: [/user/, /new\suser(\s+(?.*))?/], 56 | title: ({ name }) => { 57 | if (name) { 58 | return `Create user with name *${name}*`; 59 | } 60 | return 'Create new user'; 61 | } 62 | } 63 | } 64 | }, 65 | { 66 | name: 'goToUser', 67 | path: '/user/:id', 68 | component: () => import('./View.vue') 69 | }, 70 | { 71 | name: 'viewUser', 72 | path: 'view/:username', 73 | component: () => import('./View.vue'), 74 | smart: { 75 | matcher: { 76 | search: [ 77 | /user\s*(?\d+)/, 78 | /user\s*(?[^\d\s]*)(\s(?\d+))?/ 79 | ], 80 | title: ({ username, id }) => { 81 | if (!username && id) { 82 | return `View user with ID *${id}*`; 83 | } 84 | if (username && id) { 85 | return `View user with username *${username}* and ID *${id}*`; 86 | } 87 | if (username) { 88 | return `View user with username *${username}*`; 89 | } 90 | return 'View all users'; 91 | } 92 | }, 93 | handler(route, next) { 94 | if (!route.params.username && route.query.id) { 95 | next({ ...route, name: 'goToUser' }); 96 | return; 97 | } 98 | if (!route.params.username && !route.query.id) { 99 | next({ name: 'users' }); 100 | return; 101 | } 102 | next(route); 103 | } 104 | } 105 | } 106 | ] 107 | }, 108 | { 109 | name: 'home', 110 | path: '/', 111 | component: () => import('./View.vue'), 112 | smart: { 113 | matcher: { 114 | search: [/home/], 115 | title: () => 'Go to homepage' 116 | } 117 | } 118 | }, 119 | { 120 | name: 'about', 121 | path: '/about', 122 | component: () => import('./View.vue'), 123 | smart: { 124 | matcher: { 125 | search: [/about/], 126 | title: () => 'About us' 127 | } 128 | } 129 | }, 130 | { 131 | name: 'search', 132 | path: '/search', 133 | component: () => import('./View.vue'), 134 | smart: { 135 | matcher: { 136 | search: [/^(search|q)\:?\s+(?.+)/i], 137 | title: ({ query }) => `Search about *${query}*` 138 | } 139 | } 140 | }, 141 | { 142 | name: 'sendMail', 143 | path: '/mail', 144 | component: () => import('./View.vue'), 145 | smart: { 146 | matcher: { 147 | search: [ 148 | /(?[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)/i, 149 | /(?[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)\s+(?\w+)/i 150 | ], 151 | title({ email, subject }) { 152 | if (subject) { 153 | return `Send email to *${email}* with subject *${subject}*`; 154 | } 155 | return `Send email to *${email}*`; 156 | } 157 | } 158 | // handler(route, next) { 159 | // if (route.params.subject) { 160 | // location.href = `mailto:${route.params.email}?subject=${ 161 | // route.params.subject 162 | // }`; 163 | // next(route); 164 | // return; 165 | // } 166 | // location.href = `mailto:${route.params.email}`; 167 | // next(route); 168 | // } 169 | } 170 | } 171 | ] 172 | }); 173 | 174 | new Vue({ 175 | el: '#app', 176 | router, 177 | render(createElement) { 178 | return createElement(App); 179 | } 180 | }); 181 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-smart-route", 3 | "version": "0.3.2", 4 | "description": "Smart Router for vue-router", 5 | "main": "dist/vue-smart-route.js", 6 | "files": [ 7 | "dist" 8 | ], 9 | "scripts": { 10 | "test": "exit 0;", 11 | "build": "poi src/vue-smart-route.js --prod --format umd --module-name VueSmartRoute --file-names.js vue-smart-route.js", 12 | "dev": "poi example/main.js --serve", 13 | "gh-pages": "poi example/main.js --prod --public-url ./ --out-dir=example/dist && gh-pages -d example/dist" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/f/vue-smart-route.git" 18 | }, 19 | "keywords": [ 20 | "vue-router", 21 | "search", 22 | "smart" 23 | ], 24 | "author": "Fatih Kadir Akin ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/f/vue-smart-route/issues" 28 | }, 29 | "homepage": "https://github.com/f/vue-smart-route#readme", 30 | "lint-staged": { 31 | "src/**/*.js": [ 32 | "prettier --single-quote --write", 33 | "git add" 34 | ], 35 | "example/**/*.js": [ 36 | "prettier --single-quote --write", 37 | "git add" 38 | ] 39 | }, 40 | "peerDependencies": { 41 | "vue": "^2.5.17", 42 | "vue-router": "^3.0.2" 43 | }, 44 | "devDependencies": { 45 | "cross-env": "^5.2.0", 46 | "eslint": "^5.9.0", 47 | "gh-pages": "^2.0.1", 48 | "husky": "^1.2.0", 49 | "lint-staged": "^8.1.0", 50 | "poi": "^12.0.0-beta.6", 51 | "prettier": "^1.15.3", 52 | "vue": "^2.5.17", 53 | "vue-router": "^3.0.2", 54 | "vue-template-compiler": "^2.5.17" 55 | }, 56 | "husky": { 57 | "hooks": { 58 | "pre-commit": "lint-staged" 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /resources/ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-smart-route/d1410e989145c8654864cdbea46c558d86fc2f56/resources/ss1.png -------------------------------------------------------------------------------- /resources/ss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-smart-route/d1410e989145c8654864cdbea46c558d86fc2f56/resources/ss2.png -------------------------------------------------------------------------------- /resources/ss3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-smart-route/d1410e989145c8654864cdbea46c558d86fc2f56/resources/ss3.png -------------------------------------------------------------------------------- /resources/vue-smart-route-swapi.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-smart-route/d1410e989145c8654864cdbea46c558d86fc2f56/resources/vue-smart-route-swapi.gif -------------------------------------------------------------------------------- /resources/vue-smart-route.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-smart-route/d1410e989145c8654864cdbea46c558d86fc2f56/resources/vue-smart-route.gif -------------------------------------------------------------------------------- /resources/vue-smart-route.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f/vue-smart-route/d1410e989145c8654864cdbea46c558d86fc2f56/resources/vue-smart-route.png -------------------------------------------------------------------------------- /src/vue-smart-route.js: -------------------------------------------------------------------------------- 1 | function flattenRoutes(routes = [], level = 0) { 2 | if (Array.isArray(routes)) { 3 | return routes.reduce( 4 | (accumulator, { name, path, smart, children = [] }) => { 5 | accumulator.push({ name, path, smart, children, level }); 6 | if (children.length) { 7 | accumulator = accumulator.concat(flattenRoutes(children, level + 1)); 8 | } 9 | return accumulator; 10 | }, 11 | [] 12 | ); 13 | } 14 | } 15 | 16 | function splitMatch(path, query) { 17 | const matches = path.match(/(:[0-9a-z_\-]+)/gi); 18 | if (!matches) return { query }; 19 | 20 | const params = matches.map(m => m.slice(1).trim()); 21 | const splitted = { query: {}, params: {} }; 22 | Object.keys(query).forEach(key => { 23 | splitted[params.includes(key) ? 'params' : 'query'][key] = query[key]; 24 | }); 25 | return splitted; 26 | } 27 | 28 | function buildRoute(route, title, smart, next, context) { 29 | return { 30 | ...route, 31 | title: title.replace(/\*([^*]+)\*/g, '$1'), 32 | handler: () => smart.handler(route, next, context) 33 | }; 34 | } 35 | 36 | async function findSmartRoutes(value, context) { 37 | const routes = context.$router.options.routes; 38 | const allRoutes = flattenRoutes(routes); 39 | 40 | // Find Smart Routes 41 | const smartRoutes = allRoutes 42 | .filter(route => route.smart) 43 | .map(({ name, path, smart }) => ({ name, path, smart })); 44 | 45 | // Find Matching Routes with thte Value 46 | const matchingRoutes = smartRoutes.map(async ({ name, path, smart }) => { 47 | if (!smart.matcher) { 48 | throw new Error('Smart routes must have matchers!'); 49 | } 50 | 51 | const next = route => context.$router.push(route); 52 | if (!smart.handler) { 53 | smart.handler = next; 54 | } 55 | 56 | const matching = (typeof smart.matcher.search === 'function' 57 | ? smart.matcher.search(context) 58 | : smart.matcher.search 59 | ) 60 | .map(matcher => value.toString().match(matcher)) 61 | .filter(Boolean); 62 | 63 | const routes = await Promise.all( 64 | matching.map(async match => { 65 | if (!match) return; 66 | const query = match.groups ? match.groups : match; 67 | const route = { 68 | name, 69 | path, 70 | ...splitMatch(path, match.groups) 71 | }; 72 | 73 | if (typeof smart.matcher.routes === 'function') { 74 | const routesToBuild = await smart.matcher.routes(query, context); 75 | return routesToBuild.map(r => 76 | buildRoute(r, r.title, smart, next, context) 77 | ); 78 | } 79 | 80 | const title = smart.matcher.title(query, context); 81 | return buildRoute(route, title, smart, next, context); 82 | }) 83 | ); 84 | return [].concat.apply([], routes).filter(Boolean); 85 | }); 86 | const doneRoutes = await Promise.all(matchingRoutes); 87 | return [].concat(...doneRoutes); 88 | } 89 | 90 | export default { 91 | install(Vue) { 92 | Vue.directive('smart-routes', { 93 | bind: function(el, binding, vnode) { 94 | var model = vnode.data.directives.filter(d => d.name === 'model'); 95 | if (!model.length) { 96 | throw new Error( 97 | 'An input with v-smart-routes directive must have v-model.' 98 | ); 99 | } 100 | vnode.context.$watch(model[0].expression, async function(value) { 101 | this[binding.expression] = await findSmartRoutes(value, this); 102 | }); 103 | } 104 | }); 105 | } 106 | }; 107 | --------------------------------------------------------------------------------