Criando aplicações web com AngularJS, Node e Concise CSS
Esse artigo explora as bibliotecas do AngularJS, Node.js e Concise CSS em conjunto, mostrando desde a instalação, configuração e uso, bem como conceitos em relação ao seu uso responsivo nas páginas web.
O advento de tecnologias e frameworks baseados em JavaScript representa um dos maiores avanços no desenvolvimento de softwares front-end do mercado atualmente. É nítida a importância que bibliotecas como o AngularJS, Bootstrap, Node.js, etc. alcançaram ao longo de suas trajetórias, a maioria norteada pela comunidade e guiada por grandes companhias, como Google, Twitter, entre outras. O fato é que usar JavaScript em todo lugar (cliente, servidor, etc.) não se trata apenas de uma onda que atingiu a comunidade front-end, porém uma nova forma de pensar no software em si.
Guia do artigo:
E a maturidade do JavaScript permite que todo esse contexto funcione muito bem. Uma linguagem fracamente tipada, que obedece aos principais tópicos da programação funcional, simples, leve e altamente flexível para realizar praticamente toda e qualquer implementação que quisermos. O JavaScript representa, hoje, um novo conceito multiplataforma de linguagem de programação, tal como o Java foi em meados dos anos 2000, e continua sendo de certa forma. Ela ainda não atingiu todos os milhões de dispositivos que o Java ostenta há um bom tempo, mas já podemos ter JavaScript executando as nossas requisições HTTP dentro de um servidor qualquer que tenha consigo o Node.js.
Saiba mais Série Programe com o Node.jsAliado a tudo isso, no que remete ao próprio desenvolvedor, temos o nicho de outras tecnologias clientes que precisam se comunicar com o Node. A lista é imensa e só tende a crescer a cada dia: AngularJS, Bootstrap, Material Lite, ReactJS, Underscore.js, etc. Todavia, existe uma dificuldade nata em se integrar tais bibliotecas quando suas documentações oficiais tendem a disponibilizar tutoriais focados apenas em cada uma, nos seus recursos mais básicos. E é justamente nesse ponto que foca este artigo: tratar de expor ao leitor um meio de integrar o Node.js a tecnologias cliente mais comuns e presentes no dia a dia da comunidade. Especificamente, trataremos de expor tais detalhes focados no AngularJS em conjunto com o framework Concise CSS, um framework de componentes semelhante ao Angular Material que foca sua atenção no design em si, com algumas funções utilitárias de contexto.
Aqui, veremos como criar uma aplicação completa chamada Baú Online, de gerenciamento de mídias no formato CRUD para operações de cadastro, alteração e remoção de dados via web. Será algo bem simples, mas o suficiente para que o usuário entenda que passos o mesmo precisa realizar para integrar quaisquer outras bibliotecas que se façam necessárias nesse quesito. Também trataremos de conectar nossa aplicação JavaScript do servidor com um banco de dados MySQL, de modo a complementar mais uma integração também necessária e pouco explorada pela web como um todo.
Configurando o ambiente
O primeiro passo necessário para iniciar nossa implementação é configurar as ferramentas necessárias para tal. Como lidaremos com a persistência dos dados, comecemos pelo banco em si. A maioria das aplicações desenvolvidas com o Node.js no lado do servidor fazem uso do MongoDB como banco default, porém usaremos o MySQL por ser um banco tão robusto quanto, além de ser totalmente relacional. Na seção Links o leitor encontra a URL oficial para download das ferramentas do MySQL. Lá, você encontrará uma lista grande de itens, dos quais precisaremos apenas do:
- MySQL Community Server (o servidor do banco em si);
- MySQL Workbench (a ferramenta gráfica padrão do MySQL).
Veja que o link já redireciona automaticamente para o menu Community do site do MySQL, isso porque o mesmo também disponibiliza instaladores corporativos em outros links (alguns deles pagos, via Oracle), os quais não usaremos.
Portanto, efetue o download de ambos os instaladores e execute os mesmos. Siga o procedimento normalmente e, no meio dos passos, informe um usuário e senha e guarde tais informações pois precisaremos das mesmas no momento de conectar nossa aplicação com a base de dados.
Uma vez com o MySQL instalado, já podemos efetuar a criação da estrutura inicial de esquema e tabela para salvar nossos dados. Vejamos na Listagem 1 o script para criação dessa estrutura, apenas com uma tabela para salvar os dados das músicas da nossa aplicação de baú.
CREATE SCHEMA `devmedia_bau`;
USE `devmedia_bau`;
CREATE TABLE `tb_musicas` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`titulo` varchar(200) NOT NULL,
`album` varchar(45) DEFAULT NULL,
`cantor` varchar(45) DEFAULT NULL,
`ano` int(4) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=latin1;
Veja que a chave primária da nossa tabela é autoincremento, logo não precisamos informar seu valor no momento das inserções de dados que efetuarmos via código Node.js.
A próxima instalação será a do próprio Node.js. Na seção Links está a URL para efetuar o download do instalador de acordo com o seu sistema operacional. Efetue os passos de execução até o final e, após isso, abra o prompt de comandos do seu SO e execute o seguinte comando:
node -v
Após isso, você verá ser impressa a versão atual do seu Node.js na tela do cmd. Isso será o suficiente para termos não somente os pacotes binários do Node em si, como também o seu gerenciador de pacotes, o npm (Node Package Manager). O npm ficou popularizado por ser referência como repositório de pacotes de bibliotecas, plugins ou outros tipos de add-ons necessários para todo tipo de desenvolvimento: seja via Node.js, Ruby, Python, etc.
Partindo para a criação do projeto, selecione um diretório de sua preferência e crie uma nova pasta onde você adicionará todas as subpastas do projeto de servidor, pois é por ele que começaremos nossa implementação, deixando o serviço pronto para retornar todos os dados consumidos do banco e vice-versa. Para isso, faremos uso do framework Express, um framework minimalista que flexibiliza nossas implementações em Node.js ao disponibilizar templates para estruturar a aplicação, acessar o banco, criar web services, gerenciar dados em JSON e muito mais. Além disso, ele fornece uma estrutura interna para criar APIs e disponibilizar seus próprios serviços publicamente de maneira rápida e flexível.
Como o framework em si é bem grande, optaremos por não baixar todo o seu código fonte, mas sim apenas o do express generator. Logo, na linha de comando, acesse o diretório do projeto e execute o seguinte comando:
mkdir bau-node-mysql
cd bau-node-mysql
Esse será o nome do nosso projeto de servidor. Logo após, execute o seguinte comando:
npm install -g express-generator
Isso será o suficiente para instalar os pacotes dos binários do express-generator no nosso sistema operacional. A flag –g, nesse caso, é importante para que a instalação seja feita de forma global, isto é, para todos os usuários da máquina.
Em seguida, precisamos criar o projeto express de fato. Para isso, execute o seguinte comando:
express bau-online
O leitor verá uma série de arquivos serem impressos na tela, referentes aos que foram criados pelo express (views, rotas, binários, etc.). E no final da impressão, teremos algumas sugestões do que fazer em seguida, como instalar todas as dependências do projeto gerado, ou iniciar o mesmo no servidor. Portanto, entre na pasta recém-gerada (comando: cd bau-online) e execute o seguinte comando:
npm install
Esse comando efetuará o download e configuração de todas as dependências externas do projeto visíveis dentro da pasta recém-criada /node_modules. Todos os plugins e módulos do Node que precisamos num projeto Express ficam, por padrão, dentro dessa pasta e você não precisa se preocupar em gerenciá-los, uma vez que o próprio Node.js se encarrega de fazê-lo por si só.
Entretanto, ainda precisamos informar ao nosso projeto que iremos trabalhar especificamente com o MySQL e que o express precisa efetuar as configurações necessárias. Logo, execute o seguinte comando:
npm install -S mysql express-myconnection
A flag –S funciona de forma semelhante à --save, salvando a configuração para o projeto e não somente para a execução corrente. Após isso, um novo módulo /mysql será adicionado à pasta node_modules e o nosso projeto estará pronto para se conectar com qualquer servidor MySQL. Esse, por sua vez, precisa estar devidamente mapeado no arquivo app.js, o coração da aplicação. Esse arquivo foi autogerado pelo Express e se encontra na raiz do projeto, portanto abra-o e adicione as seguintes linhas de código logo após a importação do módulo bodyParser:
var mysql = require("mysql");
var connection = require("express-myconnection");
Elas se encarregam de importar os módulos do mysql e do myconnection do express devidamente no projeto, funcionando de forma semelhante aos conectores de linguagens como Java e Python. Finalmente, adicione os dados de acesso ao nosso banco específico via função use() do objeto app, tal como demonstrado na Listagem 2. Tratam-se de dados básicos, como o host, usuário e senha, a porta do banco e o nome do esquema que criamos. Certifique-se de alterar tais dados para os seus locais, caso contrário nossa conexão não será estabelecida.
app.use(
connection(mysql,{
host: "localhost",
user: "root",
password : "root",
port : 3306,
database:"devmedia_bau"
},"request")
);
Isso já será o suficiente para criarmos nossas primeiras consultas. A pasta /routes se encarrega de conter os arquivos JavaScript que direcionarão os fluxos de requisição HTTP que chegarem à aplicação no servidor para determinadas operações, como uma consulta à lista de músicas da base de dados, por exemplo. Para efetuar essa busca, abra o arquivo index.js, localizado na referida pasta, e mantenha-o tal como demonstrado na Listagem 3. Nas duas primeiras linhas importamos as bibliotecas que se farão necessárias no roteamento de requisições: a do express e a API router, também do express. Essa API tem métodos para todos os métodos HTTP de requisição, logo faremos uso do GET (via função get) para recuperar a lista de músicas adicionadas na base.
A string de primeiro parâmetro se refere à URI interna que deverá ser chamada para redirecionar para essa função, e a função passada como segundo parâmetro representa o retorno da requisição, portanto ambos os objetos de request e reponse do HTTP lidam com a resposta ao usuário. O método getConnection(), por sua vez, retorna um objeto do tipo connection que representa a própria conexão ao banco MySQL. Nele, poderemos executar consultas via função query() passando como parâmetros uma string que representa o SQL a ser executado em si, os argumentos da query e a função de callback, respectivamente. Após isso, só precisamos tratar o retorno da consulta verificando se houve algum erro (via objeto err retornado do banco): caso tenha havido, retornamos uma reposta HTTP 400 de erro, caso contrário retornamos o status 200, ou seja, ok.
var express = require("express");
var router = express.Router();
/* GET home page. */
router.get("/musicas", function(req, res) {
req.getConnection(function(err,connection){
connection.query("SELECT * FROM tb_musicas", [], function(err, result){
if (err) {
return res.status(400).json(err);
}
return res.status(200).json(result);
});
});
});
module.exports = router;
Após isso, já podemos fazer nossos primeiros testes chamando a URL desse serviço através do seu navegador ou qualquer ferramenta cliente de Web Services. Para isso, devemos iniciar o nosso projeto através do seguinte comando:
node bin\www
Existe um arquivo de nome www dentro da pasta bin que contém as instruções de execução do projeto e disponibilização do mesmo de forma local (localhost) na porta 3000. Mas para isso, precisamos nos assegurar de ter algum dado já inserido na base, logo execute o seguinte script no MySQL Workbench:
INSERT INTO `devmedia_bau`.`tb_musicas` (`titulo`, `album`, `cantor`, `ano`)
VALUES ("Prince - For You", "For You", "Prince", "1978");
INSERT INTO `devmedia_bau`.`tb_musicas` (`titulo`, `album`, `cantor`, `ano`)
VALUES ("Loving You", "Loving You", "Elvis Presley", "1957");
O resultado será semelhante ao que temos na Figura 1.
Se você já tiver usado express antes, notará que o objetivo do index.js neste caso não é acessar uma base de dados, para isso o mais ideal seria usar um arquivo de serviços numa nova pasta. A função do index se resume a de fato rotear as requisições para os seus respectivos métodos de serviço, que, por sua vez, é quem farão o acesso à base final. Portanto, na raiz do projeto, crie uma nova pasta chamada /api e dentro dela um novo arquivo de nome musicas.js. Abra o arquivo em questão e adicione ao mesmo o conteúdo listado na Listagem 4.
Veja que aqui temos, além da consulta SQL que lista todas as músicas da referida tabela, algumas outras para inserir, buscar uma música pelo id, atualizar ou removê-las. Ou seja, temos um resumo de todas as operações que a nossa aplicação Web Service deve fornecer para os possíveis clientes que a consumirão. Todavia, as configurações estão sendo feitas dentro de funções de nomes que definimos por nós mesmos, como list, create, getById, etc., e que fazem uso do módulo de exportação de funções do express: o exports. Cada primeira nova função JavaScript no código se inicia com essa palavra reservada, que, por sua vez, foi instalada previamente junto com o framework Express. Seu uso no restante da aplicação está autorizado na última linha do arquivo app.js:
module.exports = app;
Isso é o suficiente para configurar no express que toda a aplicação será “exportável”, ou seja, disponível para os demais arquivos internos do projeto. Em relação às funções seguintes, o funcionamento é o mesmo, mudando apenas o script SQL a ser executado, os parâmetros que são passados (no caso do insert, update e delete precisamos receber o id e/ou entidade de música a ser executada) e o resultado a ser retornado pelo serviço via função json().
exports.list = function(req, res) {
req.getConnection(function(err, connection){
connection.query("SELECT * FROM tb_musicas", [], function(err, result){
if(err) return res.status(400).json();
return res.status(200).json(result);
});
});
}
exports.create = function(req, res) {
var data = req.body;
req.getConnection(function(err, connection){
connection.query("INSERT INTO tb_musicas SET ?", [data], function(err, result){
if(err) return res.status(400).json(err);
return res.status(200).json(result);
});
});
}
exports.getById = function(req, res) {
var id = req.params.id;
req.getConnection(function(err, connection){
connection.query("SELECT * FROM tb_musicas WHERE id = ?", [id],
function(err, result){
if(err) return res.status(400).json(err);
return res.status(200).json(result[0]);
});
});
}
exports.update = function(req, res) {
var data = req.body,
id = req.params.id;
req.getConnection(function(err, connection){
connection.query("UPDATE tb_musicas SET ? WHERE id = ? ", [data, id],
function(err, result){
if(err) return res.status(400).json(err);
return res.status(200).json(result);
});
});
}
exports.delete = function(req, res) {
var id = req.params.id;
req.getConnection(function(err, connection){
connection.query("DELETE FROM tb_musicas WHERE id = ? ", [id], function(err, result){
if(err) return res.status(400).json(err);
return res.status(200).json(result);
});
});
}
Após isso, precisamos ainda instalar um segundo módulo do Node.js para lidar especificamente com a recuperação de módulos dentro dos arquivos js da aplicação. Trata-se do getmodule: um módulo para importar outros módulos. Portanto, execute o seguinte comando no seu prompt cmd:
npm install getmodule -S
Após finalizada a instalação, abra novamente o arquivo app.js e acrescente a seguinte linha de código no início do mesmo:
require("getmodule");
Isso será o bastante para que possamos importar módulos internos no projeto sem se preocupar com a sua localização exata no mesmo. E sua primeira usabilidade real será na importação do novo arquivo musicas.js dentro do arquivo de rotas /routes/index.js. Edite-o conforme nos mostra a Listagem 5. Veja que a primeira coisa necessária é recuperar o módulo de músicas que criamos e adicionar o mesmo numa variável (linha 3).
Veja que também fizemos um misto entre os métodos HTTP disponíveis para que você possa ver o poder que essa API nos dá. O método GET será usado para lidar com a listagem das músicas e com a recuperação de uma em específico pelo seu id (linhas 7 e 11); o POST lidará com a criação de uma nova música via função create da API (linha 8); o PUT se responsabilizará pela atualização dos dados de uma música (linha 12); e o DELETE fará a remoção da mesma da base de dados (linha 13). Cada um deles está roteado para ser chamado a partir de uma certa URL interna: os métodos de listagem e criação virão através da URL principal /musicas, enquanto para os demais precisamos passar o seu identificador, cuja URL será /musicas/:id. O operador “:id” nesse caso funcionará apenas como um alias para simular na API que algo deve ser passado ali, caso contrário a chamada não ocorrerá. Assim, criamos uma certa segurança para as nossas requisições ao exigir certos parâmetros que devem ser atendidos. No fim, linha 15, precisamos exportar também o objeto App no qual as rotas foram inseridas para que as mesmas sejam identificadas pelos demais arquivos js do projeto.
01 var express = require("express");
02 var App = express.Router();
03 var Musicas = getmodule("api/musicas");
04
05 /* GET home page. */
06 App.route("/musicas")
07 .get(Musicas.list)
08 .post(Musicas.create);
09
10 App.route("/musicas/:id")
11 .get(Musicas.getById)
12 .put(Musicas.update)
13 .delete(Musicas.delete);
14
15 module.exports = App;
Configurando o projeto AngularJS
Para lidar mais facilmente com o framework do AngularJS em si, precisaremos instalar alguns módulos de dependência do Bower, um gerenciador de pacotes semelhante ao npm, tão robusto quanto. Para isso, execute no prompt o seguinte comando:
npm install bower -g
Aguarde até que o processo termine e o mesmo esteja devidamente instalado. Não esqueça a flag –g para efetuar o mesmo globalmente. Como o AngularJS se trata de um framework MVC, precisaremos organizar nossas pastas e arquivos internos em uma hierarquia bem definida, formada essencialmente por modelos, views e controllers. Por isso, criemos primeiramente o diretório raiz do nosso projeto, de preferência ao lado do anterior:
mkdir bau-node-angularjs
cd bau-node-angularjs
Isso criará a nova pasta e entrará na mesma via linha de comando. Em seguida, execute os seguintes comandos:
bower install angular
bower install angular-route
Os mesmos instalarão dois módulos necessários ao nosso projeto web diretamente na pasta /bower_components: o módulo principal do Angular em si, que contém todos os binários do framework, incluindo os arquivos utilitários JavaScript que o compõem; e o módulo route, que funciona de forma semelhante ao roteamento que vimos no Node.js do servidor, abstraindo as requisições HTTP que serão enviadas aos nossos serviços na aplicação de servidor criada anteriormente.
Agora, foquemos na construção da nossa estrutura de diretórios e arquivos iniciais do projeto. Vejamos na Listagem 6 como ela deve ficar. Certifique-se de replicá-la no seu projeto recém-criado a fim de que possamos dar prosseguimento à implementação, ou, se preferir, efetue o download do projeto através da opção de download deste artigo.
Veja que criamos alguns arquivos JavaScript: app.js (referente às configurações gerais da aplicação, tal como temos no projeto de servidor), controller.js (conterá todos os nossos controllers da aplicação, que intermediarão as requisições HTTP com o servidor, bem como seus respectivos retornos) e service.js (fará, de fato, os acessos aos Web Services da aplicação do servidor). Também criamos um arquivo index.html na raiz do projeto que será responsável por chamar os demais arquivos de views. Essa pasta, por sua vez, contém três subpastas para salvar os arquivos HTML referentes aos tópicos de Músicas, Mídias e Anotações que o nosso site exibirá, em teoria.
+-| bau-node-angularjs
+------| bower_components
+------------| angular
+------------| angular-route
+------| css
+------| img
+------| js
+------------ app.js
+------------ controller.js
+------------ service.js
+------| views
+------------| anotacoes
+------------| midias
+------------| musicas
+------| index.html
Após isso, vamos importar os arquivos CSS do framework Concise CSS, para que possamos criar já nossa estrutura de design baseada no mesmo. Portanto, acesse a página do framework disponível na seção Links e, ao clicar no botão Download, selecione a opção “Compiled CSS with UI”, que irá baixar os arquivos compactados num .zip. Extraia os mesmos e mova-os para a pasta /css do nosso projeto AngularJS.
Comecemos então pela index.html, construindo a estrutura HTML da nossa página Home. Portanto, abra o referido arquivo e adicione ao mesmo o conteúdo da Listagem 7. Algumas de suas partes foram extraídas dos templates que o Concise CSS disponibiliza em sua documentação e adaptadas para a nossa realidade. Vejamos alguns dos pontos importantes sobre o código em questão:
- Na tag head da página temos apenas algumas tags meta para lidar com a exibição do conteúdo em dispositivos móveis e a importação do arquivo do Concise minificado, ou seja, com seu conteúdo comprimido para facilitar a importação nos navegadores. O segundo arquivo CSS será exibido mais adiante pois conterá propriedades específicas para esta página;
- Na linha 16 iniciamos o conteúdo da nossa aplicação AngularJS como um todo, via atributo ng-app, que define o nome do objeto JavaScript que lidará com as operações dinâmicas dessa página junto ao AngularJS. O conteúdo interno a essa tag representa o nosso template de aplicação, isto é, o esqueleto da página que será o mesmo para todas as subpáginas a serem exibidas;
- Para o Concise, de forma semelhante ao Bootstrap, se quisermos exibir um cabeçalho responsivo com menus de opções basta criar uma tag definindo sua classe CSS como siteHeader (linha 17). Dentro dela, cada nova linha de informações que quisermos exibir deve estar contida em uma tag com o atributo row inserido na mesma. Isso é o bastante para dividir aquela linha em um total de doze colunas imaginárias, que, por sua vez, precisam ser definidas via atributos column (tal como nas linhas 19 e 23, por exemplo) em qualquer elemento HTML, independentemente de sua tag. No nosso caso, criamos apenas uma logo com o nome “Baú Online”, cuja propriedade href aponta para o valor “#/” (isso diz ao angular-route que estamos requisitando a página home do projeto, veremos mais exemplos mais adiante). A classe CSS desse h1, por sua vez, deve ser definida como logo, própria do template que estamos usando. Em seguida, criamos uma tag de classe nav e oito colunas que conterão os itens de menu, aqui usados apenas para que o leitor tenha uma noção de como eles funcionam no framework;
- O próximo elemento da nossa página se refere ao corpo principal da mesma: é nele que injetaremos via AngularJS qual o “pedaço” de HTML que deverá aparecer na div de atributo ng-view (por padrão, deixaremos seu conteúdo ocupando o total de doze colunas);
- O restante do código traz o nosso rodapé (linhas 42 a 44) e as importações dos scripts do AngularJS (diretamente da pasta de componentes do Bower) e dos que criamos na pasta /js.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<title>App Meu Baú</title>
<link rel="stylesheet" type="text/css" media="screen" href="css/concise.min.css" />
<link rel="stylesheet" type="text/css" media="screen" href="css/masthead.css" />
</head>
<body ng-app="App">
<header container class="siteHeader">
<div row>
<h1 column=4 class="logo">
<a href="#/">Baú Online</a>
</h1>
<nav column="8" class="nav">
<ul>
<li><a href="">Home</a></li>
<li><a href="">Sobre</a></li>
<li><a href="">Contato</a></li>
</ul>
</nav>
</div>
</header>
<div class="container">
<div class="row">
<div class="col-md-12">
<div ng-view>
</div>
</div>
</div>
</div>
<footer container class="siteFooter">
<p>Copyright © 2016 por Baú Online</p>
</footer>
<script src="bower_components/angular/angular.min.js"></script>
<script src="bower_components/angular-route/angular-route.min.js"></script>
<script src="js/app.js"></script>
<script src="js/controller.js"></script>
<script src="js/service.js"></script>
</body>
</html>
Em sequência, devemos criar o arquivo CSS de nome masthead.css na pasta /css do projeto e inserir o conteúdo da Listagem 8 no mesmo. Esse traz apenas configurações genéricas de alinhamento, cor de fundo, margens e paddings para os elementos da nossa página Home. O leitor pode ficar à vontade para customizá-lo a bel prazer.
.siteHeader {
border-bottom: 1px solid #eee;
}
.logo {
font-size: 22px;
margin-bottom: 0;
}
.nav { text-align: right; }
.nav li {
display: inline;
line-height: 4;
margin-left: 25px;
}
.nav li:first-of-type {
margin-left: 0;
}
.masthead {
background-color: #eee;
padding: 50px 0;
}
@media (min-width: 48em) {
.masthead {
padding: 100px 0;
}
}
.masthead-title {
margin-top: 0;
}
.siteContent {
padding: 50px 0;
}
.siteFooter {
border-top: 1px solid #eee;
font-size: .875em;
padding-top: 15px;
}
.ico-content {
float: left;
padding: 20px;
}
.opt-content {
max-width: 60%;
float: left;
}
Para que possamos visualizar algum conteúdo da página inicial além do cabeçalho e rodapé, vamos criar o pedaço da nossa página de home que será substituído dinamicamente no template da index.html. Portanto, dentro da pasta /views, crie um novo arquivo chamado home.html e adicione o conteúdo da Listagem 9 ao mesmo. O código em si é bem simples e fácil de assimilar, dando destaque especial ao uso do mecanismo de linhas e colunas do Concise, bem como das suas classes CSS características. Nele, vemos pela primeira vez como usar elementos de botões do framework: basta criar links ou botões HTML comuns e adicionar as classes CSS button--lg (para botões largos), button--bordered (para botões com borda), button--sm (para botões pequenos), e assim por diante (vide documentação oficial para mais detalhes).
Os arquivos das imagens usadas nessa página podem ser baixados diretamente no arquivo de fontes deste artigo. Veja que o link de músicas que temos na base da página aponta para o caminho “#/musicas/list/all”, que, por sua vez, diz ao angular-route para redirecionar para a rota definida na mesma URL, a qual faremos mais adiante.
<div class="masthead">
<div container>
<div >
<img src="img/icon_chest.png" />
</div>
<h2 class="masthead-title kilo">Meu Baú Online</h2>
<p>This is a masthead template for the <a
href="http://concisecss.com">Concise CSS framework</a>
that can be easily copied and customized for projects.</p>
<button class="button--lg button--bordered">Leia Mais</button>
</div>
</div>
<main container class="siteContent">
<div row>
<div column=4>
<div class="ico-content">
<img src="img/icon_music.png" />
</div>
<div class="opt-content">
<h3>Músicas</h3>
<p>Gerencie suas músicas em um só lugar.</p>
<a class="button button--sm"
href="#/musicas/list/all">Ver Mais »</a>
</div>
</div>
<div column=4>
<div class="ico-content">
<img src="img/icon_photos.png" />
</div>
<div class="opt-content">
<h3>Mídias</h3>
<p>Guarde todas as lembranças das suas aventuras.</p>
<a class="button button--sm">Ver Mais »</a>
</div>
</div>
<div column=4>
<div class="ico-content">
<img src="img/icon_notepad.png" />
</div>
<div class="opt-content">
<h3>Anotações</h3>
<p>Guarde seus textos, poemas, frases prediletas e muito mais.</p>
<a class="button button--sm">Ver Mais »</a>
</div>
</div>
</div>
</main>
Finalmente, para que possamos testar, é necessário popular o arquivo app.js com os índices da rota que apontará para o arquivo recém-criado home.html e que dirá que o mesmo deve ser importado no nosso template do AngularJS. Portanto, abra o referido arquivo e adicione o conteúdo da Listagem 10. Nele, estamos apenas importando o módulo de routers do AngularJS (ngRoute), os controllers e os serviços que ainda criaremos. Note que para testar a aplicação até este ponto deve-se retirar essas duas últimas importações já que ainda não inserimos nada nos arquivos controller.js e service.js.
No método config() do objeto geral da aplicação App definimos no objeto de router que quando a URL / for chamada (ou seja, a raiz da aplicação) devemos usar o template definido na propriedade templateUrl. No final, setamos o valor do serviço que consumiremos na aplicação Node.js associando-o à variável API.
Agora, é só abrir a página no seu navegador e você verá algo semelhante ao que temos na Figura 2.
var App = angular.module("App", [
"ngRoute",
"controllers",
"services"
]);
App.config(function($routeProvider){
$routeProvider
.when("/", {
templateUrl: "views/home.html"
})
});
App.value("API", "http://localhost:3000/");
Controllers e Services
Uma vez com a estrutura principal do projeto criada, podemos prosseguir com a implementação da lógica dos controllers e services que usaremos no mesmo. Comecemos então por definir o restante das rotas que direcionarão para tais controllers. No mesmo arquivo app.js adicione o conteúdo da Listagem 11 às rotas após a primeira que definimos na listagem anterior. Veja que agora confrontamos a URL definida no href da home.html com a rota que ela deverá tomar quando for chamada.
A primeira rota será a de listagem das músicas, demarcada pela URI /musicas/list/:msg, onde a variável :msg representa um parâmetro a ser passado na URL, o qual o AngularJS deve reconhecer e tratar automaticamente. A segunda trata de criar um novo registro de música e a terceira de editar o mesmo, recebendo para isso o parâmetro do id daquela música em específico.
Observe que cada uma delas recebe uma URL de template com o HTML do mesmo e o nome do controller que lidará com isso, a ser definido no arquivo controller.js.
.when("/musicas/list/:msg", {
templateUrl: "views/musicas/list.html",
controller: "ListCtrl"
})
.when("/musicas/create", {
templateUrl: "views/musicas/create.html",
controller: "CreateCtrl"
})
.when("/musicas/edit/:id", {
templateUrl: "views/musicas/edit.html",
controller: "EditCtrl"
})
Agora, abra o arquivo controller.js e insira o conteúdo da Listagem 12. Nele, temos os seguintes controllers definidos:
- ListCtrl - responsável por listar as músicas, enviando-as para o referido template. Faz uso do serviço MusicasService (criado mais adiante) chamando a função list() e processando a lista de resultados. Sempre que um valor precisar ser enviado ao template HTML basta adicionar o mesmo valor ao objeto $scope do AngularJS (tal como na linha 7) e, assim, ele se fará disponível. A propriedade notFound, por sua vez, verifica se a lista veio vazia, para que possamos exibir uma mensagem correspondente na tela. Como não temos um controller para remover os itens, optamos por criar uma função JavaScript atrelada ao próprio $scope que recebe o id da música e chama o método respectivo do service. No fim, se tudo correr bem, basta chamar a função path() do objeto $location passando a URL da rota a ser redirecionada. Para o caso da remoção, chamamos o controller ListCtrl mais uma vez, passando a msg que deve ser exibida e manipulada pela função handleMsgs(), da linha 19;
- CreateCtrl - lida com a criação de uma nova música, chamando o método do serviço create() e passando o próprio objeto de item criado no formulário. Se tudo correr bem, no final redirecionamos para a tela de listagem com a mensagem de sucesso;
- EditCtrl - lida com a edição de um item, primeiramente buscando o mesmo na base via função getById() e redirecionando para a tela de edição com os dados preenchidos. No objeto de escopo também definimos, na linha 36, a ação efetiva de edição que chama o método update() do nosso service, enviando o item em si e seu id. Se tudo correr bem, redirecionamos para a tela de listagem com a mensagem de sucesso;
- Função handleMsgs - lida com o recebimento das mensagens e adição das mesmas no objeto de $scope para torná-las visíveis na tela.
01 var App = angular.module("controllers", []);
02
03 App.controller("ListCtrl", function($scope, MusicasService, $routeParams, $location){
04 $scope.musicas = [];
05 $scope.notFound = false;
06 MusicasService.list().then(function(data){
07 $scope.musicas = data.data;
08 if(data.data.length == 0){
09 $scope.notFound = true;
10 }
11 });
12
13 $scope.deletar = function(id){
14 MusicasService.delete(id).then(function(data){
15 $location.path("/musicas/list/delete-success");
16 });
17 }
18
19 handleMsgs($scope, $routeParams);
20 });
21
22 App.controller("CreateCtrl", function($scope, MusicasService, $location){
23 $scope.cadastrar = function(item){
24 MusicasService.create(item).then(function(data){
25 $location.path("/musicas/list/add-success");
26 });
27 }
28 });
29
30 App.controller("EditCtrl", function($scope, MusicasService, $routeParams, $location){
31 var id = $routeParams.id;
32 MusicasService.getById(id).then(function(data){
33 $scope.item = data.data;
34 })
35
36 $scope.atualizar = function(item){
37 MusicasService.update(item, item.id).then(function(data){
38 $location.path("/musicas/list/edit-success");
39 });
40 }
41 });
42
43 handleMsgs = function($scope, $routeParams) {
44 var msg = $routeParams.msg;
45 if (msg) {
46 switch(msg) {
47 case "add-success":
48 $scope.showMsg = true;
49 $scope.msgClass = "success";
50 $scope.msg = "Música adicionada com sucesso!";
51 break;
52 case "edit-success":
53 $scope.showMsg = true;
54 $scope.msgClass = "success";
55 $scope.msg = "Música atualizada com sucesso!";
56 break;
57 case "delete-success":
58 $scope.showMsg = true;
59 $scope.msgClass = "success";
60 $scope.msg = "Música removida com sucesso!";
61 break;
62 }
63 }
64 }
Na sequência, abra agora o arquivo service.js para adicionarmos os fluxos dos serviços que enviarão as requisições HTTP ao servidor. Portanto, inclua o conteúdo da Listagem 13 ao mesmo. Aqui, criamos uma fábrica de serviços que recebe sempre o objeto $http do AngularJS e a variável API que definimos no app.js anteriormente, com o caminho relativo da aplicação no servidor. Os nomes dos métodos devem ser os mesmos que estamos usando para consumi-los nos controllers, bem como os atributos que serão passados para eles. Assim, é só garantir que cada método HTTP corresponda aos tipos que definimos no servidor:
- Método GET: para listagem e busca de um item pelo id;
- Método POST: para inserção de um novo item na base;
- Método PUT: para atualização de um item na base;
- Método DELETE: para remoção de um item na base.
var App = angular.module("services", []);
App.factory("MusicasService", function($http, API){
return {
list: function(){
return $http.get(API + "musicas");
},
create: function(item){
return $http.post(API + "musicas", item);
},
getById: function(id){
return $http.get(API + "musicas/" + id);
},
update: function(item, id){
return $http.put(API + "musicas/" + id, item);
},
delete: function(id){
return $http.delete(API + "musicas/" + id);
}
}
})
Templates
Agora que finalizamos a implementação JavaScript de controllers e serviços, podemos focar na construção das páginas de templates para listagem, criação e edição dos itens de cadastro. Comecemos pela página list.html (que deve ser criada na pasta /views/musicas), inserindo o código da Listagem 14 na mesma.
A primeira coisa a exibir na tela, quando existirem, são as mensagens de validação. O Concise lida com vários tipos de mensagens do tipo para erros, alertas, mensagens de sucesso, etc. Como estamos lidando apenas com as de sucesso, optaremos por exibi-las somente. A classe CSS para isso é a alert em conjunto com as filhas --warning, --error, --success, etc. Os valores dessa div estão sendo preenchidos diretamente do objeto $scope, uma vez que já informamos tais valores no controller. Para imprimir qualquer valor que estiver dentro do objeto de escopo no HTML basta envolver o mesmo entre os operadores {{}}. A diretiva Angular ng-show serve para exibir ou esconder o elemento com base num valor booleano, assim evitamos deixar a mensagem o tempo todo sendo exibida na tela.
O link da linha 8 chama a rota que direciona para o método do controller de criação de uma nova música. Aqui, também passamos a explorar o recurso das tabelas no Concise CSS, que tem vários tipos padrão de tabelas: com ou sem borda, com efeito zebrado, responsiva, com efeito hover, etc. (vide documentação para mais exemplos). Na mesma tabela, exibiremos todas as informações da tabela tb_musica que vierem do serviço. A coluna de ano deve estar alinhada à direita por se tratar de um valor numérico, logo podemos usar a classe CSS float--right do Concise para tal. A diretiva ng-repeat do AngularJS, por sua vez, itera sobre os itens da lista de músicas retornada do controller exibindo cada um de seus itens nas tags td subsequentes. Já em relação aos links de edição/remoção (linha 32), não esqueça de passar os parâmetros corretamente para cada função. Como não temos uma rota criada para a remoção de um item, precisamos chamá-lo por meio da diretiva ng-click do Angular.
Por fim, na linha 34, exibimos a div que aparecerá caso nenhuma música seja retornada na listagem.
<div class="masthead">
<div container>
<div row>
<div class="alert alert--{}" ng-show="showMsg">
<p><strong>{}</strong></p>
</div>
<h2 class="masthead-title kilo">Minhas músicas</h2>
<a href="#/musicas/create" class="button button--bordered border--warning">
<b>Nova Música</b>
</a>
</div>
<br/>
<div row>
<table class="table table--full table--borderHorizontal
table--hoverRow table--responsive">
<thead>
<tr>
<td>ID</td>
<td>Título</td>
<td>Álbum</td>
<td>Cantor(a)</td>
<td class="float--right">Ano</td>
<td>Ações</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in musicas">
<td>{{item.id}}</td>
<td>{{item.titulo}}</td>
<td>{{item.album}}</td>
<td>{{item.cantor}}</td>
<td class="float--right">{{item.ano}}</td>
<td><a href="#/musicas/edit/{{item.id}}">Editar</a>
/ <a ng-click="deletar(item.id)" href="#">Deletar</a></td>
</tr>
<tr ng-show="notFound" class="text-center">
<td colspan="4">Nenhuma música na sua coleção...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
Em seguida, crie o arquivo de criação de uma nova música (create.html), tal como temos na Listagem 15. Essa está bem simples, apenas com o formulário e os itens do cadastro adicionados dentro de um parágrafo cada para facilitar a organização. No atributo ng-submit definimos a função do controller que receberá os dados do cadastro para enviar ao servidor. Cada campo segue o mesmo template definindo sempre o ng-model para que o Angular saiba a quem associar cada valor digitado. No final, criamos o botão que irá disparar o submit para o controller e um botão de cancelar que volta para a tela de listagem.
Para a página de edição (edit.html) o leitor pode simplesmente duplicar essa página, modificando o ng-submit para atualizar(item) e o nome do botão de envio.
<div class="masthead">
<div container>
<div class="row">
<h2>Cadastrar música</h2>
</div>
<div class="row">
<form class="form-horizontal" ng-submit="cadastrar(item)">
<fieldset>
<p>
<label for="name">Título:</label>
<input type="text" ng-model="item.titulo" class="form-control"
placeholder="Título" />
<input type="hidden" ng-model="item.id">
</p>
<p>
<label for="name">Álbum:</label>
<input type="text" ng-model="item.album" class="form-control"
placeholder="Álbum" />
</p>
<p>
<label for="name">Cantor(a):</label>
<input type="text" ng-model="item.cantor" class="form-control"
placeholder="Cantor(a)" />
</p>
<p>
<label for="name">Ano:</label>
<input type="number" ng-model="item.ano" class="form-control"
placeholder="Ano" maxlength="4" />
</p>
</fieldset>
<div class="form-group">
<button class="btn btn-success">Cadastrar</button>
<a href="#/musicas/list/all" class="button button--bordered
border--muted">Cancelar</a>
</div>
</form>
</div>
</div>
</div>
As Figuras 3 e 4 exemplificam como a aplicação funcionará no final. Não esqueça de se certificar que o servidor esteja inicializado para isso.
Dependendo do browser que você esteja usando para testar poderá se deparar com problemas de Cross-Origin, isto é, o navegador não aceitará o envio de requisições Ajax de uma origem diferente de onde a aplicação está hospedada. Para resolver isso, você pode efetuar a instalação de um pacote npm chamado cors (vide seção Links para instruções, ou replique as demonstradas no código fonte desse artigo) que resolverá o problema.
Isso tudo é apenas uma pequena demonstração do poder que o AngularJS tem integrado a outros frameworks, como o Node.js e o Concise CSS. Praticamente tudo pode ser integrado no mundo de desenvolvimento front-end e, neste caso em especial, não fizemos uso de nenhuma tecnologia back-end convencional. O leitor pode agora finalizar as outras implementações, tentar simular o que fizemos aqui com as anotações e vídeos do projeto do Baú Online, praticando assim cada vez mais seu arsenal de conhecimentos sobre tais tecnologias. Bons estudos!
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Artigo