{"id":13423947,"url":"https://github.com/inca/voie","last_synced_at":"2025-04-09T22:20:50.894Z","repository":{"id":57393693,"uuid":"47722317","full_name":"inca/voie","owner":"inca","description":"[UNMAINTAINED] Simple Vue.js router / layout manager","archived":false,"fork":false,"pushed_at":"2016-09-28T09:43:28.000Z","size":314,"stargazers_count":141,"open_issues_count":6,"forks_count":7,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-03-24T00:13:49.316Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/inca.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-12-09T22:12:28.000Z","updated_at":"2024-05-14T14:27:39.000Z","dependencies_parsed_at":"2022-09-26T16:51:14.415Z","dependency_job_id":null,"html_url":"https://github.com/inca/voie","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inca%2Fvoie","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inca%2Fvoie/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inca%2Fvoie/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inca%2Fvoie/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inca","download_url":"https://codeload.github.com/inca/voie/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247953105,"owners_count":21023946,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T00:00:45.454Z","updated_at":"2025-04-09T22:20:50.862Z","avatar_url":"https://github.com/inca.png","language":"JavaScript","funding_links":[],"categories":["Awesome Vue.js [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome)","Awesome Vue.js"],"sub_categories":["Libraries \u0026 Plugins"],"readme":"# Voie.js\n\n**Important! Due to [breaking changes](https://github.com/vuejs/vue/issues/2873)\nin Vue 2.0 current versions of Voie (0.x.x) are now deprecated\n(feel free to use it with Vue 1.x.x).\nThe 1.x.x will most probably be a major rewrite, so there's\nno guarantee of backwards compatibility. Sorry.**\n\n**Voie** /vwa/ (fr. \"way\") is a simple router / layout manager for [Vue.js](http://vuejs.org).\nUse it to build SPAs of your dreams.\n\nCurrent status: **active development** — any feedback is appreciated.\n\nSimple example app is available on [GitHub](https://github.com/inca/voie-example)\nand [live on Netlify](http://voie-example.netlify.com/).\n\n[Standalone bundles](dist/) are also available, mostly for using with\njsfiddle, jsbin, codepen, etc. (note, Vue.js is **not** included in bundles).\n\nYou should never use them in real development — use module bundlers instead.\n\n## Core concepts\n\nUnlike official [vue-router](https://github.com/vuejs/vue-router) which\nis organized around URLs, Voie is organized around _states_. Voie-based apps\nare basically [finite-state machines](https://en.wikipedia.org/wiki/Finite-state_machine).\n\nState is simply a _named_ logical \"place\" within your application.\n\nEach state can _optionally_ have:\n\n  * URL pattern\n  * Vue component\n  * enter hook to populate state with data\n  * leave hook to cleanup things\n\nStates are organized into hierarchies: child states will inherit parameters and data\nfrom parent state. Also, if child state has a component, then it will be rendered at the location\nspecified by parent (or nearest ancestor) state denoted by `\u003cv-view\u003e` directive.\n\nConsider this example:\n\n```es6\napp.add('user', {\n  path: '/user/:userId',\n  redirect: 'user.dashboard',   // specify \"default\" sub-state\n  enter: (ctx) =\u003e {             // can return a Promise\n    return fetch('/user/' + ctx.params.userId)\n      .then(res =\u003e res.json())\n      .then(data = ctx.data.user = data);\n  },\n  component: {\n    template: '\u003cdiv class=\"user-layout\"\u003e\u003cv-view\u003e\u003c/v-view\u003e\u003c/div\u003e'\n  }\n});\n\napp.add('user.dashboard', {\n  component: {\n    template: '\u003ch1\u003eHello, {{ user.name }}!\u003c/h1\u003e'\n  }\n});\n```\n\nIn this example visiting `/user/123` would fetch a user with id `123` from a server\nand then render following markup (assuming user has name \"Alice\"):\n\n```html\n\u003cdiv class=\"user-layout\"\u003e\n  \u003ch1\u003eHello, Alice!\u003c/h1\u003e\n\u003c/div\u003e\n```\n\n**Note:** [fragment instances](http://vuejs.org/guide/components.html#Fragment_Instance)\nare not supported as components. In other words, make sure all components\ncontain a single top-level element without flow control directives (`v-if`, `v-for`, etc.)\n\n## Installation\n\nExamples assume ES6 and build environment\n([browserify](http://browserify.org/) + [babelify](https://github.com/babel/babelify)\nor [webpack](https://webpack.github.io/) + [babel-loader](https://github.com/babel/babel-loader))\nwhich is a mainstream.\n\n```bash\nnpm i --save voie\n```\n\nYou also need an [es6-shim](https://github.com/paulmillr/es6-shim) to make everything\ngo smooth in not-so-modern browsers.\n\n## Usage\n\n### State manager\n\nVoie app is an instance of `StateManager`. You provide it with `el`, which\nis an \"entry-point\" DOM node where views will be entered.\n\n```es6\nimport { StateManager } from 'voie'\n\nexport default new StateManager({\n  el: '#app'    // entry point, either a selector or HTMLElement\n});\n```\n\nIt is a good idea to expose state manager instance as a singleton module\n(i.e. single instance per application)\nsince you will often want to use it in your Vue methods and stores.\n\n### Define states\n\nNext thing you want to do is to register some states.\nYour app will probably contain plenty of states, so you'll need some structure.\nI prefer \"domain-centric\" directory structure:\n\n```es6\n// states.js\nimport './users';\nimport './groups';\n// ...\n```\n\n```es6\n// users/index.js\nimport app from '../app';\nimport UsersLayout from './layout.vue';\nimport UsersList from './list.vue';\n\napp.add('users', {\n  component: UsersLayout\n  ...\n});\napp.add('users.list', {\n  component: UsersList,\n  ...\n});\napp.add('users.create', { ... });\napp.add('user', { ... });\napp.add('user.view', { ... });\napp.add('user.edit', { ... });\napp.add('user.delete', { ... });\n```\n\n```es6\n// groups/index.js\nimport app from '../app';\n\napp.add('groups', { ... });\n// ...\n```\n\nStructuring apps is a matter of preference, so you are free to choose\nwhatever suits you best.\n\n### Running\n\nFinally, run your state manager like this:\n\n```es6\n// index.js\nimport app from './app';\nimport './states';\n\napp.start();\n```\n\nIt will begin listening for history events and match-and-render current route.\n\n## More usage\n\n### States hierarchy\n\nStates are automatically organized into a tree-like structure. Each state\nwill have a single parent state. \"Root\" states would have a `null` parent\n\nThere are two ways of specifying a parent:\n\n  * using dot character `.` in state name\n    (e.g. `user` -\u003e `user.transaction` -\u003e `user.transaction.details`)\n\n  * explicitly using `parent` configuration parameter:\n\n    ```es6\n    app.add('users', { ... });\n    app.add('user', {\n      parent: 'users'\n    });\n    ```\n\nEach way has its own advantages, so it's usually OK to use both styles in the same app.\nSpecifically, use qualified names to outline context (e.g. \"User's profile page\" would\nbe `user.profile`) or entity-relationship (e.g. \"User's transactions list would\nbe `user.transactions`).\n\nSpecifying `parent` is handy in cases when you want to preserve concise and clean state name\nwhile being able to use a different layout or add some global \"enter\" hook on state subtree.\n\nA typical example would be authentication/authorization:\n\n```es6\napp.add('root', { ... });\n\napp.add('login', {\n  parent: 'root',\n  ...\n});\n\napp.add('authenticated', {\n  parent: 'root',\n  enter: ctx =\u003e {\n    if (!UserService.isAuthenticated()) {\n      return { redirect: 'login' };\n    }\n  }\n});\n\napp.add('users', {\n  parent: 'authenticated',\n  ...\n});\n\napp.add('groups', {\n  parent: 'authenticated',\n  ...\n});\n\n```\n\n### Navigating states\n\nUse `stateManager.go` to navigate programmatically:\n\n```es6\nstateManager.go({\n  name: 'user.dashboard',\n  params: {\n    userId: '123'\n  }\n});\n```\n\nIn templates you can use `v-link` directive with the same semantics:\n\n```html\n\u003ca v-link=\"{ name: 'user.dashboard', params: { userId: 123 } }\"\u003e\n  Dashboard\n\u003c/a\u003e\n```\n\nIn addition to invoking `stateManager.go` it will also update the `href`\nattribute and apply an `active` class if current state \"includes\"\nthe state specified by link.\n\nActive class name can be customised globally:\n\n```es6\nnew StateManager({\n  el: '#el',\n  activeClass: 'highlighted'\n});\n```\n\n### Enter / leave\n\nState can optionally define `enter` and `leave` hooks which are functions\nthat accept _state context_ object.\n\nState context contains:\n\n  * `params` — a hash of `string` parameters matched from URL pattern,\n    specified explicitly via `stateManager.go(...)` and inherited from parent context\n  * `data` — object where you can write data to be exposed to Vue component and\n    inherited states\n  * `state` — `State` object to which this context corresponds\n  * `parent` — parent context of this object\n\nTypical `enter` hook will use `params` to fetch or prepare some data and expose it\nvia `data` object.\n\nBoth `enter` and `leave` can return a `Promise`, which makes hooks asynchronous.\nIn case of `enter` the component will only be entered when promise is resolved.\n\nExample:\n\n```es6\n{\n  enter: (ctx) =\u003e UserService.findByEmail(ctx.params.email)\n    .then(user =\u003e ctx.data.user = user)\n}\n```\n\n### Before each / after each\n\nAdditionally one can configure global `beforeEach` and `afterEach` hooks\nthat will be applied before `enter` hooks and after `leave` hook on each state\nrespectively.\n\nGlobal hooks are configured on `StateManager`:\n\n```es6\nnew StateManager({\n\n  beforeEach(ctx) {\n    if (ctx.state.name === 'private') {\n      return { redirect: 'not_allowed' };\n    }\n  }\n\n});\n```\n\n### Redirecting\n\nEnter can optionally redirect to another state by\nreturning (or resolving via promise) an object like this:\n`{ redirect: 'state.name' }` or\n`{ redirect: { name: 'state.name', params: {} }`.\n\nWhen `redirect` is returned by `enter` hook the transition will always redirect\nwhenever it enters specified state (even if this state was not a destination).\n\nRedirect can also be specified at state configuration level:\n\n```es6\napp.add('users', {\n  redirect: 'users.list',\n  // or with params\n  redirect: {\n    name: 'users.list',\n    params: { sort: '+name' }\n  },\n  // or even function Transition =\u003e Promise(stateName)\n  redirect: (transition) =\u003e {\n    transition.params.sort = '+name';\n    return Promise.resolve('users.list');\n  }\n});\n```\n\nWhen `redirect` is specified as state configuration option it will\nonly be effective when moving specifically to this state (in other words,\nno redirect occurs when transitioning through this state to another one).\n\n### State transitions\n\nConsider following components hierarchy:\n\n```\n  A\n/   \\\nB   D\n|   |\nC   E\n```\n\nGoing from C to E implies:\n\n  * leaving state C\n  * leaving state B\n  * entering state D\n  * entering state E\n\nBy \"leaving\" we mean:\n\n  * executing `leave` hook\n  * destroying Vue component, if any\n  * restoring the original state of `\u003cv-view\u003e` element where the component was rendered\n\nBy \"entering\" we mean:\n\n  * preparing new context\n  * executing `enter` hook\n  * rendering Vue component, if any\n  * preserving the original state of `\u003cv-view\u003e` so that it could later be restored\n\n### Parameters\n\nEach state has a specification of parameters it can accept when entered.\nMandatory parameters (e.g. `userId` for state `user`) are classically specified\nin pathname (e.g. `/user/28`).\nOptional parameters (e.g. `page`, `limit` for lists) are usually specified\nin querystring (e.g. `/users?page=5\u0026limit=100`).\n\nHere's how you define both parameter types when registering states:\n\n```es6\napp.add('user', {\n  path: '/user/:userId',   // userId param is mandatory\n  params: {\n    section: null,        // these are optional\n    collapsed: false      // with optional default values\n  }\n});\n```\n\nBoth querystring and pathname parameters are accessible in `ctx.params` object\nwhich is exposed both to `enter` hook and components.\n\nExample:\n\n```\nlocation.href = '/user/123?section=profile\u0026collapsed=true';\napp.context.params\n// { userId: '123', section: 'profile', collapsed: 'true' }\n```\n\n**Note:** Voie doesn't do any type conversion on params, so they are returned as strings.\n\nWhen navigating between states specify parameters in `go` (or `v-link`):\n\n```es6\napp.go({\n  name: 'user',\n  params: {\n    userId: '123',\n    section: 'profile',\n    unknown: 'wut?'     // Important, this will be dropped!\n  }\n});\n```\n\n**Note:** params must be listed explicitly when registering states,\nall other parameters will be dropped. In the example above\nparameter `unknown` is not specified in `path` or `params` of `user` state (or its ancestors),\nso it's not part of `user` state spec and, therefore, will not be accessible in `ctx.params`.\n\n### History\n\nA common need for any web application is to update browser URL upon navigating to\na state with URL mapping, so that when the user presses \"Refresh\" application\nloads the most recent state (not the \"start\" screen).\nDecent SPAs would also have Back/Forward buttons working as expected.\nWe refer to these features as \"history support\".\n\nNow there's two ways of implementing history support in your application:\n\n  * **hash** (uncool, but fairly simple) — state will be maintained using\n    hash portions of URL (e.g. `https://myapp/#user/1/transactions`)\n\n  * **HTML5** (cool, a bit more complex) — state will be maintained using\n    pathname portion of URL (e.g. `https://myapp/user/1/transactions`)\n\nHTML5 history requires server-side setup: server must reply with the same HTML\nwrapper to **all URLs** used by your application.\n\n#### HTML5 server setup example\n\nHere's an example [Express](http://expressjs.com) server:\n\n```es6\nimport express from 'express';\n\nlet app = express();\n\n// Allow using static resources with `/static` prefix\napp.use('/static', express.static('static'));\n\n// Serve application data with `/~` prefix\napp.use('/~', appDataRouter);\n\n// Serve SPA entry-point HTML to all other routes\napp.get('/*', (req, res) =\u003e res.sendFile('app.html'))\n```\n\nThis example shows potential gotchas:\nsince `app.html` will be served for all GET requests, in order\nto serve other resources (e.g. static or compiled assets, scripts,\nstylesheets, application data, etc.) you'll need separate prefixes.\nWithout these prefixes it won't be easy to configure your front web server\n([nginx](http://nginx.org/en/) or Apache) for production.\n\n#### History setup\n\nVoie uses awesome [history](https://github.com/rackt/history)\nto provide apps with history support. HTML5 mode is used by default.\n\nHere's how to switch to hash-history (you need to install `history`):\n\n```es6\n// app.js\nimport { StateManager } from 'voie';\nimport { createHashHistory } from 'history';\n\nexport default new StateManager({\n  el: '#app',\n  history: createHashHistory()\n});\n```\n\nSee [history docs](https://github.com/rackt/history/tree/master/docs)\nfor more options.\n\n#### Base URL\n\nIt's a bit easier to setup servers using \"base\" URL for your app, e.g.:\n\n```\napp.get('/app*', (req, res) =\u003e res.sendFile('app.html'))\n```\n\nIn this case all you need to do is to add `\u003cbase href=\"/app\"/\u003e`\ninside the `\u003chead\u003e` of your HTML wrapper.\n\nOr you can just specify `base` configuration parameter (it's only used\nwhen `history` is not present):\n\n```es6\n// app.js\nimport { StateManager } from 'voie';\n\nexport default new StateManager({\n  el: '#app',\n  base: '/app'\n});\n```\n\n### More docs\n\nVoie is in active development, so more docs are coming soon.\n\n## License (ISC)\n\nCopyright (c) 2015-2016, Boris Okunskiy \u003cboris@okunskiy.name\u003e\n\nPermission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finca%2Fvoie","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finca%2Fvoie","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finca%2Fvoie/lists"}