Formulario A-B-C con MySQL, PHP y AngularJS

¡AngularJS es una maravilla!, no puedo describirlo con palabras. De hecho, creo que intentarlo sería perdida de tiempo. Haré algo mejor, les voy a mostrar como usarlo con PHP y MySQL.
Así que hoy voy a mostrarles como crear un super facil y escalable formulario ABC (Altas, Bajas y Cambios). La idea básica es que tengamos un listado de los registros, que podamos agregar, editar y eliminar segun lo deseemos.
Voy a obviar explicaciones innecesarias, aunque no estaría de mas leer un poco en la página oficial de AngularJS o el super-fácil tutorial de AngularJS en W3Schools.
Este tutorial está diseñado para programadores de nivel intermedio; aunque sospecho que cualquier programador "tyro" de PHP podría comprenderlo y aplicarlo a diferentes situaciones. Las ideas que voy a presentarles son bastante simples y basicas, pero tan sólidas que pueden usarse de base para diseñar todos tus catalogos usando este método, incluso alguien con suficientes conocimientos podría combinarlo con otros plugins mejorarlo. Cabe mencionar que mas adelante voy a publicar este mismo método para ASP.Net.



¡Manos a la obra!

Requisitos previos:

1. MySQL
2. MySQLWorkbench
3. Software para programar PHP. Los tyros usan IDE como NetBeans o Eclipse. Los avanzados usan editores de texto.

Para obviar la parte de MySQL, les dejo aquí el código para crear la base de datos y los objetos que vamos a necesitar (utilizar MySQLWorkbench para ejecutarlo):

create schema if not exists ejemplos default character set latin1 collate latin1_general_ci;
use ejemplos;
drop table if exists productos;
create table productos
(
 id_producto int not null auto_increment,
    codigo_barras nvarchar(50) not null,
    nombre_producto nvarchar(100) not null,
    stock float not null default 0,
    precio_venta float not null default 0,
    constraint pk_productos primary key(id_producto),
 constraint uq_productos_codigo_barras unique(codigo_barras)
);
/*insertar datos de prueba*/
insert into productos(codigo_barras, nombre_producto, stock, precio_venta) values('0000000001','COCA-COLA 600 ML', 10, 15);
insert into productos(codigo_barras, nombre_producto, stock, precio_venta) values('0000000002','COCA-COLA LIGHT 600 ML', 10, 15);
insert into productos(codigo_barras, nombre_producto, stock, precio_venta) values('0000000003','SPRITE 600 ML', 10, 15);
/*mostrar datos*/
select * from productos
/*procedimientos almacenados*/
delimiter ;
drop procedure if exists proc_ProductoBuscar;
delimiter //
create procedure proc_ProductoBuscar
(
 prmTextoBuscar nvarchar(50)
)
begin
 select p.id_producto, p.codigo_barras, p.nombre_producto, p.stock, p.precio_venta
    from productos p
    where concat(p.codigo_barras,' ', p.nombre_producto) like concat('%', prmTextoBuscar, '%');
end
//
delimiter ;
drop procedure if exists proc_ProductoGrabar;
delimiter //
create procedure proc_ProductoGrabar
(
 prmIdProducto int,
    prmCodigoBarras nvarchar(50),
    prmNombreProducto nvarchar(100),
    prmStock float,
    prmPrecioVenta float
)
begin
 if (prmIdProducto = 0) then
  begin
   if exists(select 1 from productos where codigo_barras = prmCodigoBarras) then
    signal sqlstate '45000' set message_text = 'Ya existe otro producto con el mismo codigo de barras';
   end if;
   /*Insertar registro*/
   insert into productos(codigo_barras, nombre_producto, stock, precio_venta) 
            values(prmCodigoBarras,prmNombreProducto, prmStock, prmPrecioVenta);
            /*Obtener Id generado*/
            set prmIdProducto = last_insert_id();

  end;
 else
  begin
   if exists(select 1 from productos where codigo_barras = prmCodigoBarras and id_producto <> prmIdProducto) then
    signal sqlstate '45000' set message_text = 'Ya existe otro producto con el mismo codigo de barras';
   end if;
   update productos set
            codigo_barras = prmCodigoBarras,
            nombre_producto = prmNombreProducto,
            stock = prmStock,
            precio_venta = prmPrecioVenta
            where id_producto = prmIdProducto;
        end;
    end if;
    select prmIdProducto;
