En la publicación anterior vimos como pasamos datos de nuestro controller a la vista y los manipulamos con Blade.

Puedes ver la publicación anterior aquí:

Ahora es turno para manipular la vista y hacer peticiones asíncronas con Vue.

Para prepar el controller y que pueda recibir peticiones asíncronas desde la vista modificamos el controller(app/Http/Controllers/PokemonController.php) de esta manera:

class PokemonController extends Controller
{
public function index()
{
return view('welcome', ['pokemons' => Pokemon::all()]);
}

public function show()
{
return response()->json(Pokemon::all(), 200);
}

}

Agregando un layout a nuestra vista

En la publicación anterior solo ejecutamos una vista, pero que pasa si el mismo código HTML y mas las librerías de JS y CSS se repiten en todas las vistas. Cada vez que creamos una vista tendríamos que copiar el código esencial para que se ejecute bien.

Para no estar copiando código de vista en vista crearemos un layout con lo esencial para que se pueda ejecutar y el contenido sera dinámico, todo esto con las funciones que trae Blade para desarrollar este tipo de casos.

Creamos el archivo y la carpeta “resources/views/layouts/layout.blade.php” y ponemos lo siguiente:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title>Pokedex</title>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css" integrity="sha256-h20CPZ0QyXlBuAw7A+KluUYx/3pK+c7lYEpqLTlxjYQ=" crossorigin="anonymous" />
<link rel="stylesheet" type="text/css" href="{{ asset('css/app.css') }}" >
</head>
<body>
<body>
<div id="app">
<!--- The Nav -->
<div class="relative bg-red-600">
<div class="max-w-7xl mx-auto px-4 sm:px-6">
<div class="flex justify-between items-center border-b-1 border-gray-100 py-6 md:justify-start md:space-x-10">
<div class="lg:w-0 lg:flex-1 rounded-full text-white shadow-solid">
<a href="#" class="flex">
<img class="h-12 w-12" src="https://upload.wikimedia.org/wikipedia/commons/5/53/Pok%C3%A9_Ball_icon.svg" alt="Workflow">
</a>
</div>

<div class="md:flex items-center justify-center space-x-8 md:flex-1 lg:w-0">
<h1 class="text-white text-3xl">The Pokedex</h1>
</div>

<div class="md:flex items-center justify-center space-x-8 md:flex-1 lg:w-0">
&nbsp;
</div>
</div>
</div>
</div>

<!-- Content -->
<div class="container mx-auto">
@yield('content')
</div>

<!--- The Footer -->
<footer class="footer bg-white relative pt-1 border-b-2 border-blue-700">
<div class="container mx-auto px-6">
<div class="mt-16 border-t-2 border-gray-300 flex flex-col items-center">
<div class="sm:w-2/3 text-center py-6">
<p class="text-sm text-blue-700 font-bold mb-2">
© 2021 Pokedex Project by Chris López.
</p>
</div>
</div>
</div>
</footer>
</div>
</body>
</body>
</html>

La función “@yield” de Blade ejecutara una vista cuando la ejecute y esta extienda de este layout.

En nuestra vista “resources/views/welcome.blade.php” ponemos lo siguiente:

@extends('layouts.layout')
@section('content')
<div class="grid grid-cols-3 gap-4">
@foreach($pokemons as $pokemon)
<div class="max-w-xs rounded overflow-hidden shadow-lg my-5">
<img loading="lazy" class="w-full" src="{{ $pokemon->image_url }}" alt="{{ $pokemon->name }}">
<div class="px-6 py-4">
<div class="font-bold text-xl mb-2">{{ $pokemon->code }} - {{ $pokemon->name }}</div>
<p class="text-grey-darker text-base">
<b>Height: </b> {{ $pokemon->height }} <br>
<b>Weight: </b> {{ $pokemon->weight }} <br>
<b>Ability: </b> {{ $pokemon->ability }}
</p>
</div>
<div class="px-6 py-4">
<a href="{{ $pokemon->more_info_url }}" target="_blank"
class="tracking-wider text-white bg-red-500 px-4 py-1 text-sm rounded leading-loose mx-2 font-semibold"
title="{{ $pokemon->name }}">
<i class="fas fa-info-circle" aria-hidden="true"></i> More Info
</a>
</div>
</div>
@endforeach
</div>
@endsection

Deberíamos de ver la misma vista del Pokedex

Integrando Vue en nuestra vista

Ya vimos en gran parte como se desarrolla nuestra vista en Blade y le agregamos un layout, así que para integrar Vue en nuestra vista primero lo tenemos que configurar por medio del paquete “ui” de Laravel, el cual nos da un scaffolding de Vue.

Instalamos el paquete “UI”:

composer require laravel/ui

Ejecutamos el UI para Vue:

sail artisan ui vue

Este comando nos generara la configuración tanto del package.json como el webpack.mix.js así como también archivos de ejemplo de componentes y la plantilla para los archivos bootstrap.js y app.js.

Modificamos el archivo webpack.mix.js, ya que con el scaffold del comando “UI” nos borro la configuración de Tailwind, ponemos lo siguiente:

mix.js('resources/js/app.js', 'public/js')
.vue()
.postCss('resources/css/app.css', 'public/css', [
require('tailwindcss'),
])

Ahora abrimos el archivo “resources/js/app.js” y ponemos lo siguiente:

require('./bootstrap');

window.Vue = require('vue').default;
window.Vuex = require('vuex');

const PokedexContent = () => import("./components/PokedexContent")
const PokedexNav = () => import("./components/PokedexNav")
const PokedexFooter = () => import("./components/PokedexFooter")

const app = new Vue({
el: '#app',
components: {
'pokedex-content': PokedexContent,
'pokedex-nav': PokedexNav,
'pokedex-footer': PokedexFooter,
}
});

