├── .gitignore ├── component-validation ├── README.md ├── app.js └── index.html ├── components ├── README.md ├── app.js └── index.html ├── routing ├── README.md ├── app.js ├── index.html └── named-views │ ├── app.js │ └── index.html ├── setup ├── README.md ├── app.js └── index.html ├── structural-directives ├── README.md ├── app.js └── index.html └── vuex-examples ├── actions ├── README.md ├── app.css ├── app.js └── index.html ├── hello-world ├── README.md ├── app.css ├── app.js └── index.html └── modules ├── README.md ├── app.css ├── app.js └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | -------------------------------------------------------------------------------- /component-validation/README.md: -------------------------------------------------------------------------------- 1 | # Components validation 2 | 3 | There is a way for us to set some validation on our props. Why would we want that you ask? Well, the main reason is that we want to make sure the consumers of our component use it correctly, so what we can do is to set up a bunch of validation and give out a warning in the console if the validation fails. 4 | 5 | ## Required 6 | 7 | If we don't explicitly set a property as `required` it will be optional. What we do to make it so is to assign an object to our property instead of using the string syntax `['prop']` that we are used to, like the below: 8 | 9 | ``` 10 | Vue.component('validate-test', { 11 | template: ` 12 |
13 |

14 | Year {{ year }} 15 |

16 |

17 | Title {{ title }} 18 |

19 | `, 20 | 21 | props : { 22 | year: Number, 23 | title : { 24 | type: String, 25 | required: true 26 | }, 27 | } 28 | }); 29 | ``` 30 | 31 | Let's highlight the interesting code: 32 | 33 | ``` 34 | title : { 35 | type: String, 36 | required: true 37 | } 38 | ``` 39 | 40 | Above we give it a type `String` and set the `required` property to `true`. 41 | 42 | ## Validating an object 43 | 44 | Objects are interesting in that they have many different properties to validate. One thing we can do is to rely on a factory function `default()`. If we leave out the property in question then the factory function will be invoked and whatever it returns will take the place of where the bound object should have been. Let's look at some code: 45 | 46 | ``` 47 | Vue.component('validate-test', { 48 | template: ` 49 |
50 |

51 | Year: {{ year }} 52 |

53 |

54 |

55 | Title: {{ title }} 56 |

57 |

58 | Product: {{ product.name }} 59 |

60 |
61 | `, 62 | 63 | props : { 64 | year: Number, 65 | title : { 66 | type: String, 67 | required: true 68 | }, 69 | product: { 70 | type: Object, 71 | default: function() { 72 | return { 73 | name: 'Unknown product' 74 | } 75 | } 76 | } 77 | } 78 | }); 79 | ``` 80 | 81 | And using said component like so: 82 | 83 | ``` 84 | 85 | ``` 86 | 87 | As you can see above we are clearly omitting the `product` property in our component and this is where the `default()` method kicks in and returns its object instead. Our template therefore renders 88 | 89 | ``` 90 | Product: Unknown product 91 | ``` 92 | 93 | ## Validator function 94 | 95 | Not only are we able to ensure that the input reaching our prop is of the right data type but we are also ablo to inspect the value and ensure it fulfills the necessary criteria. Let's have a look at what that looks like in code: 96 | 97 | ``` 98 | Vue.component('validate-test', { 99 | template: ` 100 |
101 |

102 | Year: {{ year }} 103 |

104 |

105 |

106 | Title: {{ title }} 107 |

108 |

109 | Product: {{ product.name }} 110 |

111 |
112 | `, 113 | 114 | props : { 115 | year: Number, 116 | title : { 117 | type: String, 118 | required: true 119 | }, 120 | product: { 121 | type: Object, 122 | default: function() { 123 | return { 124 | name: 'Unknown product' 125 | } 126 | } 127 | }, 128 | age: { 129 | validate: function(value) { 130 | return value >=18; 131 | } 132 | } 133 | } 134 | }); 135 | ``` 136 | 137 | Let's highlight the interesting code: 138 | 139 | ``` 140 | age: { 141 | validate: function(value) { 142 | return value >=18; 143 | } 144 | } 145 | ``` 146 | 147 | Above we can see that we added the function `validate()` and we define a function body where we end up with a response that either evaluates to `true` or `false`. If it returns `false` then a warning will be raised in the console that this value is not valid. 148 | -------------------------------------------------------------------------------- /component-validation/app.js: -------------------------------------------------------------------------------- 1 | Vue.component('validate-test', { 2 | template: ` 3 |
4 |

5 | Year {{ year }} 6 |

7 |

8 | Title {{ title }} 9 |

10 |

11 | Product {{ product.name }} 12 |

13 |

14 | Age: {{ age }} 15 |