end;
//
delimiter ;
drop procedure if exists proc_ProductoEliminar;
delimiter //
create procedure proc_ProductoEliminar
(
 prmIdProducto int
)
begin
 delete
    from productos 
    where id_producto=prmIdProducto;
    select prmIdProducto;
end
//
delimiter ;

Lo primero que tenemos que hacer es agregar una página PHP a nuestro proyecto. La llamaremos "formulario-abc.php" y tendrá el siguiente código base:

<?php
error_reporting(E_ALL & ~E_DEPRECATED);
ini_set("display_errors", 1);
?>
<!DOCTYPE html>
<!--
 * Description of Productos
 *
 * @author tyrodeveloper
-->
<html>
    <head>
        <meta charset="UTF-8">
        <title>Formulario A-B-C</title>
        <!--Referencias CSS-->
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    </head>
    <body>
        <!--PONER AQUÍ EL CÓDIGO HTML-->
        
        <!--Referencias Javascript-->
        <script src="https://code.jquery.com/jquery-3.2.1.min.js" type="text/javascript"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" type="text/javascript"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js" type="text/javascript"></script>
       
        <script type="text/javascript">
            //Código Javascript
        </script>
    </body>
</html>

Ésta página se comportará principalmente como una página HTML y contiene todas las referencias que vamos a necesitar. Tendrá la siguiente apariencia inicial:

Ahora vamos a darle un poco de forma. Hagamos las modificaciones necesarias para que el código quede asi:

<?php
error_reporting(E_ALL & ~E_DEPRECATED);
ini_set("display_errors", 1);
?>
<!DOCTYPE html>
<!--
 * Description of Productos
 *
 * @author tyrodeveloper
-->
<html>
    <head>
        <meta charset="UTF-8">
        <title>Formulario A-B-C</title>
        <!--Referencias CSS-->
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    </head>
    <body>
        <!--PONER AQUÍ EL CÓDIGO HTML-->
        <div class="container-fluid">
            <h1>Formulario A-B-C</h1>
            <hr />
            <a href="#" class="btn btn-primary" ><i class="glyphicon glyphicon-plus"></i> Agregar nuevo producto</a>
            <br /><br />
            <div class ="row">
                <div class="col-sm-6">
                    <div class="form-group input-group">
                        <input type="text" class="form-control" id="txtTextoBuscar" placeholder="Código de barras, Nombre del producto" />
                        <span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span>
                    </div>
                </div>
            </div>

            <div class ="row">
                <div class="col-sm-12">
                    <table class="table table-bordered table-striped table-hover">
                        <thead>
                            <tr>
                                <th colspan="6" class="text-center">Listado de Productos</th>
                            </tr>
                            <tr>
                                <th class="text-center"><i class="glyphicon glyphicon-pencil"></i></th>
                                <th class="text-center"><i class="glyphicon glyphicon-trash"></i></th>
                                <th>Código de barras</th>
                                <th>Nombre Producto</th>
                                <th class="text-right">Stock</th>
                                <th class="text-right">Precio Venta</th>
                            </tr>
                        </thead>
                    </table> 
                </div>
            </div>
            <div class="row text-center">
                <small><a href="http://www.tyrodeveloper.com">www.tyrodeveloper.com</a></small>
            </div>
        </div>
        <!--Referencias Javascript-->
        <script src="https://code.jquery.com/jquery-3.2.1.min.js" type="text/javascript"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" type="text/javascript"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js" type="text/javascript"></script>

        <script type="text/javascript">
            //Código Javascript
        </script>
    </body>
