# Módulo AngularJS

{% embed url="<https://www.youtube.com/watch?v=psL6M4vwKrQ>" %}

A implementação abordada é continuação do plugin criado no capítulo anterior. O CRUD será em cima da entidade `Item`.

Antes da abordagem da implementação do módulo em si, vamos criar uma base para testes. Uma rota `/meu-plugin/itens`, um arquivo de visão e um template part com o template angular da listagem.

```php
// arquivo Controllers/MeuPlugin.php
    function GET_itens () {
        $this->requireAuthentication();
        $this->render('items');
    }
```

```php
// arquivo views/meu-plugin/items.php
<div class="main-content">
    <?php $this->part('meu-plugin/items-list'); ?>
</div>
```

```markup
// layouts/parts/meu-plugin/items-list.php
<div>
  <h1>Lista de itens</h1>
  <ul>
    <li>Item 1 - <a>remover</a></li>
    <li>Item 2 - <a>remover</a></li>
  </ul>
</div>
```

## Esqueleto do Módulo

Primeiro, deve-se criar um arquivo JS com o esqueleto do módulo contendo controller e service vazios. É necessário modificar o conteúdo das requisições para que o Angular não envie um JSON no corpo da requisição, pois o Slim não vai parsear corretamente.

```javascript
// arquivo assets/js/meu-plugin/ng.module.item.js
(function (angular) {
    var module = angular.module('module.item', ['ngSanitize']);

    // modifica as requisições POST para que sejam lidas corretamente pelo Slim
    module.config(['$httpProvider', function ($httpProvider) {
        $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
        $httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
        $httpProvider.defaults.headers.common['X_REQUESTED_WITH'] = 'XMLHttpRequest';
        $httpProvider.defaults.transformRequest = function (data) {
            var result = angular.isObject(data) && String(data) !== '[object File]' ? $.param(data) : data;

            return result;
        };
    }]);

    // Seriço que executa no servidor as requisições HTTP
    module.factory('ItemService', ['$http', function ($http) {
        return {};
    }]);

    // Controlador da interface
    module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
        $scope.data = {};
    }]);
})(angular);
```

## Enfileirando Arquivo JS

Com o arquivo JS do módulo criado, é necessário que ele seja incluído na visão. Isto pode ser feito de diferentes maneiras, mas para este exemplo, faremos o enfileiramento no próprio arquivo do template part:

```php
// layouts/parts/meu-plugin/items-list.php
<?php 
$this->enqueueScript(
    'app', // grupo de scripts
    'ng-module-item',  // nome do script
    'js/meu-plugin/ng.module.item.js', // arquivo do script
    [] // dependências do script
);
?>
<div>
  <h1>Lista de itens</h1>
  <ul>
    <li>Item 1 - <a>remover</a></li>
    <li>Item 2 - <a>remover</a></li>
  </ul>
</div>
```

## Chamando o Módulo no Template

O AngulaJS precisa de um elemento com a diretiva `ng-app` que indica qual o módulo principal da página. Como utilizaremos o módulo numa nova rota que ainda não tem nenhum `ng-app` definido, colocaremos a propriedade na tag com a classe `main-content`:

```php
// arquivo views/meu-plugin/items.php
<div ng-app="module.item" class="main-content">
    <h1> Lista de itens </h1>
    <?php $this->part('meu-plugin/items-list'); ?>
</div>
```

Definido o `ng-app`, podemos chamar o controlador no arquivo do template part colocando a diretiva `ng-controller` na div que envolve o `ul`:

```markup
// arquivo layouts/parts/meu-plugin/items-list.php
<div ng-controlle="ItemController">
  <h1>Lista de itens</h1>
  <ul>
    <li>Item 1 - <a>remover</a></li>
    <li>Item 2 - <a>remover</a></li>
  </ul>
</div>
```

## Listando Itens

Vamos primeiro colocar no controller alguns itens fakes no objeto `$scope.data` para utilizarmos na listagem:

