Aplicaciones de una sola página con Vue.js y Flask: navegación por el enrutador Vue

Bienvenido a la segunda publicación sobre el uso de Vue.js y Flask para el desarrollo web completo. El tema principal de este artículo será sobre el enrutador Vue, pero también cubriré...

Bienvenido a la segunda publicación sobre el uso de Vue.js y Flask para el desarrollo web completo. El tema principal de este artículo será sobre Vue Router, pero también cubriré la directiva v-model, así como los métodos Vue y las propiedades calculadas. Dicho esto, toma algo con cafeína y consume algo de la bondad de Vue. El código de esta publicación está en mi GitHub.

Contenido de la serie

  1. Configuración y familiarización con VueJS
  2. Navegando por el enrutador Vue (usted está aquí)
  3. Gestión de estados con Vuex
  4. API RESTful con Flask
  5. Integración AJAX con API REST
  6. Autenticación JWT
  7. Implementación en un servidor privado virtual

Familiarizarse con Vue Router

Como la mayoría de los otros aspectos del marco Vue.js, usar Enrutador Vue para navegar por las distintas páginas y los componentes subsiguientes es tremendamente fácil.

Aparte 1: instancias independientes de Vue y vue-router

Algunos de los temas presentados en esta publicación se describirán mejor con ejemplos de juguetes más pequeños, por lo que habrá momentos en los que haré un aparte para hacerlo. Aparte de esto, demostraré lo que se requiere para instalar una instancia y un enrutador independientes de Vue. Si bien Vue es absolutamente fenomenal para crear aplicaciones SPA completas, también tiene un valor real la capacidad de colocarlo en una página HTML normal.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- index.html -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>

<div id="app">
  <div>
    <h3>Cartoon characters</h3>
    <div v-for="(character, i) in characters" v-bind:key="i">
      <h4>{{ character.name }}</h4>
      <p><img v-bind:src="character.imgSrc" v-bind:alt="character.name"/></p>
    </div>
  </div>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    characters: [{
      name: 'Scooby',
      imgSrc: 'https://www.wbkidsgo.com/Portals/4/Images/Content/Characters/Scooby/characterArt-scooby-SD.png'
    }, {
      name: 'Shaggy',
      imgSrc: 'https://upload.wikimedia.org/wikipedia/en/thumb/8/87/ShaggyRogers.png/150px-ShaggyRogers.png'
    } ]
  }
})

</script>

Esto muestra los personajes de dibujos animados Scooby y Shaggy. El ejemplo presenta la directiva v-bind: para vincular dinámicamente los datos de la matriz characters a los atributos src y alt del elemento img, lo que permite que los datos controlen el contenido. Esto es similar a cómo se realiza la interpolación de texto usando {{}}, excepto que v-bind interpola los datos en atributos. Puede encontrar un ejemplo práctico de esto aquí.

Scooby and Shaggy{.img-responsive}

En lugar de mostrar ambos personajes, cambiemos nuestro enfoque, permitiéndonos hacer clic en un enlace para que cada personaje muestre un componente específico de "Scooby" o "Shaggy". Para lograr esto, extraeré la biblioteca vue-router y realizaré los siguientes cambios:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!-- index.html -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
  <p>
    <router-link to="/scooby">Scooby</router-link>
    <router-link to="/shaggy">Shaggy</router-link>
  </p>
  <router-view></router-view>
</div>

<script>
const Scooby = {
  template: `
    <div>
      <h4>Scooby</h4>
      <p>
        <img src="https://www.wbkidsgo.com/Portals/4/Images/Content/Characters/Scooby/characterArt-scooby-SD.png" alt="scooby"/>
      </p>
    </div>`
}

const Shaggy = {
  template: `
    <div class="character">
        <h4>Shaggy</h4>
      <p><img src="https://upload.wikimedia.org/wikipedia/en/thumb/8/87/ShaggyRogers.png/150px-ShaggyRogers.png" alt="shaggy"/></p>
    </div>`
}

const router = new vue-router({
  routes: [
    { path: '/scooby', component: Scooby },
    { path: '/shaggy', component: Shaggy }
  ]
})

const app = new Vue({ router: router }).$mount('#app')
</script>

Como probablemente pueda ver, esta implementación está impulsada por componentes codificados, lo que no es un cambio beneficioso desde el punto de vista de la reutilización. Sin embargo, muestra un uso fácil de seguir de vue-router. Además de obtener la biblioteca vue-router, el HTML contiene dos elementos nuevos, componentes en realidad, específicos de vue-router.