</html>

El código anterior nos debe dar la siguiente apariencia:

Te sugiero que no avances hasta conseguirlo.

Con mucho cuidado, vamos a agregar las lineas necesarias para habilitar "AngularJS". En primera instancia, modificaremos la etiqueta "body" de nuestro documento php. Luego de ubicarla, nos aseguramos de que quede asi:

<body id="ng-producto-lista" ng-app="appCatalogos" ng-controller="cProductos">

Luego, dentro del código Javascript, agregamos lo siguiente:

var myApp = angular.module('appCatalogos', []);

De esta manera habilitamos AngularJS en nuestro documento php.

Mostrar el listado de productos

Agregamos un archivo PHP llamado "Connection.php", con el siguiente código:

<?php
error_reporting(E_ALL);
ini_set('display_errors',1);
class Connection{
    private $conn;
    public function __construct(){
        $this->conn = new mysqli("127.0.0.1", "root", "admin", "ejemplos");
    }
    
    public function getConnection(){
        return $this->conn;
    }
}
?>

Es necesario modificar los parámetros para conectarnos al servidor MySQL.

Luego, agregamos un archivo PHP llamado "cod-formulario-abc.php", con el siguiente código:

<?php

/**
 * Description of class
 *
 * @author tyrodeveloper
 */
error_reporting(E_ALL & ~E_DEPRECATED);
ini_set("display_errors", "1");
date_default_timezone_set("America/Mexico_City");
spl_autoload_register(function( $NombreClase ) {
    require_once $NombreClase . '.php';
});

class Productos {

    public $id_producto = 0;
    public $codigo_barras = "";
    public $nombre_producto = "";
    public $stock = 0;
    public $precio_venta = 0;

    function Buscar($textoBuscar) {
        $mysql = new Connection();
        $cnn = $mysql->getConnection();
        $retorno = array();
        $query = $cnn->prepare("call proc_ProductoBuscar (?)");
        $query->bind_param("s", $textoBuscar);
        $query->execute();
        $producto = new Productos(); //Variable
        $query->bind_result(
                $id_producto, $codigo_barras, $nombre_producto, $stock, $precio_venta
        );
        while ($query->fetch()) {
            $producto = new Productos();
            $producto->id_producto = $id_producto;
            $producto->codigo_barras = $codigo_barras;
            $producto->nombre_producto = $nombre_producto;
            $producto->stock = $stock;
            $producto->precio_venta = $precio_venta;
            array_push($retorno, $producto);
        }
        $query->close();
        $cnn->close();
        return $retorno;
    }
    function ArrayMessage($status, $message) {
       $retorno = array("status" => $status, "message" => $message, "date" => date("Y-m-d H:i:s"));
       return $retorno;
    }
}

if (session_status() == PHP_SESSION_NONE) {
    session_start();
}

if (isset($_GET["functionToCall"]) && !empty($_GET["functionToCall"])) {
    $functionToCall = $_GET["functionToCall"];
    $json_data = json_decode(file_get_contents('php://input'));
    switch ($functionToCall) {
        case "buscar_producto":
            $producto = new Productos();
            echo json_encode($producto->Buscar(utf8_decode($json_data->textoBuscar)));
            break;
    }
}

Una vez hecho esto, regresamos a nuestro documento PHP principal y modificamos el objeto de texto agregando la propiedad "ng-keyup", así:

<input type="text" class="form-control" ng-keyup="$event.keyCode == 13 ? BuscarProducto() : null" id="txtTextoBuscar" placeholder="Código de barras, Nombre del producto" />

De esta manera, cuando presionemos la tecla "Enter" se ejecutará el método "BuscarProducto", el cual crearemos un poco mas adelante.

También es necesario modificar la tabla en la que se mostrarán los registros, para hacerlo, modificaremos su código para que quede asi:

