Seção 22 - Design Pattern: MVC
Aula 273 - Apresentação do projeto
O projeto foi construido com o framework
Materialize e com um pequeno código
CSS sem maiores detalhes.
O HTML tem uma estrutura básica com um
<input>, e um <button> e uma tabela cujos dados
serão trabalhados posteriormente.
Ao final um script com um array de objetos que serão
tratados no decorrer do projeto.
Funções de cálculo e média vide Aula 99 e seguintes.
Nesta aula criamos a rotina para recuperação de dados e cálculo das médias. Para criar as médias percorremos os dados do array de alunos com o método forEach() e criamos um novo atributo média para o array. Em seguida criamos um loop for...in no objeto notas de cada aluno recuperando o nome do atributo e o valor de cada nota. Os valores são passados com o spread operator como parâmetro e tratados pela função avarege() e atribuidos como atributo / valor no objeto média. Esses dados serão enviados para as tabelas do HTML portanto os valores fixos do <thead> e <tbody> foram apagados.
Aula 275 - Escrever tag thead
Para a inclusão dos dados no thead fiz um resolução um
pouco diferente ddo professor. Na minha resolução criei
um array de materias recuperando os valores diretamente
na rotina de criação das médias e utilizei um
indexOf() para evitar que fossem incluidas
matérias repetidas por ter mais de um aluno. Com esse
array montado, fiz a inclusão dos valores nas <td>
através de um forEach().
Na resolução do professor ele utilizou o método
Object.keys(alunos[0]).notas).map(), ou seja,
ele recuperou os valores das chaves do objeto notas no
array alunos e fez um map() de cada elemento
para criar as <td> com as matérias.
Essa abordagem é interessante para utilizar os conceitos
aprendidos em objetos, porém tem a desvantagem de
utilizar especificamente as matérias do aluno no índice
'0'. Caso outros alunos tenham matérias diferentes em
sua grade elas não serão listadas na tabela.
Uma vantagem da execução do professor é que ele
utilizaou o método
join() para criar uma única linha com todos os
<td> e inserí-los de uma só vez no html, o que
diminui a quantidade de maipulações do DOM. Na minha
resolução fiz uma manipulação para cada passo do loop
forEach()
Nesta aula fizemos o lançamento das médias na tabela.
Para a obtenção do nome do aluno fiz da mesma forma que
o professor. Já para a obtenção da média utilizei o
método map() assim as notas devem estar na
mesma ordem que as notas que foram lançadas no array, ou
seja, todos os objetos de média devem estar na mesma
sequencia (o que nesse caso acontece).
Na execução do professor ele utilizou o método
forEach() percorrendo o objeto notas e
recuperando o nome das matérias. Esse nome é passado
para recuperar o valor das médias de cada matéria. A
consulta para ser testada no console seria a seguinte:
alunos[0].media["ciencias"]
ou seja a busca dos valores é feita pelo nome da matéria
independente da ordem em que ela esteja no objeto.
Em princípio meu erro para executar esse passo está no
modo que eu estava recuperando o listener. Estava
fazendo o listener no <button> mas como é um
<form> está sendo gerado um
?action= na URL. Relembrando precisamos
fazer o listener no submit e passar o método
preventDefault(). No mais inclui o novo aluno
além dos demais dados, inclusive as notas utilizando os
métodos estudados defineProperty(). No caso
desse método tive alguns problemas para incluir os
valores nas propriedades o que consegui resolver
alterando as propriedades enumerable e
writable.
Quanto a resolução do professor, não houve nenhuma
grande diferença da minha, o únnico porém é que ele não
gerou os dados da estrutura de dados de forma
programática, mas sim inseriu o objeto inteiro de forma
rígida apenas alterando o nome do aluno.