El primer componente de vue-router es <enlace de enrutador>, que es un componente que recibe una ruta de ruta a través del atributo to, que en realidad se denomina "parámetro" en un componente de Vue. El componente <router-link> produce un elemento de hipervínculo que responde a los eventos de clic y le dice a Vue que muestre el componente asociado con su parámetro to, ya sea "/scooby" o "/shaggy".

Debajo de <enlace de enrutador> hay otro componente nuevo, <vista de enrutador>, que es donde vue-router le dice a Vue que inyecte los componentes de la interfaz de usuario, Scooby y Shaggy. Los componentes de la plantilla personalizada de Scooby y Shaggy se definen en el elemento script en la parte inferior de esta página index.html de ejemplo.

A continuación, se crea una instancia de un objeto vue-router con un objeto routes que define una matriz de rutas similar a la que vimos en la aplicación Survey del primer artículo. Aquí, las rutas de ruta se asignan a los componentes Scooby y Shaggy. Lo último que debe hacer es instanciar una instancia de Vue, darle un objeto de enrutador como propiedad a su objeto de opciones y vincular la instancia a la aplicación div.

Puede hacer clic en el enlace del enrutador Scooby o Shaggy para mostrarlos, como se muestra a continuación. El código de este ejemplo se puede encontrar aquí.

Scooby link{.img-responsive}

Shaggy link{.img-responsive}

Uso de vue-router para mostrar una encuesta individual

Volviendo a la aplicación Survey, comencemos nuestra discusión echando un vistazo al archivo route/index.js.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    }
  ]
})

La página de inicio muestra las encuestas de la aplicación cuando se solicita la URL raíz en el servidor local: 8080 porque está asignada al componente “Inicio” a través de las rutas del enrutador.

También necesito conectarme a Vue a través de la función Vue.use(Router) para una aplicación SPA modular como esta. Además, debo incluir el enrutador en el objeto de opciones, que se envía a la instancia de Vue en main.js de forma similar al ejemplo del juguete Scooby/Shaggy.

Continuando con la aplicación Encuesta, agrego una nueva ruta al objeto “rutas” que asignará cada encuesta y sus preguntas a un componente reutilizable basado en el archivo Survey.vue. En el archivo route/index.js, importe un componente Survey y luego agregue una ruta para asignar cada encuesta por su id al componente Survey.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// ... omitted for brevity
import Survey from '@/components/Survey'

// ... omitted for brevity

export default new Router({
  routes: [
    {
      // ... omitted for brevity
    }, {
      path: '/surveys/:id',
      name: 'Survey',
      component: Survey
    }
  ]
})

Tenga en cuenta la parte :id de la nueva ruta /surveys/:id. Esto se conoce como un segmento dinámico que puede considerar como una variable dentro de una ruta de ruta. En este caso, estoy diciendo que :id se usará para identificar una encuesta específica para mostrar en el componente Survey que se construirá a continuación.

En el directorio "src/components/", cree un archivo llamado Survey.vue, luego ábralo y agregue las secciones estándar de plantilla, script y estilo junto con el código que se muestra a continuación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
  <h3>I'm a Survey Component</h3>
</div>
</template>

<script>
export default {
  data() {
    return {
      survey: {}
    }
  },
  beforeMount() {
    console.log('Survey.beforeMount -> :id === ', this.$route.params.id)
  }
}
</script>

<style>

</style>

Guardo todos los archivos e inicio el servidor dev con npm run dev, luego ingreso la siguiente URL en el navegador: localhost:8080/#/encuestas/23. En la consola de las herramientas de desarrollo de mi navegador, veo la siguiente imagen.

console.log in beforeMount{.img-responsive}

Entonces, ¿qué acaba de pasar?

En la sección plantilla agregué un código sin sentido para dejar en claro que el enrutador sirve el componente Survey. En la sección script, inicialicé un objeto de encuesta que contendrá los datos de la encuesta eventualmente. En el gancho del ciclo de vida beforeMount está sucediendo algo muy interesante. En esta función estoy accediendo a la ruta de la ventana actual y al siguiente parámetro :id definido en el módulo route.

Esta última parte es posible porque el objeto Vue del componente Survey tiene una referencia a la instancia de vue-router que da acceso a la ruta y me permite acceder a ella con this.$route.params.id. Una ruta puede tener múltiples segmentos dinámicos y todos son accesibles en sus componentes correspondientes a través del miembro params del objeto this.$route.

