-
Notifications
You must be signed in to change notification settings - Fork 0
4. Aprendendo sobre Views
Caso não tenha visto a terceira parte, peço que dê uma pequena pausa e dê uma olhada nela, já que vamos dar continuidade a partir daquele.
Terceira parte: 3. Aprendendo sobre Models
Projeto completo feito nesse tutorial para download.
No nosso projeto do artigo 3, nós já temos nossos controllers, models, todo o projeto básico mas não temos nenhuma parte visual, vamos então criar, vamos lá, falaremos sobre views.
A primeira coisa que precisamos fazer é escolher a template engine que vai criar a nossa view. A engine é o responsável por compilar e retornar o html da nossa pagina, existem alguns disponíveis (como Pug, Mustache, e EJS), mas neste projeto usaremos o handlebarsjs.
O handlebarsjs é um template engine, ou seja, ele permite você usar arquivos de modelo estáticos na sua aplicação. Em tempo de execução, essa engine substitui as variáveis destes arquivos de modelo e transforma-o em um arquivo HTML que é enviado para o cliente. O que facilita a criação de um página HTML.
Vamos então instalá-lo
NodeJS
npm i express-handlebars --save
Agora precisamos dizer para a aplicação qual engine vamos utilizar, então no nosso arquivo bootstrap/app.js vamos configurá-lo:
var logger = require('morgan');
var express = require('express');
var consign = require('consign');
var bodyParser = require('body-parser');
//Adicionar o handlebars
var exphbs = require('express-handlebars');
var app = express();
app.use(logger('dev'));
app.use(bodyParser.urlencoded({
extended: true
}));
//Configuração do template engine
var hbs = exphbs.create({
//Dizemos que o arquivo de layout padrão é o main
defaultLayout: 'main',
//E dizemos que as extensões dos arquivos é .hbs
extname: '.hbs',
//Aqui nos exemplificamos o diretório de layouts, mas essa configuração é opcional, por padrão o diretório
//utilizado é o 'views/layouts'
layoutsDir:'views/layouts/',
//Aqui nos exemplificamos o diretório de layouts, mas essa configuração é opcional, por padrão o diretório
//utilizado é o 'views/partials'
partialsDir: 'views/partials/'
});
//Diz ao express que a engine criada acima é reconhecida como '.hbs'
app.engine('.hbs', hbs.engine);
//Diz ao express que a view engine é o .hbs que configuramos anteriormente
app.set('view engine', '.hbs');
consign()
.include('models')
.then('controllers')
.then('routes')
.into(app);
module.exports = app;
var port = process.env.PORT || 3000;
app.listen(port, function () {
console.log('Servidor rodando em http://localhost:%s', port);
});Obs.: Por padrão o express busca por views na pasta "views", mas pode-se alterar deste modo:
app.set('views', 'some/path/');
Partialssão basicamente templates para reuso em diversas views. Se quiser ver aspartialscarregadas pelohandlebars, você pode adicionar este trecho de código após ocreate().hbs.getPartials().then(function (partials) { console.log(partials); });
Agora podemos criar nossos arquivos .hbs, que são nossos layouts. Vamos então criar nossa página principal e nossa home. Primeiro dentro do diretório principal, cria a pasta views, e dentro dala as pastas layouts e partials.
Windows
mkdir views cd views mkdir layouts mkdir partials
Linux
mkdir views cd views mkdir layouts mkdir partials
Agora que temos nossas pastas, vamos criar dentro da pasta layouts o nosso arquivo views/layouts/main.hbs, dentro da pasta views o arquivo views/home.hbs e dentro da pasta partials o arquivo views/tasklist.hbs.
Windows
cd views type nul > home.hbs cd layouts type nul > main.hbs cd.. cd partials type nul > tasklist.hbs
Linux
touch views/home.hbs touch views/layouts/main.hbs touch views/partials/tasklist.hbs
Tendo nossos arquivos views/layouts/main.hbs,views/home.hbs e views/partials/tasklist, vamos escrever nosso HTML dentro deles, vai ficar assim:
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tasklist - NodeJS e Express</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
{{{body}}}
</body>
</html><div class="container">
<div class="col-md-6 col-md-offset-3">
<div class="page-header">
<h1>
Tarefas <a class="btn btn-primary pull-right" href="/create">Adicionar tarefa</span></a>
</h1>
</div><!-- /.page-header -->
<table class="table table-hover">
<thead>
<th width="25px"><strong>#</strong></th>
<th width="55%"><strong>Titulo</strong></th>
<th colspan="2"><strong>Status:</strong></th>
</thead>
<tbody>
<!-- Aqui nós chamamos nossa tasklist, para chamar uma tasklist carregada use sempre o '>' na frente -->
{{> tasklist }}
</tbody>
</table>
</div><!-- /.col-sm-4 -->
</div><!-- /.container -->{{#each tasks}}
<tr>
<td><span>{{_id}}</span></td>
<td><span>{{title}}</span></td>
{{#if status}}
<td>Concluído</td>
{{else}}
<td>Não concluído</td>
{{/if}}
<td width="100px">
<a class="btn btn-info" href="/edit/{{_id}}"><span class="fa fa-pencil"></span></a>
<a class="btn btn-danger" href="/task/{{_id}}"><span class="fa fa-trash"></span></a>
</td>
</tr>
{{/each}}Não se preocupe com os links do href agora, chegaremos lá ainda.
Tendo nossas views prontas, agora precisamos retornar nossos dados do controller, em controllers/tasks.js, altere o método index ficando assim:
module.exports = function (app) {
var Task = app.models.task
return {
index: function (request, response) {
//Pegar todos as tasks
let tasks = Task.all();
//Reponse de retorno
response.format({
json: function () {
return response.json(tasks);
},
html: function () {
return response.render('home', {tasks: tasks.data});
}
});
},
store: function (request, response) {
Task.save({
title: request.body.title,
status: 0,
created_at: new Date()
});
response.redirect('/');
},
update: function (request, response) {
var id = request.params.id;
var task = request.body;
Task.update(id, task);
response.redirect('/');
},
destroy: function (request, response) {
Task.remove(request.params.id);
response.redirect('/');
}
}
}Por que let ao invés de var? var vs let.
Como podemos ver, removemos o response.json que havia antes e colocamos response.format, mas porque fizemos isso? Como queremos tanto uma interface web como também a possibilidade de os dados serem utilizados via API, então formatamos o retorno, existindo dois, um em formato JSON e um HTML.
O response.json nos já conhecemos, vai retornar a lista das tasks em formato JSON. Mas o response.render, como o nome sugere, renderiza o template através do nome, no nosso caso o home.hbs, e o outro parâmetro é o que queremos passar para este template como variável.
Este método de retonar mais de um tipo de dado pelo response é conhecido como Content Negotiation, que de acordo com o accept da requisição ele envia um ou outro formato de dado.
Agora é só realizar o GET para localhost:3000, através do Postman e abrir o pelo navegador também para ver que você consegue tanto uma página web, quanto um JSON.
Para dar continuidade, que tal se realizarmos um dela de cadastro e edição para os dados. Pois bem, primeiramente vamos configurar a nossa rota para tela de cadastro, vamos usar \create.
Vamos começar criando o método que vai criar nossa task. Em controller/tasks.js vamos criar o método create, ficando assim:
module.exports = function (app) {
var Task = app.models.task
return {
index: function (request, response) {
//Pegar todos as tasks
let tasks = Task.all();
//Reponse de retorno
response.format({
json: function () {
return response.json(tasks);
},
html: function () {
return response.render('home', {tasks: tasks.data});
}
});
},
store: function (request, response) {
Task.save({
title: request.body.title,
status: 0,
created_at: new Date()
});
response.redirect('/');
},
update: function (request, response) {
var id = request.params.id;
var task = request.body;
Task.update(id, task);
response.redirect('/');
},
destroy: function (request, response) {
Task.remove(request.params.id);
response.redirect('/');
},
//Nosso método CREATE
create: function (request, response) {
response.render('createform');
}
}
}Como queremos que o método apenas retorne o formulário é apenas isso que o método fará, agora sim vamos definir a rota de fato. Adicionando o create o arquivo routes/index.js ficará assim:
module.exports = function (app) {
app.get('/', app.controllers.tasks.index);
app.post('/', app.controllers.tasks.store);
app.post('/task/:id', app.controllers.tasks.update);
app.get('/task/:id', app.controllers.tasks.destroy);
app.get('/create', app.controllers.tasks.create);
}Agora vamos fazer nosso formulário de cadastro, dentro da pasta views crie o arquivo createform.hbs.
Windows
cd views type nul > createform.hbs
Linux
touch views/createform.hbs
Tendo nossos arquivos views/createform.hbs, vamos escrever nosso HTML dentro dele, vai ficar assim:
<div class="container">
<div class="col-md-6 col-md-offset-3">
<div class="page-header">
<h1>
Nova Tarefa <a class="btn btn-primary pull-right" href="/">Home</span></a>
</h1>
</div><!-- /.page-header -->
<form class="form" action="/" method="post">
<div class="form-group">
<label>Titulo:</label>
<input class="form-control" type="text" class="form-group" name="title">
</div>
<button class="btn btn-primary btn-block">Criar</button>
</form>
</div><!-- /.col-sm-4 -->
</div><!-- /.container -->Para testar basta acessar http://localhost:3000/create e vera que temos nosso formulário, e o melhor, já vai estar funcionando. O motivo é que como já tínhamos um método para cadastro pronto (o método store), apenas configuramos no nosso <form> para realizar um post na rota onde o store é chamado, no caso nosso diretório raiz /.
Agora vamos editar nossas tarefas, primeiramente vamos criar um método para nos auxiliar no nosso modelo Task. Em model/task.js vamos criar o método find, ficando assim:
var tasks = {
data: [
{_id: 1, title: 'Limpar a casa', status: 0, created_at: new Date()},
{_id: 2, title: 'Lavar o carro', status: 0, created_at: new Date()}
]
};
function all() {
return tasks;
}
function save(task) {
task._id = tasks.data[tasks.data.length - 1]._id + 1;
tasks.data.push(task);
}
function update(id, task) {
var index = tasks.data.findIndex(function (task) {
return task._id == id
});
tasks.data[index].title = task.title;
tasks.data[index].status = task.status;
return
}
function remove(id) {
tasks.data = tasks.data.filter(function (task) {
return task._id != id;
})
return
}
//Nosso método find
function find (id) {
return tasks.data.filter(function (task) {
return task._id == id
})[0];
}
//Adicione ele aqui também
module.exports = {all, save, update, remove, find};E agora para não precisar utilizar outra view, vamos alterar a nossa createform para poder editar também. Depois de mudarmos ele vai ficar assim:
//Colocar novo createform.hbs aqui
Repare que agora ele recebe alguns parâmetros como title que é o título da nossa tarefa e também action que é o tipo de ação que vamos chamar no formulário. A nova view createform também verifica se foi passado um parâmetro task, caso sim faz algumas mudanças pertinentes (como mudar o botão) e mostrar ou não um pedaço do formulário relativo ao status.
Como mudamos a nossa view createform, vamos ter que alterar um pouco nosso método create de controllers/tasks.js e vamos aproveitar para já criar o método edit, ficando assim:
module.exports = function (app) {
var Task = app.models.task
return {
index: function (request, response) {
//Pegar todos as tasks
let tasks = Task.all();
//Reponse de retorno
response.format({
json: function () {
return response.json(tasks);
},
html: function () {
return response.render('home', {tasks: tasks.data});
}
});
},
store: function (request, response) {
Task.save({
title: request.body.title,
status: 0,
created_at: new Date()
});
response.redirect('/');
},
update: function (request, response) {
var id = request.params.id;
var task = request.body;
Task.update(id, task);
response.redirect('/');
},
destroy: function (request, response) {
Task.remove(request.params.id);
response.redirect('/');
},
//Novo método CREATE
create: function (request, response) {
response.render('createform', {
action: '/',
title: 'Nova Tarefa'
});
},
//Nosso método EDIT
edit: function (request, response) {
var task = Task.find(request.params.id);
response.render('createform', {
task: task,
title: 'Editar',
action: '/task/' + request.params.id,
});
}
}
}No nosso método create retornamos para nosso formulário somente o título e a ação que ele fará, enquanto no edit retornamos também a task a ser editada.
Para finalizar, adicionamos as nossas rotas routes/index.js a rota para edição da task, ficando assim:
module.exports = function (app) {
app.get('/', app.controllers.tasks.index);
app.post('/', app.controllers.tasks.store);
app.post('/task/:id', app.controllers.tasks.update);
app.get('/task/:id', app.controllers.tasks.destroy);
app.get('/create', app.controllers.tasks.create);
app.get('/edit/:id', app.controllers.tasks.edit);
}Agora é só testar todas as funcionalidades no seu http://localhost:3000/. Lembrando novamente que não temos persistência de dados, portanto os dados só existem enquanto o servidor estiver rodando.