16 |
17 | `, 18 | 19 | props : { 20 | year: Number, 21 | title : { 22 | type: String, 23 | required: true 24 | }, 25 | product : { 26 | type: Object, 27 | default: function() { 28 | return { 29 | name: 'Unknown product' 30 | }; 31 | } 32 | }, 33 | age : { 34 | type: Number, 35 | validator: function(value){ 36 | return value >=18; 37 | } 38 | 39 | } 40 | } 41 | }); 42 | 43 | 44 | var app = new Vue({ 45 | el: '#app', 46 | data: { 47 | message: 'Hello Vue!', 48 | year: 1990, 49 | title: 'a title', 50 | age: 3 51 | }, 52 | methods : { 53 | // points to window this 54 | showDetail : (product) => { 55 | alert('detail ' + product.name); 56 | }, 57 | // points to window this 58 | save: (person) => { 59 | alert('called from component'); 60 | }, 61 | // points to correct this, no => 62 | changeTitle() { 63 | this.title = "new title"; 64 | } 65 | } 66 | }) 67 | -------------------------------------------------------------------------------- /component-validation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | {{ message }} 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /components/README.md: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | We declare a component by using the `Vue` object and calling its component method with two arguments: 4 | 5 | * component name 6 | * component definition object 7 | 8 | ``` 9 | Vue.component('person-detail', { 10 | template: `
a component
` 11 | }) 12 | ``` 13 | 14 | Declaring the above means we can start adding this component wherever we need it, like so: 15 | 16 | ``` 17 | 18 | ``` 19 | 20 | ## Adding input property 21 | 22 | We are going to want to add an input property to the component so we can pass data for it to render. We do that by doing the following: 23 | 24 | * adding v-bind directive when creating the component 25 | * adding a `props` property to component definition 26 | 27 | ``` 28 | Vue.component('person-detail', { 29 | template: `
{{person.name}}
`, 30 | props: ['person'] 31 | }) 32 | ``` 33 | 34 | We did two things above, we added the `props` to our component definition object. We also updated the template to render our property like so: 35 | 36 | ``` 37 | template: `
{{person.name}}
` 38 | ``` 39 | 40 | At this point we need to creating the binding in the markup. We create this binding by using the structural directive `v-bind`, like so: 41 | 42 | ``` 43 |
44 | ``` 45 | 46 | For our component it means we change it to the following: 47 | 48 | ``` 49 | 50 | ``` 51 | 52 | This of course means that the context this component lives in knows about `person`, like so: 53 | 54 | ``` 55 | var app = new Vue({ 56 | el: '#app', 57 | data: { 58 | person: { 59 | name: 'chris' 60 | } 61 | } 62 | }) 63 | ``` 64 | 65 | ### Naming 66 | 67 | So far we have been using the `v-bind:propertyInComponent` binding syntax, but it is a bit verbose. We can shorten this to `:propertyInComponent`. There is another naming convention that we need to know about. When we name things in our props of our component like so: 68 | 69 | ``` 70 | Vue.component('component', { 71 | props : ['title','longTitle'] 72 | }); 73 | ``` 74 | 75 | The casing we use matter. When we create our component in markup, we need to name it correctly 76 | 77 | * title becomes title 78 | BUT 79 | * longTitle becomes long-title when used in the markup 80 | 81 | ``` 82 | 83 | ``` 84 | 85 | ### More on binding 86 | 87 | We can bind anything to a component, a primitive like a string, number or boolean or a list and even an object. However there is a difference in how different types behave. Primitives are being one-time binded so binding against the following primitives, would be one-time: 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | Binding to an object would be another thing though: 94 | 95 | ``` 96 | 97 | ``` 98 | 99 | The above shows how we bind towards the property `person` and binds it to an object that may look like this: 100 | 101 | ``` 102 | { 103 | name: 'chris' 104 | } 105 | ``` 106 | 107 | Changing the property inside of the component with a `v-model`, like so: 108 | 109 | ``` 110 | 111 | ``` 112 | 113 | would lead to even the parent object being affected. 114 | 115 | ### fixing the double binding 116 | 117 | What you usually want when you bind in a list or object is to change things in the component first and when you are happy with it you invoke an action so the parent knows all about this updated list or object. You usually don't want the parent to be alerted about every change that you do. You can fix this by using the `data()` method with the component like so: 118 | 119 | ``` 120 | data() { 121 | const copyObject = Object.assign({}, this.object); 122 | 123 | const copyList = this.list.map(item => Object.assign(item)); 124 | 125 | return { 126 | copyObject, 127 | copyList 128 | }; 129 | 130 | } 131 | ``` 132 | 133 | ## Output 134 | 135 | How do we communicate with upwards, i.e how do we invoke a method, simple, we can just bind a method to it like so: 136 | 137 | ``` 138 | 139 | ``` 140 | 141 | The above tells us that we can call the event whatever we want, as indicated by the name `customEVentName`. 142 | 143 | And of course our parent component needs updating, like so: 144 | 145 | ``` 146 | var app = new Vue({ 147 | el: '#app', 148 | data: { 149 | person: { 150 | name: 'chris' 151 | } 152 | }, 153 | methods: { 154 | method: () => { 155 | alert('called from component') 156 | } 157 | } 158 | }) 159 | ``` 160 | 161 | The interesting part happens when we invoke the method from the component itself, then it looks like this: 162 | 163 | ``` 164 | Vue.component('person-detail',{ 165 | template: ` 166 |
167 | 168 |
`, 169 | props: ['person'], 170 | methods: { 171 | increment() { 172 | this.counter += 1 ; 173 | } 174 | } 175 | }) 176 | ``` 177 | 178 | ### Actual example 179 | 180 | Not how we below call our custom event `save`. We will soon see how this matches up in our component. 181 | 182 | ``` 183 | 184 | ``` 185 | 186 | Let's now turn to our component and look at how we can invoke the event we just set up: 187 | 188 | ``` 189 | 190 | ``` 191 | 192 | Zooming in we see `v-on:click` and that it invokes `$emit`. 193 | -------------------------------------------------------------------------------- /components/app.js: -------------------------------------------------------------------------------- 1 | Vue.component('person-detail',{ 2 | template: ` 3 |
4 | 5 | Person object 6 |
7 | 8 | Name: 9 | 10 | 11 | Person: {{person.name}} 12 | Counter: {{counter}} 13 | 14 |

Copy person

15 | 16 | 17 |

Copy jedis

18 |
19 | {{ jedi.name }} 20 | 21 |
22 | 23 |

Jedis

24 |
25 | {{ jedi.name }} 26 | 27 |
28 | 29 | 30 | 31 |

List

32 |
33 | {{ item.name }} 34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 |
`, 42 | props: ['person', 'counter', 'name', 'jedis'], 43 | methods: { 44 | increment() { 45 | this.counter += 1 ; 46 | }, 47 | add() { 48 | this.list.push({ name: this.newJedi}); 49 | this.newItem = ""; 50 | }, 51 | addJedi() { 52 | this.jedis.push({ name: this.newJedi}); 53 | this.newJedi = ""; 54 | } 55 | }, 56 | data() { 57 | const copyJedis = this.jedis.map( j => Object.assign({},j)); 58 | const copyPerson = Object.assign({}, this.person); 59 | 60 | return { 61 | newJedi: '', 62 | newItem: '', 63 | list: [], 64 | copyJedis, 65 | copyPerson 66 | }; 67 | } 68 | }) 69 | 70 | var app = new Vue({ 71 | el: '#app', 72 | data: { 73 | message: 'Hello Vue!', 74 | person: { 75 | name: 'chris' 76 | }, 77 | counter: 0, 78 | title: 'Star Wars', 79 | jedis: [{ 80 | name: 'luke' 81 | }, 82 | { 83 | name: 'vader' 84 | }] 85 | }, 86 | methods : { 87 | // points to window this 88 | showDetail : (product) => { 89 | alert('detail ' + product.name); 90 | }, 91 | // points to window this 92 | save: (person) => { 93 | alert('called from component'); 94 | }, 95 | // points to correct this, no => 96 | changeTitle() { 97 | this.title = "new title"; 98 | } 99 | } 100 | }) 101 | -------------------------------------------------------------------------------- /components/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | {{ message }} {{person.name}} counter from parent: {{counter}} 6 | 7 |
{{title}}
8 | 9 | 10 |
11 | {{jedi.name}} 12 |
13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /routing/README.md: -------------------------------------------------------------------------------- 1 | # Routing 2 | 3 | There is a reason a SPA is called a SPA. The term itself means Single Page Application. Our app can be logically divided into many sections or pages. A router can help us to just that. What changes isn't the exact URL but something called the hashban, #. 4 | 5 | ## Set up 6 | 7 | The set up for the router is quite easy, you can either: 8 | 9 | * install it using NPM: 10 | * add a script tag that points to an unpkg link 11 | 12 | ### Install 13 | 14 | Install it via NPM 15 | 16 | ``` 17 | npm install vue-router 18 | ``` 19 | 20 | We can also just add a script tag, like so: 21 | 22 | ``` 23 | 24 | ``` 25 | 26 | ### Define routes 27 | 28 | At this point we are ready to go but we need to define the routes we need to support. What we need is a list of objects containing the `path` to match and what `component` that should handle it. A routing list can therefore look like this: 29 | 30 | ``` 31 | const routes = [ 32 | { path: '/products', component: Products }, 33 | { path: '/about', component: About }, 34 | { path: '/', component: Home } 35 | ] 36 | ``` 37 | 38 | There are two ways for us to create a component: 39 | 40 | * Vue.component 41 | * shorthand version 42 | 43 | Let's show the `Vue.component()` version first: 44 | 45 | ``` 46 | const Products = Vue.component('products', { 47 | template : ` 48 |
products
49 | ` 50 | }); 51 | ``` 52 | 53 | Above is what we are used to, but there is an even shorter version: 54 | 55 | ``` 56 | const Products = { 57 | template: ` 58 |
Products
59 | ` 60 | } 61 | ``` 62 | 63 | ### Inject router 64 | At this point we have set up our router list so we support `/products`, `/about` and `/`. Last step is to instantiate the router and inject it into our main Vue object. 65 | 66 | Instantation of the router looks like this: 67 | 68 | ``` 69 | const routes = [ 70 | { path: '/products', component: Products }, 71 | { path: '/about', component: About }, 72 | { path: '/', component: Home } 73 | ] 74 | 75 | const router = new VueRouter({ 76 | routes 77 | }) 78 | ``` 79 | 80 | Last step is injecting it to the main Vue object: 81 | 82 | ``` 83 | const app = new Vue({ 84 | router 85 | }).$mount('#app') 86 | ``` 87 | 88 | ### creating navigation routes 89 | 90 | We want to be able to actually get to the routes we set up so therefore we need to add those in the markup in `index.html`: 91 | 92 | ``` 93 |
94 |

