Código fuente
Código fuente
En el directorio src
tenemos el código:
import Vue from 'vue' import './plugins/vuetify' import App from './App.vue' import router from './router' import store from './store' import firebase from 'firebase' firebase.initializeApp({ apiKey: "AIzaSyBZXlv0CUZBdPWvyzzi2eaBlXT764hoIDM", authDomain: "prueba-28ec5.firebaseapp.com", databaseURL: "https://prueba-28ec5.firebaseio.com", projectId: "prueba-28ec5" }) /* eslint-disable */ Vue.config.productionTip = false const unsubscribe = firebase.auth() .onAuthStateChanged((firebaseUser) => { new Vue({ //el: '#app', router, store, render: h => h(App), created () { if (firebaseUser) { store.dispatch('autoSignIn', firebaseUser) } } }).$mount('#app') unsubscribe() })
import Vue from 'vue' import Vuex from 'vuex' import firebase from 'firebase' import router from '@/router' Vue.use(Vuex) // const store = export default new Vuex.Store({ state: { appTitle: 'My Awesome App', user: null, error: null, loading: false }, mutations: { setUser (state, payload) { state.user = payload }, setError (state, payload) { state.error = payload }, setLoading (state, payload) { state.loading = payload } }, actions: { userSignUp ({commit}, payload) { commit('setLoading', true) firebase.auth().createUserWithEmailAndPassword(payload.email, payload.password) .then(firebaseUser => { commit('setUser', {email: firebaseUser.user.email}) commit('setLoading', false) router.push('/home') }) .catch(error => { commit('setError', error.message) commit('setLoading', false) }) }, userSignIn ({commit}, payload) { commit('setLoading', true) firebase.auth().signInWithEmailAndPassword(payload.email, payload.password) .then(firebaseUser => { commit('setUser', {email: firebaseUser.user.email}) commit('setLoading', false) commit('setError', null) router.push('/home') }) .catch(error => { commit('setError', error.message) commit('setLoading', false) }) }, autoSignIn ({commit}, payload) { commit('setUser', {email: payload.email}) }, userSignOut ({commit}) { firebase.auth().signOut() commit('setUser', null) router.push('/') } }, getters: { isAuthenticated (state) { return state.user !== null && state.user !== undefined } } })
import Vue from 'vue' import Router from 'vue-router' import firebase from 'firebase' const routerOptions = [ { path: '/', component: 'Landing' }, { path: '/signin', component: 'Signin' }, { path: '/signup', component: 'Signup' }, { path: '/home', component: 'Home', meta: { requiresAuth: true } }, { path: '*', component: 'NotFound' } ] const routes = routerOptions.map(route => { return { ...route, component: () => import(`@/components/${route.component}.vue`) } }) Vue.use(Router) const router = new Router({ mode: 'history', routes }) router.beforeEach((to, from, next) => { const requiresAuth = to.matched.some(record => record.meta.requiresAuth) const isAuthenticated = firebase.auth().currentUser if (requiresAuth && !isAuthenticated) { next('/signin') } else { next() } }) export default router
Los ficheros son:
main.js
: es el punto de entrada a la aplicación. Llama a la aplicaciónApp.vue
, al store vuex, el router, inicializa firebase y vue.router.js
: indica las vistas y las rutas adecuadas.store.js
: contiene el store basado en Vuex. Pero también importa Firebase para poder hacer la autentificación y el router para poder ir a la vista deseada.
Vistas
La vista principal es:
<template> <v-app> <v-navigation-drawer v-model="sidebar" app> <v-list> <v-list-tile v-for="item in menuItems" :key="item.title" :to="item.path"> <v-list-tile-action> <v-icon>{{ item.icon }}</v-icon> </v-list-tile-action> <v-list-tile-content>{{ item.title }}</v-list-tile-content> </v-list-tile> <v-list-tile @click="userSignOut" v-if="isAuthenticated"> <v-list-tile-action> <v-icon>exit_to_app</v-icon> </v-list-tile-action> <v-list-tile-content>Sign Out</v-list-tile-content> </v-list-tile> </v-list> </v-navigation-drawer> <v-toolbar app> <span class="hidden-sm-and-up"> <v-toolbar-side-icon @click="sidebar = !sidebar"> </v-toolbar-side-icon> </span> <v-toolbar-title> <router-link to="/" tag="span" style="cursor: pointer"> {{ appTitle }} </router-link> </v-toolbar-title> <v-spacer></v-spacer> <v-toolbar-items class="hidden-xs-only"> <v-btn flat v-for="item in menuItems" :key="item.title" :to="item.path"> <v-icon left dark>{{ item.icon }}</v-icon> {{ item.title }} </v-btn> <v-btn flat @click="userSignOut" v-if="isAuthenticated"> <v-icon left>exit_to_app</v-icon> Sign Out </v-btn> </v-toolbar-items> </v-toolbar> <v-content> <router-view></router-view> </v-content> </v-app> </template> <script> export default { data () { return { // appTitle: 'Awesome App', sidebar: false // menuItems: [ // { title: 'Home', path: '/home', icon: 'home' }, // { title: 'Sign Up', path: '/signup', icon: 'face' }, // { title: 'Sign In', path: '/signin', icon: 'lock_open' } // ] } }, computed: { appTitle () { return this.$store.state.appTitle }, isAuthenticated () { return this.$store.getters.isAuthenticated }, menuItems () { if (this.isAuthenticated) { return [ { title: 'Home', path: '/home', icon: 'home' } ] } else { return [ { title: 'Sign Up', path: '/signup', icon: 'face' }, { title: 'Sign In', path: '/signin', icon: 'lock_open' } ] } } }, methods: { userSignOut () { this.$store.dispatch('userSignOut') } } } </script>
Y en ./src/components/
:
<template> <v-container fluid> <v-layout column> <v-flex xs12 class="text-xs-center" mt-5> <h1>Landing page</h1> </v-flex> </v-layout> </v-container> </template> <script> export default {} </script>
<template> <v-container fluid> <v-layout row wrap> <v-flex xs12 class="text-xs-center" mt-5> <h1>Sign In</h1> </v-flex> <v-flex xs12 sm6 offset-sm3 mt-3> <form @submit.prevent="userSignIn"> <v-layout column> <v-flex> <v-alert type="error" dismissible v-model="alert"> {{ error }} </v-alert> </v-flex> <v-flex> <v-text-field name="email" label="Email" id="email" type="email" v-model="email" required></v-text-field> </v-flex> <v-flex> <v-text-field name="password" label="Password" id="password" type="password" v-model="password" required></v-text-field> </v-flex> <v-flex class="text-xs-center" mt-5> <v-btn color="primary" type="submit">Sign In</v-btn> </v-flex> </v-layout> </form> </v-flex> </v-layout> </v-container> </template> <script> export default { data () { return { email: '', password: '', alert: false } }, methods: { userSignIn () { this.$store.dispatch('userSignIn', { email: this.email, password: this.password }) } }, computed: { error () { return this.$store.state.error }, loading () { return this.$store.state.loading } }, watch: { error (value) { if (value) { this.alert = true } }, alert (value) { if (!value) { this.$store.commit('setError', null) } } } } </script>
<template> <v-container fluid> <v-layout row wrap> <v-flex xs12 class="text-xs-center" mt-5> <h1>Sign Up</h1> </v-flex> <v-flex xs12 sm6 offset-sm3 mt-3> <form @submit.prevent="userSignUp"> <v-layout column> <v-flex> <v-alert type="error" dismissible v-model="alert"> {{ error }} </v-alert> </v-flex> <v-flex> <v-text-field name="email" label="Email" id="email" type="email" v-model="email" required></v-text-field> </v-flex> <v-flex> <v-text-field name="password" label="Password" id="password" type="password" v-model="password" required></v-text-field> </v-flex> <v-flex> <v-text-field name="confirmPassword" label="Confirm Password" id="confirmPassword" type="password" v-model="passwordConfirm" :rules="[comparePasswords]" required ></v-text-field> </v-flex> <v-flex class="text-xs-center" mt-5> <v-btn color="primary" type="submit" :disabled="loading">Sign Up</v-btn> </v-flex> </v-layout> </form> </v-flex> </v-layout> </v-container> </template> <script> export default { data () { return { email: '', password: '', passwordConfirm: '', alert: false } }, computed: { comparePasswords () { return this.password === this.passwordConfirm ? true : 'Passwords don\'t match' }, error () { return this.$store.state.error }, loading () { return this.$store.state.loading } }, methods: { userSignUp () { if (this.comparePasswords !== true) { return } this.$store.dispatch('userSignUp', { email: this.email, password: this.password }) } }, watch: { error (value) { if (value) { this.alert = true } }, alert (value) { if (!value) { this.$store.commit('setError', null) } } } } </script>
<template> <v-container fluid> <v-layout row wrap> <v-flex xs12 class="text-xs-center" mt-5> <h1>Home page</h1> </v-flex> <v-flex xs12 class="text-xs-center" mt-3> <p>This is a user's home page</p> </v-flex> </v-layout> </v-container> </template> <script> export default {} </script>
<template> <v-container fluid> <v-layout row wrap> <v-flex xs12 class="text-xs-center" mt-5> <h1>Error - page not found</h1> </v-flex> </v-layout> </v-container> </template> <script> export default {} </script>
En estas vistas, observamos que todos los componentes (o vistas) tienen dos partes:
- El
<template>
- El
<script>
Dentro de <template>
hay un contenedor: <v-container fluid>
(la propiedad fluid extiendo al ancho completo).
Dentro de <v-container fluid>
tenemos el componente <v-layout row wrap>
. Sirve para separar secciones. Contiene las propiedades:
- row: pone flex direction a row
- wrap: allows children to wrap within the container if the elements use more than 100%.
Dentro de <v-layout>
tendremos <v-flex>
(puede haber varias instancias de v-flex
). Que ajusta el grid. Entre las posibles propiedades tenemos:
-
(size)(1-12): (ej. xs12) en donde el tamaño puede ser:
-
xs: extra small
- sm: small
- md: medium
- lg: large
-
xl: extra large
-
Spacing: por ejemplo,
mt-5
pone un margen superior de 5. En el enlace viene un playground para ver el ajuste adecuado.
Ajustar el alineamiento del texto mediante class="lo que sea"
:
<p class="text-lg-right">Right align on large viewport sizes</p> <p class="text-md-center">Center align on medium viewport sizes</p> <p class="text-sm-left">Left align on small viewport sizes</p> <p class="text-xs-center">Center align on all viewport sizes</p> <p class="text-xs-right">Right align on all viewport sizes</p>
- lg: pantallas grandes
- md: pantallas medianas
- sm: pantallas pequeñas
- xs: todas las pantallas
Dentro de flex, tenemos componentes como:
<h1>
: heading<p>
: párrafo<form>
: dentro puede haberv-layout
con los correspondientesflex
.<v-text-field>
:<v-btn>
:<v-alert>
:
Aplicación
La página principal de la aplicación es un poco especial.
Dentro de <template>
tenemos <v-app>
. Dentro tendremos componentes.
Digno de mención es la lista. La lista comienza con <v-list>
y después contiene tiles <v-list-tile>
. Dentro del tile podemos tener:
<v-list-tile-action>
: dentro tenemos<v-icon>
<v-list-tile-content>
En los <v-list-tile>
puede tener propiedades como:
@click=
: indica la función a la que llamará cuando se haga click en el elemento.v-if
: muestra el tile si el valor de la variable asociada es verdadero.v-for
: hace un bucle para pintar tiles
V-list
<v-list> <v-list-tile v-for="item in menuItems" :key="item.title" :to="item.path"> <v-list-tile-action> <v-icon>{{ item.icon }}</v-icon> </v-list-tile-action> <v-list-tile-content>{{ item.title }}</v-list-tile-content> </v-list-tile> <v-list-tile @click="userSignOut" v-if="isAuthenticated"> <v-list-tile-action> <v-icon>exit_to_app</v-icon> </v-list-tile-action> <v-list-tile-content>Sign Out</v-list-tile-content> </v-list-tile> </v-list>