Categorias
PWA - Progressive web apps Tutoriais

Geolocation API

Vamos falar sobre geolocation esse post servirá como base para o conteúdo do curso de PWA que estou rodando no meu canal. Na versão 2020 do curso iremos abordar Web APIs disponíveis para iOS e nesse primeiro posts iremos abordar a Geolocation web API.

Umas das primeiras web APIs lançadas junto com o HTML5, para termos noção esse recurso estava disponível na versão 5 do Chrome e Safari em 2010. A Geolocation API possui uma série de funções e eventos que permitem a localização do usuário através doso dados: latitude e longitude.

Relatório do “can I use”

Suporte

Esse recurso é totalmente dependente do device do usuário vimos que o recurso é presente 94% dos browsers que suporte alto. Mas a sua precisão depende que o device que possua GPS, caso contrário essa informação será gerada através de cruzamento de dados, por exemplo, rede e endereço IP. Isso significa se o device não possuir suporte essa informações não serão tão precisas.

GPS

Outro ponto importante é o consumo de bateria, para o os dispositivos móveis o acesso ao GPS causa um custo maior de processamento assim aumenta o consumo de bateria, então é importante fazer o uso consciente desse recurso.

Privacidade

Na maioria dos devices esse recurso é dependente de uma conexão HTTPS, também antes do acesso a esse recurso o browser irá requisitar permissão de acesso a Geolocalização do usuário. Como podemos ver na imagem a seguir:

Exemplo pop up de permissão da apple

Ideal caso armazene ou monitore a movimentação do usuário deixe claro o uso desse recurso. Esse recurso também só será acessado em uma aplicação que estiver rodando em um servidor web.

Código

Depois de uma introdução sobre a API agora vamos ver como utiliza-la em nossa aplicação:

navigator.geolocation.getCurrentPosition(geoSuccess, geoError); const geoSuccess = (position) => { console.log(position.coords.latitude, position.coords.longitude); }; const geoError = (error) => { console.log('Error occurred. Error code: ' + error.code); };

No código acima fazem a chamada da api dentro de navigator, podemos conferir se o browser tem suporte a geolocation verificando se o atribulo é nulo mas em 2020 a api está presente na maioria dos browsers então não vou me preocupar como esse ponto.

Chamamos a função get position responsável por requisitar a latitude e longitude do usuário. Ela recebe como parâmetro duas funções uma parar executar caso sucesso ou falha de acesso ao recurso.

Se a requisição for bem sucedida teremos a chamada da função geoSuccess com o retorno da latitude(position.coords.latitude) e longitude(position.coords.longitude). Em caso de erro teremos a chamada da função geoError com os possíveis códigos:

  • 0: erro desconhecido
  • 1: permissão negada
  • 2: erro na resposta da fonte da informação
  • 3: timed out

Essa é uma introdução ao assunto em breve estarei postando um vídeo sobre o assunto em meu canal. Qualquer dúvida deixe um comentário ou continue lendo mais tutoriais na página da categoria.

Referência: MDN Geolocation API

Veja também as aulas sobre o assunto no curso de PWA

Categorias
HTML5 Tutoriais

Introdução a imagens responsivas com srcset

Imagens são responsáveis pela maior parte do carregamento de bytes de uma aplicação web em geral. Não podemos evitar-las pois imagens são um ponto crucial de nossa aplicação. Mas temos alternativas para reduzir esse impacto, por exemplo, geralmente nossa aplicação web possui diferentes usuários com diferentes tamanhos de tela.

Imagine o seguinte cenário, em seu website você possui uma imagem de apresentação do seu produto com 1600 pixels, na versão desktop não temos nenhum problema mas o usuário em um dispositivo móvel carrega essa mesma imagem numa tela de 340px. Porque então não servimos diferentes tamanhos de imagens para diferentes tamanhos de tela?

Imagens responsivas é uma parte do design responsivo que pode resolver essa questão. com elas podemos prover diferentes tamanhos de imagens para tamanhos de telas específicos também conseguimos fazer direção de arte disponibilizando recortes específicos da mesma imagem.

Criando imagens responsivas

Com HTML temos algumas alternativas para servir diferentes tamanhos de imagens de acordo com sua resolução de tela.

  • Media queries
  • srcset
  • Tag picture

Srcset

Para esse post iremos trabalhar com a propriedade srcset. Ela permite mais de uma resolução possível das mesma image separadas por vírgula, cada valor dessa lista representa um caso possível ela pode ser atrelada a uma condicional definida pela propriedade sizes.

Vamos usar como exemplo uma tag img tradicional para os nossos testes:

<img src="/imgs/amp-workshop-dublin-1024w.jpg" alt="AMP Workshop Dublin">
Imagem utilizada no exemplo

Agora vamos adicionar os diferentes tamanhos para a imagem:

  • amp-workshop-dublin-640w.jpg
  • amp-workshop-dublin-960w.jpg
  • amp-workshop-dublin-1024w.jpg

Agora que temos os tamanhos definidos vamos alterar nossa img tag:

<img srcset="./imgs/amp-workshop-dublin-640w.jpg 640w, ./imgs/amp-workshop-dublin-960w.jpg 960w, ./imgs/amp-workshop-dublin-1024w.jpg 1024w" src="/imgs/amp-workshop-dublin-1024w.jpg" alt="AMP Workshop">

Com o código acima especificamos o tamanho das imagens nesse caso o browser decidirá que imagem ira carregar de acordo com a densidade de pixels e tamanho de tela(janela). No caso das imagens por densidade de pixel definimos o valor que representa a densidade ao no lugar da largura:

<img srcset="./imgs/amp-workshop-dublin-640w.jpg 1x, ./imgs/amp-workshop-dublin-1600w.jpg 2x" src="./imgs/amp-workshop-dublin-640w.jpg" alt="AMP Workshop">

Muitas vezes queremos fazer direção de arte, por exemplo, queremos exibir em mobile e table uma imagem com largura cheia e para desktop layout duas colunas, nesse caso a imagem não sera proporcional a largura da tela precisamos especificar para o browser qual tamanho de imagem queremos nesse caso iremos fazer a utilização da propriedade size.

Size

Com a propriedade size tomamos o controle e definimos que imagens iremos carregar, passando uma media query e o tamanho que identifica a imagem. Vamos colocar tudo junto para isso vamos criar uma página para o exercício:

Versão Desktop
versão tablet e mobile

A imagem do header contém o código que virmos anteriormente agora vamos adicionar o código para os thumbnails, quando o usuário estiver no desktop iremos exibir o layout em duas colunas e quando ele estiver no tablet e desktop iremos exibir em uma coluna. O controle sobre a quantidade de colunas vem do css, mas com a tag image vamos garantir o carregamento da imagem correta.

Para o thumb vamos adicionar o seguinte código:

<img srcset="./imgs/thumb-480w.jpg 480w, ./imgs/thumb-960w.jpg 960w" sizes="(max-width: 480px) 480px, (max-width: 960px) 960px, (min-width: 961px) 480px" src="./imgs/thumb-960w.jpg" class="thumb-post" alt="post thumb">

No código acima temos dois tamanhos 480px e 960px, na propriedade sizes definimos as medias queries que iremos trabalhar nesse caso mobile(480px max), tablet(960px max), desktop(961px em diante).

O código completa fica da seguinte forma:

HTML

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Responsive images</title> <link rel="stylesheet" href="style.css"> </head> <body> <section> <header> <img srcset="./imgs/amp-workshop-dublin-640w.jpg 640w, ./imgs/amp-workshop-dublin-960w.jpg 960w, ./imgs/amp-workshop-dublin-1024w.jpg 1024w, ./imgs/amp-workshop-dublin-1600w.jpg 1600w" src="/imgs/amp-workshop-dublin-1024w.jpg" alt="AMP Workshop"> <h1 class="page-title">Fellyph Cintra</h1> </header> <div class="posts"> <article class="post"> <img srcset="./imgs/thumb-480w.jpg 480w, ./imgs/thumb-640w.jpg 640w, ./imgs/thumb-960w.jpg 960w" sizes="(max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 960px) 960px, (min-width: 961px) 480px" src="./imgs/thumb-960w.jpg" class="thumb-post" alt="post thumb"> <div class="content"> <h2 class="title">Post title</h2> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Ducimus nostrum, soluta earum dolorem magnam eos porro, aliquam ex temporibus tempore dolor suscipit quibusdam sint impedit eveniet non placeat dicta nesciunt.</p> </div> </article> <article class="post"> <img srcset="./imgs/thumb-480w.jpg 480w, ./imgs/thumb-640w.jpg 640w, ./imgs/thumb-960w.jpg 960w" sizes="(max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 960px) 960px, (min-width: 961px) 480px" src="./imgs/thumb-960w.jpg" class="thumb-post" alt="post thumb"> <div class="content"> <h2 class="title">Post title</h2> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Ducimus nostrum, soluta earum dolorem magnam eos porro, aliquam ex temporibus tempore dolor suscipit quibusdam sint impedit eveniet non placeat dicta nesciunt.</p> </div> </article> <article class="post"> <img srcset="./imgs/thumb-480w.jpg 480w, ./imgs/thumb-640w.jpg 640w, ./imgs/thumb-960w.jpg 960w" sizes="(max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 960px) 960px, (min-width: 961px) 480px" src="./imgs/thumb-960w.jpg" class="thumb-post" alt="post thumb"> <div class="content"> <h2 class="title">Post title</h2> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Ducimus nostrum, soluta earum dolorem magnam eos porro, aliquam ex temporibus tempore dolor suscipit quibusdam sint impedit eveniet non placeat dicta nesciunt.</p> </div> </article> <article class="post"> <img srcset="./imgs/thumb-480w.jpg 480w, ./imgs/thumb-640w.jpg 640w, ./imgs/thumb-960w.jpg 960w" sizes="(max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 960px) 960px, (min-width: 961px) 480px" src="./imgs/thumb-960w.jpg" class="thumb-post" alt="post thumb"> <div class="content"> <h2 class="title">Post title</h2> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Ducimus nostrum, soluta earum dolorem magnam eos porro, aliquam ex temporibus tempore dolor suscipit quibusdam sint impedit eveniet non placeat dicta nesciunt.</p> </div> </article> <article class="post"> <img srcset="./imgs/thumb-480w.jpg 480w, ./imgs/thumb-640w.jpg 640w, ./imgs/thumb-960w.jpg 960w" sizes="(max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 960px) 960px, (min-width: 961px) 480px" src="./imgs/thumb-960w.jpg" class="thumb-post" alt="post thumb"> <div class="content"> <h2 class="title">Post title</h2> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Ducimus nostrum, soluta earum dolorem magnam eos porro, aliquam ex temporibus tempore dolor suscipit quibusdam sint impedit eveniet non placeat dicta nesciunt.</p> </div> </article> <article class="post"> <img srcset="./imgs/thumb-480w.jpg 480w, ./imgs/thumb-640w.jpg 640w, ./imgs/thumb-960w.jpg 960w" sizes="(max-width: 480px) 480px, (max-width: 640px) 640px, (max-width: 960px) 960px, (min-width: 961px) 480px" src="./imgs/thumb-960w.jpg" class="thumb-post" alt="post thumb"> <div class="content"> <h2 class="title">Post title</h2> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Ducimus nostrum, soluta earum dolorem magnam eos porro, aliquam ex temporibus tempore dolor suscipit quibusdam sint impedit eveniet non placeat dicta nesciunt.</p> </div> </article> </div> <footer class="footer"> Tutorial by Fellyph </footer> </section> </body> </html>