Hello App!

95 |

96 | Products 97 | About 98 |

99 |
100 | ``` 101 | 102 | As you can see above we use the component `router-link` to create navigation links and we set its `to` attribute to the path we want to navigate to. 103 | 104 | ### route outlet 105 | The route outlet is the part of your page that should be switched out when you navigate. Remember you are still on the same page you just replace part of your page when you router from one page to the next. In Vue we use a component called `router-view` to do this, like so: 106 | 107 | ``` 108 | 109 | ``` 110 | 111 | The full code for setting up our links and route outlet therefore looks like this: 112 | 113 | ``` 114 |
115 |

Hello App!

116 |

117 | Products 118 | About 119 |

120 | 121 |
122 | ``` 123 | 124 | ## Router parameters 125 | 126 | To use router parameters you need to set up your route to support wild card matching. Let's say you start with the router `/products/` then you need to change it `/products/:id`. The `id` here is the wildcard, the value that may change depending on what product you are looking at. This route will match routes like `/products/1` or `/products/11`. When we register our route in code it should now look like the following: 127 | 128 | ``` 129 | const routes = [ 130 | { path: '/products', component: Products }, 131 | { path: '/products/:id', component: ProductDetail } 132 | ] 133 | ``` 134 | 135 | How do we access this parameter? Well, we can talk to a built in `$route`. You will find you parameter under `$router.params.id`. Let's show this in an example: 136 | 137 | ``` 138 | const ProductDetail = { 139 | template: ` 140 |
141 | Your product is: {{$route.params.id}} 142 | Product {{ product.name }} 143 |
144 | `, 145 | data() { 146 | let product = products.find(p => this.$route.params.id); 147 | if(!product) { 148 | product = { name: 'unknown product' }; 149 | } 150 | 151 | console.log(this.$route.params.id); 152 | return { 153 | a: 'a', 154 | product 155 | }; 156 | } 157 | }; 158 | ``` 159 | 160 | The example above sohw how we access the router param value like this: 161 | 162 | ``` 163 | Your product is: {{$route.params.id}} 164 | ``` 165 | 166 | But we also show another interesting thing, namely how we can access the value and use it to find the data we need and assign the found data as a property in our `data()` method: 167 | 168 | ``` 169 | data() { 170 | let product = products.find(p => this.$route.params.id); 171 | if(!product) { 172 | product = { name: 'unknown product' }; 173 | } 174 | 175 | console.log(this.$route.params.id); 176 | return { 177 | a: 'a', 178 | product 179 | }; 180 | } 181 | ``` 182 | 183 | ## Programmatic navigation 184 | So far we have been using the component `router-link` to navigate but we can also do so programmatically. To do so we use the built in `$router` and call `$router.push(newUrl)`, like so: 185 | 186 | ``` 187 | // literal string path 188 | $router.push('home') 189 | 190 | // object 191 | $router.push({ path: 'home' }) 192 | 193 | // named route 194 | $router.push({ name: 'user', params: { userId: 123 }}) 195 | ``` 196 | 197 | As can be seen above there exists different versions that we can use to perform navigation. One where we just give it a literal string `products`, one wgere give it an object `{ path: 'products' }` and the third where we give it an object with the keys `name` and `params`, `{ name: 'user', params: { userId: 123 }}`. 198 | 199 | ### Named routes 200 | 201 | The third version of routing programmatically `{ name: 'user', params: { userId: 123 }}` uses something called named routing. Named routing is us creating a `name` association to the route rather than using the `path`. You can do so when you declare the route by typing the following: 202 | 203 | ``` 204 | routes: [ 205 | { 206 | path: '/products/:id', 207 | name: 'product', 208 | component: ProductDetail 209 | } 210 | ] 211 | ``` 212 | 213 | ## Query parameters 214 | 215 | Query parameters are something we add to our url to filter our resource and is something that may be interesting to keep when we save the url as a bookmark. Example of query parameters is: 216 | 217 | ``` 218 | /products?pageSize=10&page=2 219 | ``` 220 | 221 | Here we convey that we want a part of the dataset, a page size of 10 and the results from page 2. How do we retrieve this parameter in Vue? Simple, we access the `$route.query` object. This object contains our parameters if we have any. In the example url above `/products?pageSize=10&page=2` we would access the parameters by typing: 222 | 223 | ``` 224 | $route.query.pageSize 225 | $router.query.page 226 | ``` 227 | 228 | If we want to build up our url by using programmatic navigation we can easily do so by using the `$route.push()` method and specifying an object where the `query` property is set, like so: 229 | 230 | ``` 231 | this.$route.push({ path: '/products', query: { pageSize: } }) 232 | ``` 233 | 234 | ## Named views 235 | 236 | So far we settled on having one `router-view` component, that is one place where our routed content would render, also called a viewport. It is possible for us to have several viewports. You might need this in your application as you might for example have the following visual set up of your app: 237 | 238 | ``` 239 | body 240 | footer 241 | ``` 242 | 243 | `body` this is where your page content normally is rendered 244 | 245 | `footer` this is an area that much like the `header` could look different depending on what page you are on 246 | 247 | So how would you set this up? Simple, we use the `router-view` component but we need to use so called `named views` so we can identify each viewport we mean to use. The above suggested visual page set up could therefore look like this: 248 | 249 | ``` 250 |
251 | 252 |
253 |
254 | 255 |
256 | ``` 257 | 258 | ## Passing props to components 259 | Sometimes we want our route to have some pre knowledge of what it shoud render, a common thing you want to pass it is the `title` of the page or maybe the contents of a menu `menu`. We can easily do this by adding a `props` in our route declaration, like so: 260 | 261 | ``` 262 | { path: '/', 263 | component: Home, 264 | props: { title: 'My Home' } 265 | } 266 | ``` 267 | 268 | Then we can easily let our component know about this `props` and render it out in the template, like so: 269 | 270 | ``` 271 | const Home = { 272 | template : ` 273 |

{{title}}

274 | `, 275 | props: ['title'] 276 | }; 277 | ``` 278 | 279 | ## Lazy loading 280 | 281 | TODO 282 | -------------------------------------------------------------------------------- /routing/app.js: -------------------------------------------------------------------------------- 1 | const Products = Vue.component('Products', { 2 | template : ` 3 |
4 | Products
5 | a : {{ $route.query.a }} 6 | b : {{ $route.query.b }} 7 |
8 | 9 | `, 10 | data() { 11 | console.log(this.$route.query); 12 | return { 13 | 14 | }; 15 | } 16 | }); 17 | 18 | const Home = { 19 | template : ` 20 |
Home
21 | ` 22 | }; 23 | 24 | const About = Vue.component('About', { 25 | template : ` 26 |
About
27 | ` 28 | }) 29 | 30 | const products = [{ 31 | id: "1", 32 | name: 'tomato' 33 | }, 34 | { 35 | id: "2", 36 | name: 'paprika' 37 | }]; 38 | 39 | const ProductDetail = { 40 | template: ` 41 |
42 | Your product is: {{$route.params.id}} 43 | Product {{ product.name }} 44 | 45 | 46 | 47 |
48 | `, 49 | data() { 50 | let product = products.find(p => this.$route.params.id); 51 | if(!product) { 52 | product = { name: 'unknown product' }; 53 | } 54 | 55 | console.log(this.$route.params.id); 56 | return { 57 | a: 'a', 58 | product 59 | }; 60 | }, 61 | methods: { 62 | back() { 63 | this.$router.push('/products?a=1&b=2'); 64 | }, 65 | backName() { 66 | this.$router.push({ name: 'products' }); 67 | } 68 | } 69 | }; 70 | 71 | const routes = [ 72 | { path: '/products', component: Products, name: 'products' }, 73 | { path: '/about', component: About }, 74 | { path: '/', component: Home }, 75 | { path: '/products/:id', name: 'product', component: ProductDetail } 76 | ] 77 | 78 | const router = new VueRouter({ 79 | routes // short for `routes: routes` 80 | }) 81 | 82 | const app = new Vue({ 83 | router 84 | }).$mount('#app') 85 | -------------------------------------------------------------------------------- /routing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

Hello App!

6 |

7 | 8 | 9 | 10 | Products 11 | Products detail 12 | About 13 |

14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /routing/named-views/app.js: -------------------------------------------------------------------------------- 1 | 2 | const Home = { 3 | template : ` 4 |

