Aprenda a Criar um Blog Usando Parse.js: Refatoração
Portuguese (Português) translation by Erick Patrick (you can also view the original English article)



Nos seis artigos anteriores, construimos um sistema de blog do zero. Tudo está funcionando direito! Contudo, o código está um tanto bagunçado—temos colocado tudo junto e deixado muito código repetido e soluções temporárias no lugar. Hoje, focaremos em como limpar as coisas e ajustar alguns problemas que encontramos.
1. Mesclar index.html e admin.html
Antes de tudo, já que temos um roteador (se não lembra disso, veja a Parte 5: Roteador), não precisamos mais de dosi arquivos .html
e .js
. Mesclemo-nos.
Passo 1: Mesclar Arquivos
Nossa sugestão é mesclar admin.html
e admin.js
a index.html
e blog.js
já que eles tem mais código, mas fica a seu critério. Deve ser bem simples.
Se ronemar os arquivos, apenas tenha certeza que blog.js
é chamado no index.html
(antes, admin.html
). Além disso, lembremo-nos de copiar #blogs-tpl
do antigo index.html
para o novo e copiar BlogsView
do antigo blog.js
para o novo.
Agora, visitemos http://localhost/seu-diretorio/ e será possível ver a tela de login por padrão (ou a tela de admin caso já tenhas as credenciais).