CSS

.page-title, .posts, .footer { width: 1024px; margin: 1em auto; max-width: 100%; } .page-title { text-align: center; font-size: 3em; border-bottom: 3px solid black; padding-bottom: 1em; } .posts { display: flex; flex-wrap: wrap; } .post { width: 100%; margin: 1em; background: white; display: inline-block; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.26); border: 1px solid #eee; box-sizing: border-box; } .thumb-post { max-width:100%; height: auto; } .content { padding: 1em; } .footer { border-top: 3px solid black; padding-top: 1em; text-align: center; } @media (min-width: 960px) { .post { width: calc(50% - 2em); } }

O post serviu como introdução a imagens responsivas temos outros caminhos para trabalhar com imagens responsivas e vamos abordar em mais posts, se quiser conferir mais tutoriais confira a página da categoria tutoriais ou deixe um comentário.

Categorias
HTML5 JavaScript Tutoriais

localStorage: Armazenando dados com JavaScript

Esse post é um dos posts mais visualizados do blog, foi criado em 2013 mas vou dar uma atualizada no que aconteceu de lá pra cá. Armazenamento local com localStorage é umas das features mais úteis do HTML5 podemos utilizar para uma infinidade de ações agora com Progressive Web Apps esse recursos voltou a ficar evidência.

Ela permite o armazenamento de strings com chave e valor e considerado cookies é uma forma mais segura de armazenamento, mas ela não tem acesso através de Service Worker. Mas é um recurso nativo no browser de implementação simples que não necessita de plugin ou library externa.

Para aplicações mas complexas temos a opção de utilizar o IndexedDB. Mas um dos pontos fortes do localStorage é o suporte comparado a outras APIs ele é suportado por 93% dos browsers:

LocalStorage é um dos recursos do DOM Storage com ele podemos armazenar dados apenas em formato de texto, mas claro podemos contornar essa limitação. Vamos primeiro exemplo básico de armazenamento.

localStorage.setItem("key_da_propriedade","Valor armazenado");

No código acima adicionamos uma variável com nome “key_da_propriedade” que irá armazenar “Valor armazenado”. Quando esse código for armazenado pelo browser o usuário pode sair da aplicação, continuar navegando em outras aplicações quando ele voltar para nossa aplicação o dado continuará lá. Esse recurso será útil caso utilizarmos uma versão offline de nossa aplicação com PWA.

Para visualizar a informação que foi salva, no Google Chrome para abrir o developer Tools vamos em “menu > View > developer > developer tools”, tecla de atalho ctrl+shift+i ou simples clique o com botão direito do mouse e selecionamos a opção “inspect”, com developer tools aberto vamos na aba Application, na sessão storage vemos a opções disponíveis. Quando clicamos em Local Storage vemos as aplicações separadas por url, meu teste estou rodando em localhost, clicando na url da nossa aplicação vemos os dados armazenados:

Agora que sabemos que a informação foi salva vamos resgatar esse valor com o seguinte código:

const minha_propriedade = localStorage.getItem('key_da_propriedade'); alert("Valor:" + minha_propriedade);

No código acima passamos a função getItem e como parâmetro passamos a key da informação que queremos resgatar, na linha seguinte apenas executamos um alert para exibir a informação.

Caso queria remover essa informação do localStorage utilizamos o seguinte código:

localStorage.removeItem('key_da_propriedade');

