Seção 8 - Javascript
Aula 145 - O que é JavascriptNo client-side permite aplicar dinâmica aos elementos da página web.

Diretamente no arquivo HTML dentro da tag <script>.
A partir de um arquivo externo importado com a tag <script src="meu_arquivo.js>.
Em aplicações HTML4 devemos incluir o atributo type="text/javascript". Em aplicações mais antigas podemos encontrar a instrução <script language="javascript">.


Como exemplo criamos um <input> que será modificado através do Javascript com a instrução document.getElementById('nome').value = 'Oi'.
Se invocarmos o script dentro do <head> nada vai ocorrer pois ele será executado antes da interpretação do html. Podemos verificar esse erro também no console que exibe uma mansagem de erro (Cannot set property 'value' of null).
Esse erro de precedência é corrigido colocando a execução do Javascript no final do <body>.
Tipos principais de variáveis em Javascript: string, number e boolean
Regras de sintaxe para nomes de variáveis: Não podem ser iniciadas por números, não podem utilizar caracteres especiais (ç, ^, ~) e não podem ser utilizadas palavras reservadas, são case sensitive.

Lembre que o Javascript assume o tipo de variável pelo valor atribuido a ela.
Utilizando comandos de retorno:
alert() - abre um dialog.
document.write() - escreve o valor no browser.
console.log() - exibe o valor no console do browser.

Quando a concatenação é com variáveis tipo numerica o Javascript já percebe que o operador '+' não é para uma soma e tranforma a variável, se for numérica, em string.
Em seguida atribuimos às variáveis valores obtidos do usuário com o comando prompt(). O prompt() sempre recebe os valores como string, mesmo que sejam numeros.

Poderemo notar a diferença entre elas nas expressões condicionais e no retorno de funções.


if(condição){código a ser executado} else{código a ser executado} ou
if(condição){código a ser executado} else if(condição){código a ser executado} else{código a ser executado}
== Igual: Verifica se os valores comparados são iguais.
=== Idêntico: verifica se os valores comparados são iguais e do mensmo tipo.
!= Diferente: Verifica se os valores comparados são diferentes.
!== Não idêntico: verifica se os valores comparados são diferentes e de tipos diferentes.
<, <= Menor / menor ou igual: Verifica se o valor da esquerda é menor/menor ou igual ao valor da direita.
>, >= Maior / maior ou igual: Verifica se o valor da esquerda é maior/maior ou igual ao valor da direita.


Para esse exercício utilizamos o prompt() para receber os dados. O prompt() recebe os dados como string asssim o Javascript faz as principais condicionais sem levar em consideração o tipo. A excessão é com as condicionais === e !==.

&& E(and): Verdadeiro se todas as expressões forem verdadeiras.
|| Ou(or): Verdadeiro se pelo menos uma das expressões for verdadeira.
! Negação(not): Inverte o resultado de comparação.


O resultado da condição tem que ser um valor e não pode ser uma expressão ou outro teste lógico, diferente do if/else.

switch(opção) {
case parâmetro 1:
//código a ser executado
break
case parâmetro 2:
//código a ser executado
break
default:
//código a ser executado
break
}

Lembre que os parâmetros no case não aceitam testes lógicos ou outras operações, apenas valores para comparação/verificação.
Outra questão é que o switch faz a verificação de valor e tipo, portanto a comparação é de valores identicos, assim quando pegamos o valor do prompt() por exemplo precisamos converter o valor recebido caso desejemos comparar com numeros.
+ : soma de valores (atenção para casos de concatenação)
- : subtração
* : multiplicação
/ : divisão
% : módulo (resto de divisão)
++ : pré/pós incremento
-- : pré/pós decremento

Atenção aos casos de pré incremento/decremento e pós incremento/decremento.
Devemos sempre nos atentar ao tipo dos valores que chegarão nas variáveis, pois na maioria das vezes os valores não vem como numero mais sim como string. Esse problema é mais comum nas operações de soma pois o operador + pode realizar uma concatenação.
Note que no exemplo foi feita a conversão de string para Int apenas na operação de soma (não fiz a conversão globalmente para ficar claro que o Javascript faz a conversão automaticamente nas outras operações).