A continuación, definiré una función simulada de AJAX en api/index.js, a la que llamo desde el enlace beforeMount del componente Survey para obtener una encuesta por :id. En api/index.js agregue la siguiente función:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const surveys = [{
  id: 1,
  name: 'Dogs',
  // ... omitted for brevity 
}, {
  id: 2,
  name: 'Cars',
  // ... omitted for brevity 
}]

// ... omitted for brevity

export function fetchSurvey (surveyId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const survey = surveys.find(survey => survey.id === surveyId)
      if (survey) {
        resolve(survey)
      } else {
        reject(Error('Survey does not exist'))
      }
    }, 300)
  })
}

Ahora, de vuelta en el componente Survey, necesito importar fetchSurvey y usarlo en beforeMount para recuperar la encuesta solicitada. Nuevamente, con fines visuales, mostraré el nombre de la encuesta en la plantilla como un encabezado de héroe bulma.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<div>
    <section class="hero is-primary">
      <div class="hero-body">
        <div class="container has-text-centered">
          <h2 class="title">{{ survey.name }}</h2>
        </div>
      </div>
    </section>
</div>
</template>

<script>
import { fetchSurvey } from '@/api'
export default {
  data() {
    return {
      survey: {}
    }
  },
  beforeMount() {
    fetchSurvey(parseInt(this.$route.params.id))
      .then((response) => {
        this.survey = response
      })
  }
}
</script>

<style>
</style>

Actualizando la URL del navegador a localhost:8080/encuestas/2, veo lo que se muestra a continuación:

Survey header{.img-responsive}

Bastante genial, ¿verdad?

A continuación, me gustaría hacer algo un poco más útil con mi componente ‘Encuesta’ e incluir las preguntas y opciones.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template>
  <div>
    <!-- omitted survey name header for brevity -->

    <section class="section">
      <div class="container">

        <div class="columns">
          <div class="column is-10 is-offset-1">

            <div v-for="question in survey.questions" v-bind:key="question.id">

                  <div class="column is-offset-3 is-6">
                    <h4 class='title has-text-centered'>{{ question.text }}</h4>
                  </div>
                  <div class="column is-offset-4 is-4">
                    <div class="control">
                      <div v-for="choice in question.choices" v-bind:key="choice.id">
                        <label class="radio">
                        <input type="radio" v-model="question.choice" :value="choice.id">
                        {{ choice.text }}
                      </label>
                      </div>
                    </div>
                  </div>

            </div>

          </div>
        </div>

      </div>
    </section>
  </div>
</template>

Nuevamente, al guardar y actualizar el navegador con la URL localhost:8080/#/encuestas/2 ahora se muestra una lista de preguntas y las opciones disponibles para la encuesta de autos.

Survey questions{.img-responsive}

Permítanme tratar de desempaquetar algunas de las nuevas funciones de Vue que se están utilizando. Ya estamos familiarizados con el uso de la directiva v-for para impulsar la generación de las preguntas y opciones de la encuesta, por lo que esperamos que pueda realizar un seguimiento de cómo se muestran. Sin embargo, si se enfoca en cómo se generan los botones de radio para las opciones de una pregunta, notará que estoy haciendo dos cosas nuevas o ligeramente diferentes.

1
2
3
4
5
6
<div v-for="choice in question.choices" v-bind:key="choice.id">
  <label class="radio">
    <input type="radio" v-model="question.choice" :value="choice.id">
    {{ choice.text }}
  </label>
</div>

Para la entrada de radio, he usado la directiva v-model y le he proporcionado un valor de question.choice. Lo que esto hace es crear un nuevo miembro en el objeto pregunta llamado elección y lo registra con la entrada de radio, lo que permite que los datos fluyan desde la entrada de radio real a la propiedad del objeto pregunta.elección. También estoy usando una sintaxis abreviada de :value en lugar de v-bind:value para vincular el valor de esta entrada de radio al valor de las opciones de pregunta que se iteran a través de v-for.

Aparte 2 - Uso de la directiva v-model

Me doy cuenta de que el concepto de ‘v-model’ es probablemente un poco confuso, así que permítanme hacerme a un lado y hacer otro ejemplo simple para demostrar el punto. Considere el siguiente ejemplo trivial. Nuevamente, puede ver un ejemplo funcional de este código aquí.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- index.html -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>

