Paso 4: Crear, editar y eliminar autor.

Este paso se desarrolla con un CRUD para la entidad Editorial, y tiene dos opciones para visualizar la asociación de libros a un autor en el CRUD.

Paso 4

CRUD para Editorial

URL: https://github.com/Uniandes-isis2603/frontstepbystep.git
Release: git checkout -f paso-4

Paso 4-a

CRUD para Autor con drag and drop

URL: https://github.com/Uniandes-isis2603/frontstepbystep.git
Release: git checkout -f paso-4-a

Paso 4-b

CRUD para Editorial con select

URL: https://github.com/Uniandes-isis2603/frontstepbystep.git
Release: git checkout -f paso-4-b

Aplicación

Visualización de la lista de Editorial

Visualización de la lista de Autor

Requerimientos Nuevos

  1. Crear una Editorial.
  2. Editar una Editorial.
  3. Eliminar una Editorial.
  4. Crear un autor.
  5. Editar un autor.
  6. Eliminar un autor.

Crear una editorial

  1. Actualizar el template de lista de editorial para que se pueda acceder a crear la editorial.
  2. Crear en el módulo editorialModule el estado asociado a crear editorial.
  3. Crear el controlador asociado con ese estado.
  4. Crear el template para crear la editorial.
  5. Actualizar el index.html para incluir el nuevo controlador.

Actualizar el template de lista de editorial

En el template de editorials.list.html se añade el siguiente bloque de código para que el usuario tenga acceso al estado de creación de un nuevo autor. El estado se llama: editorialsCreate.

<!DOCTYPE html> <div class="btn-group mr-2" role="group" aria-label="First group"> <a class="btn btn-info" ui-sref="editorialsCreate"><span class="glyphicon glyphicon-plus-sign"></span> Add new editorial</a> </div>

El anterior bloque de código sigue la misma dinámica que en los pasos anteriores se hace uso de la etiqueta <a> y el ui-sref para enviarlo al estado editorialsCreate que se encuentra en el módulo de la editorial.

Crear en el módulo editorialModule el estado asociado a crear editorial

Para crear el estado asociado, se sigue la misma dinámica de los estados. Este nuevo estado también tiene de padre el estado editorials. Tiene su propio template y su propio controlador.