<table class="table table-bordered table-striped table-hover">
    <thead>
        <tr>
            <th colspan="6" class="text-center">Listado de Productos</th>
        </tr>
        <tr>
            <th class="text-center"><i class="glyphicon glyphicon-pencil"></i></th>
            <th class="text-center"><i class="glyphicon glyphicon-trash"></i></th>
            <th>Código de barras</th>
            <th>Nombre Producto</th>
            <th class="text-right">Stock</th>
            <th class="text-right">Precio Venta</th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="producto in listaProductos">
            <td class="text-center"><a href="#" class="btn btn-sm btn-primary"><i class="glyphicon glyphicon-pencil"></i></a></td>
            <td class="text-center"><a href="#" class="btn btn-sm btn-danger"><i class="glyphicon glyphicon-trash"></i></a></td>
            <td>{{producto.codigo_barras}}</td>
            <td>{{producto.nombre_producto}}</td>
            <td class="text-right">{{producto.stock|number:2}}</td>
            <td class="text-right">{{producto.precio_venta| currency}}</td>
        </tr>
    </tbody>
</table>

Para finalizar esta parte, agregamos el asiguiente código Javascript:

myApp.controller('cProductos', function ($scope, $http) {
    var myData = {textoBuscar: ''};
    $scope.producto = {id_producto: 0, codigo_barras: "", nombre_producto: "", stock: 0, precio_venta: 0};
    $http({
        method: "POST",
        url: 'cod-formulario-abc.php?functionToCall=buscar_producto',
        data: myData}).then(function (response) {
        $scope.listaProductos = response.data;
    });
    $scope.BuscarProducto = function () {
        var myData = {textoBuscar: String($("#txtTextoBuscar").val())};
        $http({
            method: "POST",
            url: 'cod-formulario-abc.php?functionToCall=buscar_producto',
            data: myData}).then(function (response) {
            $scope.listaProductos = response.data;
        });
    };
});

Si hemos hecho bien este proceso, podemos ejecutar nuestra aplicación. Veremos algo como lo siguiente:

Hemos terminado la primera parte de este tutorial.

Solo nos resta hacer pruebas de funcionamiento para confirmar que hayamos hecho todo bien.

Agregando registros

Lo primero que haremos será agregar un formulario "modal". Para conseguirlo, agregamos el siguiente código:

<!-- Modal Producto -->
<div id="modalProducto" class="modal fade" role="dialog">
    <div class="modal-dialog">
        <!-- Modal content-->
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">×</button>
                <h4 class="modal-title">Producto</h4>
            </div>
            <div class="modal-body">
                <div class="row">
                    <div class="col-sm-6">
                        <label>Código de barras:</label>
                        <div class="form-group input-group">

                            <input type="text" id="txtCodigoBarras" ng-model="producto.codigo_barras" class="form-control" />
                            <span class="input-group-addon"><i class="glyphicon glyphicon-barcode"></i></span>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-sm-12">
                        <div class="form-group">
                            <label>Nombre del producto:</label>
                            <input type="text" id="txtNombreProducto" ng-model="producto.nombre_producto" class="form-control" />
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-sm-6">
                        <label>Stock:</label>
                        <div class="form-group input-group">
                            <input type="text" id="txtStock" ng-model="producto.stock" class="form-control" />
                            <span class="input-group-addon">0.00</span>
                        </div>
                    </div>
                    <div class="col-sm-6">
                        <label>Precio Venta:</label>
                        <div class="form-group input-group">

                            <input type="text" id="txtStock" ng-model="producto.precio_venta" class="form-control" />
                            <span class="input-group-addon"><i class="glyphicon glyphicon-usd"></i></span>
                        </div>
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" ng-click="Grabar()" class="btn btn-primary" ><i class="glyphicon glyphicon-floppy-disk"></i> Grabar</button>
                <button type="button" class="btn btn-default" data-dismiss="modal"><i class="glyphicon glyphicon-remove"></i> Cancelar</button>
            </div>
        </div>

    </div>