<div id="app">
  <div>
    <label for="name">What is your name</label>
    <input id="name" type="text" v-model="textInput" />
    <span>Hello {{ textInput }}</span>
  </div>

  <h4>Which do you like better?</h4>
  <div v-for="choice in radioChoices" :key="choice">
    <label>{{ choice }}</label>
    <input name="fruit" type="radio" v-model="favoriteFruit" :value="choice"/>
  </div>
  <h4>So you like {{ favoriteFruit }}</h4>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    textInput: '',
    radioChoices: ['apples', 'oranges'],
    favoriteFruit: ''
  }
})
</script>

La primera entrada es una entrada de texto que solicita el nombre del usuario. Esta entrada de texto tiene un v-model registrado con la propiedad de datos textInput adjunta, que mantiene la entrada de texto sincronizada con la propiedad de datos textInput de la instancia Vue. Tómese un segundo para escribir su nombre en la entrada de texto y ver cómo se actualiza en la salida HTML <span>Hello {{ textInput }}</span>.

¿Increíble verdad?

La segunda entrada es una entrada de radio llamada "fruta" que muestra las frutas "manzanas" y "naranjas" y le pide al usuario que seleccione su favorita. La entrada de radio se registra en la propiedad de datos favoriteFruit de la instancia Vue a través del v-model, que asocia el valor asociado con cada entrada de radio a través de la sintaxis de vinculación del atributo :value="choice" para mantener favoriteFruit en sincronización con la entrada de radio seleccionada. Una vez más, puede ver el valor de la actualización favoriteFruit en la salida del elemento <h4>So you like {{favoriteFruit }}</h4>.

A continuación muestro algunos ejemplos de salida. Te animo a que juegues con este ejemplo hasta que la noción de v-model esté clara.

v-model binding{.img-responsive}

Cómo completar la experiencia en la realización de encuestas {#completar la experiencia en la realización de encuestas}

Bien, volvamos a la aplicación de encuestas. Piense en el caso en el que una encuesta tiene muchas más preguntas debajo de la altura de pantalla predeterminada. En general, queremos evitar que las personas tengan que desplazarse hacia abajo para ver su contenido más importante. Una mejor opción sería paginar las preguntas mostrando una pregunta y sus respuestas a la vez.

El componente Survey actualizado logra esto a continuación.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<template>
  <div>
    <!-- omitted for brevity -->
    <section class="section">
      <div class="container">

        <div class="columns">
          <div class="column is-10 is-offset-1">

            <div
              v-for="(question, idx) in survey.questions" <!-- modified v-for -->
              v-bind:key="question.id"
              v-show="currentQuestion === idx"> <!-- new v-show directive -->

                  <div class="column is-offset-3 is-6">
                    <!-- <h4 class='title'>{{ idx }}) {{ question.text }}</h4> -->
                    <h4 class='title has-text-centered'>{{ question.text }}</h4>
                  </div>
                  <div class="column is-offset-4 is-4">
                    <div class="control">
                      <div v-for="choice in question.choices" v-bind:key="choice.id">
                        <label class="radio">
                        <input type="radio" v-model="question.choice" :value="choice.id">
                        {{ choice.text }}
                        </label>
                      </div>
                    </div>
                  </div>

            </div>

            <!-- new pagination buttons -->
            <div class="column is-offset-one-quarter is-half">
              <nav class="pagination is-centered" role="navigation" aria-label="pagination">
                <a class="pagination-previous" @click.stop="goToPreviousQuestion"><i class="fa fa-chevron-left" aria-hidden="true"></i> &nbsp;&nbsp; Back</a>
                <a class="pagination-next" @click.stop="goToNextQuestion">Next &nbsp;&nbsp; <i class="fa fa-chevron-right" aria-hidden="true"></i></a>
              </nav>
            </div>

            <!-- new submit button -->
            <div class="column has-text-centered">
              <a v-if="surveyComplete" class='button is-focused is-primary is-large'
                @click.stop="handleSubmit">
                Submit
              </a>
            </div>

          </div>
        </div>

      </div>
    </section>
  </div>
</template>

<script>
import { fetchSurvey, saveSurveyResponse } from '@/api' // new AJAX func
export default {
  data() {
    return {
      survey: {},
      currentQuestion: 0  // new data prop
    }
  },
  beforeMount() {
    // omitted for brevity
  },
  methods: { // new Vue obj member
    goToNextQuestion() {
      if (this.currentQuestion === this.survey.questions.length - 1) {
        this.currentQuestion = 0
      } else {
        this.currentQuestion++
      }
    },
    goToPreviousQuestion() {
      if (this.currentQuestion === 0) {
        this.currentQuestion = this.survey.questions.lenth - 1
      } else {
        this.currentQuestion--
      }
    },
    handleSubmit() {
      saveSurveyResponse(this.survey)
        .then(() => this.$router.push('/'))
    }
  },
  computed: {  // new Vue obj member
    surveyComplete() {
      if (this.survey.questions) {
        const numQuestions = this.survey.questions.length
        const numCompleted = this.survey.questions.filter(q => q.choice).length
        return numQuestions === numCompleted
      }
      return false
    }
  }
}
</script>