En este paso inicializamos nuestra aplicación Vue al mismo tiempo que nuestros componentes de entrada.

Creamos la carpeta “components” dentro de “resources/js” , así como también los archivos “.vue” de los componentes. Modificamos esos archivos con lo siguiente:

Editamos PokedexNav.vue(resources/js/components):

<template>
<!--- The Nav -->
<div class="relative bg-red-600">
<div class="max-w-7xl mx-auto px-4 sm:px-6">
<div class="flex justify-between items-center border-b-1 border-gray-100 py-6 md:justify-start md:space-x-10">
<div class="lg:w-0 lg:flex-1 rounded-full text-white shadow-solid">
<a href="#" class="flex">
<img class="h-12 w-12" src="https://upload.wikimedia.org/wikipedia/commons/5/53/Pok%C3%A9_Ball_icon.svg" alt="Workflow">
</a>
</div>

<div class="md:flex items-center justify-center space-x-8 md:flex-1 lg:w-0">
<h1 class="text-white text-3xl">The Pokedex</h1>
</div>

<div class="md:flex items-center justify-center space-x-8 md:flex-1 lg:w-0">
&nbsp;
</div>
</div>
</div>
</div>
</template>

<script>
export default {
name: "PokedexNav"
}
</script>

Editamos PokedexFooter.vue(resources/js/components):

<template>
<!--- The Footer -->
<footer class="footer bg-white relative pt-1 border-b-2 border-blue-700">
<div class="container mx-auto px-6">
<div class="mt-16 border-t-2 border-gray-300 flex flex-col items-center">
<div class="sm:w-2/3 text-center py-6">
<p class="text-sm text-blue-700 font-bold mb-2">
© 2021 Pokedex Project by Chris López.
</p>
</div>
</div>
</div>
</footer>
</template>

<script>
export default {
name: "PokedexFooter"
}
</script>

Editamos PokedexContent.vue(resources/js/components):

<template>
<div>
<h1>Hola Pokedex!!!!</h1>
</div>
</template>

<script>
export default {
name: "PokedexContent"
}
</script>

Estos componentes los ponemos en el layout(PokedexNav y PokedexFooter) y la vista(PokedexContent):

Ponemos lo siguiente en el layout(resources/views/layouts/layout.blade.php):

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<title>Pokedex</title>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css" integrity="sha256-h20CPZ0QyXlBuAw7A+KluUYx/3pK+c7lYEpqLTlxjYQ=" crossorigin="anonymous" />
<link rel="stylesheet" type="text/css" href="{{ asset('css/app.css') }}" >

<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
</head>
<body>
<body>
<div id="app">
<!-- The Nav -->
<pokedex-nav></pokedex-nav>


<!-- Content -->
<div class="container mx-auto">
@yield('content')
</div>

<!--- The Footer -->
<pokedex-footer></pokedex-footer>
</div>
</body>
</body>
</html>

Y modificamos el archivo de la vista(welcome.blade.php):

@extends('layouts.layout')
@section('content')
<pokedex-content></pokedex-content>
@endsection

Modificando el contenido del Pokedex

En este paso tendremos que modificar el contenido donde se visualizan los Pokemons, antes teníamos este proceso sobre Blade ahora la cambiaremos a componentes de Vue y usando peticiones asíncronas o AJAX para obtener la información de nuestros Pokemones.

Creamos y editamos el componente que tenga la información de cada Pokemon, este componente se llamara “TheItemData.vue”:

<template>
<div class="max-w-xs rounded overflow-hidden shadow-lg my-5">
<img loading="lazy" class="w-full" :src="dataItem.image_url" :alt="dataItem.name">
<div class="px-6 py-4">
<div class="font-bold text-xl mb-2">{{ dataItem.code }} - {{ dataItem.name }}</div>
<p class="text-grey-darker text-base">
<b>Height: </b> {{ dataItem.height }} <br>
<b>Weight: </b> {{ dataItem.weight }} <br>
<b>Ability: </b> {{ dataItem.ability }}
</p>
</div>
<div class="px-6 py-4">
<a :href="dataItem.more_info_url" target="_blank"
class="tracking-wider text-white bg-red-500 px-4 py-1 text-sm rounded leading-loose mx-2 font-semibold"
:title="dataItem.name">
<i class="fas fa-info-circle" aria-hidden="true"></i> More Info
</a>
</div>
</div>
</template>

<script>
export default {
name: "TheItemData",
props: {
dataItem: {
type: Object,
},
}
}
</script>

Modificamos PokedexContent.vue:

<template>
<div class="grid grid-cols-3 gap-4">
<the-item-data v-for="item in items" :data-item="item" :key="item.code" />
</div>
</template>

<script>
import TheItemData from "./TheItemData";

export default {
name: "PokedexContent",
components: {
'the-item-data': TheItemData,
},
created() {
this.getPokemonsData()
},
data () {
return {
items: [],
}
},
methods: {
async getPokemonsData () {
let { data } = await axios.get('/pokemons')
this.items = data
}
}
}
</script>

En este componente(PokedexContent.vue) importamos el Componente que dará la vista de los registros de los Pokemon(TheItemData.vue). Luego lo iteramos por cada Pokemon que exista en nuestra colección, esta colección se obtiene de una llamada asíncrona al controlador en su método show().

El resultado seria el mismo, aunque nuestro pokedex ya tiene componentes reusables en Vue.

En la próxima publicación adaptaremos Vuex para el manejo de estado de nuestros componentes.

Pueden consultar el código fuente aquí:

Si quieren aprender mas sobre Vue pueden consultar aquí:

--

--

Chris Lopez
Chris Lopez

Written by Chris Lopez

Laravel, PHP, Python, Js, Vue Developer

No responses yet