Seção 2 - Revisão Lógica de Programação
Aula 12 / Aula 13 - Variáveis parte 1 / Variáveis parte 2
Introdução a variáveis. Tipos de variáveis em JS: var, let, const.
Utilização de ' ou ". Tipagem fraca, ou seja pode mudar o tipo da
variável.
O operador let, diferente do var não permite que a
mesma variável seja inicializada duas vezes. Podemos reatribuir
valores mas não reiniciar uma variável com let.
As variáveis tipo const não podem ser alteradas* e devem
ter seu valor declarado no momento da inicialização diferente dos
outros tipos.
Tipos de dados primitivos em JS. Nomes de variáveis, utilização de
'escape (\)' para ignorar caracteres. Literal template com '` ${ }
`'. Concatenação com '+'.
Utilização do typeof para saber o tipo de uma variável.
Variáveis tipo número (inteiro e float).
Váriaveis boolean (true / false).
Tipos undefined quando não há a atribuição de valor e
null deve ser declarada explicitamente como 'null' e seu
typeof retorna, por problema na estrutura do JS, o tipo
object.
Quando trabalhamos com variáveis de tipos diferentes, o Javascript
pode fazer conversões implícitas para por exemplo executar uma
operação aritmética entre dois números mesmo quando um deles é
declarado como string sem mudar o tipo da string. Isso não funciona
assim para a operação de adição pois nesse caso há uma concatenação.
É importante lembrar que toda a informação que vem da interface do
usuário, mesmo que em um input do tipo número, chega no JS como
string.
Apesar disso devemos tratar os valores com as funções
parseInt, parseFloat e o método construtor
Number().
O parseInt e parseFloat convertem strings em
numeros, mesmo que a string tenha além de caracteres numéricos,
outros caracteres. O único porém é que eles ignoram os valores que
estiverem a partir do primeiro caractere não numérico. Dessa forma
caso a string inicie com um caractere não numérico o retorno
será NaN. Já o método construtor Number() exige que
tenhamos apenas caracteres numéricos na string retornando
NaN em todos os outros casos.
Já a conversão de numero para string pode ocorrer de duas formas. A
primeira concatenando uma string vazia com o número e a segunda
utilizando a função toString(). A função
toString() admite por padrão que o número está na base 10,
entretanto podemos alterar a base como parâmetro da função por
exemplo toString(16)
Operadores aritméticos: ' + ', ' - ', ' * ', ' / ', ' % ',
' ** '
Operadores de atribuição: ' = ', ' += ', ' -= ', ' *= ', '
/= ', ' %= ', ' **= '
Incremento e Decremento: ' ++i ', ' --i ', ' i++ ', ' i-- '
(Atenção para os efeitos da posição dos sinais)
Operadores de comparação: ' == ', ' === ', ' > ', ' <
', ' >= ', ' <= ', ' != ', ' !== '
Operadores lógicos: ' && ', ' || ', ' ! '
Precedencia de operadores: ' && ' > ' || ', ' ** ' > ' / '
' * ' > ' + ' ' - '
Utilização do condicional if / else, if / else if / else.
Condicional com operador ternário (condição ? true : false)
Alguns valores em JS são considerados por padrão como false. São eles: 0, "", NaN, undefined, null, false . Entretanto se fizermos um teste com uma string cujo valor é 'false' o resultado será true pois existe um valor (a string com o conteúdo 'false'). Todos os outros valores são considerados 'truthy' ou seja tendem a ser verdadeiros.
Aula 30 - Curto-circuito
Quando temos o valor de uma variável igual a '0' e queremos que
substituir esse valor por 10, por exemplo, podemos efetuar a
seguinte expressão:
var = var || 10
Ou seja caso o valor de 'var' retorne falso (no caso 0 é falsy como
visto) ele será reatribuido para 10.
Outro tipo de curto-circuito é com o operador '&&'. Caso tenhamos um
teste que retorna 'true' junto com o operador '&&' e uma outra
função que não seja 'false' temos a execução da função. Por exemplo:
operação_true && console.log('é verdade')
Já se utilizamos o operador '||' caso o primeiro valor seja 'true' o
segundo valor nem será executado. No caso do
console.log() ele não apresentará nenhum resultado.
O switch é utilizado para testar valores específicos.
A sintaxe é:
switch(valor_teste){
case valor1:
operação
break
...
default:
operação
}
O teste é sempre por valor e tipo (===).
Utilização das estruturas de loop while, for e
do / while
Utilização do break para interromper a execução e do
continue para pular uma instrução. Atenção para a execução
do continue em loops do tipo while pois ele pode
interromper o incremento e entrar em um loop infinito.
Funções são blocos de código reaproveitável que são executadas
quando solicitado.
Declaramos funções com a palavra reservada function seguida
do nome da função.
Podemos declarar como function expressions que consiste em
atribuir a função a uma variável. Nesse caso o nome da função é o
nome da variável em que ela foi atribuida e a declaração da função é
anonima.
E de modo mais recente temos as arrow functions que
consiste em atribuir a função a uma variável como a
function expressions, mas nesse caso não utilizamos o
operador function mas apenas o '( ) => {}'.
O return em uma função encaminha o resultado do que foi
executado pela função para quem fez a chamada da função. Desse modo
se executarmos a função a partir da atribuição de uma variável a
variável vai armazenar o valor encaminhado pelo return. No
exemplo utilizamos o método Date().getDay() para retornar o
dia da semana.
Podemos passar parametros para a função indicando na declaração da
função quantos/quais parâmetros serão recebidos e passando esses
parâmetros na chamada da função. No JS podemos passsar parâmetros a
mais ou a menos que é executado do mesmo modo. O que pode ocorrer é
que podemos ter resultados indesejados dependendo da operação que
for realizada pela função. Podemos resolver isso passando parâmetros
'default' utilizando por exemplo 'curto-circuitos'.
Podemos, a partir do ES2015 passar os parâmetros 'default'
simplesmente atribuindo o valor nos parâmetros na criação da função.
O procedimento com 'curto-circuito' era utilizado nas versões
anteriores.
Apresentar na tela a tabuada de acordo com o valor selecionado. Fiz
a entrada de dados através de <input> no html, no exemplo foi
feito com prompt.
Para a apresentação dos dados na tela, não lembrei como fazer de
modo simples (apresentação diretamente com o innerHTML).
Acabei utilizando &letable> com insertRow() e
insertCell().
O modo correto de fazer é através de operador de atribuição com
adição em uma variável e atribuir o valor da variável completa na
tela.
Atenção para o uso de return. Quando utilizamos o break ou o continue esses operadores de algum modo interrompem ou pulam um passo no loop, mas continuam a execução da função. Quando utilizamos o return a função para de ser executada, mesmo que tenhamos algo fora do loop.
Aula 41 - Escopo de variáveis
Quando utilizamos variáveis no escopo global ela pode ser manipulada
em qualquer local de nosso script, inclusive dentro de blocos. Dessa
forma se reatribuirmos o valor de uma variável global dentro de um
bloco essa atribuição será refletida globalmente.
Entretanto caso declaremos uma nova variável com escopo em um bloco,
mesmo que essa nova variável tenha o mesmo nome da variável global,
essa atribuição ficará restrita ao bloco que ela pertence.
Quando utilizamos o var para declarar variáveis ela terá
escopo de função, portanto pode ser acessada de qualquer lugar
dentro da função, mesmo que esteja dentro de um outro bloco. Já
quando utilizamos o let a variável fica restrita ao bloco
em que ela foi declarada.
Arrays são conjunto de dados. Em JS os arrays permitem que
coloquemos qualquer tipo de dado, inclusive outro array. Em JS não
temos um tipo específico para arrays, elas são consideradas
object
Podemos criar arrays utilizando a sintaxe formal que é como segue:
const array = new Array()
Para acessarmos o conteudo de uma array utilizamos o
nome_da_array[indice]. Quando não temos valor no indice
solicitado o retorno é undefined.
Para sabermos a quantidade de elementos em um array utilizamos a
propriedade length. Para selecionarmos o ultimo indice de
um array podemos utilizar nome_do_array.lenght - 1.
Podemos criar arrays também com a sintaxe literal da seguinte forma:
const array = []
Podemos acrescentar valores em um array utilizando o valor do
próximo índice livre utilizando:
nome_do_array[nome_do_array.length] = novo_valor
Podemos também acrescentar valores em um array utilizando o método
push
Objetos são conjuntos de dados compostos de chaves e valores. Assim
como os arrays podemos criar objetos pela sintaxe formal:
const objeto = new Object()
Criado o objeto podemos incluir valores da seguinte forma:
nome_obj.nome_atributo = valor
Podemos passar qualquer conjunto de atributo / valor, independente
de esse conjunto existir ou não no objeto.
Podemos consultar o valor dos atributos através do
nome_do_objeto.nome_do_atributo ou com
nome_do_objeto["nome_do_atributo"]. Com a segunda sintaxe
podemos criar uma variável para indicar o nome_do_atributo.
Já na sintaxe literal inserimos os dados da seguinte forma:
const nome_do_objeto = { atributo_1: valor_atributo_1,
atributo_2: valor_atributo_2, ... atributo_n:
valor_atributo_n}
Podemos percorrer um array utilizando o loop for iterando cada índice do array.
Aula 45 - Iterar Objetos
Podemos iterar objetos utilizando o operador
for / in.
Quando utilizamos esse operador a variável que criamos para o irá
mapear os atributos como se fossem índices. Desse modo se acessarmos
o objeto com essa variável como índice obteremos os valores dos
atributos. O processe é como segue:
for (let variavel in objeto ) {
objeto[variavel]
}
Métodos são funções dentro de objetos. Podemos criar métodos quando
declaramos os objetos literais decalrando o nome do método e
passando a função como valor ou declarando o método direto sem o
operador function.
Podemos também criar o método utilizando o atributo e uma
arrow function.
O operador this quando utilizado dentro do escopo do objeto
se refere ao objeto como um todo.
Aqui temos uma observação. Quando criamos um método tanto
simplificado como com o operador function temos acesso ao
objeto com o operador this. Já quando utilizamos
arrow function o retorno do ehis é um objeto
vazio. Na verdade se verificarmos no browser estamos
manipulando o window (esse é o retorno no console).
Podemos criar arrays de objetos, utilizando a notação
'[ { }, { }, ... , { } ]'.
Esses arrays são conjuntos de dados e permitem que se acesse os
objetos pelo índice, e pelo atributo.
Em JS temos os primitivos que são objetosa imutáveis e que não tem
métodos. Temos também objetos wrapper para os primitivos, ou
seja, objetos que representam os primitivos. Para saber mais ver
Mozilla Docs.
Quando analisamos duas variáveis diferentes com valores primitivos
iguais, quando fazemos uma comparação recebemos como retorno
true. Entretanto quando criamos duas variáveis atribuindo a
elas arrays vazio temos como retorno false. Isso ocorre pois
quando atribuimos um valor a uma variável ele ocupa determinado
espaço na memória e define um valor específico. Já quando atribuimos
um array vazio, estamos fazendo referencia ao array, que mesmo que
tenha o mesmo conteúdo (no caso vazio) não é igual. Já se
atribuirmos o valor de uma variável à outra variável teremos uma
espelhando a outra, ou seja ambos os arrays apontam para o mesmo
endereço na memória.
Quando executamos em uma função um método que manipula um array por
exemplo, o método pode alterar o valor do array. Entretanto quando
tentamos fazer o messmo com primitivos, não conseguimos fazer a
alteração fora da função, assim sem que retornemos o valor para a
variável externa a função não teremos alteração no valor da
variável.
Vamos criar um array utilizando o método Math.random().
Para verificar se o valor não será repetido no array utilizaremos o
método indexOf() para testar os números existentes no
array. Caso o número não seja repetido incluimos ele no array
utilizando o método push
Esse exemplo é melhor executado com o loop while pois no
loop for precisamos saber o número de repetições. O loop
while permite que realizemos alguma ação
até que determinada condição seja satisfeita.
Quando precisamos passar mensagens de erro podemos enviar um
alert ou outro tipo de mensagem, porém essas mensagens são
proprietárias do navegador e não serão interpretadas em outros
sistemas.
Para enviar mensagens de modo correto temos o comando
throw Error(mensagem_de_erro). Essa mensagem será
apresentada no console do navegador.
Para otimizar esse procedimento podemos utilizar a propriedade
try / catch.
Com essa propriedade podemos testar a função que desejamos e
retornar a mensagem de erro de diversas formas, independente do
sistema utilizado.
Se quisermos apenas enviar a mensagem de erro por exemplo podemos
utilizar a propriedade .message
Temos ainda o operador finally que pode ser incluido após o
catch e que será executado de qualquer modo, em caso de
execução correta ou em caso de erro.
Seção 3 - Sistema Léxico do Javascript
Aula 53 a 55 - Sistema léxico parte 1 / Sistema léxico parte 2 / Sistema léxico parte 3
Fundamentos do sistema léxico do JS.
Sintaxe, comentários, palavras reservadas, case sensitive, uso de
';' (obrigatório na mesma linha), nomes de variáveis e funções,
camel case.
O use strict limita algumas ações indesejadas do JS como
por exemplo, não permite a atribuição de variáveis não declaradas. O
use strict DEVE ser declarado na primeira linha de
código, caso haja alguma declaração antes o comando não funciona. O
use strict pode também ser declarado no escopo de função,
nesse caso ele é restrito ao escopo da função. A grande vantagem de
utilizar o use strict é evitar problemas de escopo em
variáveis. Outro problema resolvido é que o JS permite a declaração
de parametros em funções com o mesmo nome. Nesse caso a função
considera o segundo parâmetro passado e ignora o primeiro. Caso seja
passado apenas o primeiro o resultado é 'NaN' ou 'undefined'
(conforme o caso). Ele também evita problemas com o operador
this em funções. Em funções o this se refere ao
'window' do navegador e não ao objeto. Dessa forma com o
use strict o this em uma função retorna undefined.
A tipagem do JS é dinamica, ou seja ele pode alterar seu tipo
simplesmente reatribuindo o valor a variável.
A aspas podem ser ' ou " devendo obedecer apenas a ordem de abertura
e fechamento. Podemos escapar caracteres utilizando '\'.
O NaN ocorre quando fazemos uma operação matemática com
caracteres não numéricos.
Por fim temos a conversão implícita no JS, ou seja, mesmo que voce
passe um número como string se for possível o JS converte a string
automaticamente para satisfazer a operação.
Seção 4 - Funções
Aula 56 - Introdução
Funções são objetos que tem a capacidade de ser
invocados/executados.
Características: podem ser literais, ocorre o hoisting, são objetos
ded primeira classe, são a única maneira de gerar escopo, podem ser
auto-invocáveis (IIFE) podem ter propriedades internas como
arguments e name.
Podem ser literais:
function nyFunc() { }
Hoisting:
Pode ser "içada" com function declaration. Quando declaramos
uma função de forma literal podemos executar essa função antes da
declaração da função. Entretanto quando atribuimos a função a uma
variável ela só poderá ser executada após a declaração. O
hoisting de variável também funciona. Se chamamos a variável
antes de ela ser inicializada com var ela é içada,
simplesmente não tem valor (undefined). Isso não funciona com
variáveis iniciadas com const e let.
Funções em JS são objetos de primeira classe. Podemos passar funções
como parametro para outra função, podem ser atribuidas em
propriedades de objetos (métodos), retornadas como resultado de
outra função e ter suas próprias propriedades.
As funções auto-invocáveis(IIFE) servem para evitar poluir
o escopo global.
Quando utilizamos mais de um arquivo de script nas aplicações,
quando tinhamos a inicialização de variáveis por var caso
tivessemos uma variável igual nos dois arquivos uma sobreescrevia a
outra. Isso não ocorre com variáveis do tipo let. Entretanto
isso ainda ocorre com funções.
Podemos isolar as funções em seus respectivos arquivos utilizando as
funções auto-invocáveis. A sintaxe para isso é:
(function() {
//código a ser executado pelo arquivo, incluindo variáveis e
funções
} ()
Com esse procedimento, isolamos o escopo de cada arquivo dentro dele
mesmo, não sendo seu conteúdo acessável pelo escopo global. Dessa
forma evitamos problemas como repetir funções em carquivos
diferentes.
Hoje com os frameworks e outras ferramentas, não é mais necessário
utilizar as IIFE pois os arquivos só se comunicam desde que
haja o import entre eles.
Podemos passar parâmetros nas IIFE do mesmo modo que
passamos para qualquer função, utilizando os últimos parenteses para
passar os parâmetros e os primeiros para receber. Era comum passar
como parâmetro para esse tipo de função os objetos globais
window e document.
Para garantir mais ainda a consistencia dos escopos podemos utilizar
juntamente com a IIFE o use strict. Com esse
conjunto de sintaxe garantimos que todas as variáveis e funções
estarão protegidas de utilização fora dos escopos.
Quando precisamos criar uma função com numero de parâmetros
indefinido (que pode variar conforme o caso) podemos resolver de
várias formas. Uma delas é passar os valores em um array. O outro e
mais correto é utilizar a propriedade arguments, que cria
um pseudo array, com os dados passados no parâmetro. Essa
propriedade permite também que se consulte o length dos
dados passados.
O arguments funciona apenas para funções criadas com
function expression ou function declaration. Não é
possível utilizá-lo com arrow function.
Podemos consulatar também o nome da função utilizando a propriedade
.name.
Podemos passar uma função como parâmetro para outra função. Isso é
chamado função de callback. Nesse caso a função principal
espera receber uma função para continuar sua execução. Podemos
utilizar o teste de curto circuito para testar se o valor recebido é
uma função para evitar quebrar a execução caso o parâmetro seja
diferente do esperado.
Podemos passar a função como parâmetro diretamente na chamada da
função principal através de uma função anonima, ou passar o nome de
uma função do tipo declaration ou expression.
Podemos atribuir uma função como atributo em um objeto.
Também é possível passar uma função como retorno de outra função.
Esse caso é um pouco mais complexo e tive um pouco de dificuldade de
entender a lógica disso.
Como a função é considerada um objeto podemos incluir atributos ou
propriedades.
App para cáclulo de média com n parametros. A diferença da minha solução para a do professor foi o 'curto-circuito' utilizado para retorno caso os parametros forem em branco.
Aula 66 a 67 - Desafio: Calcular IMC / Classificação IMCSistema para cálculo de IMC. Fiz meu sistema recebendo e mandando dados para um página.
Aula 68 - Resolução: Calcular IMCA diferença para o sistema desenvolvido pelo professor foi que eu fiz uma redundancia desnecessária no conjunto de teste lógico 'if'. Outra coisa é que eu não usei 'return' para os dados das funções. Fiz esse ajuste na minha resolução mas de outra forma.
Aula 69 - CallbacksFunções de callback são funções passadas como parâmetro para outra função. Quando passamos uma função como parâmetro para outra não podemos executar essa função na função que a recebe (não podemos por o nome da função seguida de () no campo de parâmetros da função principal). Podemos passar parâmetros para a função de callback dentro da função que a executa.
Aula 70 - Resolução: Calcular IMC (com Callbacks)Passando a função de descrição do grau de obesidade como 'callback' para a função de cálculo do IMC.
Seção 5 - DOM
Aula 71 - Introdução
DOM é o acronimo para Document Object Model. É uma API
disponibilizada no browser para editar o que é mostrado na tela.
Para o JS os elementos criados em html formam um árvore
hierárquica, com os elementos pai e filhos de acordo com sua
estrutura.
Podemos selecionar elementos do html utilizando o objeto
document. Esse objeto tem alguns métodos para manipulação
dos elementos:
getElementById(): seleciona o elemento pelo nome do
id atribuido a ele.
querrySelector(): seleciona o primeiro elemento com o
atributo selecionado (nesse caso devemos indicar o elemento como no
CSS).
textContent: propriedade que altera o texto no objeto
selecionado pelos métodos anteriores.
getElementsByClassName: cria uma collection que é
como se fosse um array, com os elementos que tenham a classe
indicada. Os elementos podem ser acessados pelo índice, como nos
arrays.
querySelectorAll: funciona como o
getElementsByClassName porém cria um NodeList ao
invés de um collection.
getElementsByTagName: cria uma collection baseada na
tag html escolhida.
Podemos navegar pela árvore do DOM criando variáveis com elementos
específicos e utilizando essas variáveis para acessar elementos
filhos.
Também é possível acessar os elementos utilizando o
querySelector e selecionando o elemento através do caminho
até o elemento mesclando id, class e tag como
no css (utilizando a herança entre os elementos).
Quando manipulamos elementos no DOM não podemos incluir o script na
tag <head> pois o browser vai tentar manipular a propriedade
chamada por ele antes de carregar os elementos html.
Manipular o html para inserir o nome 'dinamicamente' na saudação da
página com JS.
Fizemos isso utilizando inicialmente a propriedade
textContent com a concatenação(soma) por atribuição (+=).
Em seguida para passar o nome do usuário para negrito utilizamos a
propriedade innerHTML para concatenar o valor junto com as
tags <b>.
Lembrando que a propriedade textContent manipula apenas o
conteudo da tag enquanto o innerHTML permite passar novas
tags para serem interpretadas.
Manipulando as propriedades de style através do seletor em
JS. Utilizando o parentElement, o parentNode e o
children para navegar nos elementos HTML através dos
seletores em JS.
Caso não queiramos ocultar o elemento com o
display = 'none' temos a opção ded remover o elemento com o
método remove() ou removeChild(). O método
remove() não funciona em versões antigas de navegadores.
Esse modo tem um problema pois ao carregar a página até que o script
seja carregado aparece a barra que deve ser removida por uma fração
de segundos.
Poderíamos adicionar os elementos com o atributo
innerHTML mas esse método pode gerar problemas. Para
fazermos a inclusão de modo correto devemos incluir um
node diretamente na árvore do DOM.
Vamos portanto criar um novo elemento utilizando o método
createElement(tipo_de_elemento) atribuido a uma variável. A
partir dessa variável podemos incluir um classe utilizando o
atributo className e inserir o conteúdo dentro do elemento
utilizando, aqui sim, o innerHTML.
Feito isso vamos selecionar a posição em que o novo node será
inserido e executar o método que fará a inserção. O método que vamos
utilizar é insertBefore() e possui a seguinte sintaxe:
elementoPai.insertBefore(novoElemento, elementoReferencia)
Para selecionar os elementos vamos navegar pelo DOM utilizando os
métodos de seleção, no caso os métodos firstChild ou
firstElementChild.
Atenção: o método firstChild considera qualquer elemento
no HTML, inclusive comentários
Foi feita uma alteração no documento HTML para incluir um
<input> para inserir um e-mail e um <button> para enviar o
valor.
Para isso vamos utilizar o atributo value do campo
<input> e o evento onclick do <button>.
Como estamos lidando com funções chamadas pelo HTML (no caso pelo
evento onclick) não podemos utilizar a função auto-invocável
pois ela bloqueia o escopo do arquivo JS.
Outro ponto importante é sempre verificar o momento de utilizar a
atribuição de valores a variáveis pois o valor de <input> só
estará disponível se tiver alguma ação que faça sua leitura.
Manipulando os atributos disabled do elemento <input> do HTML utilizando o evento onclick e onblur, e utilizando o método focus().
Aula 83 a 85 - Propriedades / Desafio checkbox / Resolução checkbox
Utilizando propriedades HTML no JS.
Utilizamos as propriedades value, disabled,
readOnly (readonly) (note que quando utilizamos
propriedades HTML em JS utilizamos os nomes compostos em
camel-case), htmlFor ('for' atributo de label),
maxLength (maxlength), className
Para fazer o desafio do checkbox utilizamos a propriedade
checked. Para monitorar o click no ckeckbox utilizei o
evento onclick mas podemos utilizar também o evento
onchange.
Como utilizamos duas propriedades com retorno true ou false podemos
simplificar o teste lógico if que verifica se o checkbox
está clicado e atribuir o resultado true ou false dele diretamemnte
para a propriedade disabled. Essa simplificação fica como segue:
nome_botão.disabled = !checkbox.checked
O último detalhe nesse exercício é que o disabled do <button> de
cadastro estava com a propriedade disabled inserida
diretamente no HTML. O problema é que se houver algum problema com a
carga do JS o usuário não terá como fazer nada. Para contornar essa
situação passamos o controle da propriedade disabled para o
JS, assim ele só ativará essa propriedade caso o JS seja carregado.
Seção 6 - Arrays
Aula 86 - every(), some(), forEach(), filter() e map()
Relembrando, podemos criar arrays de forma literal:
const arr = []
ou criando um objeto do tipo array:
const arr = new Array()
As funções every() e some() retornam um boolean.
A função every() requer uma função anonima como parametro.
Esta função vai verificar todos os elementos do array e
retornar true ou false se todos os elementos atenderem
a condição especificada no corpo da função.
A função some() faz o mesmo que a função
every() porém retorna true se apenas um elemento
atender a condição solicitada pela função de parâmetro.
A função filter() não modifica o array original ele
retorna um novo array. Ela deve receber uma função que pode
receber três parâmetros (elemento, indice, array).
A função forEach() não retorna nenhum valor, ela apenas faz
a iteração entre os elementos do array, portanto não adianta
atribuirmos o valor de forEach() em variáveis. Apenas
utilizamos ele dentro de uma lógica.
O método map() retorna uma nova array com os dados
transformados. A sintaxe é a mesma dos demais recebendo os mesmos
parâmetros. A diferença para o forEach() é que ele retorna
os dados modificados.
Primeiramente, o método find() não funciona em versões do
Internet Explorer. Para saber quais implementações funcionam em
navegadores podemos verificar o site
Can I use.
Os métodos indexOf() e lastIndexOf() retornam o
primeiro ou o útimo índice dos valores consultados. Caso o valor não
seja encontrado o retorno será '-1'.
O método includes() retorna um booleano true ou
false caso exista o valor pesquisado. Podemos substituir o
includes() por um teste com indexOf() > -1 pois
esta expressão retornará true ou false como no
includes().
O método find() recebe uma fução como parâmetro e retorna o
valor encontrado na função que satisfaça a condição. Caso não
encontre retornará undefined.
o método findIndex() faz o mesmo que o find() mas
retorna o índice. Caso não encontre valores retorna '-1'.
O método toString() converte o array em um string com seus
valores separados por virgula.
O método join() faz o mesmo que o toString() porém
aceita um parâmetro que define como serão separados os elementos do
array.
O método concat() concatena arrays criando um novo array
com os elementos. Podemos incluir novos elementos e mesmo novos
arrays.
Quando apontamos um objeto não primitivo no JS estamos apontando
para uma referencia dele na memória. Desse modo quando atribuimos o
valor de uma array para outro array estamos apenas apontando o novo
array para o endereço do array original. Dessa forma quando
incluimos um dado no array que recebe o apontamento estamos
incluindo os dados no array original. Sabendo disso, para criarmos
realmente uma cópia do array e não simpelsmente um reapontamento do
array na memória podemos atribuir ao array um valor vazio([ ]) e
concatenar com o array que se deseja copiar da seguinte forma:
let novo_array = [].concat(array_original)
O método push() insere dados ao final do array e retorna o
'length' do arquivo após a inclusão dos dados.
O método pop() remove o último elemento do array e retorna
este elemento.
Note que tanto o push() quanto o pop() alteram o
array original e não retorna o array modificado, ou seja não atribui
o valor do array resultante a um novo array, ele apenas retorna os
valores indicados e modifica o array original na memória. Portanto
se precisamos manter o array original, ao utilizar esse métodos
devemos fazer uma cópia desse array.
Caso seja necessário apenas recuperar o valor do ultimo item do
array é mais indicado utilizar outra forma de consulta como pelo
índice do último item.
O método shift() faz o mesmo que o método
pop() mas manipula o primeiro item do array. Já o método
unshift() faz o mesmo que o pop() porém inclui os
valores no início do array.
O método slice(i_1, i_2) recorta o array nos valores de
parametro indicados pelos indices. Ela não modifica o array
original, apenas retorna os valores de indice1 até indice2(não
inclusive). Podemos passar apenas o primeiro indice para recortar de
indice1 até o fim do array.
O método splice() pode remover ou inserir itens no array.
Ele modifica o array oruginal e retorna os elementos modificados.
Caso passemos apenas o primeiro parametro ele remove todos os
elementos a partir do indice informado e retorna todos os elementos
removidos. Os outros parametros são o número de valores a serem
removidos e os valores a serem incluidos. A sintaxe ficaria a
seguinte:
arr.splice(indice_inicial, numero_valores_serem_removidos,
valores_serem_inseridos)
Desse modo se selecionarmos o indice 'n' e passarmos '0' como
valores_serem_removidos iremos incluir o valores_serem_inseridos a
partir do indice sem remover nada.
O método reverse() inverte a posição dos elementos no
array. Ele modifica o array original e seu retorno é o novo array.
O método reduce() percorre o array e retorna um único
valor. Ele recebe como parametro uma função com até 4 parametros:
function(acumulador, atual, indice, _arr){}
Em regra o acumulador recebe o primeiro valor do array e o atual o
seguinte que é incrementado conforme as iterações. Podemos por
exemplo fazer a soma dos valores do array passando para a função a
soma do acumulador com o valor atual.
Podemos passar como parametro do reduce() (este parametro
não é da função) o valor inicial do acumulador. Esse parametro será
passado após a função. Note a sintaxe:
arr.reduce(function(acumulador, atual, indice, _arr){
lógica_da_função
}, valor_inicial_acumulador)
Se passarmos uma string vazia como valor inicial do acumulador o
resultado será a concatenação dos valores do array (no caso do
exemplo os valores do array são numeros, portanto concatenados com
uma string tudo vira uma string). Podemos também passar como valor
inicial do acumulador um array ou um objeto vazio. Neste caso os
valores que serão iterados serão incluido no array ou objeto de
acordo com a lógica da função. Para entender verificar a lógica
utilizada no exemplo da aula (criar um objeto que indique quantos
nomes existem no array com determinada letra).
No exemplo citado utilizamos o conceito de que uma string é um array
de caracteres, portanto pegando o valor da string no indice '0'
receberemos a primeira letra da string.
O desafio consiste em utilizar o reduce() para criar um
array (passado como valor inicial do acumulador) com os números do
array original, sem repetir os valores. Tentei de várias formas
(utilizando forEach() e some()) mas não consegui
resolver.
A solução foi basicamente a que eu pensei, mas o método a ser
utilizado para verificar se já existe o valor no array de retorno é
o indexOf(). Assim criamos o array com os valores do array
original e testamos com 'if' se o indexOf() do valor atual
não existe no array criado (se o retorno do indexOf() é menor que 0,
ou seja, -1). Caso não exista inserimos o valor no array acumulador
com o método push().
Os métodos anteriores eram acessados pelo objeto array.
Notamos issso pois utilizamos na notação a primeira letra do array
minúscula(lambrando que isso é apenas notação). Já os métodos
from() e of() acessam diretamente a função
construtora (ou classe) do Array.
Os métodos Array.from() e Array.of() são
incompatíveis com diversas versões de browsers mas possuem uma opção
de compatibilidade que é a utilização do código polyfill,
que é um código que emula a função em browsers incompatíveis. Mais
informações em
MDN Web Docs Array.from().
O método Array.from() cria um array a partir de outro array
ou de um objeto que se parece com um array. Desse modo quando
utilizamos o método Array.from(array_origem) criamos uma
cópia do array de origem (??como o método é um método construtor ele
cria um novo objeto e não apenas um novo apontamento de memória??).
O método Array.of() inclui um valor em um objeto do tipo
Array. Quando passamos para um objeto do tipo Array apenas um valor
numérico é criado um Array vazio com o número de posições indicados
no valor do Array. Quando queremos criar o Array com apenas um
elemento numérico precisamos utilizar o Array.of(numero).
Na aula foi demonstrado em seguida que é possível criar um NodeList
de parágrafos com o método querrySelectorAll('p') e que
esse NodeList se comporta como um array podendo inclusive receber
métodos de array. No exemplo utilizaou-se o forEach() para
perceorrer todos os elementos do NodeList e incluir em seu
textContent o valor do índice para numerar os parágrafos.
Visto o exemplo acima, caso não possamos utilizar o
forEach() caso tentemos utilizar o map() não será
possível. Isso porque o método map() não é acessado pelo
NodeList, pois o NodeList não é um array verdadeiro, ele apenas
aparenta ser um array e aceita alguns de seus métodos. Para
convertermos o NodeList utilizamos o método
Array.from(nome_NodeList). Desta forma convertemos o
NodeList em um Array verdadeiro sendo possível acessar os métodos de
array.
Resumindo, utilizamos o método Array.from() para converter
o NodeList (ou outros objetos que parecem array) em um array que
permite a utilização dos métodos próprios de array.
O spread operator 'espalha' os elementos de um array
separando-os como se fossem valores separados. Seu operador é
....
Podemos utilizar o spread operator quando desejamos unir
dois arrays com o método push(). Nesse caso se utilizarmos
o push() diretamente no array teremos um array dentro do
array principal. Para incluirmos os elementos do segundo array ao
primeiro podemos utilizar o spread operator para espalhar
os elementos do segundo array e inclui-los individualmente como
elementos no array principal.
O destructuring converte os elementos do array em variáveis isoladas de acordo com o número de elementos passados como parametro. Seu operador é o '[ ]' sendo que passamos dentro do operador o nome das variáveis resultantes. Podemos selecionar um elemento e utilizar o operador spread operator (...) para pegar os elementos restantes do array (neste caso o operador se chama rest operator). Podemos também pular elementos do array na atribuição das variáves bastando deixar o espaço daquele valor em branco. Identificamos o destructuring em um código observando a estrutura de array do lado esquerdo do operador de atribuição.
Aula 95 - for...ofO loop for..of funciona da mesma forma que o for...in entretanto o segundo pode ser utilizado em objetos e necessita que se declare a variável para o loop. Já o for...of faz a iteração apenas de arrays e não exige que se declare a variável de loop.
Aula 96 - Desafio
Desafio criar uma função de soma e uma função de média que podem
receber n valores como parametro.
Fiz as funções inicialmente passando os valores em um array, mas não
era isso o que devia ser feito e sim passar os valores como
parametro na chamada da função.
Na resolução do desafio eu fiz utilizando o método
Array.from e o loop for...of Depois para fazer
alguns testes, retornei os valores em uma array e utilizei o
destructuring para apresntar o resultado.
O professor utilizou o spread operator e o método
reduce().
Os métodos com prototype são úteis para programação
utilizando a versão ES5, pois nesta versão não há suporte para
métodos como Array.from(), spread operator e
destructuring.
O prototype é o local onde se armazenam todos os métodos do
método construtor Array. Desse modo, quando chamamos o construtor
Array.prototype podemos chamar, a partir dele, qualquer
método de array.
Já as funções podem ser executadas de três formas: chamando a função
pelo nome seguida de '()', com ou sem parametros ou utilizando os
métodos call() e apply(). Esses métodos quando
utilizados alteram o escopo do this da função executada.
Para utilizarmos esses métodos utilizamos a sintaxe:
nome_da_funcao.call(null, parametros)
ou
nome_da_funcao.apply(null, [parametros])
A diferença como se nota é que no call passamos os
parametros diretamente enquanto no apply passamos um array
com os parâmetros.
Com base nessa informações, como o elemento arguments não é
um array e sim um conjunto de elementos, não podemos utilizar os
métodos nativos de array. Por isso utilizamos o prototype.
Quando utilizamos a sintaxe
Array.prototype.forEach.call(arguments, função_do_forEach)
na verdade estamos "pegando emprestado" o método
forEach() na biblioteca de funções de Array e aplicando ele
em um objeto que não é um array (no caso a lista de dados
arguments).
Essa abordagem serve para todos os métodos nativos de Array que
precisemos utilizar em dados que não sejam arrays.
Seção 7 - Exercício proposto: Lista de alunos
Aula 99 - Introdução
O exercício é composto de um arquivo HTML com a tabela de dados a
serem calculados, estilizado com o Materialize. Já temos as
funções criadas na aula 97 para soma e média.
Para acessar os dados da tabela vamos recuperar primeiramente todas
as linhas da tabela (<tr>) com o querrySelectorAll().
Em seguida para recuperar os dados da tabela vamos percorrer os
valores da NodeList para selecionar cada <td>.
Nesse ponto pensei em criar um array de dados e remover o nome do
aluno da primeira posição com o método shift() para em
seguida utilizar o array para passar os dados para a função de média
no js. A solução utilizada foi bem mais simples. Apenas passamos o
valor como parametro para a função média selecionando cada valor
pelo índice dele no NodeList e utilizando o atributo
textContent.
Para não utilizar os valores dos índices fizemos a seleção através
de propriedades criadas nas tags <th> no <thead>. Essas
propriedades receberam nomes (n1, n2, n3, n4, media) e esses valores
foram passados como atributos em um objeto literal cujos valores são
os índices do array da tabela. Dessa forma podemos substituir os
numeros dos indices por uma referencia a tag onde estão os valores.
Essa é apenas uma solução didática pois não tem fundamento utilizar
isso na prática.
Uma observação: como criamos a variável com os índices fora da
função auto-invocável (pois ela faz referencia a dados do DOM)
precisei colocar um ponto e vírgula no fim do objeto pois ele estava
causando um erro na função e o prettier do VSCode não estava
colocando o ';' automaticamente.
O erro que eu observei no na criação do objeto foi comentado aqui.
Esse erro é comum e por padrão quando se utiliza funções auto
invocáveis iniciamos o código com o ';' antes da declaração da
função para evitar que esse erro ocorra. O Prettier transfere esse
';' automaticamente para o objeto acima da função. De qualquer modo
o objeto criado pode ser colocado dentro da função (diferente do que
eu pensei não existe influencia do DOM).
Para melhorar a forma como recuperamos os índices nesse momento
vamos criar uma função para recuperar os índices automaticamente, de
acordo com o atributo criado em cada <th>. Note que mesmo quando
criamos o objeto indicando o valor de cada atributo, esses valores
continuam fixos.
Para fazer isso inicialmente referenciamos por variáveis os
elementos do <thead> da tabela. Após isso vamos recuperar o
indexOf() dos elementos necessários. O método
indexOf() não é nativo do NodeList portanto devemos
converter o NodeList para um objeto tipo array para podemos acessar
seu indice. Podemos utilizar os tres modos aprendidos (
Array.prototype, Array.from ou
...[](spread operator)).
Aqui vamos ajustar a forma como montamos o objeto de atributos e
índices das <td> de dados. Para isso criamos uma variável que
irá receber os elementos cuja propriedade contenha o valor
[aluno-nota].
Criamos um array desse elemento com o método Array.from() e
percorremos o array com o método forEach().
A função de callback do forEach() vai recuperar em cada
<th> o valor de sua propriedade com o método
getAttribute(). Esse método retorna o valor atribuido a
propriedade.
Com esses dados populamos o objeto INDICE_NOTAS com os valores das
propriedades (recuperados com o getAttribute) e o valor dos
índices (recuperado com a função pegaIndice() criada na aula
anterior).
Aqui vamos refazer a lista de alunos a partir de um array de objetos
com os dados de cada aluno.
Inicialmente percorremos o array de objetos com o método
forEach(), calculamos a média (com a função average()) e
criamos um atributo media que será inserido nos objetos.
Em seguida vamos utilizar o método map() para mapear cada
índice do array e retornar em cada elemento uma string contendo a
estrutura HTML da <tr> de cada linha com os valores de cada
coluna, tudo em um literal template. Um detalhe. Como o
map() retorna os elementos do array como uma string
separados por vírgula esse caractere aparece no documento HTML. Para
resolver isso utilizamos o método join('') passando uma
string vazia como separador entre os elementos.
A mudança na estrutura de dados é que as notas agora estão em um
array chamado 'notas' dentro do array de objetos 'alunos'.
A primeira forma, e mais fácil de resolver a mudança é chamar cada
nota pelo número do índice dela no array.
A segunda forma é utilizar algum dos métodos de espalhamento de
array, no caso o spread operator ou o apply.
Já para os valores que são inseridos no HTML inserimos no
literal template um novo valor que agora fará o
map() dos valores do array e irá inseri-los na tag
<td>. Dessa forma o retorno do método map() será a
string <td>valor_de_n</td>.
O segundo desafio consiste em definir cores diferentes de acordo com
a média do aluno. Eu compliquei um pouco e criei, na minha resolução
um novo atributo para o objeto que armazenava a cor de acordo com a
média e refletia esse valor como um style inline no HTML.
A solução proposta foi criar classes para cada situação e utilizar o
operador ternário diretamente na definição da classe para defini-la
como 'aprovado' ou 'reprovado'.
Seção 8 - DOM - parte 2
Aula 107 - HTMLCollection vs NodeListAqui vamos entender as diferenças entre as NodeList e os HTMLCollection. Quando selecionamos os elementos com as propriedades do DOM temos resultados diferentes. O querrySelectorAll() retorna uma NodeList. Já o getElementsByTagName() ou getElementsByClassName() retornam uma HTMLCollection. A diferença entre o dois é que o NodeList é estático, ou seja, não é atualizado quando manipulamos o DOM, já o HTMLCollection é atualizado, mesmo quando limpamos o seu conteudo. Assim o NodeList reflete os valores do documento HTML original enquanto o HTMLCollection reflete as alterações feitas no DOM.
Aula 108 - Node vs Element
Há 12 tipos diferentes de 'nós' no DOM HTML (elemento, atributo,
texto, comentário, etc). Um elemento é um tipo específico de nó.
Desse modo quando nos referimos a tags estamos falando do nó
elemento, assim como temos o nó dos atributos (class, href), o nó de
text (para os textos das tags) o nó de comentários (por isso tive um
problema com um método de chamada (parentNode) por nó que
identificou o comentário).
Métodos de navegação entre nós:
Node.parentNode, Node.parentElement,
Node.nextSibling, Node.previousSibling,
Node.childNodes, Node.children,
Node.firstChild, Node.firstElementChild,
Node.lastChild, Node.lastElementChild e
Node.hasChildNodes().
Métodos para adicionar nós:
ParentNode.prepend() e ParentNode.append(),
Node.appendChild(), Node.insertBefore() e
Node.cloneNode(), ChildNode.after() e
ChildNode.before() e
Element.insertAdjacentElement(),
Element.insertAdjacentHTML() e
Element.insertAdjacentText().
Métodos para remover nós:
Node.replaceChild() e Node.removeChild() e
ChildNode.remove().
Métodos para criar nós:
document.createElement(),
document.createAttribute(),
document.createTextNode() e document.write().
O parentElement e o parentNode são basicamente
iguais e representam o elemento acima do elemento selecionado. As
diferenças residem que em alguns casos o elemento não tem um
elemento pai mas apenas um nó pai.
As propriedades nextSibling, nextElementSibling,
previousSibling e
previousElementSibling selecionam os nós ou elementos
irmãos anteriores ou posteriores.
A propriedade childNode recupera todos os nós filhos do
elemento seletor(em NodeList), já o children recupera
apenas os 'elementos' filhos do seletor (em HTMLCollection).
As propriedades lastChild, firstChild recuperam os
últimos e primeiros filhos do seletor. Já o
lastElementChild e firstElementChild recuperam o
último e primeiro elementos filhos do seletor.
O método hasChildNodes() verifica se o seletor tem ou não
nós filhos. Retorna 'true' mesmo que haja apenas um espaço dentro do
nó verificado.
Podemos inserir elementos no DOM com o innerHTML,
entretanto em algumas situações é ncessário utilizar os métodos de
criação do DOM.
Para criar elementos utilizando os métodods do DOM podemos utilizar
os métodos:
createElement('nome_do_elemento') para criar as tags.
createAttribute('nome_do_atributo') para criar atributos
(como id, class, style). Feito isso passamos o valor do atributo com
variável_do_atributo.value e setamos o atributo no elemento
com setAtributeNode().
createTextNode('texto') cria o conteudo de texto do
elemento. Utilizamos o appendChild()para inserir o texto no
elemento.
Podemos criar atributos diretamente com o
setAtribute('nome_do_atributo', 'valor_do_atributo') e
inserir textos com textContent, sendo esses os métodos mais
utilizados.
Para inserir o conteudo criado no documento podemos utilizar o
método
document.querySelector(elemento_pai).appendChild(elemento_criado)
Podemos inserir os elementos também utilizando os métodos
prepend() que adiciona o elemento antes do elemento
selecionado ou append() que insere no final do elemento
selecionado (esses métodos não funcionam no IE). Com esses métodos
podemos incluir mais de um elemento no DOM, inclusive uma string sem
formatação.
Outra forma é utizando o método
insertBefore(valor_a_inserir, elemento_de_referencia).
Os elementos inseridos com os métodos acima, caso sejam indicados
para um nova posição são reescritos. Para copiar esses elementos
utilizamos o método cloneNode(). O
cloneNode() clona apenas o elemento selecionado. Para
inserir todos os filhos desse elemento devemos passar o parametro
true.
Os métodos after() e before() também inserem
elementos depois ou antes do elemento selecionado. Eles também são
incompatíveis com IE.
Os métodos de insert podem ser insertAdjacentHTML(),
insertAdjacentText() e insertAdjacentElement().
Todos eles recebem um primeiro parametro que poded ser
beforebegin, afterbegin, beforeend e
afterend.
A diferença dos três é no segundo parâmetro. O primeiro aceita
strings com código HTML, o segundo apenas textos enquanto o terceiro
exige a criação de um nó através do createElement().
Os útimos métodos são para remover elementos. Para isso temos o
remove(), o removeChild() que dede ser invocado
pelo parentNode e o replaceChild() que recebe como
parametro (novo_elemento, velho_elemento).
O desafio consiste em movimentar algumas tags de acordo com algumas
instruções.
Na resolução tive duas divergencias. A primeira foi que inseri os
elementos clonados na <ul> quando deveria ter inserido na
<li>. Com isso prcesamos manipular o elemento filho do filho. A
solução para isso é remover o elemento diretamente no clone, pois
ele continua sendo apontado como clone refletindo no documento.
A segunda é que fiz um loop para remover as <li> e o professor
fez apenas uma limpeza do conteúdo passando um valor '' com o
innerHTML
Seção 9 - Eventos
Aula 119 - Introdução
Evento é uma ocorrecia interativa que ocorre por intervenção do
usuário ou não. Event handlers é uma função que é executada
quando um evento ocorre. Disparador de eventos é o elemento que
diparou o evento ou seja o elemento ao qual foi atrelado o
event handler.
Quando utilizamos o DOM - Level 0 utilizamos os eventos inline do
tipo onclick. Já para o DOM - Level 2 que é a mais moderna
vamos utilizar o método
addEventListener(evento, função, fase).
Uma das desvantagens de utilizar o DOM - Level 0 é que não podemos
passar duas funções para o mesmo evento de um elemento.
Quando executamos uma função disparada por um evento, tanto com
onclick quanto com addEventListener não devemos
passar os '( )' da função (se o evento estiver sendo executado no
script).
Note pelos exemplos que podemos colocar vários eventos quando
utilizamos a notação de DOM Level 2, e que podemos utilizar ainda o
DOM Level 0 junto, mas apenas as notações de DOM Level 2 permitem
mais de uma interação.
O terceiro parametro do addEventListener() á a fase que pode ser a fase de captura ou a fase de borbulhamento (bubbling) que é a padrão. Caso setemos o parametro como true estamos selecionando a fase de captura. Essas a fase de bubbling ocorre quando o elemento é executado de baixo para cima na arvore do document. Já a fase de captura ocorre de modo inverso.
Aula 121 - O objeto eventPodemos acessar o objeto event passando um parametro para a função de callback do evento que queremos manipular. Esse objeto possui diversos atributos do evento, entre eles o stopPropagation() esse método para a propagação do evento.
Aula 122 - Delegação de eventos
Utilizamos a delegação de eventos quando precisamos passar o mesmo
evento para vários elementos que estejam na mesma estrutura. Nesse
caso aplicamos o evento ao elemento pai delgando esse evento aos
filhos.
Para aplicar a delegação primeiramente selecionamos todos os
elementos filhos com querrySelectorAll(). O resultado é uma
NodeList portanto não podemos passar métodos diretamente para
eles.
Para passar os listeners para os elementos podemos utilizar cada
índice do NodeList ou criar um loop com o spread operator e o
método forEach() aplicando o listener em cada elemento do
NodeList. Entretanto esses modos são muito custosos em memória para
o usuário pois cria diversos listeners (um para cada
elemento).
Portanto para fazer a delegação criamos o
addEventListener() no objeto pai (geralmente o container
dos elementos). Feito isso podemos acessar pela função de callback
do addEventListener() com o event object a
propriedade target que mostra qual elemento foi manipulado.
O problema é que nesse caso todo o container responderá ao
listener, ou seja, mesmo as áreas externas aos elementos
tratados. Para contornar esse problema podemos fazer um condicional
para só aceitar a propagação caso o elemento seja do tipo desejado.
Quando utilizamos o atributo target temos como resposta o
elemento clicado. Já quando selecionamos o
currentTarget temos como retorno o elemento que chama o
método addEventListener()
Nesse contexto, quando utilizamos funções do tipo
function expression o this se refere ao
currentTarget. Já quando utilizamos arrow function o
this deixa de se referir ao currentTarget e passa
a se referir ao objeto global ou seja o Window
Habilitar o botão de envio apenas quando o checkbox estiver clicado. Enviar uma mensagem caso o Título não esteja preenchido.
Aula 125 - Cancelar envio formulário
O click do botão (tipo <input type='submit'>) que eu fiz está
correto. Umas das coisas que foram ajustadas é que o 'submit' tem
por padrão enviar o formulário, mesmo que não tenha sido validado.
Para evitar esse comportamento devemos configurar no
event object o atributo preventDefault() para evitar
o comportamento padrão. Outro atributo que foi incluido é o
focus() para o <input> do título caso ele passe a
mensagem de campo não preechido.
Apesar disso tudo, o correto é monitorar não o <input
type='submit'> ou seja o botão de envio, mas sim o evento
submit do <form> portanto mudamos a abordagem para
monitorar com o addEventListener() o evento
'submit'.
Temos o contador de caracteres sendo exibido no formulário,
entretanto caso o JS não funcione ele não terá nenhuma utilidade,
portanto deixamos ele com display= none até que o script seja
executado.
Criamos as variáveis de acesso aos elementos e manipulamos o display
do elemento. Em seguida vamos acessar o <span> que contém o
contador. Como apontamos para ele com o
getElementsByTagName() teremos uma
HTMLCollection (mesmo tendo apenas um elemento do tipo span)
então precisamos acessar o índice [0].
Para definir o número de caracteres vamos recuperar o valor do
maxLegth definido no <textarea> do HTML.
Em seguida vamos definir qual evento será monitorado pelo contador
no <textarea>. Podemos utilizar os eventos keyup,
keydown, change input, e
keypress. O evento change, não responde a
mundanças por digitação, portanto pode ser descartado. O
keyup não vai funcionar caso a tecla fique pressionada por
mais tempo (pois nesse caso conta-se os outros eventos). Já os
eventos keypress e keydown não monitoram ações de
copiar e colar. Portanto o evento mais confiável é o input.
Com isso podemos recuperar a quantidade de caracteres digitados
verificando o número de letras digitadas através do
value.length do valor recebido no <textarea>. O valor a
ser exibido no <span> do contador é a diferença do valor máximo
(definido pelo maxlength) e o valor recebido. Recuperamos ess valor
com o operador this que é o currentTarget da
função. Essa função pode inclusive ser criada fora do listener e
executada conforme necessário.
O número de caracteres disponíveis inicial estava fixo na <span>
que o armazena. Vamos agora tornar esse valor dinamico para que ele
mude caso mudemos o maxlength do <textarea>. Para isso
atribuimos o valor da variável que retem o maxlength ao
apontador da <span>. Depois foi criada uma função para fazer
esse processo.
Para a verificação do checkbox eu havia utilizado um
if para verificar se o checked era 'true' ou 'false'.
Entretanto podemos fazer isso atribuindo diretamente o valor a
propriedade disabled do botão, utilizando a negação/inverso
(!) da propriedade checked do input check.
Para mostrar a mensagem de erro quando não tiver título digitado,
precisamos acrescentar uma classe show na <div> que
encapsula a mensagem de erro. Essa classe está no CSS da página e
muda a propriedade transform: translateY para apresentar o
modal na tela. Se utilizarmos as propriedades setAtribute,
ou className vamos sobreescrever as classes que por ventura
tenham sido definidas para o elemento. Para evitar isso utilizamos o
método classList.add() que adiciona uma classe a outras se
existirem. Criamos também uma função para remover a classe
show quando o botão de fechar do modal for clicado utilizando
o addEventListener() no clique do botão. Em seguida vamos
alterar o focus() do título para que ele ocorra apenas
quando cliquemos no botão de fechar o modal. Para isso criamos uma
função de callback que será executada como parâmetro da
função que exibe o modal. Para isso passamos a função de fechar o
modal para dentro da função que exibe o modal, pois um só vai
existir se o primeiro for executado.
Ao passarmos a função de fechar o modal para a função de erro temos
um novo problema. A cada vez que clicamos no botão fechar um novo
listener é adicionado. Podemos solucionar isso utilizando o
método removeEventListener, porém ele não é executado
quando passamos funções anonimas. Criamos portanto uma função
nominada para fechar o modal de erro e incluimos nela o
removeEventListener().
A chamada da função de callback que executa o
focus() ficará dentro da função de fechamento do modal e
executará quando o botão for clicado.
Aqui criamos um listener para monitorar o teclado quando o modal estiver aberto. Caso a tecla ESC seja pressionada, monitoramos o evento keyup para executar a função de fechamento do modal.
Aula 132 - Eventos de tecladoEntender os eventos de teclado keyup, keydown e keypress. O keydown e o keypress só apresentam o valor digitado após registrar o evento portanto só captura o valor digitado após ser disparado, já o keyup, como depende de que a tecla seja liberada apresenta o valor quando dispara o evento. O keypress por sua vez não detecta teclas especiais como shift, ctrl e esc
Seção 10 - Exercício proposto: lista de tarefas
Aula 133 a 135 - Introdução / Adicionar li / Melhoria de usabilidade
Apresentação do exercício: criar uma lista de tarefas. Vai ser
utilizado neste projeto o Local Storage do browser.
O primeiro passo realisado foi a inclusão das <li> utilizando o
método mais simples: o método innerHTML com
literal template.
As melhorias foram limpar o conteudo do input após a inclusão e
manter o foco no campo.
Nesta aula vamos criar uma função para poder recuperar as <li>
inseridas ao clicar no item na tela. Para isso precisamos criar um
addEventListener monitorando os itens da tabela. Para fazer
issso vamos utilizar o spread operator em uma variável que
irá conter a HTMLCollection das <li> e aplicar com o
método forEach() o listener em cada uma. O problema é que
quando utilizamos o innerHTML += a cada elemento inserido
limpamos o conteúdo e reinserimos os novos dados. Portanto nesse
caso deveremos utilizar os eventos de createElement, e
appendChild. Para tanto criamos uma função que cria a
estrutura que necessitamos e passamos essa função para o listener do
submit. O problema ainda persiste pois não é adicionado o
'listener' das <li> pelo spread operator.
Temporariamente vamos adicionar os listeners na função que
cria as <li>.
Vamos agora entender as propriedades de texto:
as propriedades textContent e innerText retornam o
texto do elemento. A diferença é que o
textContent recuperamos parte das quebras de linha e
espaços enquanto o innerText não mostra essas formatações.
A propriedade innerHTML recupera a <tag> em que o
elemento está contido. Já a propriedade outerHTML mostra o
elemento anterior ao elemento em que ele está encapsulado.
Fizemos uma pequena refatoração para incluir uma função que, nesse
momento, apenas encapsula os console.log() das <li>.
Vamos a partir daqui remover todas as <li> do HTML para apenas
inseri-las dinamicamente. Para isso vamos trabalhar com estrutura de
dados. Essa estrutura de dados será um array de objetos com tres
atributos definidos.
Criada a array de objetos a função que adicionava os valores em
<li> no HTML passará a incluir os objetos na array com o método
push() e substituindo o valor do atributo name do
objeto pela task digitada (passada por parâmetro pela função).
Criamos uma nova função para criar a estrutura das <li> que irá
receber o objeto e incluir na li o valor do atributo
objeto.name. Essa função irá retornar a <li> montada que
será renderizada a cada inclusão pela função
renderTasks() que limpa o conteudo da <ul> do HTML, e
reinsere todas as <li> através da leitura do array de objetos
com o método forEach().
Criação dos outros elementos internos das <li> do HTML através
da inserção das tags na construção da string.
Criação do container para a edição das tasks.
Aqui vamos começar a criar os eventos para os pontos de click das
<li>. Como temos muitos itens vamos utilizar a delegação de
eventos.
Criamos para isso atributos para cada elemento clicável de modo que
eles possam ser identificados individualmente. Poderíamos ter
utilizado outros modos de selecionar esses elementos como class ou
id. O problema de utilizar classes é que caso posteriormente
utilizemos novas classes no elemento o retorno do evento
e.target.className também mudará e precisaremos ajustar o
listener. Uma opção é utilizar o
e.target.classList.contains('nome_da_classe') mas isso
também deixa o nome da classe engessada, muito embora em não veja
mutios problemas, apesar que nesse caso específico a classe se
refere a um ícone (classe do font awesome) e podemos querer mudar
esse ícone.
Criamos os atributos pois eles não serão utilizados para mais nada
no código.
Definidos os atributos podemos fazer testes lógicos para identificar
qual elemento foi clicado. Uma opção é utilizar
if...else if, outra é utilizar o switch.
Ainda não criamos o resto do objeto de 'data-action' mas aqui vamos
começar a identificar qual <li> está sendo manipulada. Para isso
montamos uma lógica com while verificando qual o
nodeName do elemento. Caso o ele não seja uma <li>
buscamos o parentElement, ou seja, subimos a árvore de
elementos do DOM até chegar na <li>.
Em seguida vamos utilizar o getElementsByTagName para
recuperar as <li>. Importante lembrar que o
getElementsByTagName retorna um HTMLCollection que
é dinamica (diferente da NodeList), ou seja, é atualizado conforme
vão se inserindo novos elementos. Se utilizassemos o
querrySelectorAll não receberíamos nenhum valor pois ele
retorna uma NodeList.
Uma vez que temos acesso a HTMLCollection de <li>
recuperamos o índice do item clicado utilizando um
spread operator na HTMLCollection e buscando o
indexOf() do elemento clicado. Esse valor será utilizado em
todos os métodos daqui para frente.
Para remover os elementos com o clique no delete pensei em utilizar
o método remove na lista de <li> porém isso não
funciona pois devemos remover da coleção criada no array de objetos.
Como é um array utilizamos o método splice() passando como
parametro o índice do elemento a ser removido e '1' que é o número
de elementos a ser removido. Feito isso só precisamos renderizar
novamente a <ul> para remontar a lista. Nesse momento poríamos
utilizar o remove() para remover apenas a <li>
excluida, porém é mais seguro remontar toda a lista para que
tenhamos certeza de que o valor apresentado continua sendo um
reflexo do conjunto de dados, mesmo que esse método exija um pouco
mais de processamento pois a cada evento remonta toda a estrutura do
HTML.
Para editar a tarefa, devemos abrir o modal de edição. Faemos isso
alterando o display: none no container do modal. Para isso
selecionamos a classe dentro do <li> selecionado e alteramos a
propriedade display para display: flex.
Com essa alteração exibiremos o modal, porém como ela insere um
estilo inline quando clicamos em outro botão de edit abiremos um
novo modal sem fechar o anterior. Para resolver isso foi feito um
spread operator na <ul> para utilizar um
forEach() que remove todos os estilos inline das <li>,
para depois inserir o display: flex apenas na <li>
selecionada.
Para gravar o valor digitado no modal de edição na lista
selecionamos o campo de input do modal e recuperamos o seu
value. Esse valor deve ser inserido no atributo
name do objeto no índice selcionado. Uma nova questão
surgiu e foi feito o auto-complete do input do modal com o
valor atual da tarefa. Eu fiz isso diretamente na função executada
pelo botão edit, mas o professor inseriu diretamente na criação do
modal na função que gera as tags.
Para fazer o marcador de tarefa completa ou não, primeiramente
precisamos inserir um atributo data-action na tag <i> que
contém a classe do ícone de check (esse ícone faz parte da
biblioteca do font awesome e controla o tick através de uma classe
display). Essa classe ('displayNone') que determina o display ou não
do tick.
O listener do checkButton monitora um atributo do array de
objetos (completed: true or false).
Com isso monitoramos o atributo data-action e alteramos a classe
displayNone da tag na função que constroi a tabela.
Como vamos começar a incluir dados no LocalStorage vamos mudar o
valor atribuido para o 'arrTasks' para uma função que irá recuperar
os dados.
Esses dados devem ser armazenados como JSON ou seja uma string de
dados em formato JSON.
Na função que irá recuperar o array para alimentar o HTML vamos
recuperar os dados do LocalStorage com o método
localStorage.getItem('nome_biblioteca_no_LocalStorage').
Esses dados devem ser tratados com o JSON.parse() para
converte-los em array. Caso não tenhamos dados no LocalStorage,
fazemos um teste com um operador ternário em que caso não tenha nada
crie uma lista padrão.
Para finalizar precisamos de uma função para armazenar os dados no
LocalStorage, que irá executar o método
localStorage.setItem('nome_biblioteca_no_LocalStorage',
valor_a_adicionar), sendo que o valor a adicionar deve ser convertido para string
JSON com o método JSON.stringify
Entretanto quando limpamos o LocalStorage o return da função que
recupera os dados resulta em erro pois não testamos se existe o
objeto no LocalStorage. Para resolver isso fiz um teste lógico que
verifica se existe a chave dos dados armazenados e caso contrário
cria a chave.
O professor criou apenas um 'curto-circuito' no próprio return
testando tasksData && tasksData.length no operador
ternário, assim caso o tasksData tem que existir 'e' o
tasksData.length deve ser diferente de false.