O MVC é um padrão de projeto (design pattern).
Seu significado é Model, View , Controller.
O Model é a classe que representa os dados da
aplicação. Possui as propriedades do objeto.
A View é a classe que possui os métodos e
propriedades para, a partir dos dados, gerar a
visualização em tela.
O Controller é a classe que liga os dados (Model)
à tela (View). É a classe que orquestra a comunicação
entre ambas.
As classes do Model não tem funcionalidade. O Controller
liga a View aos Services que é quem trata os
dados do Model.
Nesta aula criamos primeiramente os diretórios para
trabalhar com o MVC. Note que o VSCode já cria icones
especiais para as pastas com nomes no padrão MVC.
Nos diretórios específicos vamos criar os arquivos para
cada tipo de operação.
No diretório Models criamos o arquivo que irá
conter as classes das estruturas de dados.
No diretório Services criamos os arquivos que
serão responsáveis pelas operações executadas com os
dados obtidos de Models.
No diretório View criamos o arquivo que fará a
comunicação externa do programa.
Por fim o diretório Controller recebe os
arquivos que fazem a ligação entre Services e
View. Nesse arquivo o constructor() da
classe recebe como parametros service e
view, ou seja toda a orquestração entre os
dados manipulados pelo services e retornado para
a view é feita nessa classe.
Iniciamos agora a criação das propriedades das classes,
partindo de
Model.
Para a classe Model recebemos os parametros
nome, _id e notas. Para
notas criamos um valor default que é um objeto
vazio { }.
Para o id foi criada uma lógica simples criando
um novo parametro maxId que inicia em '0'. Para
cada novo objeto criado na classe
AlunoModel testamos se foi passado um número de
id. Caso não tenha sido passado esse valor ele
irá recuperar o valor do atributo maxId. Caso
exista ele receberá o valor passado no parametro e em
todos os casos será verificado se o valor do
id recebido é maior que o valor armazenado em
maxId. Caso positivo ele substitui o valor de
maxId.
Para as notas criamos um parametro que recebe as notas
com um
spread operator. Não entendi bem o motivo, vou
esperar para tentar assimilar.
A última propriedade a ser incluida é a media que
recebe um objeto vazio { }. Em seguida incluimos o
código de cálculo utilizado anteriormente aproveitando a
função avarege(). Atenção para a ordem dos
arquivos js no HTML.
Para os Services criamos a propriedade
alunos que é um array que irá receber os objetos
aluno instanciados a partir do Model.
Esses objetos são testados para verificar se são
instancia de AlunoModel e caso positivo são
includos com o método push()no array.
Para finalizar essa aula instanciamos a classe
AlunoService e utilizamos o método
forEach() para instanciar cada aluno do array
pré-definido alunos no Model
Se pesquisarmos no console o nome alunos vamos
recuperar o array pre-definido. Entretanto podemos
verificar os alunos chamando o objeto instanciado
alunosService. Esse objeto que irá receber
todos os alunos criados no AlunoModel
Criação da classe View. Essa classe recebe como
parametro do constructor a referencia a tag
<table>. Instanciamos a classe View no
arquivo app.
Essa classe tem como propriedades a
table recebida por parametro, as referencias ao
<thead> e <tbody> e a propriedade
materias que recebe um array com o nomes da
matérias.
Criamos uma função(método) para renderizar os dados na
tela. Esse método vai receber a mesma sintaxe construida
no início do exercício, porém com alguns ajustes das
referencias que passarão a utilizar o this (ou
seja a referencia da classe) como seletor dos elementos.
Para renderizar as informações dos alunos criou-se uma
função
render() que recebe como parametro alunos.
Nessa função copiamos a estrutura utilizada no arquivo
app. Fiz uma estrutura que recuperava os dados
nos parametros, mas o correto é trabalhar com o padrão
MVC. Dessa forma a função render apenas trata os dados
recebido do Controller.
Então o método render() fica na estrutura de
View e recebe os parâmetros do
Controller. O Controller por sua vez
recebe como parâmetros service e
view e executa a função
view.render() que recebe como parametro
service.alunos.
No arquivo app instanciamos a classe
Controller passando como parametro os objetos
alunosService e alunosView.
A separação da rotina de add é feita no
app e no controller. No arquivo
app vamos manter o listener que monitora o
submit, o preventDefault() do
submit e a recuperação do valor digitado.
Em seguida enviamos o valor recebido no <input> para
o objeto
alunosController atravé do método
add() que recebe como parametro o nome.
Note que o professor utilizou como variável a própria
palavra nome portanto não precisou passar o
conjunto chave: valor poiss os dois tem o mesmo
valor (nome: nome = nome). Eu utilizei como valor
_name portanto tive que passar o par
nome: _name.
No controller criamos o método
add() que recebe aluno como parametro e
executa o método add() de services e o
método render() de view.
Para cada alunos criado incluimos o nome. Nesse
momento as notas e a média são incluidas
como um objeto vazio pela classe
AlunoModel (notas é passado como parametro
default e média é um atributo que tem como valor um
objeto vazio padrão). Já o _id passa pela lógica
generateId.
Para fazer a inclusão das notas criamos no
render() do view om operador ternário
no loop forEach() que le os valores das notas.
Esse ternário faz o teste para verificar se as
notas são undefined. Caso seja cria um link para
a página de inclusão de notas (a ser desenvolvida).
Para finalizar foi criada uma lógica para verificar sse
existe alguma nota no objeto notas. Caso não
exista cria apenas um link para adicionar todas as
notas.
Inclusão do arquivo HTML para a incluão de notas
e explicação de sua forma.
Note que alguns dos módulos JS não são necessários nessa
página (View, Controller) pois contém classes
específicas para a página principal, como criação da
tabela.
O objeto fixo alunos foi transferido para a página de
edit, bem como a instacia do objeto
alunosService mas apenas de forma provisória
pois esses dados vão causar problemas. Outra coisa que
precisamos fazer é tratar o id do aluno que recuperamos
na URL.
Para recuperar o valor pasado no URL utilizamos a
propriedade window.location.search que retorna
o valor a partir do '?'.
Para recuperar apena o valor instanciamos o método
URLSearchParams(parametro_da_URL)
const URLParams = new
URLSearchParams(parametro_da_URL);
console.log(URLParams.get('nome_do_parametro'));
Feito isso criamos um método em search para
fazer o filtro dos alunos pelo id. Para isso
utilizamos o método find() no array de objetos
alunos.
O valor recuperado no get() deve ser convertido
para inteiro. Com isso conseguimos recuperar o aluno
individualmente.
Primeiramente criamo um botão Cancelar para o
formulário de edição que retorna para a página
principal.
Em seguida vamos utilizar o
JSON.stringify() para converter os dados do
objeto em string para armazená-lo no
localStorage.
O método para inclusão de dados no localStorage poderia
ser criado no Controller mas o mais apropriado
é no Services pois é onde é processado o método
add(). Desse modo após ser incluido no array
chamamos o método que inclui os dados no localStorage.
Esse método converte os dados em uma string JSON e
inclui os dados com o método
localStorage.setItem(nome_da_chave_armazenamento,
dados_a_armazenar).
Ainda em Services criamos um novo método para
verificar se existem dados no localStorage, e caso
positivo renderiza-los na tela.
Esse método substitui a rotina que importava os dados
fixos do arquivo app. Primeiramente recuperamos
os dados com o método
localStorage.getItem(nome_da_chave_armazenamento). Em seguida fazemos o teste para verificar se há dados
e caso positivo convertemos os dados com o
JSON.parse() e executamos o loop
forEach() para criar os objetos de cada aluno.
Por fim removemos os dados fixos do arquivo
edit e passamos a recuperar os dados dos alunos
pelo id e apresentá-los na tela.
Nesse ponto vamos escolher qual abordagem utilizar para
a lista de matérias. Entre as opções a que mais me
interessou foi criar um
Model com as informações de matérias. A
desvantagem é que não poderemos manipular os dados sem a
criação de serviços e outras estruturas de controle.
Mesmo assim acho que seria a abordagem mais
profissional.
A abordagem utilizada entretanto será a de criar um
Service que armazene os dados das matérias, uma
vez que a única informação necessária é a string com o
nome da matéria.
Criamos então um novo Service para
materias. Esse Service tem apenas o
array de matérias. Criei um método add para
incluir matérias no array. Não vou nesse momento
desenvolver essa idéia, mas pelo menos tenho como
trabalhar nisso depois (vamos ver como desenrrola a
aula).
Para trabalhar com essa classe temos duas opções.
Instanciar um objeto matéria como um novo objeto ou
intanciar as matérias diretamente na View junto
com a instancia de AlunosView. Com isso
passamos as matérias como parametro para o
constructor() de
AlunosView substituindo o valor fixo pelo
parametro recebido. Como materias não foi instanciado
acredito que não seja possível adicionar matérias nesse
modo.
Nesta aula criamos um arquivo para gerenciar o campo de
inclusão de notas. Além de alguns ajustes no HTML
tranferimos as tags que criam os campos de entrada dos
dados das notas para o arquivo de
View criado de forma que as linhas sejam
adicionadas na port de edição de acordo como as notas a
serem incluidas.
O constructor() da View do
edit vai receber como parametros o
container dos campos a serem editados e a lista
de matérias recuperada do Services de matérias.
Instaciamos essa classe no script do arquivo
edit.html
Em seguida criamos o arquivo Controller do
edit que recebe como parametros o
model que é o aluno a ser editado e o
objeto instanciado da classe View que será
renderizado no Controller.
Na sequencia criamos o parametro materias na
View e utilizamos o método map() para
recuperar todas as matérias e retornar uma linha de
entrada de dados para cada uma.
Por fim incluimos os nomes das matérias no
value do primeiro <input> da estrutura HTML e
tornamos ela disabled para que não se edite seus
valores.
Aqui vamos alterar a abrangencia do
EditAluno para que possamos também recuperar na
View os dados do aluno para que possam ser
editados. Para isso criamos uma nova propriedade na tag
<form> para que ela faça parte dos campos que são
passados como parametro para a View.
Ainda no arquivo edit.html incluimos um listener
para o botão de salvar que vai recuperar os dados do
campo nome para possibilitar a edição do nome do
aluno e vai executar o método edit() em
editAlunoController, passando como parametro
aluno e nome.
O Controller de EditAluno vai ter o
método edit() que irá receber o nome do aluno
para edição e terá um objeto notas que irá
receber as notas dos alunos e irá chamar o método
edit() do Service que futuramente irá
tratar o aluno e as notas.
Primeiro fizemos alguns ajustes na construção das tags
da
View para unificar os nomes dos <input> e
criamos um atributo na row para selecionar a
origem de dados. Em seguida incluimos o mome da matéria
nos id das notas e criamos atributos para cada
trimestre com valores de 0 a 3.
No Controller criamos um array com todas as
matérias de cada linha. Para isso utilizamos o método
Array.from() recuperando cada matéria. Nesse
array percorremos cada posição com o método
forEach() e criamos um novo array para cada
matéria para recuperar as notas de cada matéria. Em
ambos os casos recuperamos as informações utilizando
querySelectorAll() com os atributos criados nas
tags.
Em seguida recuperamos os nomes das matérias com
getAttribute nos atributos matéria de
cada linha. Esses valores são atribuidos ao objeto
notas como chave de atributo. Após isso fizemos
um map() nos valores dos <input> para criar
o array de notas.
A diferença para a minha resolução foi que eu utilizei o
forEach() e tive que fazer o
push() dos dados no array, ao passo que
utilizando o map() o retorno é um array.
Para gravar os valores em LocalStorage atribuimos
a propriedade aluno.notas o valor de
notas (por algum motivo isso não funcionou para
mim).
No Services executamos no método
edit() o método
updateLocalStorage() para armazenar os dados em
LocalStorage, entretanto não é claculada a média.
Para resolver isso retiramos do constructor() no
Model a rotina que executava o cálculo de média
e criamos um método para essa operação que é executada
tanto no Model quanto no Service. A
próxima tarefa é apresentar na tela de
edit() os valores atuais das notas. Se passamos
o value diretamente nas tags <input> temos um
erro.
Veremos como resolver isso na próxima aula.
Encadeamento opcional (optional chaining) faz um teste
para verificar se determinado objeto existe ou não em
uma cadeia de objetos. Por exemplo caso tenhamos o
seguinte objeto:
obj = {foo1: {foo2: {foo3: {foo4: ''}}}}
Para verificar essa estrutura sem receber erros em casos
em que queremos verificar se existem objetos poderíamos
fazer verificações do tipo:
obj.foo1 && obj.foo1.foo2 &&
obj.foo1.foo2.foo3...
Já com o encadeamento opcional podemos fazer da seguinte
forma:
obj.foo1?.foo2?.foo3?.foo4?.foo5?.foo'n'
Com isso recebemos uma mensagem undefined ao
invés de um erro.
Esse procedimento vai ser utilizado na View do
edit para evitar que tenhamos erros quando formos
editar alunos com os campos de valores vazios e com a
propriedade value setada para o valor de notas
do array. Isso porque quando passamos os valores e o
sistema verifica o array vazio ele retorna um erro. Com
esse procedimento ele passa a retornar
undefined e permite que acessemos os campos.
Em seguida fazemos alguns ajustes de usabillidade. A
primeira delas foi atribuir o método
window.location.assign('index.html') no arquivo
edit para ao clicar no botão
salvar retornar a tela inicial.
Para editar as notas dos alunos após a digitação apenas
incluimos um
link na tag dor nomes dos alunos referenciando o
arquivo de edit e o _id do aluno, com os mesmo
link utilizado para incluir as notas.
Para criar o filtro de nomes inserimos um pequeno código
HTML para criar o input do filtro.
Verificamos o conteudo desse filtro no app e
testamos se o tamanho do valor digitado é maior que 2
caracteres ou igual a 0. Nesses casos chamaremos um
método search() no Controller. Esse
método chamará o método searchName() no
Service e fará a renderização dos dados na tela
com o métod render() da View.
O método searchName() irá executar um
filter e procurará com o indexOf() os
valores no nome dos alunos.
Diferente do localStorage 0
sessionStorage armazena dados que precisam ser
recuperados apenas naquela seção como por exemplo as
seleções, filtros e dados digitados pelo usuário durante
a seção e apenas nela.
A sintaxe e os métodos são os mesmos dos utilizados no
localStorage, passamos uma chave para criar a
referencia no sessionStorage e o valor a ser
armazenado. Feito isso trabalhamos com os métodos
setItem() e getItem().
Criamos o setItem() no listener do evento a ser
armazenado, e recuperamos caso exista valor armazenado.
Entretanto quando retornamos a página anterior apesar de
manter o valor não realizmos novamente o filtro. Para
reexecutar esse código criamos um objeto
Event() monitorando o mesmo evento que dispara o
filtro (no caso o 'input'). Em seguida chamanos o
envento que está sendo monitorado e utilizamos o método
dispatchEvent() com o objeto de evento criado
como parametro. Esse método não é compatível com todas
as versões de navegadores. Para executar em modo de
compatibilidade podemos utilizar o método
createEvent(). Nesse caso devemos iniciar o
método com
document.createEvent('nome_do_evento') e em
seguida inicializar o evento e definir qual evento será
monitorado.
Seção 23 - ES Modules
Aula 301 / Aula 302 - Introdução / Sintaxes para trabalhar com módulos
O ES Models surgiu como um modo de facilitar a
inclusão de diversos <script src=''> nas aplicações
JS. Outro problema é a ordem de execução dos arquivos de
script e as variáveis globais.
Dividir uma grande aplicação em pequenos módulos traz
vantagens para manutenção (mais fácil), namespacing
(isolar escopos de variáveis) e reutilização (criação de
módulos reutilizáveis).
No lado do servidor com o NodeJS temos o CommonJS que
trabalha com o
exports e require para separar os
módulos da aplicação. Podemos utilizar a sintaxe do
CommonJS, bem como outras (AMD / UMD) através de bundles
como o webpack. Já a ESM é nativa para o Front
End e utiliza a sintaxe export e
import. Essa sintaxe tem suporte no Back End a
partir da versão 14 com algumas adaptações.
Para criar sintaxes utilizando o ES Moddules, criamos uma arquivo JS principal que irá receber os dados dos demais arquivos. Esse arquivo deve ser invocado no HTML no <script src='' type='module'> com o type modules. Esse arquivo principal receberá as funções dos arquivos de módulos. Os arquivos de módulos utilizarão o operador export para indicar que aquela função pode ser acessada em outros arquivos e os arquivos que puderem executar essa função vão utilizar o operaddor import para receber essa função. Podemos utilizar quando exportamos o arquivo o operador default que vai fazer com que a função exportada possa ser impotada e executada com qualquer nome. Quando exportamos a função sem o default precisamos importa-la com o mesmo nome de exportação declatado entre chaves '{}'. Podemos exportar qualquer tipo de dado e não apenas funções. Podemos exportar vários elementos de uma vez declarando todos entre chaves {}.
Aula 304 - Refatorar Lista de Alunos com ESM
Vamos refatorar o código MVC para ESM. Primeiramente
alteramos o arquivo app.js para index.js, e incluimos no
<script> o
type='module'. Em seguida colocamos o operador
export em todas as classes dos arquivos MVC. Em
seguida fazemos a importação de todos os arquivos
necessários em cada módulo.
O próximo passo é remover o script que estava no arquivo
edit.html e criar um arquivo JS com esse script. Por fim
fazemos a importação dos módulos necessários no arquivo
edit.
Assim quando fazemos a aplicação utilizando o MVC, fica
fácil portar ela para o sistema de model.
Requisições assíncronas (AJAX)
Aula 305 / Aula 306 - Introdução / XMLHttpRequest
A sigla AJAX significa
Asynchronous Javascript and XML, basicamente é
quando precisamos de uma comunicação assíncrona entre
cliente e servidor. O termo ainda é aplicado muito
embora hoje não se utilize mais o XML mas sim o JSON.
A diferença entre programação sincriona e assincrona
consiste em que na primeira o código é executado na
ordem em que aparece no arquivo e é bloqueante. Já na
assíncrona o código não precisa ser executado na ordem
que aparece além de não ser bloqueante.
Já o XML é um tipo de arquivo de tranporte de dados. Ele
é estruturado de modo semelhante ao HTML e possui
inclusive alguns métodos do DOM.
Já o JSON tem uma estrutura semelhante a um
objeto e deve obedecer apenas duas regras: nome
de propriedades e strings devem estar entre aspas
duplas e não podemos utilizar vírgula após o
último elemento.
Falando agora sobre HTTP, ele é um protocolo de
comunicação que transporta não apenas HTML mas também
JSON, JS, XML, etc.
As comunicações entre cliente e servidor são feitas com
request e response.
A comunicação no browser é feita através do
XMR que podemos
analisar através do DevTools do navegador na aba
de Network. Lá podemos verificar todas as
informações das comunicações estabelecidas pelo
XHR, inclusive o status code que são os
códigos de mensagens entre as comunicações. Podemos ver
a lista de códigos em
Códigos de status de respostas HTTP
O XMLHttpRequest é um objeto que contém métodos
para conexão com o servidor. Hoje temos outras maneiras
de fazer essa comunicação com async/await,
fetch, promises. O
XMLHttpRequest possui quatro operações
necessárias:
Instaciar objeto, open(), send() e
listener onreadystatechange.
O listener de onreadystatechange pode receber
como resposta:
0 Conexão não iniciada;
1 request configurada;
2 request enviada;
3 em processamento;
4 resposta recebida (completa: sucesso ou falha).
Vamos criar um arquivo JSON para testar as requisições
com
get. Isso geralmente fazemos isso através de
APIs
que são aplicações que fazem a ligação entre o frontend
e o backend.
Os verbos HTML são GET, POST,
PUT, PATCH, DELETE,
OPTIONS.
Fizemos um arquivo HTML com um script para fazer uma
consulta ao arquivo 'JSON'. Esse script segue a
estrutura explicada na aula anterior. O método
open() recebe como parametro o tipo de operação
HTML e a URL a ser consultada. O método
send() não precisa de parametros e o listener
recebe uma função que irá processar as requisições.
No exemplo fizmos um teste lógico para verificar o
estado do
listener e o status da requisição. Caso
os dois sejam positivos (retornaram sem erro) apresentam
os dados no console. Os dados retornados em uma XHR são
do tipo string portanto para fazermos consultas
temos que converte-los para um formato tratável como o
JSON. Em cao de erro retornamos o código de erro através
do status.
Aqui isolamos em um arquivo as funções de requisição de
dados. Esse arquivo tem uma função que recebe como
parametro o tipo de requisição, a url que contém os
dados, um função de callback que irá executar a
requisição e item de dados que será parametro do
send() caso ele seja necessário.
A função de callback será responsável por tratar os
dados recebidos pela resposta ou por processar o erro
caso houver.
Alguns logs no console permitem entender como funciona o
código assincrono. Os logs são apresentados antes do
retorno dos dados por mais que a resposta do servidor
seja rápida.
Vamos reescrever o APP da aula 158 ('Todo list') para
que ele receba dados através de requisições.
Para isso vamos utilizar uma ferramenta de testes no Web
chamada
{JSON} Placeholder. Aqui temos diversas listas fake para testar
requisições.
Essa ferramenta de testes permite utilizar os verbos
GET, POST, PUT, PATCH, DELETE, etc.
Na documentação ela trabalha com o método de requisições
fetch que será estudado adiante.
O GET é utilizado para ler os dados da base. Já o POST
adiciona novas informações. O PUT e o PATCH alteram os
dados, com a diferença que o primeiro exige que se envie
todas as informações atualizadas, já o segundo recebe a
'id' pela URL e altera apenas o dado selecionado.
Podemos utilizar o DELETE para excluir dados.
Para testar as APIs temos o aplicativo
Postman que faz
teste das requisições do mesmo modo que o
Insomnia. Podemos fazer todos os tipos de
requisições e testar o seu retorno. Para testar
utilizamos os dados do site {JSON} Placeholder.
Nesta aula vamos refatorar o projeto, trabalhando com
ESM.
A primeira coisa a fazer é inserir o
type='module' na chamada do arquivo de script.
Em seguida criamos o primeiro arquivo de
Model com a função Task() e fizemos a
exportação e importação.
Em seguida copiamos o arquivo com os métodos do
XMLHttpRequest para nosso projeto. Ele será
responsável por recuperar os dados do site
{JSON} Placeholder.
Em seguida importamos a função
createXMLHttpRequest() para o arquivo
principal. Criamos uma função init() que será a
função de callback a ser executada após recebermos os
dados da requisição, e configuramos os parametros da
função de requisição.
Em seguida precisamos fazer alguns ajustes nos nomes dos
atributos do Model
Uma outra alteração foi no tratamento do erro de
verificação do status da requisição. Alteramos o
if de executar caso haja negação do erro para não
executar caso haja erro.
Quando adicionamos tarefas trabalhando com requisições
precisariamos utilizar o método POST (lembrando
que o site que estamos utilizando para as requisições
não permite enviar dados). Entretanto no nosso
aplicativo estamos simplesmente fazendo um
push() para o array de tarefas e renderizando
novamente a lista. A API por ser fake encaminha
como retorno ao método POST o código 201,
entretanto não altera realmente os dados. Para ajustar
esse problema vamos utilizar o JSON-server que
cria um JSON estático na máquina e permite fazer
requisições como se estivessemos tratando um banco de
dados.
Fizemos tamném um pequeno tratamento do CSS,
substituindo o
display: grid por display: flex e mais
alguns ajustes entre os elementos.
Aqui primeiramente verificamos a instalção do node e do
npm. Em seguida iniciamos o projeto com o comando:
$ npm init -y
Feito isso já temos um proejeto iniciado. O próximo
passo é instalar o json-server
Documentação. Para isso vamos utilizar o comando:
$ npm install json-server --save-dev ou
$ npm install json-server -D (a flag
--save-dev ou -D significa que o pacote só aparecerá no
ambiente de desenvolvimento 'devDependencies').
Como fizemos a instalação de pacotes do 'node' criei o
arquivo
.gitignore e o readme.md.
Em seguida criamos um arquivo db.json para criar
uma estrutura de dados fake que será utnilizada no
projeto.
Em seguida executamos o json-server com o
comando:
npx json-server arquivo.json
Com isso iniciamos um servidor com dois end-points
(conforme foi criado no arquivo json). Acessando esses
end-points temos uma retorno igual ao que recebemos
quando acessamos o
{JSON} Placeholder.
Essa estrutura criada permite a consulta dos end-points
ccom filtros como por exemplo:
localhost:3000/users/1/tasks
Não entendi como é feita a vinculação dos ids, preciso
estudar isso.
Para que o servidor json seja atualizado automaticamente
quando alteramos a base de dados devemos incluir a flag
--watch dessa forma:
npx json-server --watch arquivo.json.
Podemos automatizar o processo de inicialização do
json-server, incluindo o comando de inicialização
no arquivo package.json do projeto. Para isso
inserimos o comando de inicialização dos serviços que
desejamos no atributo "scripts",
"start": "módulo_a_executar" .
Uma vez criada essa entrada, basta iniciarmos o projeto
com o comando:
$ npm start
Podemos executar o comando não apenas com o atributo
start. Podemos criar um nome personalizado para
executar o módulo e executá-lo com o comando:
$ npm run nome_personalizado.
Para instalar as dependencias em um projeto que não
esteja inicializado podemos iniciar o projeto e instalar
as dependencias com o comando:
$ npm install.
Pelo postman vamos agora criar uma nova tarefa
com o método POST. Para isso selecionamos no
aplicativo o método desejado e inserimos na opção
body os dados que desejamos incluir no arquivo
JSON. Quando fizemos a inclusão a primeira vez tivemos
um erro pois é necessário que as tasks também tenham um
id. Criamos esse campo e fizemos o POST de
dados com sucesso.
Para criarmos a rotina de POST vamos refatorar a rotina
que tem a função de addTask(). Essa função é
iniciada pelo listener do botão submit.
Iremos utilizar a função
XMLHttpRequest() utilizando o método
POST e passando como parametro a url da
API do json, a função que executava o
push() dos dados e por fim os dados a serem
inseridos.
Os dados não serão mais incluidos diretamente pela
função
addTasks() mas sim pelo retorno da função de
callback do POST.
Em seguida ajustamos as variáveis url para que
possamos acessar os usuários de acordo com o valor
passado e não de modo fixo. Outro ajuste a ser feito é
no método do XMLHttpRequest(). Esse método
estava fazendo um teste apenas para os códigos de
retorno positivos da API do método GET (200 ou 304). Os
códigos de erro das comunicações AJAX são os valores
maiores que 400, assim podemos alterar o teste lógico
para retornar erro apenas nos casos de código de retorno
acima de 400.
Nesse momento ao testar a API verificamos que não
estamos gravando os dados corretamente. Isso porque os
dados não estão sendo recebidos como json. Para
corrigir isso vamos precisar fazer os seguintes passos:
Como estamos trabalhando com um método que tratava
'também' arquivos
xml precisamos informar que o tipo de estrutura
que estamos enviando é json. Fazemos isso
incluindo na função XMLHttpRequest() a
identificação do tipo de dados da seguinte forma:
xhr.setRequestHeader('Content-Type',
'application/json;charset=UTF-8').
A próxima alteração é alterar o tipo de dados enviados
para uma
string ao invés de um objeto. Fazemos isso
alterando os dados com JSON.stringify().
Apenas para entender o funcionamento do POST a
função de callback do método que faz executa esse método
recebe como resposta os dados que foram enviados para o
json-server, assim o parametro enviado para a
função de callback é o valor recuperado na resposta ou
seja response.title.
Aqui inserimos uma flag no package.json para que o
próprio
json-server seja responsável por levantar o
servidor html. Para isso incluimos no atributo que
possui as configurações de inicio do
json-server a seguinte flag: --static ./
Essa flag incia o servidor html em localhost:3000. Por
padrão ele procura a pasta public e o arquivo
index.html. Nesse caso passamos como pasta padrão
a pasta do projeto './' e chamamos o arquivo pelo nome
no navegador.
Essa alteração foi feita para evitar que tenhamos dois
servidores concorrendo no mesmo projeto.
Analisando agora a questão de sincronismo do código,
inserimos alguns consoles para entender a ordem de
execução da inclusãoo de dados.
Quando acionamos o evento de listener para adicionar uma
tarefa chamamos a função addTask(). Essa função
executa a função createXMLHttpRequest() que
envia o método http POST e chama a função de
callback que é responsável por adicionar os dados no
array que será apresentado na tela. Essa função finaliza
sua execução assim como a função addTask(),
entretanto, por ser assíncrona a função de callback só
finaliza sua execução após isso. Desse modo a função de
renderTasks() que é executada pela função
addTask() não faz nada pois os dados ainda não
foram inseridos no array.
Os métodos HTTP são os seguintes:
GET: recuperar dados - caminho: '/tasks'
POST: cadastrar dado - caminho: '/tasks'
PUT: atualizar o dado completamente - caminho:
'/tasks/:id'
PATCH: atualizar o dado parcialmente - caminho:
'/tasks/:id'
DELETE: deletar dado - caminho: '/tasks/:id'
A diferença dos métodos PUT e
PATCH são que o primeiro requer se se passe
todo o objeto de dados para alteração ao passo que o
segundo pode fazer alterações apenas do atributo
selecionado.
Uma observação: Toda vez que chamamos o endereço da API
na aba de endereços do browser estamos fazendo uma
requisição do tipo
GET.
Em seguida fizemos algumas dessas operações no
Postman.
Quando utilizamos o método PUT com dados
incompletos perdemos os dados que não foram
alterados, ou seja apagamos todos os dados gravados
que não tenham sido declarados no objeto
enviado.
Entre alguns ajustes de dados que são necessários estão
a falta de algumas informações no arquivo JSON que tem
no array original.
Outro problema é que estamos fazendo um
push() dos dados para o array de objetos o que
pode ser substituido pelo GET dos dados
armazenados no JSON.
Por fim podemos dividir a aplicação utilizando o padrão
MVC.
Criamos o arquivo de Service que passará a ser
responsável por fazer as requisições para o banco de
dados. Fizemos a importação e exportação dos arquivos
necessários. Criamos dois métodos um add e um
getTasks (pelo que entendi o método add ainda
não está implementado). Em seguida trouxemos as rotinas
de GET que estavam no arquivo principal.
Nesta aula vamos criar o Controller que será
responsável por processar as requisições do
Service e a View que fará a construção
da aplicação na tela.
Primeiramente criamos o Conrtroller que irá
receber o Service e a View como
parametro. O método add do
Controller irá executar o método
add do Service, já instanciando uma
nova Task(note que o VSCode já faz o import do
arquivo do Task automaticamente).
A partir daqui ficou complicado acompanhar o
desenvolvimento do processo. Essa parte da aula
precisarei acompanhar novamente no futuro, mas em resumo
criamos o arquivo da View apenas com a função
render(), sem implementa-la ainda. Foi feita a
rotina de add no módulo de Service que
é responsável pelo POST dos dados.
Foram criadas as instancias dos novos elementos no
arquivo principal e os ajustes no arquivo de listener.
Aqui vamos implementar o método render() na
View.
Passamos o conteúdo da função
renderTasks() para o método
render() da View e passamos como
parametro para o construtor da View a variável
ul que é a referencia para a tag de inclusão da
lista no HTML.
Em seguida movemos todo a rotina de construção do HTML
para a View porém sem exportar para outros
arquivos, ou seja, sem fazer parte da classe de View,
mas apenas como um função dentro do arquivo.
O método add() do Controller recebe o
parametro tasks de Service. Acontece
que no Service o valor adicionado em
tasks é um objeto e precisamos passar os
parametros separados para o Model. Para isso
fizemos um destructuring dos valores recebidos
para que eles virem parametros separados para o
Model.
Agora removemos a função renderTasks() pois
pela nova sintaxe que estamos utilizando não devemos
fazer essa operção no arquivo inicial, e sim no
controler.
Neste ponto os icones de edição e manipulaçoa dos dados
inseridos apenas estão mainpulando o array que está na
memória do projeto e não o arquivo json da
aplicação.
Adicionei o getter/setter no Model que
eu não tinha implementado e ajustei esses métodos na
função clickedUl().
No Service agora não estamos mais executando o
push() dos dados no array. Ao executar o
add() de dados o POST passa a executar
um função que executa o getTasks e recupera os
dados diretamente do json.
Primeiramente inserimos o id no
Model pois não tinhamos esse parametro quando
estavamos utilizando arrays. Em seguida criamos um
atributo HTML na criação da tag <li> feita na
View que irá armazenar o valor da id.
No método de delete() vamos recuperar o
id pelo atributo criado na taga <li>. A
seguir criamos em Control o método
remove() que irá chamar o método
remove() no Service e irá renderizar o
resultado novamente.
No Service o método remove() recebe o
id da tarefa a ser excluida e executa o método
DELETE e chama o método GET para
reconstruir a lista atualizada.
Tentei fazer os métodos de edição e update mas não
consegui.
Para o método de editar o conteúdo, chamamos no
Controler um método update() passando
como parametro um objeto com o valor recuperado no campo
de edição e o id a ser editado, além do
userId.
No Controler recebemos no método
update() o objeto enviado e o userId e
chamamos o Service.
No Service recebemos o objeto task e
enviamos com o PATCH no endereço recuperado
pelo id passado no objeto, o objeto
task passado pelo JSON.Stringify. Esse
objeto será enviado pelo AJAX para alteração dos dados.
A impementação do checked foi mais difícil do que
eu pensei. Meu começõ estava certo mas não soube, como
na tarefa de edição, como passar o objeto com os dados
de edição. O método utilizado para enviar os dados foi o
mesmo já criado para edição do título, mas foi criado um
novo método no Service para fazer a seleção do
item a ser alterado com o find() para filtrar o
item a ser editado. Para finalizar adicionamos ao task a
data atual para ser enviada para o campo
updatedAt.
Iniciamos uma refatoração e revisão do código. Primeiro
retiramos alguns itens desnecessários do arquivo
principal e movemos a função de listener dos clicks para
fora da funbção init(). A seguir temos duas
chamadas para os arquivos Serice e
View que não devem estar no arquivo principal.
Primeiramente criamos uma função getTasks() no
Controller que executa o GET através
do Service e renderiza os dados na tela através
do View. Com isso podemos remover a função
init().
O próximo passo é remover a variável userId do
arquivo principal. Para isso criamos um arquivo de
configuração exportando essa variável para o
Controller e removemos todos os
userId passados por parametro no arquivo
inicial, e recebidos por parametro no
Controller. A partir de agora o Controller
recebe o userId do arquivo de config e apenas
passa o seu valor como parametro. Outra variável que
pode ser incluida no arquivo config são as
variáveis de URL que passam a ser exportadas e
importadas pelo Service.
A outa mudança foi retirar o getter/setter do
Model pois não estamos utilizando as variáveis
de modo privado. O get eu já não estava utilizando
(apenas no botão de cancelar). Já o set estava
passando o time de update. Esse atributo foi
para o método update() em Service.
A estrutura da aplicação após a refatoração para o
modelo MVC passou a funcionar da seguinte forma:
O arquivo principal faz a requisição dos dados ao
Controler Este por sua vez chama o
Service para que ele recupere os dados através
do método GET. Além disso o Service a
função reder() como parametro e retorna essa
função com os dados para o Controler que pede
para o View renderizar os dados e apresentar na
saida. Para a função de adicionaro fluxo é basicamente o
mesmo, entretanto nesse caso o Service faz a
requisição de POST, espera o retorno e faz a
requisição GET, agora com os dados atualizados.
Vamos agora ajustar o tratamento de erro das requisições
da API. Podemos fazer um teste lógico e apresentar a
mensagem de erro em um alert ou em uma página de erro.
Ou podemos criar duas funções de callback, uma para
quando tivermos uma resposta de sucesso e outra para
quando temos um erro. Essas funções são incluidas no
Controller em todas as ações que são
encaminhadas para o Service, onde são recebidas
como parametro e passadas para a API do AJAX para testar
a resposta do servidor.
O promises facilita a manutenção de sistemas
que exigem muitas requisições a servidores, pois reduz a
quantidade de callbacks do sistema.
Basicamente uma função promise retorna um
objeto do tipo promise que contém uma
propriedade que identifica o status da requisição que
pode ser pendente, preenchido ou rejeitado. A
promise pode executar duas funções, uma função
de sucess ou uma função de reject.
Diferente do XMLHttpRequest, na Promise não
precisamos passar as funções de callback como parametro,
mas sim devemos retornar uma função de
sucess ou reject. O resto do código de
criação da comunicação com o Front-End é o mesmo
(criação dos pontos de acesso para consulta de dados com
os métodos de comunicação open, send, etc...).
A função de sucess retorna os dados recebidos e
a função de reject retorna um Error.
Com isso quando fazemos a requisição para o servidor só
precisamos passar o método da requisição e o endereço, e
não mais as funções de retorno. Assim tratamos o retorno
da promise com .then() para a resposta
com sucesso e com .catch()para o erro.
Para o POST recebemos a primeira
promise que é relativa a requisição de inclusão
dos dados em seguida aguardamos o processamento da
promise de retorno dos dados. Dessa forma temos
dois .then() encadeados, cada um aguardando a
resposta de uma promise. Fiz as funções de
DELETE e PATCH sozinho. Uma coisa que
fiz diferente foi que passei como parametro do retorno
da promise o userId e a cb no mesmo em
.then(), entretanto devemos esperar a primeira
promise para depois executar o cb.
Refatoração do código com promises.
Aula 338 / 341 - Fetch GET / Tratamento de erros Fetch / Fetch POST / Fetch DELETE e PATCHLink para inicilaizar o projeto com json-server ativado
O método fetch() espera uma promise e
portanto também opera com .then() e
.catch(), entretanto ele é menos sensível a
erros de end-point, ou seja, ele só encaminha um erro
quando realmente não alcançar nada. Os dados recebidos
pelo .then() contém um atributo ok que
caso tenha alcançado os dados retorna true.
Assim os dados, para serem alcançados precisam aguardar
duas promises, a primeira é a response do
fetch() que deve ser convertida com o método
response.json(). A seguir recebemos uma nova
promise que irá retornar os dados no formato esperado.
O fetch() tem o método GET como padrão
(se passamos só a URL ele executará o método GET) e ele
substitui toda a rotina que inicializa a comunicação com
o servidor (os métodos open(),
setRequestHeader, send()).
Podemos passar a verificação das promises do
fetch para o retorno do fetch() e
nesse ponto podemos fazer através do .then() o
tratamento de erros que não são avaliados pelo
.catch().
Desse modo podemos verificar a resposta ok do
fetch(), se seu retorno for
true enviamos o return da promise, e caso seja
false, enviamos uma mensagem de erro.
Para passarmos os métodos HTML para o fetch precisamos
configurar os parametros no método através de um objeto.
Esse objeto recebe o método e os dados (no caso de
inclusão ou alteração de dados) e a configuração de
Content-Type no header.
Feito isso o fetch passa a ser funcional para qualquer
método.
O async/await permite que trabalhemos com
requisições assíncronas como se fossem síncronas.
Quando trabalhamos com promises o código continua a
executar independente de a resposta ter chegado ou não.
Com o async/await podemos declarar que
determinada função é assíncrona (async) e que alguma
parte do código deve aguardar a resposta para continuar
a execução(await). Assim quando definimos a função
assíncrona com o async/await não teremos a
promise como resposta pois o código aguradará o retorno
da função assíncrona antes de continuar a execução.
O método .finally() pode ser utilizado junto
com o fetch() e sempre vai ser executado tanto
quanto tiver uma resposta válida ao
.then() quanto quando tivermos um erro com o
.catch().
Para fazermos o tratamento de erros do
async/await podemos utilizar o
try/catch.
Consegui fazer o desafio. Existem alguns arquivos que não foram utilizados (como o Model) mas podem ser utilizados futuramente então deixei.
Aula 346 / Aula 347 - Resolução desafio / Resolução desafio - parte 2
A resolução é bem mais simples que a que eu fiz.
Primeiramente não foi utilizado o MVC, o que facilitou
as comunicações entre as promises. A lógica para
monitorar o numero do CEP seguiu o mesmo princípio. As
mensagens de erro passaram os valores para o alerta de
erro. Os valores recebidos foram recuperados como um
objeto antes de serem encaminhados para os inputs na
tela.
Em seguida foi feita a alteração para que o código
funcionasse com async/await e por ultimo
utilizando o try/catch para retornar o erro.
Por se tratar de um sistema single thread, sempre que
trabalhamos com sistemas web emfileiramos todos os
processos, inclusive os do próprio navegador que está
executando o código. Desse modo todos os processos são
concorrentes, e assim o código é bloqueante.
Já o Web Worker roda fora da thread do
navegador,e portanto não é bloqueante, sendo executado
em paralelo com os demais códigos.
As comunicações nestes casos é feita por
messages através de request/response. Por não
fazer parte do processo do navegador, não permite a
utilização de métodos e eventos do DOM.
Para demonstrar o delay causado pela fila na thread,
fizemos um loop while para travar a execução
quando apertamos o botão. Com isso podemos peceber que
enquanto o sistema está no loop while, todas as denais
ações ficam na fila aguardando a execução do loop.
Nesses casos em que temos processos que podem congelar a
ação do usuário é que devemos utilizar os
web workers.
Para criarmos um worker primeiramente devemos instanciar
um new Worker(arquivo_worker.js) onde o
arquivo_worker.js receberá o código que está parando a
aplicação.
No exemplo passamos portanto o código que está parando a
aplicação para o worker e tranmitimos mensagens entre o
processo principal e o processo executado pelo worker
para demonstrar a sua ação.