</div>
<!-- Modal Producto -->

Ahora, es necesario identificar el botón "+ Agregar nuevo producto" y modificar su código, para que quede asi:

<a href="#" class="btn btn-primary" ng-click="AbrirNuevo();"><i class="glyphicon glyphicon-plus"></i> Agregar nuevo producto</a>

Con mucho cuidado, agregamos el siguiente código Javascript "dentro de myApp.controller":

$scope.AbrirEditar = function (item) {
    $scope.producto = item;
    $("#modalProducto").modal();
};
$scope.AbrirNuevo = function () {
    $scope.producto = {id_producto: 0, codigo_barras: "", nombre_producto: "", stock: 0, precio_venta: 0};
    $("#modalProducto").modal();
};
$scope.Grabar = function () {
    $http({
        method: "POST",
        url: 'cod-formulario-abc.php?functionToCall=grabar_producto',
        data: $scope.producto}).then(function (response) {
        if (response.data.status === "1") {
            alert(response.data.message);
            $scope.BuscarProducto();
            $("#modalProducto").modal("hide");
        } else {
            alert(response.data.message);
        }
    });
};

También, en la tabla, ubicamos la línea en donde está el botón para eliminar y lo modificamos para que quede asi:

<td class="text-center"><a href="#" ng-click="AbrirEditar(this.producto);" class="btn btn-sm btn-primary"><i class="glyphicon glyphicon-pencil"></i></a></td>

Modificamos el archivo "cod-formulario-abc.php" y agregamos la función "Grabar()":

function Grabar() {
    $mysql = new Connection();
    $cnn = $mysql->getConnection();
    $retorno = $this->ArrayMessage("0", "No se ha realizado ninguna acción.");
    $query = $cnn->prepare("call proc_ProductoGrabar (?,?,?,?,?)");
    $query->bind_param("issdd", $this->id_producto, $this->codigo_barras, $this->nombre_producto, $this->stock, $this->precio_venta);
    $query->execute();
    $query->store_result();
    if (mysqli_stmt_error($query) != "") {
        $retorno = $this->ArrayMessage("0", mysqli_stmt_error($query));
    }
    //Verificar si se obtubieron resultados
    if ($query->num_rows != 0) {
        $query->bind_result($this->id_producto);
        if ($query->fetch()) {
            if (is_null($this->id_producto)) {
                $retorno = $this->ArrayMessage("0", "No se ha realizado ninguna acción. El error se desconoce.");
            } else {
                $retorno = $this->ArrayMessage("1", "El producto ha sido grabado correctamente.");
            }
        }
    }
    $query->close();
    $cnn->close();
    return $retorno;
}

En el mismo archivo, agregamos otro "case" (En la parte inferior del mismo):

case "grabar_producto":
    $producto = new Productos();
    $producto->id_producto = $json_data->id_producto;
    $producto->codigo_barras = $json_data->codigo_barras;
    $producto->nombre_producto = $json_data->nombre_producto;
    $producto->stock = $json_data->stock;
    $producto->precio_venta = $json_data->precio_venta;
    echo json_encode($producto->Grabar());
    break;

Una vez hecho lo anterior, procedemos a realizar pruebas:

Si hacemos clic en el botón "+ Agregar nuevo producto", se verá lo siguiente:

Luego, llenamos todos los datos (escribimos a propósito un código de barras que ya existe):

Corregimos el código de barras y presionamos el botón "Grabar":

Se agrega el producto a la lista:


Eliminando Registros

Para continuar nuestro ejercicio y agregar la funcionalidad para eliminar, agregamos el siguiente código Javascript (Dentro del Controler):

$scope.AbrirEliminar = function (item) {
    $scope.producto = item;
    $("#modalProductoEliminar").modal();
};
$scope.Eliminar = function () {
    $http({
        method: "POST",
        url: 'cod-formulario-abc.php?functionToCall=eliminar_producto',
        data: $scope.producto}).then(function (response) {
        if (response.data.status === "1") {
            alert(response.data.message);
            $scope.BuscarProducto();
            $("#modalProductoEliminar").modal("hide");
        } else {
            alert(response.data.message);
        }
    });
};