1o. multiplicação e divisão
2o. soma e substração
function calcularAreaTerreno(largura, comprimento) {
var area = largura * comprimento
return area
Padrões para nomes de funções ( verbo_no_infinitivoSubstantivo ).
Parâmetros ou argumentos funcionam como uma entrada de dados para a função. Podemos passar nenhum parâmetro ou vários parâmetros.
Dentro do escopo da função podemos criar variáveis, criar estruturas de repetição, estruturas condicionais e inclusive chamar outras funções.
As funções podem ser do tipo void (não retornam nenhum valor apenas processam alguma lógica) ou com retorno quando processada vai retornar um valor para o script que a invocou.

Após criarmos a função devemos desenvolver a lógica utilizada por ela.
Feita a lógica podemos invocar essa função em nosso código. Quando chamamos uma função que espera algum parâmetro devemos encaminhar esses parâmetros na ordem definida na função.
Funções void realizam um processamento mas não retornam nada para quem fez a chamada. Já as funções do tipo retorno, retornam um parametro para quem realizou a chamada.
Mesmmo que façamos uma saida para a função (por exemplo com document.write()) se ela não tiver um return ela será do tipo void, mesmo apresentando o resultado na tela. Com isso a função se torna restrita àquele tipo de operação, e sua execução se limita aquele tipo de saida.
Para utilizarmos uma função em diversos momentos diferentes utilizamos o return. Com o return podemos enviar qualquer valor, seja uma string, um numero, um boolean ou uma variável. Esse retorno pode ser tratado posteriormente por quem chamou a função.
Podemos também atribuir o retorno da função a uma variável. Para isso chamamos a função no valor de atribuição da variável. Note que podemos declarar a variável externa com o mesmo nome da variável interna da função. Isso devido a diferença de escopo das duas.
Outro detalhe é que quando fazemos a função do tipo void com uma saida para a tela, toda vez que chamarmops a função ela fará o retorno na tela (note como ficou o exemplo).
Podemos passar os parametros para a função de diversas formas.
Em Javascript podemos declarar a função mesmo após sua chamada pois o Javascript faz toda a leitura do script antes de comçar a sua execução, portanto antes da execução pelo interpretador ele já aloca a posição da função na memória.

Por exemplo se declaramos em uma função dois parâmetros e passamos na chamada quatro parâmetros, o Javascript simplesmente vai ignorar os dois últimos. Lembrando que podemos declarar na função quantos parâmetros forem necessários.
Já se passamos menos parametros que o declarado, os parametros faltantes serão considerados como undefined. Nesse caso em uma soma, por exemplo, o resultado será NaN(not a number).
Nos casos em que podemos ter parâmetro a menos que o necessário, como o parâmetro faltante é cconsideredo como undefined, podemos tratar esse falta, como no exemplo
b = b === undefined ? 0 : b; (se b for undefined atribua o valor 0, se não b vale b).
O mesmo ocorre quando não passamos nenhum parâmetro. Neste caso todos os parâmetros serão considerados undefined.
Por fim podemos passar parâmetros variáveis (quantidade indefinida) mas para isso precisamos de arrays e loops.

O escopo global se refere a função como um todo.
O escopo de função correponde ao conteúdo dentro do bloco da função.
O escopo de bloco corresponde a instruções contidas dentro de comandos como o if e o while.
Quando declaramos uma variável no escopo global podemos acessá-la no escopo de bloco e no escopo de função.
Quando declaramos uma variável em um bloco com o comando 'var' temos o hoisting dessa variável, ou seja, ela é elevada ao escopo global, sendo acessível tanto no script quanto nas funções.
Já variáveis declaradas no escopo da função não sofrem a elevação e ficam restritas ao escopo da função.
Quando um bloco está dentro de uma função, a variável declarada nele também sofrerá o hoisting mas apenas do escopo de bloco para o escopo da função em que o bloco está declarado. Neste caso a variável não se eleva para o escopo global.

Funções anonimas são funções sem nome definido. Para isso utilizamos o conceito de wrapper. No Javascript podemos atribuir às variáveis não apenas valores mas também funções. Utilizando esse conceito podemos atribuir ao valor de uma variável uma função anonima que será chamada pelo nome da variável seguida de ().
Esse recurso é muito utilizado em Javascript pois utiliza-se muito as funções de callback que são funções chamadas como parametro em outras funções.

Quando criamos uma função que tem que executar outra lógica, ou seja, uma outra função nos parâmetros podemos, por exemplo, incluir essa funções como parâmetro na chamada da função principal desse modo:
funçãoPrincipal(parametro1, function(){ ... }, function(){ ... })
Entretanto dessa forma teremos dificuldade em fazer as manutenções no código, além de não podermos reaproveitar a função.
Então o correto é criarmos as funções que serão parâmetro para a função principal utilizando as funções anonimas e chama-las apenas como parametro da função principal dessa forma:
var funcao1 = function(){ ... }
var funcao2 = function(){ ... }
funçãoPrincipal(parametro1, funcao1, funcao2)
Quando chamamos uma função anonima como parâmetro em outra função não devemos utilizar parenteses pois caso contrário o Javascript vai entender que deve executar a função no momento que ela for lida nos parâmetros quando o que se espera é apenas encaminhar essa função.

Propriedades e métodos:
legth : mostra a quantidade de caracteres da string.
charAt() : exibe o caracter na posição solicitada (índices iniciam em 0).
indexOf(): exibe o índice do primeiro caracter pesquisado. Este método é case-sensitive. Caso não encontre o valor o retorno será sempre '-1'.
replace(): substitui os caracters selecionados por outro valor.
substr: extrai parte da string a partir da posição selecionada, com a quantidade de caracteres selecionada.
toLowerCase() : converte a string para caixa-baixa.
toUpperCase() : converte a string para caixa-alta.
trim(): remove espaços em branco no inpicio e no final da string.

Propriedades e métodos:
Math.ceil() : arredonda o numero sempre para cima.
Math.floor() : arredonda o numero sempre para baixo.
Math.round() : arredonda o numero pela média.
Math.random() : gera numeros flutuantes aleatórios entre 0 e 1

Diferente dos métodos Math para utilizar os métodos de data devemos instanciá-lo conforme segue:
var data = new Date()
Com isso passamos a ter um novo objeto de data atribuido a uma variável que terá as informações do momento que for instanciado. Os valores de data são obtidos de acordo com a configuração da máquina do usuário, portanto refletem o valor que estiver configurado na máquina naquele momento.
Geralmente as datas são trabalhadas no back-end, entretanto podemos trabalhar no front-end com esse objeto.
Propriedades e métodos:
getDate() : exibe o dia do mes.
getMonth() : exibe o mes (a contagem é feita de 0 a 11, assim precisamos somar 1 para ter o número do mes correto).
getFullYear : exibe o ano.
Os métodos de data além de apresentarem o tempo atual permitem que façamos a atribuição de uma data com os métodos set.

toString() : exibe a data completa (dia da semana, mes, dia do mes, ano, hora, fuso horário).
setDate() : seta o dia do mes através de soma ou subtração de dias.
setMonth() : seta o mes através de soma ou subtração de meses.
setFullYear() : seta o ano através de soma ou subtração de anos.
Podemos setar a data desejada quando instaciamos a data com o comando:
var data = new Date(ano, mes(-1), dia, hora, min, seg, ms)
Para fazer cálculos com datas transformamos a data com o método: getTime() : converte a data em um numero real em milissegundos calculada a partir de 01/01/1970.
Neste exemplo utilizamos mais uma propriedade matemática a Math.abs() que converte qualquer numero em número positivo.
Por fim precisamos fazer os cálculos matemáticos para converter a unidade de tempo que se deseja encontar para milissegundos.


onclick : acionado quando o evento é clicado.
ondblclick : acionado quando o evento é clicado duas vezes.
onmouseup : acionado quando o clique sobre o elemento é liberado.
onmouseover : acionado quando o cursor está sobre o elemento.
onmouseout : acionado quando o cursor do mouse sai da área ocupada pelo elemento.
Podemos utilizar os eventos diretamente nas tags dos elementos que serão ativados pelo evento. Isso pode ser feito incluindo o evento como uma propriedade na tag como no exemplo:
<div onclick="alert('teste')"></div>
O mais usula entretanto é passar funções para a execução das tarefas do seguinte modo:
<div onclick="funcao()"></div>
Podemos também combinar vários eventos em um mesmo elemento.
Nem todos os eventos estão diponíveis para todas as tags. Consultar a documentação na W3schools DOM Events em Technical Details

onkeydown : acionado quando uma tecla é pressionada.
onkeypress : acionado entre o keydown e o keyup ou seja quando mantem a tecla pressionada (somente quando a tecla for um caractere, não conta para teclas especiais como CTRL ).
onkeyup : acionado quando quando uma tecla é liberada.
Quando utilizamos o onkeydown o evento é disparado (no nosso exemplo um alert) e o resultado da tecla pressionada só vai ocorrer depois do evento(no caso como é um alert, somente após fecharmos o alert). Essa característica é importante para a verificação de máscaras de caracteres pois podemos bloquear a digitação de caracteres inválidos antes de eles aparecerem no campo do formulário.
O onkeypress é muito semelhante ao onkeydown. A principal diferença é, como mencionado com as teclas especiais (CTRL, ALT, SHIFT) que são ignoradas no onkeypress

onresize : acionado quando o frame ou a página é redimensionada.
onscroll : acionado quando o scroll do mouse é acionado.
O evento onresize por agir na janela como um todo é um evento típico da tag <body>.
Já o evento onscroll pode ser atribuido ao elemento que é passível de rolagem, inclusive o <body>.

onfocus : acionado quando clicamos no elemeto colocando o foco nele.
onblur : acionado quando o elemento perde o foco, ou seja, quando mudamos de campo no formulário.
onchange : acionado quando o estado do elemento é alterado, principalmente em elementos do tipo select.
O evento onresize por agir na janela como um todo é um evento típico da tag <body>.
Já o evento onscroll pode ser atribuido ao elemento que é passível de rolagem, inclusive o <body>.
Todos os elementos do HTML ficam organizados em um objeto document sendo que essa organização pode ser representada como uma árvore onde os elementos pais encapsulam os elementos filhos formando trajetos definidos em todos os elementos que compõe a página.
Cada elemento HTML contido no DOM é chamado de node(nó).

getElementById() : Seleciona o elemento pela sua id. É o método mais utilizado em Javascript para sseleções no DOM e por esse motivo as ids devem ter nomes únicos.
getElementsByTagName() : Seleciona os elementos pela tag
getElementsByClassName() : seleciona os elementos pela classe
getElementsByName() : seleciona os elementos pelo name
Podemos selecionar os elementos por seus atributos.
Note que o getElementById() é o único que tem o seu nome no singular. Isso porque espera-se que a id seja realmente única.
Quando utilizamos o getElementById() o retorno é o elemento pois ele é único.
Já quando utilizamos o getElementsByClassName() ou o getElementsByTagName() temos uma HTMLCollection pois podemos ter vários resultados para essa seleção. Essa collection é apresentada em forma de array.
O getElementsByName() por sua vez retorna um NodeList também em forma de array.

Para isso utilizamos o método getElementById() para 'escutar' a propriedade value do <input> e armazenar o valor em uma variável.
O valor dessa variável é testado e caso retorne um número é atribuido ao <input> de números e caso retorne uma letra é atribuido ao <input> de letras ambos também com o método getElementById().value só que desta vez com o operador de atribuição (no caso o de concatenação e atribuição '+=').
Na lógica de teste para números e letras, fiz de forma diferente. No exemplo foi utilizado o switch, mas eu fiz a lógica com if e parseInt().

Ess função seleciona o elemento utilizando o método getElementById() e alterando a propriedade style.background atribuindo o valor recebido a essa propriedade.
Podemos ver os estilos que podem ser manipulados em HTML DOM Style Object
Alteramos tabém as propriedades height e width, passando seus valores também por parâmetro.
Note que como não passamos o valor do width nos parâmetros da cor vermelha o quadrado assume o último valor atribuido a sua largura.


Minha execução utilizou o mesmo método do gabarito, porém considerou a opção de não ter caracteres (volta para transparent).

Nesse ponto criamos a função calcular() que recebe como parametros tipo e valor, onde tipo indica se é um operador(ação) ou um número(valor) e valor indica o valor da tecla.
Em seguida criamos os eventos onclick para cada botão que chama a função calcular() passando como parâmetro o tipo e o valor de cada botão.

Em seguida fazemos a lógica do botão c. Nesse caso simplesmente passamos o value para ' '.
Os valores digitados são incluidos com concatenação no elemento do display.
O próximo passo é criar a ação para os operadores. Utilizamos para isso um seletor com operador lógico || que tem como resultado a concatenação do operador com os numeros digitados.
O resultado concatenado vira uma string com a operação matemática completa.
Para transformarmos essa string com a expressão matemática em uma expressão calculável pelo Javascript utilizamos o método eval() que faz exatamente essa conversão avaliando a expressão.
Por fim esse valor é atribuido ao value do input que demonstra o valor no display.
Um array premite armazenar de forma relacionada diversos valores em uma única variável.
No Javascript precisamos declarar que os valores a serem inseridos na variável são do tipo array.

var nome_variavel = Array()
ou
var nome_variavel = []
Criada a array podemos incluir os elementos atribuindo para eles um índice. O índice não precisa necessariamente ser numérico.
Podemos misturar elementos de diversos tipos em uma array (strings, numeros, boolean).
Quando criamos a array diretamente, sem especificar os índices o JS cria esses indices automaticamente iniciando do valor '0'.

Por exemplo quando criamos uma array como no exemplo:
var lista_frutas = [];
lista_frutas[1] = "Banana";
lista_frutas[2] = "Maçã";
lista_frutas[3] = "Morango";
lista_frutas[4] = "Uva";
o retorno do length do array é: '5'.
Isso porque ele considera o valor no índice 0 como empty. Se iniciarmos os índices em '5' teremos 5 elementos com valor empty

Esse tipo de array é importante para criação de relatórios complexos com vários dados.
Note no exemplo que quando criamos o segundo array com índices 'a', 'b' e 'c' o length do array fica definido em '0'.

unshift() : inclui objetos no início do array rearranjando os índices.
pop() : exclui o elemento do final do array.
shift : exclui o elemento do início do array, rearranjando os índices.


Array.sort(nomeDaFuncao);
function nomeDaFuncao(a, b) {
return a - b;
}
Essa função já é comum a propriedade sort() pois caso utilizemos ela com a chamada nomeDaFuncao() o próprio console dá o seguinte erro:
Uncaught TypeError: The comparison function must be either a function or undefined
Essa função também funciona caso os valores numéricos sejam passados como string pois o JS faz o casting dos valores ao se deparar com o operador '-' na função.

Precisei fazer uma refatoração para criar uma variável para o document.getElementById('entrada').value que estava sendo repetida várias vezes.

while(condição) {
// códigos a serem repetidos
}
Não podemos esquecer de criar uma interrupção para o loop, geralmente com o incremento da variável de controle.
Podemos também interromper a execução mediante alguma situação utilizando o comando break
Podemos ainda utilizar o comando continue, entretanto atenção pois dependendo da posição do incremento o continue pode gerar um loop infinito.

do {
// códigos a serem repetidos
while(condição)
Aqui também podedmos utilizar o break e o continue

for( variável de controle; condição; incremento) { // códigos a serem repetidos
}
Aqui também podedmos utilizar o break e o continue


Atenção ao usar as variáves de contagem em laços encadeados para que não haja sobreposição de valores.

for( var x in nomeDoArray {
// código a ser executado utilizando o índice do Array
}
Note que o retorno do for/in armazena na variável x o valor do índice do Array, portanto x é o índice de cada elemento do Array, independente dos índices serem sequenciais ou serem do mesmo tipo.

Função forEach sintaxe:
nomeDoArray.forEach(function(valor, indice, array){}) {
// lógica ou código a ser executado selecionando o índice, ou o valor ou o array inteiro
}
Os parâmetros da função de callback podem ter qualquer nome.
O forEach encerra a busca no momento que ele encontar o último valor do array.
Um detalhe quando fazemos uma manipulação do array é que quando fazemos uma alteração em algum dos valores de um indice, no console se expandirmos o array veremos o valor atualizado (na apresentação do array aparece o valor antigo o valor atualizado essa inconsistencia ocorre apenas na expansão do array).
Podemos ocultar parâmetros na função de callback e passar apenas o valor que é o primeiro parâmetro na função.
Podemos também colocar a função em uma variável e passar essa variável para o forEach facilitando a leitura do código.
O forEach só funciona para índices numéricos e ordena de forma crescente. Os valores com índice diferentes de numeros inteiros são omitidos no forEach.


Quando passamos valores na chamada da função podemos acessar esses valores precorrendo o objeto arguments utilizando, por exemplo o for / in.
Com essa abordagem podemos, por exemplo realizar somas com valores de parâmetros sem termos passado esses parâmetros na construção da função. Para fazer isso podedmos utilizar a seguinte estrutura:
function soma(){
var r = 0;
for(var i in arguments) {
r += arguments[i]
}
return r;
}
soma(1, 2, 3, 4, 5, 6,)
Nesse caso, mesmo que não tenhamos declarado os parâmetros ou criado uma função para executar a soma temos a manipulação dos valores passados na função com o objeto arguments sendo percorrido como um array e executando a atribuição com soma na variável r
Essa técnica era muito utilizada na ECMAScript 2009 e já não é tão usual na ES6 (2015).

Para o exemplo criamos um array bi-dimensional com um indice[1].
Vamos executar uma função que irá buscar um vídeo no indice[0] para forçar um erro. Essa chamada irá retornar undefined ou seja um valor inesperado. Nesse caso se tentarmos encontrar um valor do array bi-dimensional (no caso a propriedade) aí sim teremos um erro de execução. Nesse momento precisasmos de um tratamento de erro. Para as tentativas de executar uma ação quando utilizamos o try podemos 'pegar' o erro com o catch ou simplesmente finalizar a execução com o finally.
O finally pode ser executado diretamente após o try ou após capturarmos o erro com o catch. Em todos os casos vamos passar pelo finally.
Quando utilizamos o try / finally sem o catch não tratamos o erro portanto vamos ter um erro a menos que retornemos algo na função finally.
Já quando utilizamos o catch(erro) podemos tratar o erro e enviar uma mensagem desse erro para o backend através do parâmetro passado para o catch(). Esse tratamento pode ser feito através de uma função que recebe por parâmetro o erro.
O throw serve para lançar exceções. Podemos criar um objeto para lançar o erro e interromper a execução da aplicação. Preceba que o throw pode ser criado no início da aplicação de forma independente e uma vez iniciado para todo o resto da execução. O throw geralmente é utilizado junto com o catch. Ele sempre interrrompe a execução da aplicação.

O método alert() é um exemplo de método do BOM.
Podemos abrir uma nova janela com o método window.open(). A sintaxe é:
window.open(URL, name, specs, replace)
Entre os parâmetros do método open temos o specs que entre outras coisas pemite definir o tamanho da janela a ser aberta.
Já para fechar uma janela aberta com o BOM podemos utilizar o método close(), entretanto para que ele opere na janela aberta devemos ter uma referência a essa janela através da atribuição do método de abertura a uma variável. Feito isso o método close() passa a ser um método do elemento cuja variável abriu a janela.
Caso tentemos utilizar o método close() no objeto window teremos a seguinte mensagem de erro no console:
Scripts may close only the windows that were opened by them.
Outro método que podemos utilizar é o window.print() que abre uma tela de impressão da página.
Para consultar outros métodos de manipulação do window consultar The Window Object

Para consultar os métodos do objeto screen podemos acessar The Screen Object
Entre os principais métodos temos availHeight / availWidth que mostra o tamanho da tela disponível excluindo as barras das janelas e o height / width que retorna o tamanho total da tela.
Esses métodos apresentam os valores máximos que o dispositivo pode alcançar e não o valor que a tela está no momento (para saber o tamanho da janela no momento devemos utilizar os métodos window.outerHeight / window.outerWidth e window.innerHeight / window.innerWidth).
Com esses métodos podemos fazer uma lógica para menu responsivo por exemplo.

Para consultar os métodos do objeto location podemos acessar The Location Object
Com o método window.location.href="URL_redirecionada" redirecionamos o site.
Outro método interessante é o window.location.reload() recarrega a página.
O setTimeout(ação, tempo_em_milissegundos) permite a execução de determinada ação uma vez após o tempo determinado.
O setInterval(ação, tempo_em_milissegundos) permite executar uma ação de forma contínua no intervalo de tempo informado. O setInterval executa de forma indefinida até ser interrompido.
Podemos com o método setInterval e o método window.location.reload() fazer uma ação para recarregar a página a cada n segundos.
Já com o método clearInterval() podemos parar a contagem do intervalo em determinada situação (se não setarmos o clearTimeout() o código continua rodando mesmo quando o valor ficar menor que 0).
Podemos fazer o mesmo com o clearTimeout() mas nesse caso seria utilizado para cancelar o setTimeout antes do término da função.
A primeira coisa a se fazer é capturar as dimensões da tela com os métodos window.innerWidth e window.innerHeight.
Em seguida para que a atualização desses valores seja feita em tempo real criamos uma função para encapsular os métodos acima. As variáveis de altura e largura devem ser declaradas no escopo global da aplicação.
A função deve ser atribuida a tag <body> da página principal através do evento onresize para que monitore as alterações cada vez que o browser seja redimensionado.
Essas variáveis permitirão o redimensionamento da área de jogo de forma dinâmica.
Em seguida criamos a fórmula para as posições randomicas com a biblioteca Math.random() multiplicada pelos valores de altura e largura da tela. Utilizamos também o Math.floor() para arredondar os valores das posições pois as casas decimais são desnecessárias.
Para manipularmos a imagem de forma randomica na tela precisamos inclui-la através do JS. Para isso vamos criar uma variável que irá receber o método document.createElement('img') para criar uma tag <img> no HTML.
Feito isso, aplicamos esse elemento ao <body> como 'filho' com o método document.body.appendChild()
Outro detalhe é que nesse caso a referencia ao arquivo de script de ser colocada no final do <body> pois a referencia do document para realizar o appendChild é o body portanto ele precisa ser executado após a renderização da página. Podemos também manter o arquivo dentro do <head> e encapsular o código que cria o elemento <img> em uma função que será chamada no código html em uma nova tag de <script>.
Como criamos a <img> no JS, podemos excluir a tag do html principal.
Para posicionar o elemento de forma aleatória na tela utilizamos as propriedades de position no style do elemento.
Para isso setamos o elemento.style.left e elemento.style.top que receberá os valores das variáveis de posição randômica concatenada com a string 'px'. Devemos também setar o position como absolute elemento.style.position = 'absolute'.
Outro problema a ser resolvido é que o posicionamento não considera o tamanho da imagem. Desse modo temos que subtrair o valor dela do valor das variáveis position para evitar que a imagem saia da área da tela. Entretanto isso gera um problema para o caso de a posição ser 0. Nesse caso fazemos um operador ternário que irá garantir que se o valor randomico for igual a zero mesmo que ele fique negativo com a subtração seu valor seja no mínimo 0.
Esse valor deve ser testado (com if / else ou com switch) e deve retornar o nome da classe que irá definir o tamanho da imagem do mosquito. Essa função será chamada pelo método elemento.className e assim atribuirá o valor retornado como classe da imagem.
Para isso criamos uma nova função com dois valores randômicos da mesma forma que os tamanhos randômicos.
Para fazer a mudança de lado da imagem utilizamos o atributo CSS transform: scaleX(1) e transform: scaleX(-1)(esse valor inverte a imagem no eixo X).
O retorno da função deve ser concatenado com o valor da classe que define o tamanho randômico.
Esse método executará uma função anonima que executará a função posicaoRndomica() a cada 1000ms. A sintaxe ficará assim:
setInterval(function(){
posicaoRandomica();
} ,1000)
Em seguida para fazer com que apareça um mosquito por vez na tela criamos um id para a imagem utilizando o atributo do elemento.
Esse id será manipulado com o document.getElementById().remove(), mas ele deve ser testado se existe antes, pois senão na primeira execução o código dará um erro pois o id do elemento vai retornar null.
Em seguida criamos as <div> para o painel que contará a vida e o tempo de jogo.
Feita a estilização desses componentes e criadas as tags <img> dos corações de vida ainda no html.
Quando clicamos no elemento ele deve ser removido da tela e nada deve acontecer (com os pontos de vida).
Se o elemento não for clicado antes da remoção automática então os pontos de vida devem ser afetados.
A primeira coisa a se fazer é tratar o clique no elemento. Para isso criamos no elemento o evento onclick. A esse evento vamos atribuir uma função que irá trata-lo.
Como a função está associada ao evento e consequentemente ao elemento podemos nos referir a esse objeto como this.
Assim para removermos o elemento caso clicado utilizamos a chamada this.remove() que é o mesmo que elemento.remove().
Para tratarmos o caso de não clique a tempo vamos voltar ao teste lógico if criado anteriormente que verifica se existe ou não o elemento na tela.
Para manipular os corações de vida vamos criar ids para cada ponto de vida. Com esses ids vamos alterar a imagem do coração de cheio para vazio. Essa mudança é feita utilizando o método getElementById(id).src='imagem'.
Para removermos os corações de vida substituimos as imagens de src coracao_cheio por coracao_vazio.
Por fim para o contador de vidas criamos uma variável que deve ser incrementada a cada vida perdida. Essa variável controlará as ids que irão manipular os corações e determinará o fim de jogo.
Dessa forma precisamos criar um a nova página html que será referenciada pelo location.
Essa página será estilizada com o Bootstrap.
Para isso incluimos a imagem de Game Over encapsulada nas classes do Bootstrap e com o flexbox centralizado utilizando as classes container, row, col e d-flex justify-content-center (ver utilização do Bootstrap).
Dentro desse container criaremos ainda um botão com as classes btn btn-dark btn-lg e com o evento onclick que irá chamar novamente o BOM dessa vez direcionando para a página inicial window.location.href = 'pagina_inicial'
A função utilizada para o setInterval é apenas para fazer o decremento da variável tempo. Já para exibirmos o tempo do cronometro na tela criamos um <span> com um id e utilizamos o método document.getElementById('id').innerHTML = tempo.
O atributo innerHTML define o valor que será incluido entre as tags do elemento a que ele se refere.
Como o decremento do tempo ocorre antes de enviarmos ele para a tag <span> precisamos incluir o innerHTML com o valor da variável de tempo inicial no script inicial da página.
Em seguida precisamos criar uma lógica para parar o cronometro quando seu valor chegar a 0. Essa lógica com if detrminará a vitória caso o cronometro chegar a 0 e a vida ainda for maior que 0.
Feito isso precisamos parar a execução pois a função do setInterval continuará em execução. Para isso quando ocorrer a vitória devemos limpar a função com o clearInterval() recebendo como parametro a variável que inicia o setInterval (variável 'cronometro').
Mesmo assim ainda teremos a criação dos mosquitos pois a função que cria os mosquitos ainda estará em execução pois também utiliza uma função setInterval() (que está no <script> do documento html).
Precisamos portanto criar uma variável para armazenar a função setInterval que cria os mosquitos e também aplicar o clearInterval passando como parametro o nome dessa variável.
Para entender o memento de utilizar o clearInterval() precisamos limpar as funções que utilizam contadores de tempo no momento da vitória (momento que encerra o jogo, para de contar o tempo para perder vidas e para de gerar mosquitos).
Já quando o jogador perde a partida a aplicação chama uma nova página, portanto não precisa resetar os intervalos.
Para isso criamos uma nova página copiada da página de vitória e apenas alterando a mensagem de game_over.png para vitoria.png.
Outra coisa que fiz foi ajustar no CSS o background-size para cover, pois a imagem de fundo está em 1280 x 1017 pixels. Para a maioria das telas está ok (acredito até que na resolução utilizada na aula), mas para a resolução do meu monitor estava ficando menor que o tamanho da tela e portanto os mosquitos estavam aparecendo fora da área de fundo quando ficava em tela cheia.
A estrutura básica da página é a mesma das de vitoria e fim_de_jogo, utilizando o Bootstrap.
Inserimos um novo elemento com uma tag <select> e com as tags <option> que contém os diferentes níveis de dificuldade. Essa tag será encapsulada em uma <div> com a classe do Bootstrap mb-2 (define o margin bottom).
Em seguida criamos um evento onclick no botão que iniciará uma função iniciarJogo()
Essa função vai recuperar o valor da tag <option> selecionado através do comando document.getElementById('id_do_selection').value. O retorno desse valor deve ser testado para o caso de ser nulo (nesse caso retorna false) e caso positivo retorna o valor do atributo value do <option> selecionado.
Fazemos isso utilizando o windows.location.href
Para passarmos o valor das <option> passamos o parâmetro junto com o endereço html da página app.html utilizando o operador '?'.
Dessa forma utilizamos o endereço "app.html?" + nivel ou seja, concatenamos o valor de href com o valor recuperado na variável nivel.
Esse não é o modo convencional para tranferir o valor entre páginas.
Para tratarmos esse valor recebemos o endereço no arquivo app.js com o comando BOM window.location.href. Esse método encaminhará o valor completo enviado pela página. Para separarmos o valor de parâmetro utilizamos o método search da seguinte forma:
window.location.search
Esse método retorna apenas o parâmetro enviado com o '?'.
Para remover o '?' do valor recebido utilizamos o método replace para trocar o '?' por ''.
Uma vez que recebemos o valor como uma string sem caracteres especiais podemos fazer um teste lógico para cada string de dificuldade. Esse teste irá retornar um valor que será atribuido a uma variável que por sua vez será enviada para o setInterval da página app.html.
Por fim vamos alterar o cursor do mouse. Para isso acessamos no CSS indexador mais alto da página: o html.
No html aplicamos a propriedade cursor e definimos seu valor como cursor: url('imagem_do_cursor') 30 30, auto sendo que os valores são a posição para esquerda e para cima em relação a imagem.
Seção 9 - ECMAScript 2015 (ES6) e Orientação a Objetos
Aula 237 / 238 - O que é ECMAScript / Como utilizar o ECMAScript
2015 (ES6) em suas páginas?
Breve história da padronização ECMA.
Como utilizar a esspecificação ES6 em browsers sem suporte (considerando o curso em 2018). Utilizando o 'transpile' para converter códigos em ES6 para serem executados em ambientes ES5. Esse processo não deve ser mais necessário agora (em 2021) mas vamos fazer oss ajustess conforme necessário.
Aula 239 - Babel - Introdução e configuração.Como utilizar a esspecificação ES6 em browsers sem suporte (considerando o curso em 2018). Utilizando o 'transpile' para converter códigos em ES6 para serem executados em ambientes ES5. Esse processo não deve ser mais necessário agora (em 2021) mas vamos fazer oss ajustess conforme necessário.
Para incluir o Babel no projeto, utilizamos o método standalone recuperando o arquivo babel.min.js no site do Babel.
Para utilizar o Babel devemos incluir a indicação na tag dessa forma: <script type='text/babel'>. Isso é necessário para que o script entenda que deve ser intepretado pelo script do Babel incluido anteriormente.
Toda a codificação em ES6 deve ficar inserida dentro do <script> identificado como acima.
Outra inclusão necessária é uma <div id='output'>
Executando a página com o script Babel, quando inspecionamos os elementos podemos verificar que o código html possui dois <script>. Um com o código digitado e outro com a identificação type='text/javascript já convertido pelo Babel.
Lembrando, o JS tem três escopos: escopo global, escopo de função e escopo de bloco. O que 'identifica' um escopo diferente do escopo global é se a variável estiver dentro de um conjunto de { } que pode ser de uma função ou de um bloco de código.
Quando declaramos uma variável de escopo global com o operador var, podemos acessar essa variável de em qualquer parte do programa (em qualquer escopo) inclusive em window.nome_da_variável.
Quando criamos uma nova variável com o operador var, com o mesmo nome dentro do escopo de uma função, essa variável ficará acessível dentro da função e dentro dos blocos que estejam dentro dela, mas não fora do escopo da função. Assim a variável terá um valor dentro do escopo da função e seus blocos e outro valor no escopo global e seus blocos. As variáveis em escopo de função não sofrem o hoisting ou seja não 'sobem' de escopo (ficam restritas a função e seus blocos). Já uma variável criada em um bloco vai ter seu escopo elevado para escopo global caso esteja no escopo global ou para escopo de função caso esteja dentro de uma. Note que para a variável ser acessada no escopo da função a função deve ser chamada depois que a variável for criada. Portanto as variáveis criadas em escopo de blocos dentro do escopo global podem ser acessadas no escopo global, no escopo de função e no escopo de bloco, já uma variável criada dentro de um escopo de bloco que esteja dentro de uma função só poderá ser acessada dentro da função pois essa variável sobe apenas um nível.
Já quando utilizamos o operador let o escopo da variável é preservado. Note que o comportamento do let só faz diferença em escopos de bloco pois no escopo da função a variável não sofre 'hoisting' Portanto quando utilizamos o operador let para declarar uma variável em um bloco ela não ficará acessível fora do bloco retornando que a variável is not defined.
Com a criação do let e do const não se deve utilizar mais o var pois existe uma tendencia de ele ser removido em versões futuras do ECMA (não foi nessa mudança de versão do ES5 para o ES6 pois ele acabou de criar esse padrão).
A partir dessa aula não vou mais utilizar o Babel
Aula 242 - Análise de contexto 1 - Usando Var e LetSe tentarmos alterar o operador da variável global que já foi declarada com o let e declarar no escopo de bloco com o operador var teremos um erro pois haverá um conflito de declaração de variável.
Uma boa prática para criarmos variáveis com const é utilizarmos o nome da variável em letras maiúsculas.
Uma característica das variáveis const é que não podemos deixar de atribuir valor quando a inicializamos, ou seja, ela exige uma atribuição, mesmo que seja undefined.
Não podemos também atribuir um novo valor para uma const.
Podemos entretanto criar uma nova const de mesmo nome dentro de uma função e com valor diferente.
Podemos utilizar a const para armazenar links para APIs, senhas de banco de dados e outros valores que serão utilizados pelo script e que não devem ser alterados.
Para criarmos um template string utilizamos o símbolo ` ` (craze) para envolver o texto. Dentro deste sinal podemos escrever qualquer texto, sendo que os valores que serão resgatados de variáveis são declarados entre o sinal ${ }.
Podemos também incluir quebras de linha e o resultado será o que digitamos sem a correção pelo navegador ou pelo script.
Podemos também criar expressões ou executar funções dentro do template strings.
Esses parâmetros serão utilizados no caso em que a função seja invocada sem a atribuição dos valores.
Podemos passar apenas um parâmetro ou todos que serão substituidos conforme o caso.
Caso queiramos utilizar algum dos parâmetros default podemos definir na chamada da função esse parâmetro como undefined
Para criarmos arrow functions utilizamos a tribuição a uma variável da mesma forma que com a função anonima, porém não precisamos utilizar a palavra function, apenas indicamos os parenteses com os parÂmetros necessários seguido de uma seta '=>' e as chaves que determinam o bloco de código que será executado. A sintaxe é a seguinte:
let variável = ( parâmetros ) => {
//código a ser executado
}
Quando temos apenas um parâmetro na função podemos omitir os parenteses, mas quando não recebemos nenhum parâmetro precisamos utiliza-lo.
Podemos também omitir as chaves e o return (return implícito) caso a função retorne apenas uma instrução simples (como no caso de uma operação matemática).
Um exemplo de utilização da Arrow function simplificada é quando temos uma função com um teste lógico do tipo if que podemos converter em operador ternário conforme exemplo abaixo:
Arrow function normal
let parOuImpar = (numero) => {
if (numero % 2 === 0) {
return "par";
} else {
return "impar";
}
};
Arrow function simplificada
let parOuImparSimplificado = (numero) => numero % 2 === 0 ? "par" : "impar";
Paradigma é um padrão, uma forma de se executar algo.
O JS permite que no mesmo script desenvolvamos no mesmo bloco de código baseados no paradigma procedural e orientado a objetos.
Os princípios do paradigma procedural é o desenvolvimento de aplicaçãoes que seguem uma sequencia, que possuem estruturas de decisão (condicionais) e possuem também estruturas de repetição. A linguagem JS fornece suporte a funções que atendem esses requisitos.
let a = 10
let b = 7
let operador = 'mult'
functrion calcular(a, b, operador) {
if(operador === 'mult') {
console.log(a * b)
}
}
calcular(a, b, operador)
Aqui temos os pilares do paradigma procedural: a execução é sequencial, temos uma tomada de decisão com o operador if e não temos um laço de repetição por não haver necessidade.
Paradigma de Orientação a Objetos, exemplo:
class Calculadora {
constructor() {
this.a = 10
this.b = 7
this.operador = 'mult'
}
calcular(){
if(this.operador === 'mult') {
console.log(this.a * this.b)
}
}
}
calculadora = new Calculadora()
calculadora.calcular()
Na orientação a objetos criamos uma classe que é uma abstração de
alguma coisa, no caso, uma calculadora. No caso o ojeto é uma
'calculadora' pois o método que queremos executar é 'calcular'. Uma
abstração é a forma como o programador enxerga as coisas.
As variáveis que declaramos em um objeto são seus atributos.
Para que uma aplicação seja escrita seguindo o paradigma de orientação a objetos ela deve seguir alguns pilares. São eles: abstração, encapsulamento, herança e polimorfismo.
Entidade é algo do mundo real que trazemos para dentro da aplicação.
Identidade é a representação única de cada objeto originados da entidade.
Características são os atributos do objeto.
Ações são os métodos do objeto.
Os atributos e métodos devem ser criados na medida que forem necessários para a aplicação, ou podemos declarar tantas características e tantas ações quanto forem necessárias para a regra de negócio de nossa aplicação.
Para criarmos a entidade ou objeto utilizamos a palavra chave class seguida do nome do objeto por padrão iniciando com letra maiúscula e em camel case.
Em seguida para a abstração vamos determinar as características necessárias para o objeto de acordo com a regra de negócio. Note que não devemos abstrair todas as características existentes no ojeto, mas apenas as necessárias. Por padrão os atributos são descritos em camel case com a primeira letra minúscula. Esses atributos são declarados dentro de um método constructor() que será responsável por 'construir' o objeto quando ele for instanciado. Dentro do construtor os atributos são definidos com o operador this. que conecta o atributo a classe que irá construir o objeto.
As ações de cada objeto são declaradas como funções e são os métodos do objeto. Elas fazem parte também da abstração e devem obedecer as necessidades do negócio. Os métodos assim como as funções podem receber parâmetros e executar instruções e operações e retornar valores.
Definidos atributos e métodos precisamos instanciar o objeto que será a criação da entidade e definição de sua identidade.
Para isso criamos uma variável e atribuimos a ela um new objeto. Aqui criamos efetivamente a entidade e sua identidade. A partir desse momento podemos acessar os métodos e atributos através do nome da variável (identidade do objeto) instanciada.
Uma vez que instanciamos vários objetos utilizando a mesma classe cada instancia do objeto manterá seus próprios valores, mesmo que alteremos o valor em um dos objetos.
No paradigma procedural podemos criar o elemento através de variáveis que definem seus atributos e com isso gerar funções que executam seus métodos (ações). Entretanto quando temos diversos elementos que compartilham os mesmos atributos mas que tem valores diferentes no paradigma procedural devemos tratar esses valores em um array. Assim teremos que criar um array de cadeiras que em cada índice terá os atributos de cada cadeira. Para testarmos uma ação em determinada cadeira devemos chamar a ação na cadeira com o índice solicitado. Pelo exemplo podemos ver a complexidade de se executar esse processo no paradigma procedural.
No paradigma de OO criamos a classe com o método construtor. O construct() pode receber parâmetros que serão passados para os atributos no momento da construção do objeto. Podemos criar as variáveis, que no caso de objetos são os atributos com os mesmos valores que os parâmetros pois eles estão em escopos diferentes (o atributo está no escopo do objeto, o parâmetro está no escopo da função).
A reutilização dos atributos e métodos encapsulados se dá através do terceiro princípio/pillar da OO que é a herança.
O encapsulamento não é suportado nativamente pelo JS, mesmo na versão ES6, portanto existe uma convenção que indica a intenção do programador que determinados atributos ou métodos sejam public, private ou protected.
Quando criamos um classe de objeto e construimos seus atributos, para indicar que aquele atributo deveria ser privado iniciamos o nome do atributo com '_'. Isso não impede que esse atributo seja acessado diretamente no programa, mas indica ao desenvolvedor que esse atributo 'não deve' ser acessado diretamente mas sim através de uma função como deveria ser se esse atributo fosse private.
Para acessarmos esses atributos devemos criar os métodos get e set que retornam o valor do atributo propriamente. Assim para um atributo
this._atributoPrivate
criamos um método
get atributoPrivate() {
return this._atributoPrivate
}
Essa solução é necessária nas linguagens em que a OO é nativa, mas deve ser utilizada no JS mesmo que ele possa acessar os atributos diretamente por uma questão de padrão. Nesse caso o acesso ao atributo se dá através de uma pseudo-variável que na verdade é a 'função' criada pelo get. Quando invocamos essa função não precisamos utilizar o () pois o JS entende, quando utilizamos o get que aquela pseudo-variável se refere aquela função
O mesmo fazemos com o set porém como ele altera o valor dos atributos criamos uma lógica que irá proteger o atributo de ser acessado diretamente. Podemos, como no exemplo, verificar se o valor atribuido consta no array de valores definidos no construtor e impedir que se atribua valores inválidos.
Quando analisamos os objetos em uma aplicação devemos abstrair esses objetos e em certas ocasiões reagrupar atributos que pode ser comuns a mais de um objeto em outro objeto pai. Criamos com isso super-classes e sub-classes. As sub-classes podem ser chamadas de classes especializadas. As classes filhas herdam ou extendem os atributos e métodos da classe pai.
Para que as classes filhas contenham os atributos da classe pai devemos extender a super-classe nas sub-classes com a palavra extends. Além disso no método construtor, antes de declarar os atributos específicos devemos chamar o operador super().
Uma classe filha herda os atributos da classe pai e todas as heranças recebidas por ela.
Do ponto de vista da manutenção, caso em determinado momento precisemos adicionar novos atributos para nossos objetos, caso seja um atributo comum a todos basta cria-lo na classe pai, que automaticamente todos os filhos e netos herdarão esse atributo.
Podemos passar parâmetros diretamente para o construtor e podemos passar parâmetros para as classes pai através do operador super(). Desse modo quando instaciamos o objeto podemos definir cada atributo dele em parâmetros na instancia e passar para cada construtor específico definindo seus parâmetros no constructor() e enviando pelo super().
Os objetos literais são pares de nomes(atributos / chaves) e valores atribuidos a uma variável. Os nomes e valores são incluidos em '{ }' separados entre si por ':'. Cada conjunto de nome: valor são separados por ','.
Podemos declarar funções como valores.
Assim como no objeto construído através de OO, o objeto literal também pode ser referenciado com o this desde que a função esteja dentro do escopo do objeto literal.
O objeto literal não precisa ser instanciado como nas classes e podem ser acessados diretamente pelo nome da variável seguido do operador '.' e o nome do método ou atributo que se deseja acessar.
Utilizamos objetos literais quando não é necessário criar uma coleção de dados, mas sim utilizar os dados apenas uma vez, por exemplo quando temos um formulário. Nesse caso os dados são montados em um objeto literal, convertidos para JSON e armazenados no servidor.
Com a nova versão do ECMA quando temos os nomes das variáveis como nome das chaves não precisamos mais definir a notação chave: valor, bastando utilizar o nome da variável.
O mesmo ocorre com as funções. Não é mais necessário declarar a chave como nome da função e depois dos ':' declarar a função com a palavra function. Basta agora declarar o nome da chave seguida de ( ) e descrever o código da função.
De qualquer modo podemos definir o nome da chave diferente do nome da variável discriminando os dois do modo antigo.
nome_do_objeto.chave = novo_valor
Do mesmo modo podemos definir funções como valor utilizando por exemplo arrow functions conforme o exemplo:
nome_do_objeto.nome_da_função = () => {código_da_função}
Quando criamos objetos com funções construtoras devemos criar os atributos com o operador this mas sem o método constructor() pois não é uma notação baseada em classes mas sim uma função que será construida com o operador new.
Da mesma forma os métodos do objeto são criados com o operador this. Esses métodos receberão como parâmetro uma função, ou seja, serão também atributos do objeto.
Para fazermos o encapsulamento dos métodos, devemos criar os atributos que farão o papel de getter e setter. Desse modo mantemos os pilares da OO, mesmo que possamos executar a função diretamente no atributo.
Criada a função construtora devemos utilizar o operador new para criar o objeto, do mesmo modo que na criação por classes. Feito isso podemos utilizar os métodos criados da mesma forma que no modelo de classes.
Apesar de as sintaxes de constução por classes ou funções serem parecidas, mas com alguns comportamentos de contexto diferentes. Essas diferenças permitem que nas funções construtoras tenhamos encapsulamentos dos atributos para torna-los públicos ou privados.
Já quando criamos objetos através de funções construtoras, podemos definir atributos privados quando os criamos não com o operador this mas sim como variáveis dentro do escopo da função. Essas variáveis, por terem escopo local se comportam como se fossem atributos privados que só são acessados dentro da função e não são visíveis fora dela.
No exemplo de aceleração da aula anterior temos um valor de velocidade máxima que não está encapsulado e pode ser acessado fora do objeto. Entretanto é um atributo que deveria estar protegido.
Nesse caso ao declararmos esse atributo como uma variável de escopo local ela não poderá ser alterada fora do escopo da função. Mesmo que tentemos alterar seu valor ele nao será acessado nos métodos do construtor.
Podemos também crair métodos privados com funções atribuidas a variáveis do escopo local. Essas funções também só serão acessíveis dentro do escopo da função, portanto esses métodos serão privados.
Quando criamos uma função do tipo Factory declaramos os atributos e nos campos de valores indicamos parâmetros que serão passados pela função. Dessa forma quando criamos uma variável e lhe atribuimos como valor a função factory criada, em seus parâmetros passamos os valores para a criação de um objeto literal.
Podemos ainda remover os nomes das chaves caso o nome das variáveis de parâmetro sejam as mesmas das chaves como vimos na aula sobre melhorias dos objetos literais.
Entre as vantagens das funções do tipo factory a principal é o dinamismo na criação de objetos literais principalmente quando vamos receber dados através de uma lógica passada, por exemplo, por uma lógica que trata dados através de requisições http. Texto sobre Factory
Prototype é um modelo(protótipo) de alguma coisa. Quando criamos um objeto no JS ele é um descendente de Object.
Quando criamos obejtos através de métodos diferentes como no exemplo da aula (objeto literal, função construtora e classe) ao analisarmos o retorno desses objetos no console vemos que todos possuem um atributo chamado __proto__: Object. Ou seja como visto acima todos os objetos, independente de como sejam criados descendem de Object e Object não é um objeto e sim uma função.
Quando criamos objetos, muito embora todos sejam filhos de Object eles não são referenciados entre eles portanto seus atributos não são herdados.
Para extender os atributos de um objeto para outro podemos utilizar o atributo __proto__ comum de Objetct passado como parametro no objeto herdeiro tendo como valor o nome do objeto que vai tranmitir o atributo a ser herdado.
Neste caso se atribuirmos um valor para um atributo que seja herdado o JS vai sobrepor esse valor pelo de menor escopo, ou seja, o valor mais próximo do objeto que se está referenciando.
Uma observação é que o Object.prototype pode armazenar atributos que se aplicarão a todos os objetos que forem criados, pois como foi visto ele é pai de todos os objetos. Assim se definirmos um atributo em Object.prototype ele ficará automaticamente acessível em qualquer objeto criado no código. Essa propriedade deve ser utilizada com cuidado pois uma vez atribuido o atributo diretamente em Object ele ficará disponível globalmente.
O operador para rest/spread é representado por .... Na função de espalhamento podemos por exemplo espalhar uma string, ou seja separar cada caractere da string como um elemento separado. Podemos por exemplo agrupa-los em um array que terá cada caractere como um elemento.
Outra possibilidade é utilizar o spread para separar os elementos de um array e inclui-los em outro array indexado no array resultante.
Podemos também espalhar atributos de um objeto em outro objeto criando novos atributos para o objeto resultante.
Quando precisamos passar uma quantidade indefinida de parametros para uma função podemos utilizar o rest seguido do nome do parametro da função para que os dados passados na chamada da função sejam enviados em formato de array. Tendo esse array montado podemos percorrer seus valores de modo a poder trabalar com esses dados na função.
Podemos ainda passar um parametro fixo e passar outro parametro com a função rest.
Quando precisamos acessar diversos elementos de um array podemos atribuir cada um a uma variável tendo como seletor o índice do array. Entretanto quando temos muitos valores essa prática é inviável.
O operador destructuring é representado pelo token (caracteres) [ ] colocados à esquerda da atribuição podendo ser iniciado com let ou const.
Dentro do token [ ] do destructuring colocamos os nomes das variáveis que serão atribuidas a cada indice do array. Caso queiramos pular algum dos valores do array podemos deixar o espaço em branco apenas separando por ','.
Podemos criar nomes de variáveis além dos valores contidos no array. Neste caso teremos um retorno undefined. Nesse caso podemos atribuir valores default para as variáveis que serão retornados caso o valor recuperado seja undefined.
Podemos utilizazr o destructuring também em arrays multidimensionais. Para isso devemos inserir dentro do operador '[ ]' novamente '[ ]' para cada matriz do array multidimensional.
Antes porém vamos entender a composição em objetos
A composição nada mais é que a criação de objetos dentro de objetos. Quando o objeto, classe ou função construtora tem apenas um conjunto de atributos e métodos dizemos que seu tipo de composição é é um. Já quando temos um objeto como valor de um atributo de outro objeto temos a composição tem um.
Para fazer o destructuring em objetos utilizamos o token de objeto { }. Os nomes das variáveis para fazer o destructuring de objetos são os nomes dos atributos do objeto. Podemos alterar esse nome outros nomes atribuindo o valor para essas variáveis da seguinte forma: nome_do_atributo: nome_da_variável.
Também podemos passar valores default para atributos inexistentes nos objetos da mesma forma que nos arrays.
Do mesmo modo que nos arrays multimensionais, podemos também acessar objetos dentro de objetos. Para isso a sintaxe é:
let { nome_do_atributo: { atributo_do_objeto_interno1, atributo_do_objeto_interno2} }
Podemos também passar na chamada da função o array completo e selecionar os itens necessários utilizando a notação de destructuring como parametro na função. Neste caso aplicam-se todas as propriedades estudadas anteriormente.
O mesmo ocorre com os objetos, lembrando que nesse caso utilizamos o token '{ }' e chamamos os atributos pelo seu nome no objeto.
Podemos separar os valores de arrays combinando os operadores destructuring e spread/rest.
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous" ></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous" ></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous" ></script>
Em seguida o arquivo do font Awesome. Para inserir o link do Font Awesome entrar com a conta e selecionar a opção Font Awesome CDN para copiar o link das fontes.
O script será o seguinte:
<script defer src="https://use.fontawesome.com/releases/v5.0.8/js/all.js" integrity="sha384-SlE991lGASHoBfWbelyBPLsUlwY1GwNDJo3jSJO04KZ33K2bwfV9YBauFfnzvynJ" crossorigin="anonymous" ></script>
Copiei o projeto da aula que foi desenvolvido em Bootstrap, sem grandes diferenças (dá para entender o código mas evidentemente preciso me aprofundar no desenvolvimento).
Nesse momento a apresentação do App já está visível, mas sem nenhuma funcionalidade.
Note que no html os campos que serão manipulados já possuem ids para que possamos manipula-los no JS.
Aqui iniciamos a implementação.
Primeiramente criamos o evento onclick no <button> responsável pela inclusão de eventos. Esse evento irá invocar a function cadastrarDespesa().
Essa função irá acessar os dados através do método document.getElementById para a id de cada um dos itens que iremos armazenar.
Cada um dos valores será armazenado em variáveis próprias. Para recuperarmos os valores temos duas opções:
A primeira é recuperar o value diretamente na atribuição da variável da seguinte forma:
let variavel = document.getElementById('nome_da_id').value
A segunda é recuperar o value diretamente quando chamamos a variável:
variavel.value
A melhor forma de recuperar os valores é a segunda pois não vinculamos a variável ao value mas ao elemento.
Não esqueça do escopo das variáveis.
Primeiro criamos uma classe para as despesas e com o método constructor incluimos todos os atributos necessários.
Em seguida, dentro da função de cadastro, vamos instanciar o objeto Despesas() que receberá como parâmetros os valores recebidos por getElementById.
Devo me atentar para as sintaxes de construção de objetos e classes
Esses parametros devem ser incluidos no método construtor e seus valores atribuidos as variáveis da classe.
No exercício foi incluido na tag <option> o value=" " pois como não havia valor padrão, caso o campo ficasse em branco o objeto recuperava o valor do campo (no caso 'Tipo').
Atualmente temos as opções de Local Storage e Session Storage que trabalham de modo semelhante aos cookies, entretanto os dados não são anexados às requisições http, ou seja ficam armazenados no navegador como em um banco de dados. A diferença de Session Storage e Local Storage é que o session só mantém os dados enquanto a seção do browser está aberta. Já o local storage persiste os dados mesmo que o navegador seja encerrado, ou seja, mantém os dados até que eles sejam intencionalmente removidos.
Além dos métodos já citados temos também o IndexedDB e Web SQL que são formas de armazenamento com recursos mais avançados.
Importante estudar esses métodos.
Com o método localStorage podemos utilizar a propriedade setItem que irá receber dois parâmetros: a identificação do objeto que vamos armazenar e o dado que vamos armazenar, que deve ser encaminhado como um objeto JSON.
Devemos portanto converter os dados recebidos do objeto para esse formato. Para fazermos isso utilizamos um método do JS chamado stringify. A função completa para a criação desta rotina fica assim:
nome_da_funcao(dados_a_serem_enviados)
function nome_da_funcao(parametro) {
localStorage.setItem('identificacao_do_objeto', JSON.stringify(parametro))
} Com essa função inserimos os dados no Local Storage, entretanto os dados são armazenados em uma chave fixa. Assim a cada nova inserção de dados os dados anteriores são sobreescritos.
Quando criamos um objeto literal utilizamos a notação:
{atributo: 'valor_em_string', atributo: valor_numérico}
Já no JSON temos todos os dados como uma string única da seguinte forma:
'{"atributo": "valor_em_string", "atributo": valor_numérico}'
Note que no JSON temos a utilização de ' ' para criar o JSON e " " para identificar os itens em seu conteúdo. Esses sinais podem ser invertidos como em qualquer string.
O importante na diferença de objetos literais e de elementos JSON é que os objetos literais só existem dentro da aplicação, ou seja não podem ser acessados como objetos fora do contexto em que foi criado. Para que haja a comunicação dos objetos com outras camadas de aplicação precisamos encaminhá-los em um formato que seja compreendido de modo genérico, no caso, em um formato de texto. O JSON não é o único método de transferência de dados entre aplicações, mas é um dos mais utilizados pois pode ser interpretado por qualquer linguagem de programação.
O JS nativamente tem dois métodos para a manipulação de dados JSON: o método JSON.stringify() converte um objeto em JSON e o método JSON.parse() que faz o inverso.
A classe criada será responsável pelo método gravar(), ou seja, gravar deixará de ser uma função para ser um método da classe Bd. Consequentemente não será mais chamado com função mas como método da instancia de Bd.
Na classe Bd faremos a lógica para implemantação das chaves de índice. Para isso utilizaremos o método localStorage.getItem(). Da mesma forma que o setItem insere dados em localStorage, o getItem vai recuperar o último valor de chave inserido.
Como não temos nenhum id em nosso Local Storage, teremos um retorno null. Para verificarmos se temos algum valor de id fazemos um teste lógico com if. Para tanto criamos uma variável id e caso seu valor seja null atribuimos a ela o valor '0'.
Em seguida criamos a lógica para implementar o id dinâmico a cada execução do método gravar()
A classe Bd será criada portanto da seguinte forma:
Primeiro criamos um método constructor() que terá a variável id que será recuperada com o método localStorage.getItem(). Esse valor será testado e se for null será atribuido valor '0'.
Em seguida executamos o método gravar(). Esse método irá executar o método getProximoId() que vai recuperar o valor do id com o método getItem e irá retornar o valor recuperado acrescido de '1'.
Feito isso o método gravar irá executar o método setItem() com o valor do id e o valor recuperado do JSON.stringify() com os dados inseridos na página. Por fim o método gravar() irá atribuir, também com o método setItem o valor de 'id' para a chave 'id'.
Para validar esses dados vamos manipular a classe Despesa para verificar os valores dos atributos do objeto criado. Fazemos isso criando um método na classe Despesa chamado validarDados(). Esse método fará parte de um teste lógico if que verificará se o retorno é vardadeiro. Caso seja true ele executa o método bd.gravar() caso contrário ele envia uma maensagem de erro.
Para verificar os valores em cada atributo vamos utilizar o comando for in que será executado no método validarDados(). Esse comando vai percorrer todos os atributos do elemento. A sintaxe para essa vaerificação é:
for(let i in this){
console.log(i, this[i])
}
Entendendo fazemos um for onde para todos os atributos ('i in') no objeto da classe ('this') vamos ter como retorno o 'i' (atributo) e o 'this[i]' (valor que foi atribuido).
Devo relembrar o for in pois foi uma operação que eu não estudei muito
Dentro do laço for in faremos um teste lógico if para verificar se qualquer dos valores dos atributos é undefined ou ' ' (vazio) ou null. Caso algum desses testes for verdadeiro retornaremos false para o teste lógico que valida os dados. Note que DEVEMOS retornar true caso o teste lógico atenda a todas as situações, caso contrário não teremos o retorno esperado.
Para executar o modal o Bootstrap utiliza um botão que executa um código JQuery para alterar o display do modal de none para block (podemos ver isso inspecionando a janela aberta pelo modal e analisando a <div> que encapsula o modal).
O código JQuery que fará a maipulação do modal será inserido na lógica de validarDados() do nosso app com o seguinte comando:
$('#erroGravacao').modal('show')
Nesse comando o #erroGravacao é o id da <div> que encapsula o modal.
No exemplo quando removemos um botão removemos o botão close padrão do Bootstrap. Esse botão tem uma propriedade que não tem no botão que deixamos assim precisamos inserir a propriedade data-dismiss="modal" no botão que fecha nosso modal.
Para o modal de sucesso apenas copiamos o modal de erro e ajustamos os valores, alterando os textos exibidos, o texto do <button>, as cores dos elementos e a id do modal que controla sua exibição.
No código JS apenas incluimos o código JQuerry com a id correta na lógica de sucesso.
Para isso utilizamos as ferramentas de manipulação do DOM document.getElementById.innerHTML e document.getElementById.className para manipular os conteúdos das tags e as classes do Bootstrap. Na minha resolução criei variáveis para cada elemento.
A função carregaListaDespesas() vai ser chamada no evento onload() na tag <body> da página consulta, pois uma vez que chamamos esta página queremos que seja apresentados os itens armazenados. Já o método recuperarTodosRegistros() será criado na casse Bd.
Para recuperar os itens utilizaremos o método localStorage.getItem() com uma lógica para percorrer os itens de acordo com o número de ids registradas. Para recuperar o valor de quantodades de ids vamos utilizar o valor armazenado na chave id que contém o último valor e serve de base para as próximas chaves.
Com esse valor podemos fazer um loop for para percorrer todos os ids e recuperar em uma variável a descrição da despesa. Esses dados serão retornados em JSON e deve ser convertido com o método JSON.parse(). Com isso vammos gerar objetos literais que podem ser manipulados pelo app.
Com os dados em formato de objetos literais precisamos criar arrays de dados. Para isso vamos utilizar o push() dos objetos literais convertidos em despesa para o array despesas.
Nesse momento temos mais uma situação para resolver. Caso tenhamos excluido algum valor do Local Storage o índice desse valor ficará vazio e retornará null no array de despesas. Para resolver isso temos duas opções: podemos remover a entrada null na lógica que controla a view ou filtrar na lógica que recupera os dados para que o push não recupere dados nulos ao criar o array. Fazemos isso através de um teste lógico if que verifica se despesas é '===' null. Se sim retornamos continue que prossegue com o loop pulando essa iteração (no meu caso utilizei a lógica inversa !== ).
Em seguida vamos transferir esses dados para a função que o chamou e atribuir a uma variável do tipo array.
Note que no método que recupera os dados recuperarTodosRegistros() criamos uma variável despesas para armazenar o array de dados recuperados. Na função carregaListaDespesas() criamos uma variável despesas para armazenar o array gerado no método bd.recuperarTodosRegistros(). Essas duas variáveis despesas embora tenham o mesmo nome e o mesmo conteúdo são diferentes pois pertencem a escopos diferentes.
Precisamos criar os nós para os itens $lttd> e <tr> com os dados formatados para a tabela conforme o array recuperado.
O primeiro a se fazer é criar uma id no <tbody> que irá receber os dados para podermos acessa-lo com o getElementById. Criada a variável que representará o <tbody> vamos percorrer os elementos do array despesas para separar os itens com o comando forEach(). Esse forEach separa cada uma das linhas de despesas em objetos individuais. Com esses objetos separados vamos inclui-los no html através da tabela.
Para inserir as linhas na tabela utilizaremos o método insertRow() (esse método não aperece nas opções do VSCode). Aplicado esse método a listaDespesas() serão criadas automaticamente tantas linhas (<tr>) quanto o número de itens do array despesas. Essas linhas devem ser aramzenadas em uma variável que por sua vez vai acessas os itens individualmente e encapsula-los em <td>.
Para inserir dados em <td> utilizaremos o método insertCell (através da variável que referencia as <tr>). Esse método deve receber como parâmetro o número da célula da tabela (iniciando em 0). Portanto devemos criar uma para cada coluna da tabela.
Para cada célula criada vamos agora inserir com o método innerHTML os valores recebidos no parâmetro da função do método forEach().
No caso do Tipo os valores recebidos são em números(string de numeros) mas a descrição é em texto. Para fazer o ajuste da apresentação utilizamos um switch/case com as opções a serem representadas de acordo com o número recebido do array.
O método pesquisarDespesas() será disparado no onclick do botão de pesquisa.
PAra iniciar criamos a função pesquisarDespesas() que irá consultar os valores preenchidos nos campos do formulário de consulta. A diferença aqui para a função cadastrarDespesa() é que aqui vamos receber os valores(value) dos campos digitados. Vamos novamente instanciar um objeto despesa que receberá como parametro os valores digitados no campo de consulta. Esse objeto criado será passado para o método pesquisar() em bd para em seguida se comunicar com o Local Storage.
Na função pesquisarDespesas() faremos a chamada do método pesquisar() através de bd.pesquisar(despesa) transportando para o método em bd os valores capturados em despesa. Esses valores encaminhados pelo objeto criado farão o filtro dos valores selecionados.
Quando utilizamos o filter() a função de callback terá um parametro que será utilizado para verificar junto ao atributo se a condição é verdadeira ou falsa e retornará portanto apenas os objetos que tenham a resposta do teste lógico true.
Podemos ainda aplicar vários filter() diferentes simplesmente adicionanado os novos filtros em seguida da notação do primeiro.
A sintaxe do filter é a seguinte:
nome_do_array.filter(function(param) { return param.nome_do_atributo teste_lógico valor_a_ser_filtrado}).filter(function(param) {return lógica_do_segundo_filtro})
Podemos ainda utilizar arrow functions
nome_do_array.filter((param) => { return param.nome_do_atributo teste_lógico valor_a_ser_filtrado}).filter((param) => {return lógica_do_segundo_filtro})
E ainda suprimir o return e as { } pois temos apenas um valor de teste:
nome_do_array.filter((param) => param.nome_do_atributo teste_lógico valor_a_ser_filtrado).filter((param) => lógica_do_segundo_filtro)
Para isso criamos uma variável no método pesquisar() que receberá a lista gerada em recuperarTodosRegistros(). O método pesquisar() recebe como parâmetro os valores recebidos na função pesquisarDespesa(). A comparação destes dois elementos que será resposável pelo filtro.
O primeiro filtro feito é o de ano. Utilizamos o conceito aprendido acima, passando como parametro para a função de callback o ano do Array criado aqui e comparando com o valor recebido pelo método.
Repetimos esse instrução para cada item da tabela mas antes devemos criar um teste lógico para verificar se o atributo não está vazio. Caso o valor daquele campo esteja vazio devemos ignorá-lo e testar o próximo campo.
Esse filtro por enquanto está 'case sensitive' e não está ignorando números '0' a esquerda. Caso não seja ajustado isso durante as próximas aulas é algom que posso implementar.
Em seguida foi feita uma refatoração para retirar a repetição do código de criação de tabelas nas funções carregaListaDespesas() e pesquisarDespesas(). NO exemplo da aula foi realizada uma lógica aproveitando a construção em carregaListaDespesa(). Eu achei um pouco confuso e achei melhor manter no modo que eu fiz, construindo uma função separada para a construção da tabela de retorno. Para entender o modo que foi feito, assistir a aula novamente, mas acho que não será necessário (a menos que apareça algum problema na execução do meu método).
Para isso criamos uma variável para criar o botão que receberá a propriedade document.crateElement('button'). Essa variável será incluída na tabela atravé do comando linha.insertCell().append(nome_da_variável).
Feito isso já temos o botão criado. Em seguida aplicamos os estilos do Bootstrap através de nome_da_variável.className = 'btn btn-danger' para definir a cor e o formato do botão e inserimos como conteúdo da tag <button> criada o valor nome_da_variável.innerHTML = '' que inclui um ícone do Font Awesome.
O próximo passo é fazer a lógica para o botão. Aplicamos a proipriedade >nome_da_variável.onclick = e vamos criar uma função para executar a exclusão da despesa.
Para que possamos saber qual item foi clicado em Local Storage precisamos criar um indice no objeto que o identifique individulamente em relação aos demais itens já criados. Para isso não precisamos criar um novo atributo na classe construtora apenas criar o novo atributo no método que insere os dados no array de despesas (recuperarTodosRegistros).
Com esse numero no array podemos criar para cada tag <button> uma 'id' com o número correspondente a linha que ele se referencia.
Em seguida criamos um método na classe bd para remover o item de Local Storage. Esse método irá executar a função localStorage.removeItem() recebendo como parametro o número do 'id' selecionado pelo botão de remoção. Como no exemplo as ids dos botões de remoção foram criadas com um prefixo utilizamos o a propriedade replace() para remover o prefixo adicionado. Finalizando chamamos o método criado remover() com o número do id como parametro. Por fim, precisamos recarregar a página para atualizar a tabela apresentada. Fiz isso chamando o método carregaListaDespesas() novamente para atualizar a tabela. O professor utilizou o método de manipulação do DOM window.location.reload().