```javascript
// Controlador da interface
module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
  $scope.data = {
    items: [
      {id: 1, title: 'Título 1'},
      {id: 2, title: 'Título 2'}
    ]
  };
}]);
```

Em seguida, fazemos a iteração no template utilizando a diretiva `ng-repeat` na tag li:

```markup
// layouts/parts/meu-plugin/items-list.php
<div ng-controller="ItemController">
  <h1>Lista de itens</h1>
  <ul>
    <li ng-repeat="item in data.items">{{item.title}} - <a>remover</a></li>
  </ul>
</div>
```

*Para testar a listagem é necessário, nesse ponto, inserir algumas entradas na tabela `item`.*

## Recuperando Itens do Banco de Dados

Vamos implementar uma função no serviço que consulta a API de busca da entidade item:

```javascript
// arquivo assets/js/meu-plugin/ng.module.item.js

// Seriço que executa no servidor as requisições HTTP
module.factory('ItemService', ['$http', function ($http) {
  return {
    find: function(keyword) {
      var endpoint = MapasCulturais.baseURL + 'api/item/find';
      var params = {'@select': '*'};

      // para possibilitar filtro 
      if (keyword) {
        params.title = 'ILIKE(*' + keyword + '*)';
      }
      return $http.get(endpoint, {params});
    }
  };
}]);
```

Em seguida, removemos os itens fakes do array de itens, implementamos função no controlador que chama a função `find` do serviço e chamamos essa função no controller para trazer os itens da API:

```javascript
// arquivo assets/js/meu-plugin/ng.module.item.js

// Controlador da interface
module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
  $scope.data = {
    items: []
  };

  // busca itens na api
  $scope.findItems = function (keyword) {
    ItemService.find(keyword).then(function(result) {
      $scope.data.items = result.data;
    });
  }

  // faz a busca assim que carrega a página
  $scope.findItems();
```

## Inserindo Itens

No serviço, criamos uma função `insert` que faz uma requisição *POST* em `/item`:

```javascript
// arquivo assets/js/meu-plugin/ng.module.item.js

// Serviço que executa no servidor as requisições HTTP
module.factory('ItemService', ['$http', function ($http) {
  .
  .
  .
  // faz uma requisição do tipo POST
  insert: function(item) {
    var endpoint = MapasCulturais.createUrl('item', 'index');
    return $http.post(endpoint, item);
  },
```

No controlador, adicionamos uma propriedade para guardar o título do novo item, criamos uma função que utiliza essa variável para criar um item e enviar para o serviço:

```javascript
// arquivo assets/js/meu-plugin/ng.module.item.js

// Controlador da interface
module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
  $scope.data = {
    newItemTitle: '',
    items: []
  };

  $scope.insertItem = function () {
  if($scope.data.newItemTitle) {
    var item = { title: $scope.data.newItemTitle };
    ItemService.insert(item).then(function (result) {
      $scope.data.items.push(result.data);
      $scope.data.newItemTitle = '';
    });
  }
}
```

Antes da listagem, adicionamos um formulário para criação de novo item:

```markup
// layouts/parts/meu-plugin/items-list.php
<div ng-controller="ItemController">
  <h1>Lista de itens</h1>
  <form>
    <input type="text" placeholder="Título do novo item" ng-model="data.newItemTitle">
    <button ng-click="insertItem()">criar novo item</button>
  </form>
```

## Deletando Itens

No serviço, criamos uma função `remove` que faz uma requisição http do tipo *DELETE* para `/item/single/{id}`.

```javascript
// arquivo assets/js/meu-plugin/ng.module.item.js

// Serviço que executa no servidor as requisições HTTP
module.factory('ItemService', ['$http', function ($http) {
  return {
    .
    .
    .
    // faz uma requisição DELETE para deletar um item
    remove: function (item) {
      var endpoint = MapasCulturais.createUrl('item', 'single', [item.id]);
      return $http.delete(endpoint);
    }
```