Agora vamos para um exemplo prático aplicando essas três funções que virmos acima, no caso: salvar, ler e excluir os dados no localStorage.

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>LocalStorage tutorial</title> <script> window.addEventListener('load',() => { if(localStorage.getItem('name')){ sayMyName() }else{ whatsYourName() } }); function whatsYourName(){ document.body.innerHTML = ''; // criando um input para cadastrar o nome; const inputName = document.createElement('input'); inputName.type = 'text'; inputName.placeholder = 'Digite seu nome'; inputName.id = 'nome'; document.body.appendChild(inputName); // criando saveButton const saveButton = document.createElement('button'); saveButton.innerHTML = 'Salvar'; document.body.appendChild(saveButton); // adicionando listener para salvar a informação saveButton.addEventListener('click', saveName); } function sayMyName(){ document.body.innerHTML = ''; // criando mensagem const welcomeMessage = document.createElement('h1'); welcomeMessage.innerText = 'Olá' + localStorage.getItem("name"); // criando removeButton const removeButton = document.createElement('button'); removeButton.innerHTML = 'Excluir'; document.body.appendChild(removeButton); // adicionando o listener para remover informação removeButton.addEventListener('click',removeName); } function removeName(){ if(localStorage.getItem('name')){ localStorage.removeItem('name'); whatsYourName() } } function saveName(){ var nome = document.getElementById('nome').value; localStorage.setItem('name', nome) sayMyName(); } </script> </head> <body> </body> </html>

O Exemplo acima resumindo rapidamente ele verifica se tem o dado com a key “name” no localStorage, caso tenha, exibe o nome. Caso contrário adiciona um input para cadastrar o dado. Quando o nome é exibido também adicionamos um button para chamar a função de excluir os dados. Nos Exemplos anteriores apenas adicionamos e removemos algumas strings, para trabalhar com objetos no localStorage precisamos usar a Classe JSON, com ela é possível fazer a ponte entre textos e objetos.

window.addEventListener('load',() => { if(localStorage.getItem('user')){ const texto = localStorage.getItem("user"); const objeto = JSON.parse(texto); document.body.innerHTML = `nome: <strong>${objeto.name}</strong> email: <strong>${objeto.email}<strong>`; }else{ const user = { name: 'Fellyph', email: 'fellyph@fellyph.com.br' } const userString = JSON.stringify(user); localStorage.setItem('user',userString); document.body.innerHTML = 'dados salvos'; } });

No código acima focamos apenas no javaScript, verificamos se existe um dado com uma key “user”, caso exista ele resgata a informação na variável “texto” em seguida convertemos o texto em objeto utilizando a método parse da classe JSON, assim temos um objeto como retorno e adicionamos o conteúdo no body do nosso arquivo, no código acima utilizamos string literals para concatenar o nosso conteúdo.

Caso o usuário não tenha nenhuma informação salva com a key “user”, o código irá criar um objeto e em seguida converter em texto, assim temos a possibilidade de salvar os dados no em nosso localStorage. Esse exemplo não é muito funcional apenas exemplifica como é salvar um objeto em nosso localStorage.

Agora vamos partir para um exemplo mais funcional, vamos montar uma todo-list utilizando localStorage, desta vez vamos separar o código no arquivo todo-list.html, js/app.js e css/style.css. O exemplo a seguir utiliza ES2015 caso queira dar uma olhada no exemplo em EcmaScript 5 só da uma olhada no gitHub no seguinte link: https://github.com/fellyph/Tutorial-bbUI/tree/master/localstorage

Nosso HTML:

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Todo List</title> <link rel="stylesheet" href="css/style.css"> <script src="js/app.js"></script> </head> <body> <h1>todo-list</h1> <div id="tasks-output"></div> <form id="form-task"> <input type="text" name="descricao" placeholder="adicione uma nova task" required> <input type="submit" value="Salvar"> </form> </body> </html>

Nosso CSS:

#tasks-output li { cursor: pointer; } #form-task { border-top: 1px solid #ddd; padding: 10px 0; margin: 10px 0; } #tasks-output li[data-done="true"] { text-decoration: line-through; }

Detalhe para o ultimo seletor que trata quando o item da lista possui o atributo data-done igual a true, ele vai adicionar uma linha sobre o texto.

let todoList; let todoOutput; function formatDate(date) { // formata a data para o formato DD/MM/YYYY const time = new Date(date); return `${time.getDate()}/${time.getMonth()}/${time.getFullYear()}`; } function showList() { // mostra a lista de todo if (todoList.length > 0) { const htmlTemp = `<ul> ${todoList.map(todoItem => `<li data-id="${todoItem.id}" data-done="${todoItem.done}">${todoItem.descricao} - ${formatDate(todoItem.date)}</li>` )} </ul><button>Limpar tarefas realizadas</button>`; todoOutput.innerHTML = htmlTemp; } else { todoOutput.innerHTML = 'Nenhuma tarefa cadastrada'; } } function saveList() { // converte os dados em string e salva no local storage localStorage.setItem('tasks', JSON.stringify(todoList)); } function clearList() { // varre a lista a procura de tarefas realizadas for (let i = 0; i < todoList.length; i += 1) { if (todoList[i].done === 'true') { // remove 1 elemento na posição i; todoList.splice(i, 1); // voltando o indice no array para validar novamente a lista i = 0; } else { todoList[i].id = i; } } showList(); saveList(); } function clickList(e) { // somente fazer algo quando clicar em um item li if (e.target.localName === 'li') { e.target.dataset.done = !e.target.dataset.done === 'true'; todoList[e.target.dataset.id].done = e.target.dataset.done; saveList(); } else if (e.target.localName === 'button') { clearList(); } } function onSubmit(e) { const task = {}; // pego o valor cadastrado no primeiro input do meu form task.descricao = e.target[0].value; task.date = new Date(); task.id = todoList.length; task.done = 'false'; // adicionando a task na lista todoList.push(task); saveList(); showList(); // utiliza o preventDefault para evitar do form realizar o reload da página e.preventDefault(); } window.addEventListener('load', () => { // guarda em uma variável o elemento tasks-output todoOutput = document.getElementById('tasks-output'); if (localStorage.getItem('tasks')) { todoList = JSON.parse(localStorage.getItem('tasks')); showList(); } else { todoList = []; } if (todoList.length === 0) { todoOutput.innerHTML = 'Nenhuma tarefa cadastrada'; } // adiciona o listener para o evento submit, utilizei form para usar o required do input HTML document.getElementById('form-task').addEventListener('submit', onSubmit); todoOutput.addEventListener('click', clickList); });

Tentei comentar os pontos mais importantes do código, na listagem utilizo um map para montar nossa lista caso não esteja familiarizado só conferir a versão anterior desse código no git ou conferir os posts relacioanos a ecmaScript 2015, mas o código acima o usuário pode cadastrar as tasks que serão vinculadas uma data, quando o usuário clica no item da lista a propriedade “data-done” muda entre true ou false. caso o usuário queira limpar sua listas de task clica no botão “Limpar tarefas…” o código irá varrer da lista os itens com valor da propriedade data-done igual a true e retirar da lista.

Além do LocalStorage temos o SessionStorage, a diferença entre os dois é que o sessionStorage como o nome sugere ele guarda as informações apenas na seção ou seja, quando o aplicação é finalizada a informação é removida.

Fechamos por aqui e até o próximo tutorial, para saber mais como acessar recursos do device confira a página da categoria PWA.

Categorias
Mobile Tutoriais Web

Realizando consultas na base de dados do Parse

Essa semana eu iniciei uma série de posts sobre o Parse, serviço de cloud do Facebook. Neste segundo post vou abordar como realizar consultas em nossa base. Caso não conheça o Parse veja o post de introdução: https://www.fellyph.com.br/blog/tutoriais/parse/.

No post anterior abordamos um pouco sobre consultas, primeiro resgatamos todos os valores de uma base com a função “find”, em seguida resgatamos um único item com a função “get”. Criei um cadastro de carros com as propriedades: modelo, ano e valor para cadastrar os dados utilizei os mesmo padrão do post anterior:

INDEX.HTML




    
        
        
        
        Todo List App Parse
        
    
    
        

CAR LIST

Carregando...




APP.JS

Parse.initialize("YOUR_APP_ID", "YOUR_JS_KEY");

 var app = {
	 		Cars:null,
        	listOfCars:null,
        	inputModel:null,
			inputValue:null,
	 		inputYear:null,
	 
	 		//função para inicializar nosso app
        	initApp: function(){
        		app.Cars = Parse.Object.extend("Cars");
        		
				//showAllCars busca tarefas já cadastradas em nosso app
        		app.showAllCars();
        		
				//Adicionamos um lister para o nosso form
        		document.getElementById("form-car").addEventListener("submit",app.onSubmit);
        		
        		//Fazemos um cache dos itens que acessamos mais de uma vez em nosso app
        		app.listOfCars 	= document.getElementById("list-cars");
        		app.inputModel 	= document.getElementById("model-car");
				app.inputYear	= document.getElementById("year-car");
				app.inputValue	= document.getElementById("value-car")
        	},
	 
	 	//função responsável por consultar os dados na nuvem
        	showAllCars: function(){
				//chamamos a função Query do Parse parar varer a nossa base
        		var query = new Parse.Query("Cars");
        		
        		//a função trata a query para sucesso ou erro de nossa consulta
        		query.find({
					success:function(results){
						//esse bloco será executado se ocorrer tudo bem com nossa query
						app.updateOutputList(results)
					},
					error:function(error){
						//tratamento para caso de erro
						console.log("error",error)
					}
        		})
        	},
	 
	 		updateOutputList: function(results){
 				var markupList = "";
				if(results.length ModeloAnoValor";
						
						//os resultados vem em um array de objetos
						//varremos o nosso array e montamos um markup
						for(var id in results){
							markupList += "";
							markupList += ""+ results[id].attributes.model +"";
							markupList += ""+ results[id].attributes.year +"";
							markupList += ""+ results[id].attributes.value +"";
							markupList +=
							"";
						}
				}else{
					markupList = "Nenhum Resultado Encontrado";
				}
				app.listOfCars.innerHTML = markupList;
 			},

			//função para fazer o tratamento quando o usuário envia os dados do form
        onSubmit: function(e){
		var car = {};
				
		car.model = app.inputModel.value;
		//utilizamos parseInt para converter o nosso value que é uma string e inteiro
                car.year = parseInt(app.inputYear.value);
		car.value = parseInt(app.inputValue.value);
		//passamos o nosso objeto para ser salvo na cloud
		app.saveCar(car);
		// utiliza o preventDefault para evitar do form realizar o reload da página
		e.preventDefault();
	},

        	//função específica para salvar as cars na nuvem
        	saveCar: function (car){
        	 	var carCloud = new app.Cars();
        	 	carCloud.save(car).then(function(object) {
                	console.log("Carro salvo com sucesso!");
					//após a informação salva limpamos os dados dos inputs e atualizamos a lista
                	app.showAllCars();
                	app.inputModel.value =
					app.inputYear.value =
					app.inputValue.value = "";
            	});
        	}
 }
 			
window.addEventListener("load", app.initApp)

Cadastrei algumas informações na minha base para realizar a consulta:

Agora vamos dar uma olhada em algumas funções que podemos trabalhar com a classe Query. A primeira delas será a função equalTo:

equalTo

        searchByModel: function (modelCar) {
		/*
			Meu App Já instaciou a Class Cars
			caso contrario teria que instancia-la:
			var Cars = Parse.Object.extend("Cars");
		*/
		var query = new Parse.Query(app.Cars);

		query.equalTo("model", modelCar);
		query.find({
			success: function (results) {
				app.updateOutputList(results)
			},
			error: function (error) {
				alert("Error: " + error.code + " " + error.message);
			}
		});
	}

Na função equealTo passamos dois parâmetros, o primeiro o nome da propriedade e a segunda o valor que você deseja comparar, detalhe sobre a função que sua consulta é case sensitive.

notEqualTo

query.notEqualTo("modelo", "Uno");

A função notEqualTo é o inverso da função equalsTo ela retorna todos os valores diferentes do valor repassado. Também é possível realizar queries compostas:

query.notEqualTo("model",  "Uno");
query.equalTo("year",2000);

Na consulta acima o modelo do carro tem que ser diferente de "Uno" E o ano tem que ser igual a 2000, pensando em SQL seria um operador AND as duas condições tem que ser verdadeiras.

greaterThan & lessThan

Com essa consulta podemos receber valores maiores do que o valor informado para a função, por exemplo caso queira receber os carros com o ano superior a 2000 utilizamos a seguinte função:
query.greaterThan("year",2000);

O função oposta seria lessThan ela retorna apenas os valores menores:

query.lessThan("year",2000);

limit

Por padrão as consultas tem um limite 100 resultados por query, podemos modificar esse resultado de 1 até 1000. Para aplicar esse limite utilizamos a seguinte função:

query.limit(10);

first

Em nossa query podemos resgatar apenas o primeiro resultado isso é possível com a função first:

query.greaterThan("year",2000);
query.first({
  success: function(object) {
    // caso sucesso retorna o primeiro resultado
  },
  error: function(error) {
    alert("Error: " + error.code + " " + error.message);
  }
});

skip

Podemos definir o limite de resultados e também podemos definir o índice inicial do resultado com a função skip, com essas duas funções podemos criar um esquema de paginação.

query.skip(10);

ascending & descending

Além de criar filtros, busca por valores, limitar a busca... criar paginação... Pensa que acabou? Não ainda podemos ordenar nossa consulta(por ascendente ou descendente):

//Instanciamos a query sobre "Cars"
var query = new Parse.Query(app.Cars);

//resgatamos os valores maiores que 2000
query.greaterThan("year",2000);
//definimos a ordenação
query.ascending("year");
		
query.find({
	success: function (results) {
		app.updateOutputList(results)
	},
	error: function (error) {
		alert("Error: " + error.code + " " + error.message);
	}
}); 

O resultado será o seguinte:

Outra opção com listagem de valores maiores para menores:

query.descending("year");

containedIn & notContainedIn

Caso queiramos realizar a consulta com mais de um modelo de carro, por exemplo, podemos utilizar a função containedIn ela espera um Array como parâmetro:

query.containedIn("model",["Uno", "Civic", "Gol"]);

O inverso da função containedIn seguindo o padrão é a função notContainedIn, ela retorna o resultado das palavras diferente das que constam no Array:

query.notContainedIn("model",["Uno", "Civic", "Gol"]);

exists & doesNotExist

O Parse permite criar objetos com propriedades vazias ou modificar os objetos depois de criados adicionando novos atributos, assim os objetos antigos ficam sem a informação gravada, para isso podemos utilizar a função exists para verificar se o objeto possui informação gravada na propriedade requisitada.

Vamos a um exemplo:

query.exists("year");

No código acima verificamos os itens que tem a propriedade year cadastrada, o inverso seria:

query.doesNotexists("year");

matchesKeyInQuery & doesNotMatchKeyInQuery

A matchKeyInQuery serve para comparar dois objetos de classes diferentes que possuem duas propriedades iguais, por exemplo:

var Team = Parse.Object.extend("Team");
var teamQuery = new Parse.Query(Team);
var userQuery = new Parse.Query(Parse.User);
userQuery.matchesKeyInQuery("hometown", "city", teamQuery);
userQuery.find({ 
  success: function(results) {
    // results has the list of users with a hometown team with a winning record
  }
});

No código acima temos duas class Team e Users esta classe deriva do Parse Porque é uma classe especial, vamos falar dela em um próximo post. Mas voltando a query, comparamos duas propriedades "hometown" e "city" presentes na classe User e Team quando eles forem idênticos apareceram em nossa consulta.

select

Caso queira consultar propriedades específicas com a função select passamos as keys das propriedades que queremos:

query.greaterThan("year",2000);
query.select("model", "year");

No caso da imagem acima utilizei a mesma função de exibir os resultados, por conta de utilizar o select a coluna valor veio "undefined" porque eu só escolhi duas propriedades "model" e "year".

startsWith

Na função equalsTo ele retorna valores estritamente idênticos, com a função podemos resgatar valores que iniciam com a String repassada, ela tem um funcionamento parecido com o operador LIKE do MySQL. Esta consulta também é case sensitive.

query.startsWith("model","F")

count

A função count conta "Dan", isso mesmo ela serve para contar o número de itens do resultado ela funciona da seguinte forma:

query.startsWith("model","F")
query.count({
		  success: function(count) {
			// The count request succeeded. Show the count
			alert("Carros que começam com F: " + count);
		  },
		  error: function(error) {
			// The request failed
		  }
		});

No exemplo anterior eu realizei um filtro sobre a query e a função count tem o tratamento similar a função find, falando nela podemos aplicar as duas em conjunto sem problemas. Temos outras queries aplicadas em objetos relacionais mas isso vamos abordar em um futuro. Até o próximo post Pessoal.

Categorias
Mobile Tutoriais Web

Crie aplicações na nuvem com Parse

No ano passado tive oportunidade de conferir o Parse Developer Day em San Francisco, foi o primeiro e um dos melhores eventos que conferir lá na capital da tecnologia. O Parse para que não conhecer é um serviço de cloud para aplicações e games mobile. Ele tem suporte a IOS , Android, Windows Phone, Unity e JavaScript.

Dentre os serviços que o Parse oferece, temos:

  • Cloud Storage
  • Push Notification
  • Social Connection
  • Hosting
  • Analytics
  • Cloud Code

Neste tutorial vou dar uma introdução sobre o serviço de Cloud Storage com JavaScript esse recurso possibilita salvarmos informações em um server sem precisar de uma linguagem Back-end, também podemos utilizar o JavaScript SDK junto com Phonegap e exporta-ló para diversas plataformas. O JavaScript SDK é baseado no framework BackBone.js: Isso possibilita total compatibilidade com aplicações que usar tal framework. O JavaScript SDK não possui dependência por outras libs, você pode trabalhar com Parser + JS e mais nada, mas claro que você pode utilizar outras libs. Durante o tutorial eu vou falando sobre mais recursos. Agora vamos aos passos iniciais.

Primeiro passo é realizar o cadastro no site do Parse: https://parse.com/. Não se preocupe com questões de preço eles fornecem um pacote free bem “parrudo”. O plano free tem 1 milhão de requisições/mês e 1 milhão de Pushes Notifications/mês. Se sua aplicação realiza mais de um 1 milhão de requisições mês já está na hora de ganhar algo com seu app ou repensar no esquema de requisições que ele realiza.

Você pode criar uma conta passando os dados convencionais ou vincular sua conta a uma conta do Facebook/GitHub . Com sua conta devidamente criada ele vai liberar para você um painel para você administrar suas aplicações.

Crie sua aplicação clicando no botão “+ Create New App”, só adicionar o nome do seu app:

E você já tem uma aplicação na nuvem!

Agora vamos fazer um pequeno exemplo para ler e gravar informações na nuvem. Precisamos realizar o download do JavaScript SDK:

https://parse.com/downloads/javascript/parse-js-blank/latest

Ou utilizar o link da CDN :

  

Com o Parse devidamente importado temos vamos ao código de nossa aplicação:



    
        
        
        
        Hello World Parse
    
    
        

App com Parse

No código acima temos um markup simples em HTML, no final de nosso código temos a parte que interessa que é o nosso código JavaScript. após chamar o Parse via CDN no segundo bloco de código JavaScript inicializamos o Parse com a função “Parse.initialize(“APPLICATION_ID”,”JAVASCRIPT_KEY”)” esse dois parâmetros você deve substituir pelo application ID e o JavaScript Key de sua aplicação, você vai descobrir essa informação no settings de sua aplicação, na imagem abaixo temos marcados o application id e o JavaScript Key.

Copie as duas infos e substitua em sua inicialização. Na linha seguinte criamos um objeto da classe “MyObject” ela estende do Parse.Object, ele é responsável por toda regra de armazenamento de dados. Quando chamamos a função extend o Parse irá verificar em sua base se existe a classe MyObject, caso não exista ele irá criar uma nova base de dados com a classe. Também não é necessário definir o esquema padrão para armazenamento previamente, as propriedades do elemento podem mudar, não é uma ação recomendável, mas você pode adicionar uma propriedade nova em sua classe sem precisar criar uma nova classe.

Nesse ponto eles simplificaram ao máximo, você chega a não acreditar como isso funciona. Na linha 18 ele cria uma nova instancia(test) da Classe MyObject e por fim na linha 20 ele salva essa instancia passando um objeto com as informações que serão salvas nesse momento temos um códido “like jQuery” temos mais uma função atrelada a then ela será disparada caso os dados seja gravados com sucesso, essa função também recebe um objeto no callback se inspecionarmos esse objeto teremos os seguintes valores:

Como podemos ver na imagem acima recebemos um objeto com uma série de informações que foram gravadas em sua aplicação, algumas informações úteis como o ID do objeto esse atributo é um identificador único do objeto na aplicação, também podemos verificar os atributos do objeto salvo.

Se testarmos nossa aplicação e ela retornar um alert significa que as informações foram salvas. Para verificar isso basta acessar o painel database no dashboard de sua aplicação como na imagem a seguir:

Como podemos ver a informação está salva na nuvem, agora vamos para algo mais prático no próximo exemplo eu vou montar um cadastro de TODO-LIST. O código vai ficar um pouco extenso por isso vou separar o código em dois(HTML/CSS), primeiro vamos ver o nosso HTML:



    
        
        
        
        Hello World Parse
        
    
    
        

App com Parse - TODO LIST

    Carregando...


Em seguida o nosso JavaScript, comentei algumas linhas mas também vou comentar a seguir.

  		Parse.initialize("APPLICATION_ID","JAVASCRIPT_KEY")
        	var Tasks;
        	var listOfTasks;
        	var inputTask;
        	
        	//função para inicializar nosso app
        	function initApp(){
        		Tasks = Parse.Object.extend("Tasks");
        		//showTasks busca tarefas já cadastradas em nosso app
        		showTasks();
        		//Adicionamos um lister para o nosso form
        		document.getElementById("form-task").addEventListener("submit",onSubmit);
        		
        		//Fazemos um cache dos itens que acessamos mais de uma vez em nosso app
        		listOfTasks = document.getElementById("list-tasks");
        		inputTask = document.getElementById("tarefa")
        	}
        	
        	//função resposável por consultar os dados na nuvem
        	function showTasks(){
        		//chamamos a função Query do Parse parar varer a nossa base
        		var query = new Parse.Query("Tasks");
        		
        		//a função trata a query para sucesso ou erro de nossa consulta
        		query.find({
        		success:function(results){
        			//esse bloco será executado se ocorrer tudo bem com nossa query
        			var markupList = "";
        			//os resultados vem em um array de objetos
        			//varremos o nosso array e montamos um markup
        			for(var id in results){
        				console.log("success",results[id].attributes.descricao);
        				markupList += "
  • "+ results[id].attributes.descricao +"
  • " } listOfTasks.innerHTML = markupList; }, error:function(error){ //tratamento para caso de erro console.log("error",error) } }) console.log(query); } //função para fazer o trantamento quando o usuário envia os dados do form function onSubmit(e){ var task = {}; //pegamos o valor cadastrado em nosso input task.descricao = inputTask.value; task.done = "false"; //passamos o nosso objeto para ser salvo na cloud saveTask(task); // utiliza o preventDefault para evitar do form realizar o reload da página e.preventDefault(); } //função específica para salvar as tasks na nuvem function saveTask(task){ var taskCloud = new Tasks(); taskCloud.save(task).then(function(object) { alert("Task salva com sucesso!"); showTasks(); inputTask.value = ""; }); } window.addEventListener("load", initApp)

    Nas primeiras linhas inicializamos o Parse, em seguida criamos algumas variáveis para armazenar alguns itens importantes em nosso app. A função initApp será disparada por um listener para quando nossa página for totalmente carregada. Continuando na função initApp inicializamos nossa Class Tasks. Na linha 11 chamamos a função showTasks ela será responsável por exibir nossas tasks. Nas últimas 3 linhas adicionamos um listener para nosso form, guardamos o container das tasks a ul com id list-tasks e o input que iremos digitar nossas tasks.

    Na Função “showTasks” temos um item novo, para realizamos uma consulta em nossa base utilizamos a Classe Parse.Query(“Tasks”), armazenamos ela na variável “query” com a função find pedimos para resgatar a lista de valores cadastrados, também podemos atribuir callbacks para serem chamados em caso de sucesso ou erro. Em caso de sucesso recebemos um array de objetos, tratamos o array e o exibimos em nosso ul.

    A função “onSubmit” é disparada quando o usuário tenta salvar alguma informação, utilizei o submit para utilizar o recurso de required de nosso input, nesta função pegamos o dado cadastrado no input e passamos para função saveTask.

    A “saveTask” será responsável por salvar os dados na nuvem, não comentarei essa função porque já abordamos o assunto anteriormente sobre salvar objetos na nuvem.

    Até o momento nosso app só salva os itens agora vamos implementar um função de editar os itens, mas antes vamos adicionar um CSS para sinalizar nossos itens editados. vamos adicionar o seguinte CSS:

    .list-tasks{
    	list-style:none;
    	margin:0;
    	padding:0;
    }
    
    .item-task{
    	padding: 10px;
    	border-bottom: 1px solid #ccc;
    }
    
    .item-task[data-done="true"]{
    	text-decoration:line-through;
    }
    

    Detalhe para o código acima a regra aplicada para quando o elemento com class .item-task tiver sua propriedade setada para true ele receberá uma linha sobre o texto.

    Em nossa função initApp adicionamos o evento de click para lista com:

    listOfTasks.addEventListener("click",clickList);
    

    Adicionamos o tratamento no click:

    function clickList(e) {
    				if(e.target.localName == "li"){
    					e.target.dataset.done = (e.target.dataset.done === 'true')? false : true;
    					editTask(e.target.dataset.id,e.target.dataset.done);
    				}
    			}
    

    Atribui o click no “ul” pelo fato de não implementar o click em cada filho, observo o item pai e valido se o meu target foi o “li” e pego as informações contidas no elemento e passa para a função editTask o id do elemento e o novo valor da variável done. Agora vamos ao código da função editTask:

    function editTask(id,done){
    				var query = new Parse.Query(Tasks);
    				query.get(id, {
    				  success: function(task) {
    					 task.set("done", done);
    					 task.save()
    				  },
    				  error: function(object, error) {
    					console.log("erro ao salvar o objeto", object, error)
    				  }
    				});
    			}
    

    Na função editTask instanciamos uma nova query e realizamos uma busca pelo id da task com a função get, caso a função ache o item, executamos a função success modificamos a propriedade com a função set e salvamos novamente a nossa task. Esse esquema não possui nenhum esquema de tratamento para controle de requisições e em todo o clique iremos executar um nova requisição.

    Para finalizar vamos adicionar uma função para excluir a task, na função showTasks, em cada “li” vou adicionar um button com o id de cada item vou adicionar somente a linha modificada:

    markupList += "
  • "+ results[id].attributes.descricao +"
  • ";

    Agora vou adicionar na função clickList uma condicional para quando o usuário clicar no button:

     
    function clickList(e){
    	if(e.target.localName == "li"){
    		e.target.dataset.done = (e.target.dataset.done === 'true')? false : true;
    		editTask(e.target.dataset.id,e.target.dataset.done);
    	}else if(e.target.localName == "button"){
    		e.target.disabled = "disabled";
    		e.target.innerHTML = "Removendo..."
    		removeTask(e.target.dataset.id);
    	}
    }
    

    No código acima quando o usuário clicar no button eu passo o id da task para função removeTask que possui o seguinte código:

    function removeTask(id){
    	var query = new Parse.Query(app.Tasks);
    	query.get(id, {
    		success: function(task) {
    			task.destroy({
    				success:function(task){
    					app.showTasks();
    				}
    			})
    		}
    	});
    }
    

    Na função removeTask realizamos uma Query para resgatar o objeto pelo id, quando recebermos a task chamamos a função destroy como nome sugere ela é responsável por remover o dado.

    O objetivo desse tutorial era mostrar as funções básicas do Parse e entender o seu funcionamento, os dados estão abertos para todos os usuários, ou seja todo mundo que usasse esse app compartilharia a mesma lista de task. Nos próximos posts pretendo abordar questões de usuários, vincular os dados por usuário e criar seções. Coloque o código completo no gitHub para baixar o código basta acessar o link:

    https://github.com/fellyph/Tutorial-bbUI/tree/master/parse-tutorial/www

    Referência:
    https://parse.com/docs/js_guide#javascript_guide

    Segundo post da série:

    https://www.fellyph.com.br/blog/tutoriais/realizando-consultas-na-base-de-dados-parse/