Ejemplo sencillo de socios
Ahora que ya sabemos cómo trabaja Vue, procederemos a ocuparlo en conjunto a Laravel, para potenciar estas dos tecnologías que combinan de manera perfecta el Backend y el Frontend. Para ello realizaremos un ejemplo sencillo de socios que pertenecen a un sistema de agua potable. Realizaremos una base de datos en donde nosotros podamos almacenar los datos de aquellos socios y que nosotros lo podamos visualizar y cargar los datos mediante laravel y Vue. Para ello ocuparemos laravel-mix.
Instalación
npm init -y
npm install laravel-mix --save-dev
una ves generado nuestro entorno de trabajo podemos visualizar el archivo webpack.mix.js, acá estamos trabajando con WebPack que permite que podamos trabajar con varios framework tales como Vue, Angular, Riot y Polymer.
let mix = require('laravel-mix');
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');
Base de Datos y Modelo
Tenemos que crear una base de datos, en este caso la base de datos es creada en mysql y a posterior de ello debemos configurar nuestro archivo env. para que nos podamos conectar nuestra app a la base de datos
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=socio
DB_USERNAME=root
DB_PASSWORD=password
Ahora procedemos a realizar las migraciones.
php artisan make:migration zonas
php artisan make:migration ciudades_lugares
php artisan make:migration estado_socio
php artisan make:migration socios
una vez que generamos los archivos de migraciones, procedemos a agregar nuestro código para cada tabla, para ello nos posicionamos en el directorio database/migrations.
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class Socios extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('socios', function (Blueprint $table) {
$table->increments('id');
$table->string('rut_socio',12);
$table->string('nombre',45);
$table->string('apellido_paterno',45);
$table->string('apellido_materno',45);
$table->string('domicilio',60);
$table->float('subsidio',11);
$table->date('fecha_ingreso');
$table->integer('cod_estado')->unsigned();
$table->foreign('cod_estado')->references('cod_estado')->on('estado_socios');
$table->integer('cod_ciudad')->unsigned();
$table->foreign('cod_ciudad')->references('cod_ciudad')->on('ciudades_lugares');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class EstadoSocio extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('estado_socios', function (Blueprint $table) {
$table->increments('cod_estado');
$table->string('des_estado',9);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class Zonas extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('zonas', function (Blueprint $table) {
$table->increments('cod_zona');
$table->string('des_zona',60);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CiudadesLugares extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('ciudades_lugares', function (Blueprint $table) {
$table->increments('cod_ciudad');
$table->string('des_ciudad',25);
$table->integer('cod_zona')->unsigned();
$table->foreign('cod_zona')->references('cod_zona')->on('zonas');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
Ahora que ya tenemos los datos para nuestras tablas en la base de datos vamos a crear el modelo para que podamos manejar la tabla socio, para ello creamos el archivo socio.php en el directorio app y nos posicionamos en app/socio.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class socio extends Model
{
public $fillable = ['id','rut_socio','nombre','apellido_paterno','apellido_materno','domicilio','subsidio','fecha_ingreso','cod_estado','cod_ciudad'];
}
En resumidas cuentas son todos los campos de nuestra tabla socio.
Controlador
Ahora crearemos un controlador para que podamos aplicar todos los métodos que nos permiten obtener los registros en la base de datos, actualizar los datos y eliminarlo. En este tenemos que avisar que ocuparemos el modelo socio, tendremos las funciones index, store, update y destroy. Creamos un archivo VueItemController.php en el directorio app/Http/Controllers.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Socio;
class VueItemController extends Controller
{
public function manageVue()
{
return view('manage-vue');
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$socios = Socio::latest()->paginate(5);
$response = [
'pagination' => [
'total' => $socios->total(),
'per_page' => $socios->perPage(),
'current_page' => $socios->currentPage(),
'last_page' => $socios->lastPage(),
'from' => $socios->firstItem(),
'to' => $socios->lastItem()
],
'data' => $socios
];
return response()->json($response);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$this->validate($request, [
'id' => 'required',
'rut_socio' => 'required',
'nombre' => 'required',
'apellido_paterno' => 'required',
'apellido_materno' => 'required',
'domicilio' => 'required',
'subsidio' => 'required',
'fecha_ingreso' => 'required',
'cod_estado' => 'required',
'cod_ciudad' => 'required',
]);
$create = Socio::create($request->all());
return response()->json($create);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$this->validate($request, [
'id' => 'required',
'rut_socio' => 'required',
'nombre' => 'required',
'apellido_paterno' => 'required',
'apellido_materno' => 'required',
'domicilio' => 'required',
'subsidio' => 'required',
'fecha_ingreso' => 'required',
'cod_estado' => 'required',
'cod_ciudad' => 'required',
]);
$edit = Socio::find($id)->update($request->all());
return response()->json($edit);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
Socio::find($id)->delete();
return response()->json(['done']);
}
}
Ruteo
Se agrega la ruta en el web.php y no en la sección de la api, al instalar el mix podemos estamos trabajando en Laravel con el framework de Vue instalado en la estructura de proyecto de Laravel. Aca pasamos la dirección de ruteo del controlador. Para ello nos posicionamos en /routes/web.php.
<?php
Route::get('/', function () {
return view('welcome');
});
Route::get('manage-vue', 'VueItemController@manageVue');
Route::resource('vueitems','VueItemController');
Trabajamos la vista
para ello creamos un archivo manage-vue.blade.php en el directorio /resources/views/ .En esta sección trabajamos el HTML de manera normal pero ocuparemos Vue para llamar todos los datos del modelo. Lo que haremos sera una tabla que cargue los datos de los socios, un botón que nos cargará un modal que nos permite insertar todos los campos y los botones para eliminar y editar.
<!DOCTYPE html>
<html>
<head>
<title>Laravel y VueJS</title>
<meta id="token" name="token" value="{{ csrf_token() }}">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.css">
</head>
<body>
<div>
</div>
<div class="container" id="manage-vue">
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-center">
<h2>Laravel y VueJS socios CRUD</h2>
</div>
<div class="pull-right">
<img src="https://upload.wikimedia.org/wikipedia/commons/5/5d/Vue.js_Logo.png">
<button type="button" class="btn btn-success" data-toggle="modal" data-target="#create-item">
Agregar socio
</button>
</div>
</div>
</div>
<!-- Item Listing -->
<table class="table table-bordered">
<tr>
<th>Codigo</th>
<th>Rut</th>
<th>Nombre</th>
<th>Apellido</th>
<th>Domicilio</th>
<th>Fecha de ingreso</th>
<th width="200px">Action</th>
</tr>
<tr v-for="socio in socios">
<td>@{{ socio.id }}</td>
<td>@{{ socio.rut_socio }}</td>
<td>@{{ socio.nombre }}</td>
<td>@{{ socio.apellido_paterno }}</td>
<td>@{{ socio.domicilio }}</td>
<td>@{{ socio.fecha_ingreso }}</td>
<td>
<button class="btn btn-primary" @click.prevent="editItem(socio)">Editar</button>
<button class="btn btn-danger" @click.prevent="deleteItem(socio)">Borrar</button>
</td>
</tr>
</table>
<!-- Pagination -->
<nav>
<ul class="pagination">
<li v-if="pagination.current_page > 1">
<a href="#" aria-label="Previous"
@click.prevent="changePage(pagination.current_page - 1)">
<span aria-hidden="true">«</span>
</a>
</li>
<li v-for="page in pagesNumber"
v-bind:class="[ page == isActived ? 'active' : '']">
<a href="#"
@click.prevent="changePage(page)">@{{ page }}</a>
</li>
<li v-if="pagination.current_page < pagination.last_page">
<a href="#" aria-label="Next"
@click.prevent="changePage(pagination.current_page + 1)">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
<!-- Create Item Modal -->
<div class="modal fade" id="create-item" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Agregar nuevo Cliente</h4>
</div>
<div class="modal-body">
<form method="POST" enctype="multipart/form-data" v-on:submit.prevent="createItem">
<div class="form-group">
<label for="title">Codigo de socio</label>
<input type="text" name="id" class="form-control" v-model="newItem.id" />
<span v-if="formErrors['id']" class="error text-danger">@{{ formErrors['id'] }}</span>
</div>
<div class="form-group">
<label for="title">Rut</label>
<textarea name="rut_socio" class="form-control" v-model="newItem.rut_socio"></textarea>
<span v-if="formErrors['rut_socio']" class="error text-danger">@{{ formErrors['rut_socio'] }}</span>
</div>
<div class="form-group">
<label for="title">Nombre</label>
<textarea name="nombre" class="form-control" v-model="newItem.nombre"></textarea>
<span v-if="formErrors['nombre']" class="error text-danger">@{{ formErrors['nombre'] }}</span>
</div>
<div class="form-group">
<label for="title">Apellido Paterno</label>
<textarea name="apellido_paterno" class="form-control" v-model="newItem.apellido_paterno"></textarea>
<span v-if="formErrors['apellido_paterno']" class="error text-danger">@{{ formErrors['apellido_paterno'] }}</span>
</div>
<div class="form-group">
<label for="title">Apellido Materno</label>
<textarea name="apellido_materno" class="form-control" v-model="newItem.apellido_materno"></textarea>
<span v-if="formErrors['apellido_materno']" class="error text-danger">@{{ formErrors['apellido_materno'] }}</span>
</div>
<div class="form-group">
<label for="title">Domicilio</label>
<textarea name="domicilio" class="form-control" v-model="newItem.domicilio"></textarea>
<span v-if="formErrors['domicilio']" class="error text-danger">@{{ formErrors['domicilio'] }}</span>
</div>
<div class="form-group">
<label for="title">Subcidio</label>
<textarea name="subsidio" class="form-control" v-model="newItem.subsidio"></textarea>
<span v-if="formErrors['subsidio']" class="error text-danger">@{{ formErrors['subsidio'] }}</span>
</div>
<div class="form-group">
<label for="title">Fecha de ingreso</label>
<input type="date" name="fecha_ingreso" class="form-control" v-model="newItem.fecha_ingreso">
<span v-if="formErrors['fecha_ingreso']" class="error text-danger">@{{ formErrors['fecha_ingreso'] }}</span>
</div>
<div class="form-group">
<label for="title">Codigo estado</label>
<textarea name="cod_estado" class="form-control" v-model="newItem.cod_estado"></textarea>
<span v-if="formErrors['cod_estado']" class="error text-danger">@{{ formErrors['cod_estado'] }}</span>
</div>
<div class="form-group">
<label for="title">codigo de ciudad</label>
<textarea name="cod_ciudad" class="form-control" v-model="newItem.cod_ciudad"></textarea>
<span v-if="formErrors['cod_ciudad']" class="error text-danger">@{{ formErrors['cod_ciudad'] }}</span>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success">Agregar</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Edit Item Modal -->
<div class="modal fade" id="edit-item" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Editar Socio</h4>
</div>
<div class="modal-body">
<form method="POST" enctype="multipart/form-data" v-on:submit.prevent="updateItem(fillItem.id)">
<div class="form-group">
<label for="for">Codigo del socio</label>
<input type="text" name="id" class="form-control" v-model="fillItem.id" />
<span v-if="formErrorsUpdate['id']" class="error text-danger">@{{ formErrorsUpdate['id'] }}</span>
</div>
<div class="form-group">
<label for="title">Rut</label>
<textarea name="rut_socio" class="form-control" v-model="fillItem.rut_socio"></textarea>
<span v-if="formErrorsUpdate['rut_socio']" class="error text-danger">@{{ formErrorsUpdate['rut_socio'] }}</span>
</div>
<div class="form-group">
<label for="title">Nombre</label>
<textarea name="nombre" class="form-control" v-model="fillItem.nombre"></textarea>
<span v-if="formErrorsUpdate['nombre']" class="error text-danger">@{{ formErrorsUpdate['nombre'] }}</span>
</div>
<div class="form-group">
<label for="title">Apellido Paterno</label>
<textarea name="apellido_paterno" class="form-control" v-model="fillItem.apellido_paterno"></textarea>
<span v-if="formErrorsUpdate['apellido_paterno']" class="error text-danger">@{{ formErrorsUpdate['apellido_paterno'] }}</span>
</div>
<div class="form-group">
<label for="title">Apellido Materno</label>
<textarea name="apellido_materno" class="form-control" v-model="fillItem.apellido_materno"></textarea>
<span v-if="formErrorsUpdate['apellido_materno']" class="error text-danger">@{{ formErrorsUpdate['apellido_materno'] }}</span>
</div>
<div class="form-group">
<label for="title">Domicilio</label>
<textarea name="domicilio" class="form-control" v-model="fillItem.domicilio"></textarea>
<span v-if="formErrorsUpdate['domicilio']" class="error text-danger">@{{ formErrorsUpdate['domicilio'] }}</span>
</div>
<div class="form-group">
<label for="title">Subsidio</label>
<textarea name="subsidio" class="form-control" v-model="fillItem.subsidio"></textarea>
<span v-if="formErrorsUpdate['subsidio']" class="error text-danger">@{{ formErrorsUpdate['subsidio'] }}</span>
</div>
<div class="form-group">
<label for="title">Fecha de ingreso</label>
<input type="date" name="fecha_ingreso" class="form-control" v-model="fillItem.fecha_ingreso">
<span v-if="formErrorsUpdate['fecha_ingreso']" class="error text-danger">@{{ formErrorsUpdate['fecha_ingreso'] }}</span>
</div>
<div class="form-group">
<label for="title">Codigo estado</label>
<textarea name="cod_estado" class="form-control" v-model="fillItem.cod_estado"></textarea>
<span v-if="formErrorsUpdate['cod_estado']" class="error text-danger">@{{ formErrorsUpdate['cod_estado'] }}</span>
</div>
<div class="form-group">
<label for="title">Codigo de ciudad</label>
<textarea name="cod_ciudad" class="form-control" v-model="fillItem.cod_ciudad"></textarea>
<span v-if="formErrorsUpdate['cod_ciudad']" class="error text-danger">@{{ formErrorsUpdate['cod_ciudad'] }}</span>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success">Actualizar</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/js/bootstrap.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css" rel="stylesheet">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/vue.resource/0.9.3/vue-resource.min.js"></script>
<script type="text/javascript" src="/js/socio.js"></script>
</body>
</html>
Socio.Js
En la primera parte tenemos nuestro token. A posterior tenemos la sección en donde declaramos el constructor de vue. Después vemos “el” que es en donde declaramos el dominio. El resto es en donde socio lo declaramos como arreglo, tenemos la paginación que nos permite agrupar los registros por páginas.
La sección que computaded nos permite realizar la paginaciones que en resumidas cuenta ve si la tabla agrupa más de 5 elementos (la cantidad la definimos en la “offset: 4”, la podemos cambiar a gusto personal).
Despues de ello tenemos lo métodos:
El método getVueItems es la que nos trae los registros, this.$http.get('/vueitems?page='+page).then((response), acá pasamos ocupamos el método get y pasamos como ruta vueitems, recordemos que lo definimos en la ruta para que nos cargue el controlador VueItemController.
El método createItem toma los datos que escribimos en el modal los guarda en newItem que lo definimos en la línea 19 con todos los campos que necesitavamos, y eso lo guarda en una variable. Luego por método post pasamos la ruta vueItems y pasamos la variable en donde teníamos los datos del nuevo socio, se aplica se carga la paginación en caso de que esta sea necesaria y luego un mensaje de que el socio se guardó correctamente.
Para el método deleteItem le pasamos como parámetro un socio, ocupamos el método delete al que le pasamos la ruta para acceder al controlador y concatenamos el valor del código del socio para que por medio de él lo podamos eliminar. Se aplica el cambio de pagina en caso de ser necesario y un mensaje que nos avisa que el socio fue eliminado.
El método editItem también le pasamos como parámetro un socio para que cuando se habrá la vista de edición nos aparezcan todos los datos del socio y podamos cambiar únicamente el campo que necesitamos.
El método updateItem le pasamos el código del socio guardamos en una variable todos los datos que están en el modal (esto pasa cuando pinchamos en botón actualizar en la vista de edicion). Ocupamos el método put y le pasamos la ruta mas el código del socio y los valores que deseamos que se actualicen. A posterior dejamos los campos vacios en el modal y enviamos un mensaje de aviso que se actualizo el socio.
El método changePage es el que nos permite cambiar la página dependiendo de en qué página nos encontramos o si eliminamos un registro.
Vue.http.headers.common['X-CSRF-TOKEN'] = $("#token").attr("value");
new Vue({
el: '#manage-vue',
data: {
socios: [],
pagination: {
total: 0,
per_page: 2,
from: 1,
to: 0,
current_page: 1
},
offset: 4,
formErrors:{},
formErrorsUpdate:{},
newItem : {'id':'','rut_socio':'','nombre':'','apellido_paterno':'','apellido_materno':'','domicilio':'','subsidio':'','fecha_ingreso':'','cod_estado':'','cod_ciudad':''},
fillItem : {'id':'','rut_socio':'','nombre':'','apellido_paterno':'','apellido_materno':'','domicilio':'','subsidio':'','fecha_ingreso':'','cod_estado':'','cod_ciudad':''}//pasar el id para actualiazr y elimnar
},
computed: {
isActived: function () {
return this.pagination.current_page;
},
pagesNumber: function () {
if (!this.pagination.to) {
return [];
}
var from = this.pagination.current_page - this.offset;
if (from < 1) {
from = 1;
}
var to = from + (this.offset * 2);
if (to >= this.pagination.last_page) {
to = this.pagination.last_page;
}
var pagesArray = [];
while (from <= to) {
pagesArray.push(from);
from++;
}
return pagesArray;
}
},
ready : function(){
this.getVueItems(this.pagination.current_page);
},
methods : {
getVueItems: function(page){
this.$http.get('/vueitems?page='+page).then((response) => {
this.$set('socios', response.data.data.data);
this.$set('pagination', response.data.pagination);
});
},
createItem: function(){
var input = this.newItem;
this.$http.post('/vueitems',input).then((response) => {
this.changePage(this.pagination.current_page);
this.newItem = {'id':'','rut_socio':'','nombre':'','apellido_paterno':'','apellido_materno':'','domicilio':'','subsidio':'','fecha_ingreso':'','cod_estado':'','cod_ciudad':''};
$("#create-item").modal('hide');
toastr.success('Socio agregado.', '', {timeOut: 5000});
}, (response) => {
this.formErrors = response.data;
});
},
deleteItem: function(socio){
this.$http.delete('/vueitems/'+socio.id).then( (response) => {
this.changePage(this.pagination.current_page);
toastr.success('Socio eliminado', 'Success Alert', {timeOut: 5000});
});
},
editItem: function(socio){
this.fillItem.id = socio.id;
this.fillItem.rut_socio = socio.rut_socio;
this.fillItem.nombre = socio.nombre;
this.fillItem.apellido_paterno = socio.apellido_paterno;
this.fillItem.apellido_materno = socio.apellido_materno;
this.fillItem.domicilio = socio.domicilio;
this.fillItem.subsidio = socio.subsidio;
this.fillItem.fecha_ingreso = socio.fecha_ingreso;
this.fillItem.cod_estado = socio.cod_estado;
this.fillItem.cod_ciudad = socio.cod_ciudad;
$("#edit-item").modal('show');
},
updateItem: function(cod_socio){
var input = this.fillItem;
this.$http.put('/vueitems/'+cod_socio,input).then((response) => {
this.changePage(this.pagination.current_page);
this.fillItem = {'id':'','rut_socio':'','nombre':'','apellido_paterno':'','apellido_materno':'','domicilio':'','subsidio':'','fecha_ingreso':'','cod_estado':'','cod_ciudad':''};
$("#edit-item").modal('hide');
toastr.success('Socio Actualizado.', 'Success Alert', {timeOut: 5000});
}, (response) => {
this.formErrorsUpdate = response.data;
});
},
changePage: function (page) {
this.pagination.current_page = page;
this.getVueItems(page);
}
}
});