Added Base form elements. Was kind of light on the notes though

master
androiddrew 6 years ago
parent 19a7dd3f31
commit 6a58aa50b3

@ -2,6 +2,8 @@
## Course Take aways:
- You missed it in the vuex course but they started using `import Datepicker from 'vuejs-datepicker'` in the EventCreate component.
- Installation of [NProgress](https://github.com/rstacruz/nprogress) to create a better experience for a user when they are interacting with our application.
- Axios Interceptors
- Not optimal for multiple API calls
@ -37,6 +39,12 @@ beforeRouteLeave(routeTo, routeFrom, next) {
}
```
### Base Form elements
- Look up `vue-multiselect` for when you want to use a select field and the options have objects in them.
- `v-on="$listeners"` means that this element inherits the listners of the parent component
- `v-bind="$attrs"` won't work for inheriting class and style attributes
### Global Route Gaurds & Per Route Gaurds
Global Route Gaurds allow us to add hooks that run whenever nagigation is triggered. These gaurds are called on the `router` object. We need to set the `router` object to a contant.
@ -87,6 +95,10 @@ const router = new Router({
Since all the API action calls are occuring in the router lifecycle hooks at this time. You can actually remove all vuex code from the component. This could make your code simpler and easier to maintain. You will need to ensure that the actions in your vuex store are returning the objects you are working with however to ensure this works.
_NOTE:_ Since `beforeEnter` is only called when the component is created it's use with a component that does paginatation will not work. The `fetchEvent` wouldn't be called.
_NOTE2:_ Vue Router may get another per-route guard called beforeResolve which called on Create and Update. If it is available it would be a more elegant solution to our pagination problem.
## Following along?
We encourage you to follow the course on Vue Mastery, and code along with us. This course has tags representing the start and finish of each level, just in case you get stuck. Here's the start and ending code of each lesson, if you'd like to download them.

@ -267,6 +267,24 @@
"date": "",
"time": "",
"attendees": []
},
{
"id": 7184295,
"user": {
"id": "abc123",
"name": "Adam"
},
"category": "nature",
"organizer": {
"id": "abc123",
"name": "Adam"
},
"title": "Here we go again",
"description": "",
"location": "",
"date": "",
"time": "",
"attendees": []
}
]
}

@ -0,0 +1,86 @@
<template>
<div>
<button v-on="$listeners" v-bind="$attrs" class="button" :class="buttonClass">
<slot/>
</button>
</div>
</template>
<script>
// v-on="$listeners" means that this element inherits the listners of the parent component
export default {
inheritAttrs: false, // You still want this because it could but used for passing an attribute like disabled to your button
props: {
buttonClass: {
type: String
}
}
}
</script>
<style scoped>
.button {
display: inline-flex;
align-items: center;
justify-content: space-between;
height: 52px;
padding: 0 40px;
background: transparent;
border: none;
border-radius: 6px;
text-align: center;
font-weight: 600;
white-space: nowrap;
transition: all 0.2s linear;
}
.button:hover {
-webkit-transform: scale(1.02);
transform: scale(1.02);
box-shadow: 0 7px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.button:active {
-webkit-transform: scale(1);
transform: scale(1);
box-shadow: none;
}
.button:focus {
outline: 0;
}
.button.-fill-gradient {
background: linear-gradient(to right, #16c0b0, #84cf6a);
color: #ffffff;
}
.button:disabled {
-webkit-transform: scale(1);
transform: scale(1);
box-shadow: none;
background: #eeeeee;
}
.button + .button {
margin-left: 1em;
}
.button.-fill-gray {
background: rgba(0, 0, 0, 0.5);
color: #ffffff;
}
.button.-size-small {
height: 32px;
}
.button.-icon-right {
text-align: left;
padding: 0 20px;
}
.button.-icon-right > .icon {
margin-left: 10px;
}
.button.-icon-left {
text-align: right;
padding: 0 20px;
}
.button.-icon-left > .icon {
margin-right: 10px;
}
.button.-icon-center {
padding: 0 20px;
}
</style>

@ -0,0 +1,27 @@
<template>
<div>
<label v-if="label">{{ label }}</label>
<input :value="value" @input="updateValue" v-bind="$attrs">
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
label: {
type: String,
default: ''
},
value: [String, Number]
},
methods: {
updateValue() {
this.$emit('input', event.target.value)
}
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,33 @@
<template>
<div>
<label v-if="label">{{ label }}</label>
<select :value="value" @input="updateValue" v-bind="$attrs">
<option v-for="option in options" :key="option" :selected="option === value">{{ option }}</option>
</select>
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
options: {
type: Array,
required: true
},
label: {
type: String,
default: ''
},
value: [String, Number]
},
methods: {
updateValue() {
this.$emit('input', event.target.value)
}
}
}
</script>
<style scoped>
</style>

@ -5,6 +5,8 @@ import EventList from './views/EventList.vue'
import EventShow from './views/EventShow.vue'
import NProgress from 'nprogress'
import store from '@/store/store'
import NotFound from './views/NotFound.vue'
import NetworkIssue from './views/NetworkIssue.vue'
Vue.use(Router)
@ -15,8 +17,8 @@ const router = new Router({
{
path: '/',
name: 'event-list',
component: EventList
// This path needs to call dispatch using beforeEnter like the event-show
component: EventList,
props: true
},
{
path: '/event/create',
@ -29,11 +31,35 @@ const router = new Router({
component: EventShow,
props: true,
beforeEnter(routeTo, routeFrom, next) {
store.dispatch('event/fetchEvent', routeTo.params.id).then(event => {
routeTo.params.event = event
next()
})
store
.dispatch('event/fetchEvent', routeTo.params.id)
.then(event => {
routeTo.params.event = event
next()
})
.catch(error => {
if (error.response && error.response.status == 404) {
next({ name: '404', params: { resource: 'event' } })
} else {
next({ name: 'network-issue' })
}
})
}
},
{
path: '/404',
name: '404',
component: NotFound,
props: true
},
{
path: '/network-issue',
name: 'network-issue',
component: NetworkIssue
},
{
path: '*',
redirect: { name: '404', params: { resource: 'page' } }
}
]
})

@ -7,7 +7,8 @@ const apiClient = axios.create({
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
},
timeout: 10000 // ten seconds
})
// Interceptor definition. This is like middleware

@ -5,7 +5,8 @@ export const namespaced = true
export const state = {
events: [],
eventsTotal: 0,
event: {}
event: {},
perPage: 3
}
export const mutations = {
@ -43,8 +44,9 @@ export const actions = {
throw error
})
},
fetchEvents({ commit, dispatch }, { perPage, page }) {
EventService.getEvents(perPage, page)
// perPage is removed from the payload because it is now part of the global state storage
fetchEvents({ commit, dispatch, state }, { page }) {
return EventService.getEvents(state.perPage, page) // returning the promise
.then(response => {
commit('SET_EVENTS_TOTAL', parseInt(response.headers['x-total-count']))
commit('SET_EVENTS', response.data)
@ -57,7 +59,8 @@ export const actions = {
dispatch('notification/add', notification, { root: true })
})
},
fetchEvent({ commit, getters, dispatch }, id) {
fetchEvent({ commit, getters }, id) {
// dispatch was removed as a param because we cut out the catch error block below.
var event = getters.getEventById(id)
if (event) {
@ -69,13 +72,14 @@ export const actions = {
commit('SET_EVENT', response.data)
return response.data
})
.catch(error => {
const notification = {
type: 'error',
message: 'There was a problem fetching event: ' + error.message
}
dispatch('notification/add', notification, { root: true })
})
// This was removed so that we could 404 any events that didn't exist. We want to catch the error in the beforeEnter hook instead
// .catch(error => {
// const notification = {
// type: 'error',
// message: 'There was a problem fetching event: ' + error.message
// }
// dispatch('notification/add', notification, { root: true })
// })
}
}
}

@ -2,27 +2,27 @@
<div>
<h1>Create an Event</h1>
<form @submit.prevent="createEvent">
<label>Select a category</label>
<select v-model="event.category">
<option v-for="cat in categories" :key="cat">{{ cat }}</option>
</select>
<BaseSelect label="Select a category" :options="categories" v-model="event.category"/>
<h3>Name & describe your event</h3>
<div class="field">
<label>Title</label>
<input v-model="event.title" type="text" placeholder="Add an event title"/>
</div>
<BaseInput label="Title" v-model="event.title" type="text" placeholder="Title" class="field"/>
<div class="field">
<label>Description</label>
<input v-model="event.description" type="text" placeholder="Add a description"/>
</div>
<BaseInput
label="Description"
v-model="event.description"
type="text"
placeholder="Add a description"
class="field"
/>
<h3>Where is your event?</h3>
<div class="field">
<label>Location</label>
<input v-model="event.location" type="text" placeholder="Add a location"/>
</div>
<BaseInput
label="Location"
v-model="event.location"
type="text"
placeholder="Add a location"
class="field"
/>
<h3>When is your event?</h3>
@ -31,14 +31,10 @@
<datepicker v-model="event.date" placeholder="Select a date"/>
</div>
<div class="field">
<label>Select a time</label>
<select v-model="event.time">
<option v-for="time in times" :key="time">{{ time }}</option>
</select>
</div>
<BaseSelect label="Select a time" :options="times" v-model="event.time" class="field"/>
<input type="submit" class="button -fill-gradient" value="Submit"/>
<!-- <input type="submit" class="button -fill-gradient" value="Submit"> -->
<BaseButton type="submit" buttonClass="-fill-gradient">Submit</BaseButton>
</form>
</div>
</template>
@ -46,6 +42,7 @@
<script>
import Datepicker from 'vuejs-datepicker'
import NProgress from 'nprogress'
export default {
components: {
@ -64,6 +61,7 @@ export default {
},
methods: {
createEvent() {
NProgress.start()
this.$store
.dispatch('event/createEvent', this.event)
.then(() => {
@ -73,7 +71,9 @@ export default {
})
this.event = this.createFreshEventObject()
})
.catch(() => {})
.catch(() => {
NProgress.done()
})
},
createFreshEventObject() {
const user = this.$store.state.user.user

@ -3,38 +3,64 @@
<h1>Events for {{ user.user.name }}</h1>
<EventCard v-for="event in event.events" :key="event.id" :event="event"/>
<template v-if="page != 1">
<router-link :to="{ name: 'event-list', query: { page: page - 1 } }" rel="prev">
Prev Page</router-link>
<template v-if="hasNextPage"> | </template>
<router-link :to="{ name: 'event-list', query: { page: page - 1 } }" rel="prev">Prev Page</router-link>
<template v-if="hasNextPage">|</template>
</template>
<router-link v-if="hasNextPage" :to="{ name: 'event-list', query: { page: page + 1 } }" rel="next">
Next Page</router-link>
<router-link
v-if="hasNextPage"
:to="{ name: 'event-list', query: { page: page + 1 } }"
rel="next"
>Next Page</router-link>
</div>
</template>
<script>
import EventCard from '@/components/EventCard.vue'
import { mapState } from 'vuex'
import store from '@/store/store'
function getPageEvents(routeTo, next) {
const currentPage = parseInt(routeTo.query.page) || 1
store.dispatch('event/fetchEvents', { page: currentPage }).then(() => {
routeTo.params.page = currentPage
next()
})
}
export default {
props: {
page: {
type: Number,
required: true
}
},
components: {
EventCard
},
created() {
this.perPage = 3 // Setting perPage here and not in data means it won't be reactive.
// We don't need it to be reactive, and this way our component has access to it.
this.$store.dispatch('event/fetchEvents', {
perPage: this.perPage,
page: this.page
})
beforeRouteEnter(routeTo, routeFrom, next) {
getPageEvents(routeTo, next)
},
beforeRouteUpdate(routeTo, routeFrom, next) {
getPageEvents(routeTo, next)
},
// created() {
// // This was moved to into the State store so that we had access to it in our route gaurd where we moved the vuex store action call.
// this.perPage = 3 // Setting perPage here and not in data means it won't be reactive.
// // We don't need it to be reactive, and this way our component has access to it.
// // Moved into a route gaurd
// this.$store.dispatch('event/fetchEvents', {
// perPage: this.perPage,
// page: this.page
// })
// },
computed: {
page() {
return parseInt(this.$route.query.page) || 1
},
// removed when we went to a route gaurd pattern
// page() {
// return parseInt(this.$route.query.page) || 1
// },
hasNextPage() {
return this.event.eventsTotal > this.page * this.perPage
return this.event.eventsTotal > this.page * this.event.perPage
},
...mapState(['event', 'user'])
}

@ -0,0 +1,14 @@
<template>
<div>
<h1>Uh oh</h1>
<h3>It looks like you could be experiencing some network issues. Please click the back button and try again.</h3>
<router-link :to="{name: 'event-list'}">Back to the home page</router-link>
</div>
</template>
<script>
export default {}
</script>
<style scoped>
</style>

@ -0,0 +1,21 @@
<template>
<div>
<h1>Ooops</h1>
<h3>The {{ resource }} you are looking for is not here.</h3>
<router-link :to="{name: 'event-list'}">Back to the home page</router-link>
</div>
</template>
<script>
export default {
props: {
resource: {
type: String,
required: true
}
}
}
</script>
<style scoped>
</style>
Loading…
Cancel
Save