{{title}}

5 | `, 6 | props: ['title'] 7 | }; 8 | 9 | const Products = { 10 | template : ` 11 |

Products

12 | ` 13 | }; 14 | 15 | const Menu = { 16 | template : ` 17 |
18 |

{{title}}

19 |
20 | `, 21 | props: ['menu', 'title'] 22 | }; 23 | 24 | const Footer = { 25 | template : ` 26 |

Footer products

27 | ` 28 | }; 29 | 30 | 31 | const routes = [ 32 | { path: '/', 33 | component: Home, 34 | props: { title: 'My Home' } 35 | }, 36 | { path: '/products', 37 | components: { 38 | default: Products, 39 | footer: Footer 40 | }, 41 | }, 42 | ] 43 | 44 | const router = new VueRouter({ 45 | routes // short for `routes: routes` 46 | }) 47 | 48 | const app = new Vue({ 49 | router 50 | }).$mount('#app') 51 | -------------------------------------------------------------------------------- /routing/named-views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

Hello App!

6 | 7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /setup/README.md: -------------------------------------------------------------------------------- 1 | introducing {} 2 | 3 | `data` property is what we can show 4 | 5 | ``` 6 | var app = new Vue({ 7 | el: '#app', 8 | data: { 9 | message: 'Hello Vue!' 10 | } 11 | }) 12 | 13 | {{message}} 14 | ``` 15 | 16 | This makes `message` available to template. 17 | Add another property like so: 18 | 19 | ``` 20 | var app = new Vue({ 21 | el: '#app', 22 | data: { 23 | message: 'Hello Vue!', 24 | secondmessage : 'Hello again' 25 | } 26 | }) 27 | 28 | {{secondmessage}} 29 | ``` 30 | -------------------------------------------------------------------------------- /setup/app.js: -------------------------------------------------------------------------------- 1 | var app = new Vue({ 2 | el: '#app', 3 | data: { 4 | message: 'Hello Vue!', 5 | otherMessage: 'other message' 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /setup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | {{ message }} {{ other message }} 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /structural-directives/README.md: -------------------------------------------------------------------------------- 1 | # Structural directives 2 | 3 | ## v-if 4 | 5 | Wether to show something or not 6 | 7 | ``` 8 |
9 | Show me if property is truthy 10 |
11 | ``` 12 | 13 | ## v-for 14 | 15 | Loop out a list of items 16 | 17 | ``` 18 |
19 | {{ product.name }} 20 |
21 | ``` 22 | 23 | ## v:on 24 | 25 | This how we attach methods to events. 26 | 27 | To attach to a specific event we need to use a syntax like this: 28 | 29 | ``` 30 | v-on:eventName 31 | ``` 32 | 33 | Listening to a click would therefore look like the following: 34 | 35 | ``` 36 |
37 | ``` 38 | 39 | In the component we need to declare a `methods` property and add our `handleClicked` to it, like so: 40 | 41 | ``` 42 | var app = new Vue({ 43 | el: '#app', 44 | data: {}, 45 | methods : { 46 | handleClicked : () => { 47 | alert('something clicked me'); 48 | } 49 | } 50 | }) 51 | ``` 52 | 53 | ### passing data to code 54 | 55 | How would pass the data from the markup to the js code? Simple, invoke the method with the data, like so: 56 | 57 | ``` 58 |
59 |
60 |
61 | ``` 62 | 63 | and the result js code now needs to look like this: 64 | 65 | ``` 66 | var app = new Vue({ 67 | el: '#app', 68 | data: {}, 69 | methods : { 70 | handleClicked : (product) => { 71 | alert(`show product data ${product.name}`); 72 | } 73 | } 74 | }) 75 | ``` 76 | 77 | ## v-model 78 | 79 | This is how we change values, this creates a double binding. Use it like so: 80 | 81 | ``` 82 | {{ person.name }} 83 | 84 | ``` 85 | 86 | and in the js code we just type like so: 87 | 88 | ``` 89 | var app = new Vue({ 90 | el: '#app', 91 | data: { 92 | person : { 93 | name : 'chris' 94 | } 95 | } 96 | }) 97 | ``` 98 | -------------------------------------------------------------------------------- /structural-directives/app.js: -------------------------------------------------------------------------------- 1 | var app = new Vue({ 2 | el: '#app', 3 | data: { 4 | message: 'Hello Vue!', 5 | show: true, 6 | products: [{ 7 | id: 1, 8 | name: 'tomato' 9 | }, 10 | { 11 | id: 2, 12 | name: 'paprika' 13 | }] 14 | }, 15 | methods : { 16 | showDetail : (product) => { 17 | alert('detail ' + product.name); 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /structural-directives/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | {{ message }} 6 | 7 | Show me... 8 | 9 |
10 | {{ product.name }} 11 | 12 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /vuex-examples/actions/README.md: -------------------------------------------------------------------------------- 1 | # Vuex - Using actions 2 | 3 | Actions commit mutations rather than mutating state. 4 | 5 | In this section we will explore `actions` what they are and how to use them. 6 | 7 | In Vuex we have gotten used to using `store.commit()` to persist changes to our store and its state. There is another way to do things though and that is using actions. An actions is simply an intention that can be asynchronous where `store.commit()` is synchronous. 8 | 9 | ## Creating a simple action 10 | An action is just a method on the `actions` property in your store, like so: 11 | 12 | ``` 13 | const store = new Vuex.Store({ 14 | state: { 15 | count: 0 16 | }, 17 | actions: { 18 | increment(context) { 19 | context.commit('increment') 20 | } 21 | } 22 | }) 23 | ``` 24 | 25 | Above we have added the following method: 26 | 27 | ``` 28 | increment(context) {} 29 | ``` 30 | This method internally calls 31 | 32 | ``` 33 | context.commit('name of mutations function') 34 | ``` 35 | 36 | The above will invoke a method in the `mutatations` object. 37 | 38 | ### Adding a mutations function 39 | 40 | We need to create function that matches what we write in `context.commit('mutationsName')`, so we update our store to look like this: 41 | 42 | ``` 43 | const store = new Vuex.Store({ 44 | state: { 45 | count: 0 46 | }, 47 | mutations: { 48 | // called 2nd 49 | mutations() { 50 | this.state.count++; 51 | } 52 | }, 53 | actions: { 54 | // called 1st on dispatch() 55 | increment(context) { 56 | context.commit('increment') 57 | } 58 | } 59 | }) 60 | ``` 61 | 62 | ### Have component call the action 63 | 64 | For the component to use this action we need to use the method `dispatch('actionName')` 65 | 66 | We therefore create a component to look like the following: 67 | 68 | ``` 69 | Vue.component('product', { 70 | template: ` 71 |
72 | {{ count }} 73 | 74 |
75 | `, 76 | methods: { 77 | increment() { 78 | this.$store.dispatch('increment'); 79 | } 80 | } 81 | }) 82 | ``` 83 | 84 | ## Creating asynchronous action 85 | 86 | So to make an action pay for itself we need to use its asynchronous nature. The whole point of using an action is to have it carry out a bunch of asynchronous things so that when done we can call `commit()` on it. 87 | 88 | A synchronous scenario can look in the following way: 89 | 90 | - fetch some data 91 | - update the store with the fetched data 92 | 93 | Of course to have the best possible user experience we would probably revise the above list a bit to read: 94 | 95 | - set loading spinner to indicate we are fetching data 96 | - fetch some data 97 | - if successful, update the store with the fetched data 98 | - if error, update the store with error message 99 | - hide loading spinner 100 | 101 | We would implement the above list of actions with the below code: 102 | 103 | ``` 104 | state: { 105 | loading: false; 106 | products: [], 107 | error: '' 108 | }, 109 | mutations: { 110 | products(state, products) { 111 | this.state.products = products; 112 | }, 113 | loading(state, isLoading) { 114 | this.loading = isLoading; 115 | }, 116 | error(state, error) { 117 | this.error = error; 118 | } 119 | } 120 | actions: { 121 | loadProducts({ commit, state }) { 122 | commit('loading'); 123 | try{ 124 | const products = await api.getProducts(); 125 | commit('products', products); 126 | } catch (err) { 127 | commit('error', err); 128 | } 129 | } 130 | } 131 | ``` 132 | 133 | Not especially how the `loadProducts()` calls `commit('loading')` and then calls `api.getProducts()` to fetch the data and depending on how we fare we either call `commit('products')` or `commit('error')`. 134 | 135 | ## Calling multiple actions 136 | So far we have only dispatched one action from a component but we can dispatch several. Most likely we want to do this because we care about loading data in a specific order. 137 | 138 | ### Returning a Promise 139 | Actions helps us with this by allowing us to return a Promise from an action. This means we can carry out a `dispatch()` and we would know when it is done. This is demonstrated in the below code: 140 | 141 | ``` 142 | actions : { 143 | loadProduct({ state, commit }) { 144 | return new Promise((resolve, reject) => { 145 | setTimeout(()=> { 146 | resolve('data') 147 | }, 3000) 148 | }) 149 | } 150 | } 151 | ``` 152 | 153 | And to use that in a component 154 | 155 | Vue.component('data', { 156 | template: ``, 157 | methods: { 158 | load() { 159 | this.$store.dispatch('loadProduct').then( data => { 160 | console.log('data has arrived', data); 161 | }) 162 | } 163 | } 164 | }) 165 | 166 | We can also of course use that inside of another action, like so: 167 | 168 | ``` 169 | actions: { 170 | async action1() { 171 | commit('data', await api.getData()) 172 | }, 173 | async action2() { 174 | await dispatch('action1'); 175 | commit('moredata', await, api.getMoreData); 176 | } 177 | } 178 | ``` 179 | 180 | Above we can see that we are clearly waiting for `dispatch('action1`) to finish. Internally the method `action1()` fetches data by calling `api.getData()`. 181 | Once that is done we continue in method `action2()` and call `commit()` where to a call to `api.getMoreData()`; 182 | 183 | -------------------------------------------------------------------------------- /vuex-examples/actions/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softchris/vuejs-book/9412bf1b3386c2a7d847f050b45d59c81c7578f8/vuex-examples/actions/app.css -------------------------------------------------------------------------------- /vuex-examples/actions/app.js: -------------------------------------------------------------------------------- 1 | const api = { 2 | getData: () => { 3 | return new Promise(resolve => { 4 | setTimeout(() => { 5 | resolve('data'); 6 | }, 3000); 7 | }); 8 | }, 9 | getOtherData: () => { 10 | return new Promise(resolve => { 11 | setTimeout(() => { 12 | resolve('other data'); 13 | }, 3000); 14 | }); 15 | } 16 | } 17 | 18 | const store = new Vuex.Store({ 19 | state: { 20 | count: 0, 21 | data: void 0, 22 | otherdata: void 0, 23 | loading: false 24 | }, 25 | mutations: { 26 | increment() { 27 | this.state.count++; 28 | }, 29 | data(state, data) { 30 | this.state.data = data; 31 | }, 32 | otherdata(state, data) { 33 | this.state.otherdata = data; 34 | }, 35 | loading(state, isLoading) { 36 | this.state.loading = isLoading; 37 | } 38 | }, 39 | actions: { 40 | async loadData({ dispatch, commit }) { 41 | commit('loading', true); 42 | commit('data', await api.getData()); 43 | commit('loading', false); 44 | }, 45 | async loadOtherData({ dispatch, commit }) { 46 | await dispatch('loadData'); 47 | commit('loading', true); 48 | commit('otherdata', await api.getOtherData()); 49 | commit('loading', false); 50 | }, 51 | increment (context) { 52 | setTimeout(() => { 53 | context.commit('increment') 54 | }, 2000); 55 | } 56 | } 57 | }) 58 | 59 | Vue.component('test', { 60 | template: ` 61 |
62 |
63 | Loading... 64 |
65 |
66 | {{ count }} 67 |
68 |
69 | data: {{ data }} 70 |
71 |
72 | other data: {{ otherdata }} 73 |
74 | 75 | 76 |
77 | `, 78 | computed: { 79 | count() { 80 | return this.$store.state.count; 81 | }, 82 | data() { 83 | return this.$store.state.data; 84 | }, 85 | otherdata() { 86 | return this.$store.state.otherdata; 87 | }, 88 | loading() { 89 | return this.$store.state.loading; 90 | } 91 | }, 92 | methods: { 93 | increment() { 94 | this.$store.dispatch('increment'); 95 | }, 96 | load() { 97 | this.$store.dispatch('loadOtherData'); 98 | } 99 | } 100 | }) 101 | 102 | var app = new Vue({ 103 | el: '#app', 104 | data: { 105 | message: 'Hello Vue!' 106 | }, 107 | store 108 | }) 109 | -------------------------------------------------------------------------------- /vuex-examples/actions/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /vuex-examples/hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Vuex 2 | 3 | Vuex is about state management. 4 | 5 | To use it we essentially need to do the following: 6 | 7 | * create and configure our store 8 | * create mutators 9 | * create getters ( optional ) 10 | * read from the store 11 | * write to the store 12 | 13 | # Set up the store 14 | 15 | To set up the store we need to first install it. There are two options here: 16 | 17 | * install it via npm 18 | * add script tag with unpkg url 19 | 20 | The first is achieved by typing: 21 | 22 | ``` 23 | npm install vuex 24 | ``` 25 | 26 | The second version is achieved by typing: 27 | 28 | ``` 29 | 30 | ``` 31 | 32 | ## Creating the store 33 | 34 | We create the store by creating a new instance of `Vuex.Store`, in the process we provide it with an object. The object takes many properties like: 35 | 36 | * state, our stores state, just a simple object 37 | * mutators, a property that points to an object that contains methods that can change our state 38 | * getters, a property that points to an object that reads from our state, this is used to create sub queries based on what it is in the store. 39 | 40 | Below is the code needed to create the store: 41 | 42 | ``` 43 | const store = new Vuex.Store({ 44 | state: { 45 | count: 0, 46 | } 47 | } 48 | ``` 49 | 50 | Above we have declare the store to hold the property `state` which contains one property `count`. `count` is a property that we can now display in a UI if we want. 51 | 52 | ## Reading from the store 53 | 54 | To access the store we need to either: 55 | 56 | * access the store instance we created 57 | * inject the store in our application object 58 | 59 | ### Refer to the store instance 60 | In this case we need to have access to our store instance and pass that around. We first off create the store instance like so: 61 | 62 | ``` 63 | const store = new Vuex.Store({ 64 | state: { 65 | count: 0, 66 | } 67 | } 68 | ``` 69 | 70 | Then we need to refer to that store instance inside of component when we need to read from stores state. We read from the state by setting up a computed property and return the slice of state we need, like so: 71 | 72 | ``` 73 | Vue.component('counter', { 74 | template : ` 75 |
{{count}}
76 | `, 77 | computed: { 78 | count() { 79 | return store.state.count; 80 | } 81 | } 82 | }) 83 | ``` 84 | 85 | ### Injecting the store 86 | 87 | The first case is accomplished by doing the following: 88 | 89 | ``` 90 | const store = new Vuex.Store({ 91 | state: { 92 | count: 0, 93 | } 94 | } 95 | 96 | var app = new Vue({ 97 | el: '#app', 98 | data: { 99 | message: 'Hello Vue!' 100 | }, 101 | store 102 | }) 103 | ``` 104 | 105 | Reading from the state at this point looks very similar to when we are dealing with a store instance but there is a suttle difference, we know refer to the store on `this`. So instead of writing `store.state.someProperty`, we can write `this.$store.state.someProperty`, note the use of the `$`. Our code now becomes: 106 | 107 | ``` 108 | Vue.component('counter', { 109 | template : ` 110 |
{{count}}
111 | `, 112 | computed: { 113 | count() { 114 | return this.$store.state.count; 115 | } 116 | } 117 | }) 118 | ``` 119 | 120 | ## Writing to the store 121 | 122 | Ok, so we learned how we can read from the the store, so how can we write to the store? The answer is that we can use a method called `commit()` on the `store` object. `commit()` takes two parameters: 123 | 124 | * first parameter is a string, which is the name of mutators function on the store object 125 | * second parameter is an object, which is the payload, the data that you wish change the state with 126 | 127 | We introduced a concept called `mutators` which is a property on the store, that we set up like so: 128 | 129 | ``` 130 | const store = new Vuex.Store({ 131 | state: { 132 | count: 0, 133 | }, 134 | mutators: { 135 | increment(state) { 136 | this.state.count++; 137 | } 138 | } 139 | } 140 | ``` 141 | 142 | We can see above how we define the method `increment()` which increments the value of the property `count` in our state. 143 | 144 | ### Adding a payload 145 | 146 | So far we have shown how we can add a mutator function to our store and thereby a way to change the state of the store. The example we looked at was very simple, what if we wanted to do something more complex like adding an item to a list? That's almost as simple, we need to do the following to make that happen: 147 | 148 | * extends our state with a list property 149 | * write a mutators function to allow for us to add an item to a list 150 | 151 | Starting with the first bullet we first extends our state, like so: 152 | 153 | ``` 154 | const store = new Vuex.Store({ 155 | state: { 156 | count: 0, 157 | list : [] 158 | }, 159 | mutators: { 160 | increment(state) { 161 | this.state.count++; 162 | } 163 | } 164 | } 165 | ``` 166 | 167 | We initialise our property `list` to an empty array. Then it's time to write our mutator function like so: 168 | 169 | ``` 170 | const store = new Vuex.Store({ 171 | state: { 172 | count: 0, 173 | list : [] 174 | }, 175 | mutators: { 176 | increment(state) { 177 | this.state.count++; 178 | }, 179 | add(state, item) { 180 | this.state.list.push(item); 181 | } 182 | } 183 | } 184 | ``` 185 | 186 | ## Creating a CRUD example for a list 187 | 188 | Now it's time to put things in practice. Let's create a component `products` that supports the adding and removal of items. We start off with the component: 189 | 190 | ``` 191 | Vue.component('products', { 192 | template : ` 193 |
194 | {{ item.title }} 195 |
196 | `, 197 | computed() { 198 | items() { 199 | return this.$store.state.list; 200 | } 201 | } 202 | }) 203 | ``` 204 | 205 | ### Add an item 206 | 207 | Above we read the state from the store and we render out our list by using the `v-for` directive and creating the computed property `items`. 208 | 209 | Next up let's add some support for adding an item, so we extend our component with some markup and method for talking to our store: 210 | 211 | ``` 212 | Vue.component('products', { 213 | template : ` 214 |
215 | 216 |
217 | {{ item.title }} 218 |
219 |
220 | `, 221 | data() { 222 | return { 223 | newItem: '' 224 | } 225 | }, 226 | methods: { 227 | add() { 228 | this.$store.commit('add', { title: this.newItem }); 229 | } 230 | }, 231 | computed() { 232 | items() { 233 | return this.$store.state.list; 234 | } 235 | } 236 | }) 237 | ``` 238 | 239 | We have done the following additions: 240 | 241 | * added an input element that captures our new item 242 | * added a data property `newItem` 243 | * added the method `add()` that commited the new item to the store. We can see that the string `add` matches the method under `mutators` in our store. 244 | 245 | ### Remove an item 246 | 247 | To remove an item we need to do the following: 248 | 249 | * add a remove button per item 250 | * add a `remove()` method under methods 251 | * extend our store with another mutators method `remove()` 252 | 253 | Let's do the necessary changes to the component first: 254 | 255 | ``` 256 | Vue.component('products', { 257 | template : ` 258 |
259 | 260 |
261 | {{ item.title }} 262 |
263 |
264 | `, 265 | data() { 266 | return { 267 | newItem: '' 268 | } 269 | }, 270 | methods: { 271 | add() { 272 | this.$store.commit('add', { title: this.newItem, id: idCounter++ }); 273 | }, 274 | remove() { 275 | this.$store.commit('remove', { id }); 276 | } 277 | }, 278 | computed() { 279 | items() { 280 | return this.$store.state.list; 281 | } 282 | } 283 | }) 284 | ``` 285 | 286 | Now let's turn to our store and update that: 287 | 288 | ``` 289 | const store = new Vuex.Store({ 290 | state: { 291 | count: 0, 292 | list : [] 293 | }, 294 | mutators: { 295 | increment(state) { 296 | this.state.count++; 297 | }, 298 | add(state, item) { 299 | this.state.list.push(item); 300 | }, 301 | remove(state, item) { 302 | this.state.list = this.state.list.filter( i=> i.id !== item.id); 303 | } 304 | } 305 | } 306 | ``` 307 | 308 | ### Edit an item 309 | 310 | Now this is slightly more complicated but still fairly easy. We need the following: 311 | 312 | * A component in which we can edit our item 313 | * a state property in our store that will hold the current item we are editing 314 | * a mutators function that edits the correct item when we decide to save our changes 315 | 316 | Let's start by building our edit component 317 | 318 | ``` 319 | Vue.component('product-edit', { 320 | template: ` 321 |
322 | 323 | 324 |
325 | `, 326 | methods : { 327 | save() { 328 | this.$store.commit('updateProduct', this.item); 329 | } 330 | }, 331 | data() { 332 | return { 333 | localProduct: Object.assign({}, this.$store.state.selectedProduct) 334 | }; 335 | }, 336 | props: ['product'] 337 | }) 338 | ``` 339 | 340 | Ok, so let's now support this by altering `products` component. What we need to add is the ability to select a product from our list of products. The component should therefore now look like this: 341 | 342 | ``` 343 | Vue.component('products', { 344 | template : ` 345 |
346 | 347 |
348 | {{ item.title }} 349 | 350 | 351 |
352 |
353 | `, 354 | data() { 355 | return { 356 | newItem: '' 357 | } 358 | }, 359 | methods: { 360 | add() { 361 | this.$store.commit('add', { title: this.newItem, id: idCounter++ }); 362 | }, 363 | remove() { 364 | this.$store.commit('remove', { id }); 365 | }, 366 | select(id) { 367 | this.$store.commit('selectProduct', { id }); 368 | } 369 | }, 370 | computed() { 371 | items() { 372 | return this.$store.state.list; 373 | } 374 | } 375 | }) 376 | ``` 377 | 378 | What we did above was adding a new method `select()` to the `methods` property, like so: 379 | 380 | ``` 381 | select(id) { 382 | this.$store.commit('selectProduct', { id }); 383 | } 384 | ``` 385 | 386 | Now we need to update our store so we support the `selectProduct()` method, like so: 387 | 388 | ``` 389 | const store = new Vuex.Store({ 390 | state: { 391 | count: 0, 392 | list : [], 393 | selectedProduct 394 | }, 395 | mutators: { 396 | increment(state) { 397 | this.state.count++; 398 | }, 399 | add(state, item) { 400 | this.state.list.push(item); 401 | }, 402 | remove(state, item) { 403 | this.state.list = this.state.list.filter( i=> i.id !== item.id); 404 | }, 405 | selectProduct(state, item) { 406 | this.state.selectedProduct = this.state.list.find( i => i.id === item.id ) 407 | } 408 | } 409 | } 410 | ``` 411 | 412 | At this point you notice everything works well and fine when you select a product in the list, once. As soon as you select another item in the list your `product-detail` component is NOT updated. Why is that? Well we don't listen to prop changes in our `product-edit` component. Let's do so by introducing the `watch` property. The `watch` property will allow us to listen to prop changes and reset our `localProduct` to our new selection. Let's add that to the component: 413 | 414 | ``` 415 | Vue.component('product-edit', { 416 | template: ` 417 |
418 | 419 | 420 |
421 | `, 422 | methods : { 423 | save() { 424 | this.$store.commit('updateProduct', this.item); 425 | } 426 | }, 427 | data() { 428 | return { 429 | localProduct: Object.assign({}, this.$store.state.selectedProduct) 430 | }; 431 | }, 432 | watch: { 433 | product(newVal, oldVal) { 434 | this.localProduct = Object.assign({}, newVal); 435 | } 436 | } 437 | props: ['product'] 438 | }) 439 | ``` 440 | 441 | Now everything should be working fine. 442 | -------------------------------------------------------------------------------- /vuex-examples/hello-world/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 18px; 3 | font-family: Arial, Helvetica, sans-serif; 4 | } 5 | 6 | .selected { 7 | background: yellowgreen; 8 | } 9 | 10 | .product { 11 | padding: 20px; 12 | margin: 10px; 13 | box-shadow: 0 0 5px grey; 14 | border: solid 1px #fefefe; 15 | font-size: 18px; 16 | } 17 | 18 | input, 19 | button { 20 | font-size: 18px; 21 | } 22 | -------------------------------------------------------------------------------- /vuex-examples/hello-world/app.js: -------------------------------------------------------------------------------- 1 | 2 | let productsCounter = 1; 3 | 4 | const store = new Vuex.Store({ 5 | state: { 6 | count: 0, 7 | products: [], 8 | selectedProduct: void 0 9 | }, 10 | mutations: { 11 | increment (state) { 12 | state.count++ 13 | }, 14 | productsAdd(state, product) { 15 | state.products.push(product); 16 | }, 17 | productsRemove(state, product) { 18 | state.products = state.products.filter( p => p.id !== product.id ); 19 | }, 20 | productSelect(state, product) { 21 | state.selectedProduct = state.products.find( p => p.id === product.id); 22 | }, 23 | productsEdit(state, product) { 24 | // how do we mutate an object in the list?? find index and rassign whats there 25 | for(var i=0; i< state.products.length; i++) { 26 | if(state.products[i].id === product.id) { 27 | state.products[i].title = product.title; 28 | } 29 | } 30 | } 31 | } 32 | }) 33 | 34 | Vue.component('product-detail', { 35 | template: ` 36 |
37 |

Product detail

38 | 39 | 40 |
41 | `, 42 | methods: { 43 | save() { 44 | console.log('this', this); 45 | store.commit('productsEdit', this.p); 46 | } 47 | }, 48 | data() { 49 | return { 50 | p: Object.assign({}, this.product) 51 | } 52 | }, 53 | props: ['product'], 54 | watch : { 55 | product(newVal, oldVal) { 56 | console.log('new val',newVal); 57 | console.log('old val',oldVal); 58 | this.p = Object.assign({}, newVal); 59 | } 60 | } 61 | }) 62 | 63 | Vue.component('products', { 64 | template: ` 65 |
66 |

Products

67 | 68 | 69 |
70 | {{product.id}} {{ product.title }} 71 | 72 | 73 |
74 |
75 | `, 76 | computed: { 77 | products() { 78 | return store.state.products; 79 | }, 80 | selectedProduct() { 81 | return store.state.selectedProduct ? store.state.selectedProduct.id: 0; 82 | } 83 | }, 84 | methods: { 85 | add() { 86 | store.commit('productsAdd',{ title: this.newProduct, id: productsCounter++ }); 87 | this.newProduct = ''; 88 | }, 89 | remove(id) { 90 | store.commit('productsRemove', { id }); 91 | }, 92 | select(id) { 93 | store.commit('productSelect', { id }); 94 | } 95 | }, 96 | data() { 97 | return { 98 | newProduct: '' 99 | } 100 | } 101 | }) 102 | 103 | Vue.component('counter',{ 104 | template: ` 105 |
106 |

Counter

107 |
108 | Counter value from store: {{ count }} 109 | 110 |
111 |
112 | Title: {{title}} 113 |
114 | 115 |
116 | `, 117 | props: ['title'], 118 | computed: { 119 | count() { 120 | return store.state.count; 121 | } 122 | }, 123 | methods: { 124 | increment() { 125 | store.commit('increment'); 126 | } 127 | } 128 | }) 129 | 130 | var app = new Vue({ 131 | el: '#app', 132 | data: { 133 | message: 'Hello Vue!' 134 | }, 135 | computed: { 136 | isSelected() { 137 | return store.state.selectedProduct; 138 | }, 139 | selectedProduct() { 140 | return store.state.selectedProduct; 141 | } 142 | }, 143 | store 144 | }) 145 | -------------------------------------------------------------------------------- /vuex-examples/hello-world/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | {{ message }} 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /vuex-examples/modules/README.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | Modules is a way to split up your store state in different parts. Why do we want to this? Well declaring everything in one store might clutter visibility. You may also want to load in partial states as you lazy load routes and components. 3 | 4 | ## Set up 5 | 6 | Getting started with modules is pretty straight forward. What you need is to add the `modules` property to your store defintion, like so: 7 | 8 | ``` 9 | const store = new Vuex.Store({ 10 | state: { 11 | count: 0 12 | }, 13 | modules : { 14 | moduleA, 15 | moduleB 16 | } 17 | }) 18 | ``` 19 | 20 | `modules` is an object and contains properties. Each property represents a module and its definition. Each definition looks like a normal store object, like so: 21 | 22 | ``` 23 | const moduleB = { 24 | state: { 25 | b: 'bbbb' 26 | }, 27 | mutations: {...}, 28 | actions: {...}, 29 | getters: { ... } 30 | } 31 | ``` 32 | 33 | ## Accessing module values 34 | Accessing a value in a module can be done by referring to the `state` and drill down to the module name and eventually the property. Let's have a look at how in the below app component: 35 | 36 | ``` 37 | var app = new Vue({ 38 | el: '#app', 39 | data: { 40 | message: 'Hello Vue!', 41 | input: '' 42 | }, 43 | computed: { 44 | a() { 45 | return this.$store.state.moduleA.a; 46 | }, 47 | b() { 48 | return this.$store.state.moduleB.b; 49 | } 50 | }, 51 | store 52 | }) 53 | ``` 54 | 55 | Note especially above how we register the computed properties `a` and `b` and how we return the state property for each: 56 | 57 | ``` 58 | a() { 59 | return this.$store.state.moduleA.a; 60 | } 61 | ``` 62 | Here we are drilling into our `$store.state` and we find our property by referring to our module name so the full way we access a state in another module is by typing `$store.state.moduleName.statePropertyName`, which in this case is `$store.state.moduleA.a`. Ok, so we learned to access state in module, next up let's look at how to change a value. 63 | 64 | 65 | ## Change value in a module store 66 | We have learned before how we can change the state in the store by using either `commit()` or by calling `dispatch()` and thereby use an action to change the state. Let's register some `mutations` functions on each store module, like so: 67 | 68 | ``` 69 | const moduleA = { 70 | namespaced: true, 71 | state: { 72 | a: 'aaaa' 73 | }, 74 | mutations: { 75 | change(state, val) { 76 | this.state.moduleA.a = val; 77 | } 78 | } 79 | } 80 | ``` 81 | 82 | and for `moduleB` we do: 83 | 84 | ``` 85 | const moduleB = { 86 | state: { 87 | b: 'bbbb' 88 | }, 89 | mutations: { 90 | change(state, val) { 91 | this.state.moduleB.b = val; 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | NOTE, we can access the state in a bit better way, we don't have to use the fully qualified name `this.state.moduleB.b` we can instead use the input parameter `state`, which is the state bound to our module. If we use that instead we can type `state.b` which looks way better. 98 | 99 | ### Calling commit() from component 100 | 101 | Ok, we are all set up and ready for our component to call `commit()`. Let's add that bit to our app component, like so: 102 | 103 | ``` 104 | var app = new Vue({ 105 | el: '#app', 106 | data: { 107 | message: 'Hello Vue!', 108 | input: '' 109 | }, 110 | methods : { 111 | save() { 112 | this.$store.commit('change', this.input); 113 | } 114 | }, 115 | computed: { 116 | a() { 117 | return this.$store.state.moduleA.a; 118 | }, 119 | b() { 120 | return this.$store.state.moduleB.b; 121 | } 122 | }, 123 | store 124 | }) 125 | ``` 126 | 127 | Invoking our method `save()` will trigger a call to `this.$store.commit('change', this.input)`. What will happen, what mutation function will react to this? The answer is all of them will react, both the one in `moduleA` as well as the one in `moduleB`. This may or may not be what we intended. If we me mean for only one of them to be called we will either need to make sure the mutation method name is unique or use something called namespaced modules. Let's look at namespaced modules next. 128 | 129 | ## Namespaced modules 130 | So far everything has been accessible in the global context which is the reason why when we do a `commit()` both `change()` functions were called. We can isolate ourselves a bit by using namespaced modules. We just need to add the property `namespaced` to our module definition and give it the value `true`, like so: 131 | 132 | ``` 133 | const moduleB = { 134 | namespaced: true 135 | state: { 136 | b: 'bbbb' 137 | }, 138 | mutations: { 139 | change(state, val) { 140 | this.state.moduleB.b = val; 141 | } 142 | } 143 | } 144 | ``` 145 | 146 | Trying invoke our `save()` method again will not call the `change()` method for this module anymore, we have isolated ourselves. Any module missing the `namespaced` property will still be part of the global context. So how do we call the `change()` method on this module? We can still call it if we specify its fully qualified name namely `commit('moduleB/change')`. The rule here is `moduleName/mutationFunctionName` as the full name. 147 | 148 | This will also affect other things like getters. Let'start off my adding a getter to `moduleB`, like so: 149 | 150 | ``` 151 | const moduleB = { 152 | namespaced: true 153 | state: { 154 | b: 'bbbb' 155 | }, 156 | getter: { 157 | b(state) { 158 | return state.b; 159 | } 160 | }, 161 | mutations: { 162 | change(state, val) { 163 | this.state.moduleB.b = val; 164 | } 165 | } 166 | } 167 | ``` 168 | 169 | We follow this up by creating a new computed property `bGet()` where we make a call to our getter: 170 | 171 | ``` 172 | var app = new Vue({ 173 | el: '#app', 174 | data: { 175 | message: 'Hello Vue!', 176 | input: '' 177 | }, 178 | methods : { 179 | save() { 180 | this.$store.commit('change', this.input); 181 | } 182 | }, 183 | computed: { 184 | a() { 185 | return this.$store.state.moduleA.a; 186 | }, 187 | b() { 188 | return this.$store.state.moduleB.b; 189 | }, 190 | bGet() { 191 | return this.$store.getter['moduleB/b'] 192 | } 193 | }, 194 | store 195 | }) 196 | ``` 197 | 198 | NOTE, how we call our getter with its fully qualified name `moduleB/b`, like so: 199 | 200 | ``` 201 | bGet() { 202 | return this.$store.getter['moduleB/b'] 203 | } 204 | ``` 205 | 206 | ## Dynamic registration 207 | 208 | What does it mean to dynamically register a module? It means that we are able to add said module after the store has been created. This is often the case when dealing with plugins or if you want to add store state on a lazy load for example. 209 | 210 | You add the module by calling `store.registerModule('moduleName', { 211 | ... module definition object 212 | })` 213 | 214 | There might also be situations where you want to deregister your module, you do that by calling `store.unregisterModule('moduleName')` 215 | -------------------------------------------------------------------------------- /vuex-examples/modules/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softchris/vuejs-book/9412bf1b3386c2a7d847f050b45d59c81c7578f8/vuex-examples/modules/app.css -------------------------------------------------------------------------------- /vuex-examples/modules/app.js: -------------------------------------------------------------------------------- 1 | const moduleA = { 2 | namespaced: true, 3 | state: { 4 | a: 'aaaa' 5 | }, 6 | getters: { 7 | a(state) { 8 | return state.a; 9 | } 10 | }, 11 | mutations: { 12 | change(state, val) { 13 | this.state.moduleA.a = val; 14 | } 15 | } 16 | } 17 | 18 | const moduleB = { 19 | state: { 20 | b: 'bbbb' 21 | }, 22 | mutations: { 23 | change(state, val) { 24 | console.log('local state', state.b); 25 | console.log('global state', this.state.moduleB.b); 26 | this.state.moduleB.b = val; 27 | } 28 | } 29 | } 30 | 31 | const store = new Vuex.Store({ 32 | state: { 33 | count: 0 34 | }, 35 | modules : { 36 | moduleA, 37 | moduleB 38 | } 39 | }) 40 | 41 | var app = new Vue({ 42 | el: '#app', 43 | data: { 44 | message: 'Hello Vue!', 45 | input: '' 46 | }, 47 | methods : { 48 | save() { 49 | this.$store.commit('moduleA/change', this.input); 50 | this.$store.commit('change', this.input); 51 | } 52 | }, 53 | computed: { 54 | a() { 55 | return this.$store.state.moduleA.a; 56 | }, 57 | b() { 58 | return this.$store.state.moduleB.b; 59 | }, 60 | aget() { 61 | return this.$store.getters['moduleA/a']; 62 | } 63 | }, 64 | store 65 | }) 66 | -------------------------------------------------------------------------------- /vuex-examples/modules/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | {{ message }} 5 |
6 | A through getter: {{ aget }} 7 |
8 | 9 |
10 | value of a: {{ a }} 11 |
12 |
13 | value of b: {{ b }} 14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------