.state('editorialsCreate', { url: '/create', parent: 'editorials', views: { 'detailView': { templateUrl: basePath + '/new/editorials.new.html', controller: 'editorialNewCtrl' } }

Crear el controlador asociado con ese estado

Vamos a crear el controlador llamado editorialNewCtrl que tendrá la siguiente lógica con un método llamado $scope.createEditorial

... $scope.data = {}; $scope.createEditorial = function () { $http.post(editorialsContext, $scope.data).then(function (response) { $state.go('editorialsList', {editorialId: response.data.id}, {reload: true}); }); }; ...

La constante editorialsContext tiene la url donde se va a consumir el recurso REST. Se hace uso de $http pero con el método POST porque se va a persistir la información. Es similar a los pasos anteriores donde se usaba el mismo $http pero con el método GET.

Se declara un $scope.data que contiene los datos de la editorial que vamos a crear.

El siguiente bloque lo que hace es que una vez, se obtenga la respuesta del recurso se redirige al estado editorialsList

$state.go('editorialsList', {editorialId: response.data.id}, {reload: true});

Crear el template para crear la editorial

El siguiente código muestra un formulario que representa el nombre de la editorial a crear.

<!DOCTYPE html> <form name="editorial"> <div class="form-group row"> <div class="form-group col-md-6"> <label for="editorialName" class="col-sm-2">Name:</label> <input id="editorialName" required ng-model="data.name" class="col-sm-6"/> </div> </div> <div class="form-group row"> <div class="form-group col-sm-4" ng-if="!editorial.$valid"> <div class="alert alert-warning" > <strong>Warning!</strong><p>All inputs are required*</p> </div> </div> </div> <div class="form-group row"> <div class="form-group col-sm-12"> <input ng-show="editorial.$valid" ng-click="createEditorial()" value="Save" class="btn btn-success"/> <a class="btn btn-info" ui-sref="editorialsList">Cancel</a> </div> </div> </form>

Actualizar el index.html para incluir el nuevo controlador

<!DOCTYPE html> <script src="src/modules/editorials/new/editorials.new.ctrl.js" type="text/javascript"> </script>

Crear un autor

  1. Actualizar el template de lista de author para que se pueda acceder a crear el autor.
  2. Crear en el módulo authorModule el estado asociado a crear autor.
  3. Crear el controlador asociado con ese estado.
  4. Crear el template para crear el autor.
  5. Actualizar el index.html para incluir el nuevo controlador.

Actualizar el template de lista de autor

En el template de authors.list.html se añade el siguiente bloque de código para que el usuario tenga acceso al estado de creación de un nuevo autor. El estado se llama: authorsCreate.

<!DOCTYPE html> <div class="btn-group mr-2" role="group" aria-label="First group"> <a class="btn btn-info" ui-sref="authorsCreate"> <span class="glyphicon glyphicon-plus-sign"></span> Add new author</a> </div>

El anterior bloque de código sigue la misma dinámica que en los pasos anteriores se hace uso de la etiqueta <a> y el ui-sref para enviarlo al estado authorsCreate que se encuentra en el módulo del autor.

Crear en el módulo de autor el estado asociado con crear autor

Para crear el estado asociado, se sigue la misma dinámica de los estados. Este nuevo estado también tiene de padre el estado authors. Tiene su propio template y su propio controlador.

.state('authorsCreate', { url: '/create', parent: 'authors', views: { 'detailView': { templateUrl: basePath + '/new/authors.new.html', controller: 'authorNewCtrl' } } })

Crear el controlador asociado a ese estado

Vamos a crear el controlador llamado authorNewCtrl que tendrá la siguiente lógica con un método llamado $scope.createAuthor

... mod.constant("authorsContext", "api/authors"); $scope.createAuthor = function () { $http.post(authorsContext, { name: $scope.authorName, birthDate: $scope.authorBirthDate, description: $scope.authorDescription, image: $scope.authorImage }).then(function (response) { //Author created successfully $state.go('authorsList', {authorId: response.data.id}, {reload: true}); }); }; ...

La constante authorsContext tiene la url donde se va a consumir el recurso REST. Se hace uso de $http pero con el método POST porque se va a persistir la información. Es similar a los pasos anteriores donde se usaba el mismo $http pero con el método GET.

El siguiente bloque lo que hace es que una vez, se obtenga la respuesta del recurso se redirige al estado authorsList

$state.go('authorsList', {authorId: response.data.id}, {reload: true});

Crear el template para crear el autor

El template para crear el autor será un formulario como el siguiente:

<form ng-submit="createAuthor()" name="author"> <div class="form-group row"> <div class="form-group col-md-6"> <label for="authorName" class="col-sm-2">Name:</label> <input id="authorName" required ng-model="authorName" class="col-sm-6"/> </div> </div> <div class="form-group row"> <div class="form-group col-md-6"> <label for="authorBirthDate" class="col-sm-2">Birth Date:</label> <input type="date" id="authorBirthDate" placeholder="DD/MM/YYYY" required ng-model="authorBirthDate" class="col-sm-6"/> </div> </div> <div class="form-group row"> <div class="form-group col-md-6"> <label for="authorDescription" class="col-sm-2">Biography:</label> <textarea id="authorDescription" required ng-model="authorDescription" class="col-sm-6"></textarea> </div> </div> <div class="form-group row"> <div class="form-group col-md-6"> <label for="authorImage" class="col-sm-2">Image:</label> <input id="authorImage" required ng-model="authorImage" class="col-sm-6"/> </div> </div> <div class="form-group row"> <div class="form-group col-sm-4" ng-if="!author.$valid"> <div class="alert alert-warning"> <strong>Warning!</strong><p>All inputs are required*</p> </div> </div> </div> <div class="form-group row"> <div class="form-group col-sm-12"> <input ng-show="author.$valid" type="submit" value="Save" class="btn btn-success"/> <a class="btn btn-info" ui-sref="authorsList">Cancel</a> </div> </div> </form>

Actualizar el index para incluir el nuevo controlador

<script src="src/modules/authors/new/authors.new.ctrl.js" type="text/javascript"> </script>

En la aplicación se crea un autor de prueba.

Editar un autor PASO 4-a

  1. Actualizar el template de crear un author para que se pueda acceder a editar el autor.
  2. Crear en el módulo authorModule el estado asociado a editar autor.
  3. Crear el controlador asociado a ese estado.
  4. Actualizar el template authors.list
  5. Actualizar el index.html para incluir el nuevo controlador.

Aplicación

Se da clic en editar el autor que se creo, y los datos se cargan en el formulario, pero, note que aparecen dos cosas nuevas.

  1. Los libros asociados al autor
  2. Todos los libros disponibles para asociar al autor.

Se puede arrastrar el nombre libro de la zona verde que son los disponibles hacia la zona azul para asociar el libro al autor que estamos editando.

Paso 4a

Una vez se guarden los cambios, se procede a ver el detalle del autor donde vemos los dos libros que le fueron asociados.

Actualizar el template para editar el autor

Estamos reutilizando el template de crear autor, pero, hacemos uso de la directiva ng-if de AngularJS para que se muestre o no el contenido.

También hacemos uso del drag and drop propio de HTML5 para hacer mas interactivo la asociación de los libros al autor.

<div ng-if="edit" class="panel-group" id="theBooks"> <div class="panel panel-info"> <div class="panel-heading">Associated books</div> <div class="panel-body"> <div id="div2" ondrop="angular.element(document.getElementById('theBooks')).scope().dropAdd(event)" ondragover="angular.element(document.getElementById('theBooks')).scope().allowDrop(event)"> <label></label> <div ng-show="edit" ng-repeat="book in allBooksAuthor"> <p draggable="true" ondragstart="angular.element(document.getElementById('theBooks')).scope().drag(event)" id="{{book.id}}">{{book.name}}</p> </div> </div> </div> </div> <div class="panel panel-success"> <div class="panel-heading">All books</div> <div class="panel-body"> <div id="div1" ondrop="angular.element(document.getElementById('theBooks')).scope().dropDelete(event)" ondragover="angular.element(document.getElementById('theBooks')).scope().allowDrop(event)"> <label></label> <div ng-repeat="book in allBooksShow"> <p draggable="true" ondragstart="angular.element(document.getElementById('theBooks')).scope().drag(event)" id="{{book.id}}">{{book.name}}</p> </div> </div> </div> </div> </div>

Crear en el modulo de autor el estado asociado con editar autor

Para crear este estado veremos que es muy similar a un estado donde se ve el detalle de un autor, libro o editorial. Se envía como parámetro el id del autor a editar y se hace uso del controlador asociado al comportamiento de ese template.

... .state('authorUpdate', { url: '/update/{authorId:int}', parent: 'authors', param: { authorId: null }, views: { 'detailView': { templateUrl: basePath + '/new/authors.new.html', controller: 'authorUpdateCtrl' } } }) ...

Crear el controlador asociado a ese estado

Para explicar este controlador iremos por partes ya que es un poco mas largo que los controladores que hemos trabajado, además, de tener una lógica para la relación de los libros y autor.

  • Se consulta el autor a editar, y se cargan lo datos en las variables $scope.authorName, $scope.authorBirthDate, ... etc.
//Consulto el autor a editar. $http.get(authorsContext + '/' + idAuthor).then(function (response) { var author = response.data; $scope.authorName = author.name; $scope.authorBirthDate = new Date(author.birthDate); $scope.authorDescription = author.description; $scope.authorImage = author.image; $scope.allBooksAuthor = author.books; $scope.mergeBooks($scope.allBooksAuthor); });
  • Se llama a un método nombrado como $scope.mergeBooks que se encargará de añadir los ids de los libros que ya tiene el autor asociados a un arreglo llamado idsBook
/* * Esta función añade los ids de los books que ya tiene el autor asociado. * @param {type} books: Son los books que ya tiene asociado el autor. * @returns {undefined} */ $scope.mergeBooks = function (books) { for (var item in books) { idsBook.push("" + books[item].id); } $scope.getBooks(books); };
  • Se llama a un método nombrado como $scope.getBooks que se encarga de filtras visualmente los libros que ya tiene el autor junto con los libros que tenemos en la base de datos.
/* * Esta función recibe como param los books que tiene el autor para hacer un filtro visual con todos los books que existen. * @param {type} books * @returns {undefined} */ $scope.getBooks = function (books) { $http.get(booksContext).then(function (response) { $scope.Allbooks = response.data; $scope.booksAuthor = books; var filteredBooks = $scope.Allbooks.filter(function (Allbooks) { return $scope.booksAuthor.filter(function (booksAuthor) { return booksAuthor.id == Allbooks.id; }).length == 0 }); $scope.allBooksShow = filteredBooks; }); };

La variable filteredBooks almacena el filtro de los libros no asociados al autor. Es un filtro personalizado.

Veamos un ejemplo de ese bloque de código.

$scope.Allbooks = [{"name":"book1"},{"name":"book2"}] $scope.booksAuthor = [{"name":"book1"}] var filteredBooks = [{"name":"book2"}]

Explicando el filtro personalizado tenemos $scope.Allbooks que son todos los libros, y tenemos $scope.booksAuthor que tiene el book1 es decir que la variable filteredBooks tendrá solo el book2 Esto se hace con el fin de mostrar visualmente al usuario los libros que puede asociar al autor.

Para las funciones del drag and drop puede consultar la documentación oficial.

Finalmente, se verifica que libros quedan asociados al autor y se crea un arreglo con los libros que el usuario asoció. Se hace el llamado al método PUT por medio de $http y se envía la lista de libros para que se realice la actualización del autor.

Actualizar el template authors.list

<td id="{{$index}}-actions"> <a class="btn btn-default btn-sm" ui-sref="authorUpdate({authorId: author.id})"><span class="glyphicon glyphicon-edit"></span></a> </td>

Actualizar el index para incluir el nuevo controlador

<script src="src/modules/authors/update/authors.update.ctrl.js" type="text/javascript"> </script>

Editar un autor PASO 4-b

  1. Actualizar el template de crear un author para que se pueda acceder a editar el autor.
  2. Crear en el módulo authorModule el estado asociado a editar autor.
  3. Crear el controlador asociado a ese estado.
  4. Actualizar el template authors.list
  5. Directiva move-lists
  6. Actualizar el index.html para incluir el nuevo controlador y la directiva.

Aplicación

Se da clic en editar el autor que se creo, y los datos se cargan en el formulario, pero, note que aparecen dos cosas nuevas.

  1. Los libros asociados al autor
  2. Todos los libros disponibles para asociar al autor.

Lo anterior, se ha logrado por medio de una directiva y ahorro de código en el controlador de editar el libro.

Una vez se guarden los cambios, se procede a ver el detalle del autor donde vemos los dos libros que le fueron asociados.

Actualizar el template para editar el autor

Estamos reutilizando el template de crear autor, pero, hacemos uso de la directiva ng-if de AngularJS para que se muestre o no el contenido.

También hacemos uso una directiva nos permite mostrar la selección de libros llamada move-list.

<move-lists ng-if="edit" selected="selectedItems" available="availableItems"></move-lists>

Crear en el modulo de autor el estado asociado con editar autor

Para crear este estado veremos que es muy similar a un estado donde se ve el detalle de un autor, libro o editorial. Se envía como parámetro el id del autor a editar y se hace uso del controlador asociado al comportamiento de ese template.

... .state('authorUpdate', { url: '/update/{authorId:int}', parent: 'authors', param: { authorId: null }, views: { 'detailView': { templateUrl: basePath + '/new/authors.new.html', controller: 'authorUpdateCtrl' } } }) ...

Crear el controlador asociado a ese estado

En este controlador al hacer uso de la directiva move-list nos ahorramos gran cantidad de código.

  • Se consulta el autor a editar, y se cargan lo datos en las variables $scope.authorName, $scope.authorBirthDate, ... etc.
//Consulto el autor a editar. $http.get(authorsContext + '/' + idAuthor).then(function (response) { var author = response.data; $scope.authorName = author.name; $scope.authorBirthDate = new Date(author.birthDate); $scope.authorDescription = author.description; $scope.authorImage = author.image; $scope.allBooksAuthor = author.books; $scope.mergeBooks($scope.allBooksAuthor); });
  • Se llama a un método nombrado como $scope.getBooks que se encargará de traer todos los libros y diferenciar entre cuales son los que el autor ya tiene asociados.
$scope.getBooks = function (books) { $http.get(booksContext).then(function (response) { $scope.allBooks = response.data; $scope.booksAuthor = books; var filteredBooks = $scope.allBooks.filter(function (book) { return $scope.booksAuthor.filter(function (bookAuthor) { return bookAuthor.id === book.id; }).length === 0; }); var unFilteredBooks= $scope.allBooks.filter(function (book) { return $scope.booksAuthor.filter(function (bookAuthor) { return bookAuthor.id === book.id; }).length !== 0; }); if ($scope.booksAuthor.length === 0) { $scope.availableItems = $scope.allBooks; } else { $scope.selectedItems = unFilteredBooks; $scope.availableItems = filteredBooks; } }); };

La variable filteredBooks almacena el filtro de los libros no asociados al autor, y la variable unFilteredBooks los libros asociados al autor. Es un filtro personalizado.

Veamos un ejemplo de ese bloque de código.

$scope.Allbooks = [{"name":"book1"},{"name":"book2"}] $scope.booksAuthor = [{"name":"book1"}] var filteredBooks = [{"name":"book2"}]

Explicando el filtro personalizado tenemos $scope.Allbooks que son todos los libros, y tenemos $scope.booksAuthor que tiene el book1 es decir que la variable filteredBooks tendrá solo el book2 Esto se hace con el fin de mostrar visualmente al usuario los libros que puede asociar al autor.

Finalmente, se verifica que libros quedan asociados al autor y se crea relaciona $scope.selectedItems con los libros que el usuario asoció. Se hace el llamado al método PUT por medio de $http y se envía la lista de libros para que se realice la actualización del autor.

Actualizar el template authors.list

<td id="{{$index}}-actions"> <a class="btn btn-default btn-sm" ui-sref="authorUpdate({authorId: author.id})"><span class="glyphicon glyphicon-edit"></span></a> </td>

Directiva move-lists

(function (ng) { var mod = angular.module('mainApp'); mod.constant('template', 'src/directives/templates/'); /** * @ngdoc directive * @name directive:moveLists * @scope * @priority 0 * @restrict E * * @param {array} selected list of selected items * @param {array} available list of available items * * @description * * Creates a view to swap items between two lists * * @example * <pre> * <move-lists selected="selectedItems" available="availableItems"></move-lists> * </pre> */ mod.directive('moveLists', ['template', function (tplDir) { return { scope: { selected: '=*', available: '=*' }, restrict: 'E', templateUrl: tplDir + 'move-lists.tpl.html', controllerAs: '$ctrl', controller: ['$scope', function ($scope) { function move(src, dst, marked) { // If selected is undefined, all records from src are moved to dst if (!!marked) { for (var i = 0; i < marked.length; i++) { if (marked.hasOwnProperty(i)) { var index = null; for (var j = 0; j < src.length; j++) { if (src.hasOwnProperty(j)) { if (src[j].id === marked[i].id) { index = j; break; } } } if (index !== null) { dst.push(src.splice(index, 1)[0]); } } } } else { dst.push.apply(dst, src); src.splice(0, src.length); } } move($scope.available, [], $scope.selected); $scope.selectedMarked = []; $scope.availableMarked = []; this.addSome = function () { move($scope.available, $scope.selected, $scope.availableMarked); $scope.availableMarked = []; }; this.addAll = function () { move($scope.available, $scope.selected); $scope.availableMarked = []; }; this.removeSome = function () { move($scope.selected, $scope.available, $scope.selectedMarked); $scope.selectedMarked = []; }; this.removeAll = function () { move($scope.selected, $scope.available); $scope.selectedMarked = []; }; }] }; }]); })(window.angular);

Template

<div class="col-md-12"> <div class="col-md-12 text-center"> <fieldset> <legend>Author's books</legend> </fieldset> </div> <div class="col-md-5"> <fieldset> <legend>Selected</legend> <select class="form-control" multiple ng-options="rc.name for rc in selected track by rc.id" ng-model="selectedMarked"></select> </fieldset> </div> <div class="col-md-2"> <button class="form-control btn btn-default" ng-click="$ctrl.removeAll()">&gt;&gt;</button> <button class="form-control btn btn-default" ng-click="$ctrl.removeSome()">&gt;</button> <button class="form-control btn btn-default" ng-click="$ctrl.addSome()">&lt;</button> <button class="form-control btn btn-default" ng-click="$ctrl.addAll()">&lt;&lt;</button> </div> <div class="col-md-5"> <fieldset> <legend>Available</legend> <select class="form-control" multiple ng-options="rc.name for rc in available track by rc.id" ng-model="availableMarked"></select> </fieldset> </div> </div>

Actualizar el index para incluir el nuevo controlador y la directiva

<script src="src/modules/authors/update/authors.update.ctrl.js" type="text/javascript"> <script src="src/directives/js/directives.js" type="text/javascript"></script> </script>

Eliminar un autor

  1. Crear el template asociado a eliminar el author para que se pueda acceder desde eliminar el autor.
  2. Crear en el módulo authorModule el estado asociado a eliminar autor.
  3. Crear el controlador asociado a ese estado.
  4. Actualizar el template authors.list
  5. Actualizar el index.html para incluir el nuevo controlador.

Aplicación

Una vez el usuario da clic en eliminar el autor

Se le preguntará si desea borrar el registro.

Crear el template asociado con eliminar el author

<div class="center-block well text-center"> <h1>Are you sure?</h1> <button id="confirm-delete" ng-click="deleteAuthor()" class="btn btn-danger">Delete</button> <a id="cancel-delete" ui-sref="authorsList" class="btn btn-info">Cancel</a> </div>

Crear en el módulo authorModule el estado asociado con eliminar autor

... .state('authorDelete', { url: '/delete/{authorId:int}', parent: 'authors', param: { authorId: null }, views: { 'detailView': { templateUrl: basePath + '/delete/author.delete.html', controller: 'authorDeleteCtrl' } } });

Crear el controlador asociado a ese estado

En este controlador hacemos uso del método DELETE por medio del $http. Ahora, se ve como se han usado todos los métodos para consumir los recursos que se crearon en el backstepbystep.

var idAuthor = $state.params.authorId; $scope.deleteAuthor = function () { $http.delete(authorsContext + '/' + idAuthor, {}).then(function (response) { $state.go('authorsList', {authorId: response.data.id}, {reload: true}); }); };

Actualizar el template authors.list

Añadimos la opción de eliminar en el template de la lista de autores.

<td id="{{$index}}-actions"> <a class="btn btn-danger btn-sm" ui-sref="authorDelete({authorId: author.id})"><span class="glyphicon glyphicon-trash"></span></a> </td>

Actualizar el index para incluir el nuevo controlador

<script src="src/modules/authors/delete/authors.delete.ctrl.js" type="text/javascript"> </script>

results matching ""

    No results matching ""