Passo 2: Atualizar Roteador
Depois, podemos adicionar um novo padrão de URL ao roteador para casar a URL raiz a uma nova função, index()
:
1 |
routes: { |
2 |
'': 'index', |
3 |
...
|
4 |
},
|
index()
deve renderizar o que antes estava na página inicial.
1 |
index: function() { |
2 |
this.blogs.fetch({ |
3 |
success: function(blogs) { |
4 |
var blogsView = new BlogsView({ collection: blogs }); |
5 |
blogsView.render(); |
6 |
$('.main-container').html(blogsView.el); |
7 |
},
|
8 |
error: function(blogs, error) { |
9 |
console.log(error); |
10 |
}
|
11 |
});
|
12 |
}
|
E para ver funcionando, usemos o redirecionamento padrão para essa URL quando o retoeador iniciar:
1 |
start: function(){ |
2 |
Parse.history.start({pushState: true}); |
3 |
this.navigate('', { trigger: true }); |
4 |
}
|
Passo 3: Atualizar Navegação
O próximo passo é atualizar a navegação no topo. Mudemos os arquivos HTML com as URLs:
1 |
<nav class="blog-nav"> |
2 |
<a class="blog-nav-item active" href="">Home</a> |
3 |
<a class="blog-nav-item" href="admin">Admin</a> |
4 |
</nav>
|
E para funcionarem, precisamos adicionar um evento a .blog-nav-item
para uar blogRouter.navigate()
ao invés do link de evento padrão:
1 |
$(document).on('click', '.blog-nav-item', function(e) { |
2 |
e.preventDefault(); |
3 |
var href = $(e.target).attr('href'); |
4 |
blogRouter.navigate(href, { trigger: true }); |
5 |
});
|
E adicionemos lógica para alternar a classe .active
, também:
1 |
$(document).on('click', '.blog-nav-item', function(e) { |
2 |
e.preventDefault(); |
3 |
var href = $(e.target).attr('href'); |
4 |
blogRouter.navigate(href, { trigger: true }); |
5 |
$(this).addClass('active').siblings().removeClass('active'); |
6 |
});
|
Agora, se clicarmos pelos meus, tudo deve estar funcionando!
2. Mesclar Add e Edit
Continuando, vemos que AddBlogView
e EditBlogView
são bem parecidas. Assim como update()
e create()
da classe Blog
. Mesclemo-nas também.
Passo 1: Mesclar #add-tpl e #edit-tpl
Primeiro, mesclemos os dois modelos em index.html
para ser #write-tpl
.
1 |
<script id="write-tpl" type="text/x-handlebars-template"> |
2 |
<h2>{{form_title}}</h2> |
3 |
|
4 |
<form class="form-write" role="form"> |
5 |
<div class="form-group"> |
6 |
<label for="title">Title</label> |
7 |
<input name="title" type="text" class="form-control" id="title" value="{{title}}"></input> |
8 |
</div> |
9 |
<div class="form-group"> |
10 |
<label for="content">Content</label> |
11 |
<textarea name="content" class="form-control" rows="20">{{{content}}}</textarea> |
12 |
</div> |
13 |
<button class="btn btn-lg btn-primary btn-block" type="submit">Submit</button> |
14 |
</form> |
15 |
</script>
|
Vemos que é basicamente #edit-tpl
com mudanas de classe e título de formulário dinâmico. Apenas passaremos ""
para title
e content
ao adicionar um novo blog.
Passo 2: Mesclar as funções update() e create()
Agora, mesclemos update()
e create()
na classe Blog. Podemos encadear this.set().save()
tnato para update()
quanto para create()
. Para os campos que não precisam ser tocados por update()
, podemos preencher com o valor atual:
1 |
update: function(title, content) { |
2 |
this.set({ |
3 |
'title': title, |
4 |
'content': content, |
5 |
// Set author to the existing blog author if editing, use current user if creating
|
6 |
// The same logic goes into the following three fields
|
7 |
'author': this.get('author') || Parse.User.current(), |
8 |
'authorName': this.get('authorName') || Parse.User.current().get('username'), |
9 |
'time': this.get('time') || new Date().toDateString() |
10 |
}).save(null, { |
11 |
success: function(blog) { |
12 |
alert('You updated a new blog: ' + blog.get('title')); |
13 |
},
|
14 |
error: function(blog, error) { |
15 |
console.log(blog); |
16 |
console.log(error); |
17 |
}
|
18 |
});
|
19 |
}
|
Passo 3: Mesclar AddBlogView e EditBlogView
Agora, é hora de mesclar as duas visões:
1 |
WriteBlogView = Parse.View.extend({ |
2 |
template: Handlebars.compile($('#write-tpl').html()), |
3 |
events: { |
4 |
'submit .form-write': 'submit' |
5 |
},
|
6 |
submit: function(e) { |
7 |
e.preventDefault(); |
8 |
var data = $(e.target).serializeArray(); |
9 |
// If there's no blog data, then create a new blog
|
10 |
this.model = this.model || new Blog(); |
11 |
this.model.update(data[0].value, data[1].value); |
12 |
},
|
13 |
render: function(){ |
14 |
var attributes; |
15 |
// If the user is editing a blog, that means there will be a blog set as this.model
|
16 |
// therefore, we use this logic to render different titles and pass in empty strings
|
17 |
if (this.model) { |
18 |
attributes = this.model.toJSON(); |
19 |
attributes.form_title = 'Edit Blog'; |
20 |
} else { |
21 |
attributes = { |
22 |
form_title: 'Add a Blog', |
23 |
title: '', |
24 |
content: '' |
25 |
}
|
26 |
}
|
27 |
this.$el.html(this.template(attributes)).find('textarea').wysihtml5(); |
28 |
}
|
29 |
})
|
Note como podemos usar if (this.model)
para mudar entre as funções add e edit.
Passo 4: Atualizar Roteador
Por fim, vinculemos a nova WriteBlogView
ao rotador. Apenas mudemos ambas visões para WriteBlogView
e tudo deverá continuar funcionando.
1 |
add: function() { |
2 |
// Check login
|
3 |
if (!Parse.User.current()) { |
4 |
this.navigate('login', { trigger: true }); |
5 |
} else { |
6 |
var writeBlogView = new WriteBlogView(); |
7 |
writeBlogView.render(); |
8 |
$container.html(writeBlogView.el); |
9 |
}
|
10 |
},
|
11 |
edit: function(id) { |
12 |
// Check login
|
13 |
if (!Parse.User.current()) { |
14 |
this.navigate('login', { trigger: true }); |
15 |
} else { |
16 |
var query = new Parse.Query(Blog); |
17 |
query.get(id, { |
18 |
success: function(blog) { |
19 |
var writeBlogView = new WriteBlogView({ model: blog }); |
20 |
writeBlogView.render(); |
21 |
$container.html(writeBlogView.el); |
22 |
},
|
23 |
error: function(blog, error) { |
24 |
console.log(error); |
25 |
}
|
26 |
});
|
27 |
}
|
28 |
}
|
Notemos que também podemos enviar os usuários de volta para a página de login se não tiverem as credenciais.
3. Adicionar Lista de Controle de Acesso aos Blogs
Agora uqe removemos o código repetitivo, podemos continuar com as funcionalidades que podemos aprimorar.
Muitos perguntaram como manter os dados seguros se a API está no código. Parse.js provê Listas de Controle de Acesso (ACLs) a nível de classe e item para ajudar a administrar o acesso dos usuários. Falamos delas a nível de classe na Parte 3: Login de Usuário. Hoje, falaremos sobre elas a nível de item.
Como um exemplo, assumamos que queremos toda psotagem seja editável apenas por seu autor.
Para tanto, precisamos usar um campo ACL
em update()
:
1 |
update: function(title, content) { |
2 |
|
3 |
// Only set ACL if the blog doesn't have it
|
4 |
if ( !this.get('ACL') ) { |
5 |
// Create an ACL object to grant access to the current user
|
6 |
// (also the author of the newly created blog)
|
7 |
var blogACL = new Parse.ACL(Parse.User.current()); |
8 |
// Grant read-read only access to the public so everyone can see it
|
9 |
blogACL.setPublicReadAccess(true); |
10 |
// Set this ACL object to the ACL field
|
11 |
this.setACL(blogACL); |
12 |
}
|
13 |
|
14 |
this.set({ |
15 |
...
|
16 |
});
|
17 |
}
|
4. Raiz e URL Estática
Outro ponto que muitos falaram foi da dificuldade de testar o sistema de blog que criamos. Para testarmos, temos de voltar em http://localshot/seu-diretorio/ para ativar o roteador.
Resolvamos esse problema.
Passo 1: Adicionar Root em BlogRouter.start()
Parse.js facilita isso, então mudemos BlogRouter.start() e configuremos uma raiz.
1 |
start: function(){ |
2 |
Parse.history.start({ |
3 |
// put in your directory below
|
4 |
root: '/tutorial_blog/' |
5 |
});
|
6 |
}
|
Percebamos que podemos remover this.navigate()
agora.
Passo 2: URL Estática
Outro problems é que as URLs não podem ser favoritadas ou revisitadas. Tudo que queremos fazer, temos de iniciar na URL principal. Por exemplo, se visitamos http://localhost/blog/admin, o roteador aceitar o padrão da URL mas o servidor ainda retorna 404. Isso porque ao visitar /admin
, o servidor não sabe que deve ir a index.html
primeiro para iniciar o roteador.
Uma forma de resolver esse problema e configurar o servidor para redirecionar todas URLs para index.html
. Mas isso não é o escopo dessa classe. Tentaremos de outra forma: adicionando #/
antes das URLs.
A URL do painel admin ficaria http://localhost/blog/#/admin. Não é o ideal, mas é uma saída fácil. Quando o navegador encontra /#
, não vai encarar o resto da URL como caminho de um arquivo, vai, na verdade, direcionar o usuario para index.html para o Roteador fazer o resto.
Agora, continuemos e mudemos o atributo href
de todas as tags <a>
em index.html
de:
1 |
<a class="app-link" href="edit/{{url}}">Edit</a> |
para algo como:
1 |
<a class="app-link" href="#/edit/{{url}}">Edit</a> |
De forma similar, mudemos todos BlogApp.navigate()
em blog.js
de:
1 |
BlogApp.navigate('admin', { trigger: true }); |
para algo como:
1 |
BlogApp.navigate('#/admin', { trigger: true }); |
Também podemos remover alguns dos eventos da tag <a>
:
Por exemplo, o botão "Adicionar um Novo Blog" tinha um:
1 |
events: { |
2 |
'click .add-blog': 'add' |
3 |
},
|
4 |
add: function(){ |
5 |
blogRouter.navigate('#/add', { trigger: true }); |
6 |
}
|
Podemos removê-lo e vinculá-lo a index.html
:
1 |
<a href="#/add" class="add-blog btn btn-lg btn-primary">Add a New Blog</a> |
Também podemos remover essa função já que as URLs funcionarão por conta própria:
1 |
$(document).on('click', '.blog-nav-item', function(e) { |
2 |
e.preventDefault(); |
3 |
var href = $(e.target).attr('href'); |
4 |
blogRouter.navigate(href, { trigger: true }); |
5 |
$(this).addClass('active').siblings().removeClass('active'); |
6 |
});
|
Também removamos a classe active
por hora. Em artigos futuros colocaremo-na novamente de outro jeito.
1 |
<nav class="blog-nav"> |
2 |
<a class="blog-nav-item" href="">Home</a> |
3 |
<a class="blog-nav-item" href="#/admin">Admin</a> |
4 |
</nav>
|
Certo, naveguemos pelo blog, testemos e garantamos que todos os links são, agora http://localhost/#/..., exceto a página inicial.
Agora, temos URLs que podemos atualizar e revisitar. Esperamo que tenha facilitado suas vidas!
Bônus: Outros Ajustes e Aprimoramentos
Se não se importarem com tutoriais longos, gostaríamos de aprimorar algumas coisas. Eis alguns ajustes que podemos fazer.
Passo 1: Ordenação
Talvez tenham percebido que as publicações estão ordenadas da mais antiga para a mais nova. Geralmente, queremos que os mais novos primeiro. Então, mudemos a coleção Blogs
para ordená-los dessa forma:
1 |
Blogs = Parse.Collection.extend({ |
2 |
model: Blog, |
3 |
query: (new Parse.Query(Blog)).descending('createdAt') |
4 |
})
|
Passo 2: Redirectionar para WelcomeView após update()
Eis outro ponto a aprimorar. Ao invés de abrir uma janela de alerta após atualizar o blog, podemos apenas redirecionar para a página /admin
:
1 |
this.set({ |
2 |
...
|
3 |
}).save(null, { |
4 |
success: function(blog) { |
5 |
blogRouter.navigate('#/admin', { trigger: true }); |
6 |
},
|
7 |
error: function(blog, error) { |
8 |
...
|
9 |
}
|
10 |
});
|
Passo 3: Mesclar AdminView em WelcomeView
Se gostaram da limpeza, também podemos mesclar AdminView e WelcomeView em uma—não há necessidade de ter duas visões separadas.
De novo, primeiro o modelo HTML:
1 |
<script id="admin-tpl" type="text/x-handlebars-template"> |
2 |
<h2>Welcome, {{username}}!</h2> |
3 |
<a href="#/add" class="add-blog btn btn-lg btn-primary">Add a New Blog</a> |
4 |
<table> |
5 |
<thead> |
6 |
<tr> |
7 |
<th>Title</th> |
8 |
<th>Author</th> |
9 |
<th>Time</th> |
10 |
<th>Action</th> |
11 |
</tr> |
12 |
</thead> |
13 |
<tbody> |
14 |
{{#each blog}} |
15 |
<tr> |
16 |
<td><a class="app-link" href="#/edit/{{objectId}}">{{title}}</a></td> |
17 |
<td>{{authorName}}</td> |
18 |
<td>{3:25 PM}</td> |
19 |
<td> |
20 |
<a class="app-link app-edit" href="#/edit/{{objectId}}">Edit</a> | |
21 |
<a class="app-link" href="#">Delete</a> |
22 |
</td> |
23 |
</tr> |
24 |
{{/each}} |
25 |
</tbody> |
26 |
</table> |
27 |
</script>
|
Então, mudemos BlogRouter.admin()
para enviar username
para AdminView
:
1 |
admin: function() { |
2 |
|
3 |
var currentUser = Parse.User.current(); |
4 |
|
5 |
// Check login
|
6 |
if (!currentUser) BlogApp.navigate('#/login', { trigger: true }); |
7 |
|
8 |
this.blogs.fetch({ |
9 |
success: function(blogs) { |
10 |
var blogsAdminView = new BlogsAdminView({ |
11 |
// Pass in current username to be rendered in #admin-tpl
|
12 |
username: currentUser.get('username'), |
13 |
collection: blogs |
14 |
});
|
15 |
blogsAdminView.render(); |
16 |
$('.main-container').html(blogsAdminView.el); |
17 |
},
|
18 |
error: function(blogs, error) { |
19 |
console.log(error); |
20 |
}
|
21 |
});
|
22 |
}
|
E passemos username
para renderização em #admin-tpl
:
1 |
BlogsAdminView = Parse.View.extend({ |
2 |
template: Handlebars.compile($('#admin-tpl').html()), |
3 |
render: function() { |
4 |
var collection = { |
5 |
// Pass in username as variable to be used in the template
|
6 |
username: this.options.username, |
7 |
blog: this.collection.toJSON() |
8 |
};
|
9 |
this.$el.html(this.template(collection)); |
10 |
}
|
11 |
})
|
Passo 4: $container
Por fim, podemos salvar $('.main-container')
como uma variável, evitando múltiplas consultas.
1 |
var $container = $('.main-container'); |
Depois, basta substituir $('.main-container')
por $container
.
Conclusão
Antes tudo, parabéns por chegar ao final! Foram vários artigos, mas você terminou o projeto inteiro. Além disso, adicionamos ACL aos blogs, implementamos URLs estáticas e ajustamos muitas coisas. Agora sim é um bom projeto.
Na próxima seção, aceleraremos as coisas e adicionaremos três novas funções: visualização e remoção de blog, e logout, porque agora temos um bom entendimento do Parse.js e poderemos ir bem mais rápido. Recomendamos pensar sobre como criar essas funções logo para já testar os conhecimentos um pouco. No mais, fique de olho!