No controlador, criamos uma função `$scope.removeItem` que chama a função remove do serviço:

```javascript
// arquivo assets/js/meu-plugin/ng.module.item.js
module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
    .
    .
    .

    // deleta um item
    $scope.removeItem = function(item) {
      ItemService.remove(item).then(function () {
        var index = $scope.data.items.indexOf(item);
        if (index > -1) {
          $scope.data.items.splice(index, 1);
        }
      });
    }
```

E, chamamos ela no click do link *remover*:

```markup
// layouts/parts/meu-plugin/items-list.php
<div ng-controller="ItemController">
  <h1>Lista de itens</h1>
  <ul>
    <li ng-repeat="item in data.items">{{item.title}} - <a ng-click="removeItem(item)">remover</a></li>
  </ul>
</div>
```

## Modificando Itens

No serviço, criamos uma função `update` que faz uma requisição http do tipo *PUT* para `/item/single/{id}`:

```javascript
// arquivo assets/js/meu-plugin/ng.module.item.js
module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
    .
    .
    .

    // atualiza um item
    update: function(item) {
      var endpoint = MapasCulturais.createUrl('item', 'single', [item.id]);
      return $http.put(endpoint, item);
    },
```

No controlador, adicionamos uma nova chave no `$scope.data` para guardar uma cópia do item que será editado.

E, adicionamos duas funções no $scope, a primeira que faz a cópia do objeto e a segunda que salva o objeto copiado.

```javascript
// arquivo assets/js/meu-plugin/ng.module.item.js   
module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
  $scope.data = {
    newItemTitle: '',
    editItem: null,
    items: []
  };

  // copia o item para edição
  $scope.editItem = function (item) {
    $scope.data.editItem = angular.copy(item);
  }

  // modifica um item
  $scope.updateItem = function(item) {
    ItemService.update($scope.data.editItem).then(function (result) {
      var index = $scope.data.items.indexOf(item);
      if (index > -1) {
        $scope.data.items[index] = result.data;
        $scope.data.editItem = null;
      }
    });
  }
```

No template, modificamos o conteúdo da tag *li* adicionando um formulário e um link para edição que chama a função `$scope.editItem`. Esta, faz uma cópia do item para o $data.editItem fazendo com que a condição para exibição do formulário seja verdadeira e que assim, o formulário é exibido:

```markup
// layouts/parts/meu-plugin/items-list.php
    <li ng-repeat="item in data.items">
      <p ng-if="data.editItem.id != item.id"> 
        {{item.title}} 
        - <a ng-click="editItem(item)">editar</a> 
        - <a ng-click="removeItem(item)">remover</a>
      </p>
      <form ng-if="data.editItem.id == item.id">
        <input type="text" ng-model="data.editItem.title" />
        <button ng-click="updateItem(item)">salvar</button> 
        <button ng-click="data.editItem=null">cancelar</button>
      </form>
    </li>
```

## Adicionando Campo de Busca

Como já havíamos possibilitado as buscas no método find do serviço, só precisamos modificar o controlador e o template.&#x20;

No controlador adicionamos uma nova chave no objeto `$scope.data` para guardar o termo buscado, além de uma função que efetua a busca:

```javascript
// arquivo assets/js/meu-plugin/ng.module.item.js
    // Controlador da interface
    module.controller('ItemController', ['$scope', 'ItemService', function ($scope, ItemService) {
        $scope.data = {
            newItemTitle: '',
            editItem: null,
            items: [],
            search: ''
        };

        $scope.searchItems = function () {
            $scope.findItems($scope.data.search);
        }
```

No template colocamos um formulário para a busca:

```markup
// layouts/parts/meu-plugin/items-list.php
  <form>
    <input type="text" ng-model="data.search"/>
    <button ng-click="searchItems()">buscar</button>
  </form>
```

## Incluindo Módulo em Outras Páginas

@todo