Estos cambios funcionan juntos para completar la experiencia de realizar encuestas. Como los nodos de preguntas se generan a partir de v-for="(pregunta, idx) en encuesta.preguntas", estoy usando la directiva v-show="currentQuestion === idx" para probar si el valor de los datos propiedad, currentQuestion, coincide con el valor de idx. Esto hace que la pregunta div sea visible solo si currentQuestion es igual al valor idx de esa pregunta. Dado que el valor de currectQuestion se inicializa a cero, la pregunta cero se mostrará de forma predeterminada.

Debajo de las preguntas y respuestas, los botones de paginación permiten al usuario paginar las preguntas. El elemento del botón "siguiente" tiene @click="goToNextQuestion" dentro de él, que es un controlador de eventos de clic de Vue que responde llamando a la función goToNextQuestion dentro de la nueva propiedad de objeto Vue methods. La sección métodos de un objeto componente de Vue es donde se pueden definir funciones para hacer una serie de cosas, la mayoría de las veces para cambiar el estado del componente. Aquí goToNextQuestion incrementa currentQuestion en uno, avanzando la pregunta que se muestra, o la restablece a la primera pregunta. El botón Atrás y su método goToPreviousQuestion asociado hacen exactamente lo contrario.

El último cambio es la funcionalidad para enviar la respuesta de la encuesta. El botón usa v-show nuevamente para determinar si el botón debe mostrarse en función del valor de una propiedad calculada llamada surveyCompleted. Las propiedades calculadas son otro rasgo impresionante de Vue. Son propiedades que generalmente controlan cómo se muestran los componentes de la interfaz de usuario que son útiles cuando la lógica es un poco más compleja que verificar un solo valor de una propiedad de datos. De esta forma, el código de la plantilla está limpio y puede centrarse en la presentación mientras la lógica permanece en el código JavaScript.

Un detector de eventos de clic, @click="handleSubmit", se registra en el botón ancla de envío, que llama al método handleSubmit. Este método llama a la función simulada de AJAX saveSurveyResponse, que devuelve una promesa y pasa el control a "then chain". La "entonces cadena" tiene una devolución de llamada, .then(() -> this.$router.push('/')), que llama al objeto de enrutador del componente y enruta mediante programación la aplicación de regreso al ruta raíz que muestra el componente Inicio.

En api/index.js agregue la función saveSurveyResponse en la parte inferior del archivo. Esta función recibe un objeto de respuesta de la encuesta y simplemente registra en la consola "guardando la respuesta de la encuesta..." hasta que conecte el front-end a la API REST en el futuro.

1
2
3
4
5
6
7
8
9
export function saveSurveyResponse (surveyResponse) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("saving survey response...")
      })
      resolve()
    }, 300)
  })
}

Guardando todos los archivos y refrescando el navegador con la URL localhost:8080:/#/encuestas/2 veo lo que hay debajo. Recomiendo hacer clic en la aplicación y asegurarse de que puede rastrear lógicamente el flujo de control a través del componente Survey.

Scrollable survey questions{.img-responsive}

Aparte 3 - Enrutamiento programático

Al igual que antes, quiero demostrar algunas cosas que acabamos de discutir con una variación de uno de los ejemplos de juguetes anteriores. A continuación, modifiqué el ejemplo de navegación que muestra Scooby o Shaggy para que ya no use el componente <enlace de enrutador>.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!-- index.html -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
  <p>
    <a @click="toScooby">Scooby</a>
    <a @click="toShaggy">Shaggy</a>
  </p>
  <router-view></router-view>
</div>

<script>
const Scooby = {
    template: `
    <div>
      <h4>Scooby</h4>
      <p>
        <img src="https://www.wbkidsgo.com/Portals/4/Images/Content/Characters/Scooby/characterArt-scooby-SD.png" alt="scooby"/>
      </p>
    </div>`
}