Luego, agregamos el siguiente código HTML, el cual contiene una ventana modal:

<!-- Modal Eliminar-->
<div id="modalProductoEliminar" class="modal fade" role="dialog">
    <div class="modal-dialog">

        <!-- Modal content-->
        <div class="modal-content">
            <div class="modal-header text-center">
                <button type="button" class="close" data-dismiss="modal">×</button>
                <h4 class="modal-title" style="color:red;">¿Realmente desea eliminar el producto?</h4>
            </div>
            <div class="modal-body">
                <div class="row">
                    <div class="col-sm-6">
                        <label>Código de barras:</label>
                        <div class="form-group input-group">

                            <input type="text" ng-model="producto.codigo_barras" readonly="true" class="form-control" />
                            <span class="input-group-addon"><i class="glyphicon glyphicon-barcode"></i></span>
                        </div>
                    </div>
                </div>
                <div class="row">
                    <div class="col-sm-12">
                        <div class="form-group">
                            <label>Nombre del producto:</label>
                            <input type="text" ng-model="producto.nombre_producto" readonly="true" class="form-control" />
                        </div>
                    </div>
                </div>

            </div>
            <div class="modal-footer">
                <button type="button" ng-click="Eliminar()" class="btn btn-danger" ><i class="glyphicon glyphicon-trash"></i> Eliminar</button>
                <button type="button" class="btn btn-default" data-dismiss="modal"><i class="glyphicon glyphicon-remove"></i> Cancelar</button>
            </div>
        </div>
    </div>
</div>
<!-- Modal Eliminar-->

Ahora, en la tabla, ubicamos la línea en donde se encuentra el botón para eliminar y la modificamos para que quede así:

<td class="text-center"><a href="#" ng-click="AbrirEliminar(this.producto);" class="btn btn-sm btn-danger"><i class="glyphicon glyphicon-trash"></i></a></td>

Ahora, modificamos el archivo "cod-formulario-abc.php" y agregamos la función "Eliminar($idProducto)"

function Eliminar($idProducto) {
    $mysql = new Connection();
    $cnn = $mysql->getConnection();
    $retorno = $this->ArrayMessage("0", "No se ha realizado ninguna acción.");
    $query = $cnn->prepare("call proc_ProductoEliminar (?)");
    $query->bind_param("i", $idProducto);
    $query->execute();
    $query->store_result();
    if (mysqli_stmt_error($query) != "") {
        $retorno = $this->ArrayMessage("0", mysqli_stmt_error($query));
    }
    //Verificar si se obtubieron resultados
    if ($query->num_rows != 0) {
        $query->bind_result($this->id_producto);
        if ($query->fetch()) {
            if (is_null($this->id_producto)) {
                $retorno = $this->ArrayMessage("0", "No se ha realizado ninguna acción. El error se desconoce.");
            } else {
                $retorno = $this->ArrayMessage("1", "El producto ha sido eliminado.");
            }
        }
    }
    $query->close();
    $cnn->close();
    return $retorno;
}

En el mismo archivo, agregamos otra opción "case" (Al final):

case "eliminar_producto":
    $producto = new Productos();
    echo json_encode($producto->Eliminar($json_data->id_producto));
    break;


Hagamos algunas pruebas:

Si damos clic en el botón para eliminar, debemos ver lo siguiente:

Si hacemos clic en el botón "Eliminar":


Y hemos terminado.

Espero que este tutorial les sea de utilidad. Si alguien tiene alguna sugerencia de mejora, les agradecería los comentarios, de igual manera, cualquier error que encuentren.

No olvides hacer G+1

1 comentario:

  1. Buenas, no logro que muestre los datos, esa falla a que se podría adjudicar?

    ResponderEliminar