const Shaggy = {
    template: `
    <div class="character">
      <h4>Shaggy</h4>
      <p>
        <img src="https://upload.wikimedia.org/wikipedia/en/thumb/8/87/ShaggyRogers.png/150px-ShaggyRogers.png" alt="shaggy"/>
      </p>
    </div>`
}

const router = new vue-router({
  routes: [
    { path: '/characters/scooby', component: Scooby },
    { path: '/characters/shaggy', component: Shaggy }
  ]
})

const app = new Vue({
  router: router,
  methods: {
    toScooby() { this.$router.push('/characters/scooby') },
    toShaggy() { this.$router.push('/characters/shaggy') }
  }
}).$mount('#app')

</script>

El ejemplo se comporta exactamente de la misma manera que antes, pero ahora el enrutamiento se realiza a través de una combinación de detectores de eventos de clic, métodos Vue y llamadas manuales a this.$router.push('/path'). Esto es realmente lo que <enlace-router> hace tras bambalinas usando el valor to="/path". Los animo a jugar con este ejemplo en vivo aquí.

Adición de enlaces de enrutador al componente de inicio

Lo último que debe hacer con el componente ‘Encuesta’ es proporcionar la capacidad de navegar a una encuesta desde el componente ‘Inicio’. Como se mostró anteriormente con el ejemplo de Scooby y Shaggy, vue-router lo hace increíblemente fácil con <router-link>.

De vuelta en el componente Inicio haga la siguiente modificación:

1
2
3
4
5
6
7
8
9
<div class="card" v-for="survey in surveys" v-bind:key="survey.id">
  <div class="card-content">
    <p class="title">{{ survey.name}}</p>
    <p class='subtitle'>{{survey.created_at.toDateString()}}</p>
  </div>
  <div class="card-foooter">
    <router-link :to="`surveys/${survey.id}`" class="card-footer-item">Take Survey</router-link>
  </div>
</div>

Agregué un componente <enlace de enrutador> dentro de un pie de página de la tarjeta bulma y construí dinámicamente la ruta a cada encuesta. Esto es diferente de las rutas de cadenas literales que proporcioné en mi ejemplo anterior. Para producir dinámicamente las rutas usando las cadenas de plantilla de JavaScript y las ID de la encuesta que se iteran, prefijo el parámetro to con dos puntos (":"), que es la abreviatura de la directiva v-bind.

Guardo todos los archivos y busco la ruta URL raíz, servidor local: 8080, en mi navegador para asegurarme de que funciona. Debería poder hacer clic en el enlace "Tomar encuesta" de cada encuesta y ver la interfaz de usuario del componente Encuesta.

Para completar la experiencia, agrego una barra de navegación simple con una pestaña "Inicio" usando <enlace del enrutador> y un parámetro to que apunta a la ruta raíz de la aplicación dentro de un nuevo archivo de componente llamado Header.vue en el directorio del componente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<template>
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
  <div class="navbar-menu">
    <div class="navbar-start">
      <router-link to="/" class="navbar-item">
        Home
      </router-link>
    </div>
  </div>
</nav>
</template>

<script>
</script>

<style>
</style>

Para asegurarme de que esté incluido en todas las páginas de la aplicación, lo coloco en el componente App.vue. Para hacer esto, primero importo el componente Header como AppHeader en la sección script y luego lo registro agregando una propiedad llamada components en el objeto Vue del componente App y lo establezco igual a un objeto que contiene el Componente AppHeader.

Luego agrego el componente en la plantilla colocando <app-header> justo encima del componente <router-view>. Al nombrar componentes, es común usar caso pascual concatenando las palabras que lo describen juntas, donde la primera letra de cada palabra está en mayúscula. Luego lo incluyo en la plantilla en minúsculas con guiones entre cada palabra que comienza con una letra mayúscula.

Al guardar los archivos y actualizar el navegador, ahora veo el componente “Encabezado” que contiene la barra de navegación en cada página de la aplicación.

App header{.img-responsive}

Recursos

¿Desea obtener más información sobre Vue.js y la creación de aplicaciones web front-end? Intente consultar algunos de los siguientes recursos para profundizar en este marco de front-end:

Conclusión

Si todavía estás leyendo esto, has consumido un poco de la bondad de Vue. Ciertamente cubrimos una gran cantidad de material en esta publicación y si es nuevo en Vue o en los SPA de componentes de un solo archivo, probablemente valga la pena leerlo por segunda vez y visitar los excelentes [documentos de vue] (https://vuejs.org/ v2/guide/) para una inmersión más profunda.

Como siempre, gracias por leer y no se avergüence de comentar o criticar a continuación.