Na última conferência do Google I/O em 2014, o Google anunciou o Material Design, a sua nova linguagem de design, tanto para a web quanto para dispositivos móveis. Eles já haviam convertido grande parte de suas aplicações populares para aderir a essa nova especificação, se esforçando para fornecer uma experiência consistente a seus usuários.
Depois de conhecer o Material Design, você irá obter imediatamente um sentimento minimalista ultramoderno em relação a suas aplicações. O objetivo é criar uma linguagem visual que sintetize princípios clássicos de um bom design juntamente à inovação e tecnologia. Também será possível desenvolver um único sistema subjacente que permite uma experiência unificada em várias plataformas e em diversos tamanhos de dispositivos. O Material Design é fundado em três princípios fundamentais:
- O Material é a metáfora - inspirado pelo estudo do papel e da tinta, o material vive no espaço 3D e está fundamentado na realidade tátil (ele dá a ilusão de espaço usando sombras realistas). O material de papel deve respeitar as leis da física (ou seja, dois pedaços de papel não podem atravessar um ao outro), mas pode suplantar o mundo físico (isto é, um papel pode aumentar ou diminuir);
- Negrito, gráfico e intencional - o framework trabalha com o conceito de mistura de cores, com alta definição e relevo, além do uso constante de imagens de vários tipos e definições, bem como a presença de uma tipografia escalada e espaços em branco destinados a criar uma interface ousada e gráficos que mergulham o usuário na experiência. O botão de ação flutuante, ou FAB (Float Action Button), é um excelente exemplo desse princípio. Você já reparou alguma vez no pequeno círculo com o símbolo “+” flutuando em seu aplicativo Google na caixa de entrada? O Material Design torna muito evidente que esse é um botão importante;
- O movimento fornece um significado - o movimento é algo importante no design atual porque atrai o usuário e o prende ao efeito que está ocorrendo no espaço da tela. Além disso, a sutileza passa também a impressão de profissionalismo no que se refere ao design como um todo, trazendo mais confiança para o usuário. O ponto principal aqui é fazer uso de animações somente quando se tem um propósito de fato e sem exageros.
Veja também se isso pode de interessar:
Como o AngularJS cabe no Material Design?
O AngularJS, o framework JavaScript MVW (um acrônimo alternativo ao MVC, que significa Model-View-Whatever) do Google, aborda muitos dos desafios encontrados no desenvolvimento de aplicações single-page. Ele fornece o framework necessário para a criação de aplicações web modernas que se conectam a APIs e nunca precisam de uma página para serem atualizadas.
O Angular seria a HTML se tivesse sido projetada para aplicações. A HTML é uma grande linguagem declarativa para documentos estáticos, porém nem tanto para a criação de aplicações dinâmicas. A criação de aplicações dinâmicas com a HTML tem sido sempre um exercício de enganar o navegador para fazer coisas que não deveria fazer. Existe um par de abordagens para fazer isso:
- Biblioteca – uma coleção de funções. (Por exemplo: o jQuery)
- Framework – o código preenche dinamicamente os elementos estáticos, quando necessário.
O Angular tem uma abordagem diferente para resolver esse problema. Ao invés de lutar com o HTML dado, ele cria novas construções HTML. O Angular ensina ao navegador uma nova sintaxe HTML através de uma construção chamada “diretivas”. Além de vir com um conjunto delas internamente, também permite que criemos diretivas personalizadas, podendo escrever nossos próprios elementos HTML.
Introdução ao Angular Material
O Google está desenvolvendo ativamente o Angular Material, uma implementação do Material Design no AngularJS. O Angular Material é composto por várias peças, possui uma biblioteca de CSS para a tipografia e outros elementos, fornece uma abordagem JavaScript interessante para theming (definição de temas) e seu layout responsivo usa uma grade do tipo flex. Mas a característica mais atraente do Angular Material é a sua incrível coleção de diretivas. Existem duas maneiras básicas que podemos seguir para usar o Angular Material:
- Instalação local – podemos baixar as bibliotecas utilizando o npm, jspm ou bower em sua máquina local, incluindo-as em seu código HTML.
- Versão baseada no CDN – podemos incluir os arquivos angular-material.min.css e angular-material.js em seu código HTML diretamente via Content Delivery Network (CDN), isto é, via URL remota disponibilizada pelo site da própria fabricante.
Neste artigo trataremos de explorar alguns dos principais recursos do framework do Angular Material, explorando desde a sua instalação até a importação dos arquivos nas páginas HTML em conjunto com os recursos nativos do AngularJS. O leitor terá, ao final do mesmo, insumos suficientes para entender como ambos os frameworks se comunicam, bem como adaptá-los aos seus projetos facilmente. Além disso, trataremos de expor a integração entre a estrutura HTML com os códigos de estilo do CSS que a biblioteca do Angular Material disponibiliza por padrão para incutir o design característico do framework e seus componentes.
Instalação local
Para executar os comandos desse artigo, você precisará ter o Node.js instalado na máquina, assim poderemos fazer uso do seu gerenciador de pacotes, o npm. Use o seguinte comando npm para instalar as bibliotecas:
npm install angular-material
O npm irá baixar os arquivos do Angular Material no diretório node_modules > angular-material. Por isso, é importante selecionar um diretório para guardar seus arquivos no projeto final. Vejamos o exemplo demonstrado na Listagem 1, um Alô Mundo usando os módulos iniciais do Angular Material. Veja que ele traz apenas uma série de importações dos arquivos de CSS e JavaScript que o framework faz uso. Como de praxe, toda aplicação AngularJS deve conter no body da página o ng-app com o nome do módulo da aplicação. Esse será usado no JavaScript que inicializa o módulo do ngMaterial.
<div class="pre_xml">
<html lang="pt">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="node_modules/angular-material/angular-material.css">
</head>
<body ng-app="aloMundo" ng-cloak>
<md-toolbar class="md-warn">
<div class="md-toolbar-tools">
<h2 class="md-flex">Alô Mundo Angular Material</h2>
</div>
</md-toolbar>
<md-content flex layout-padding>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat. </p> <p>Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa
qui officia deserunt mollit anim id est laborum.</p>
</md-content>
</body>
<script src="node_modules/angular/angular.js"></script>
<script src="node_modules/angular-animate/angular-animate.js"></script>
<script src="node_modules/angular-aria/angular-aria.js"></script>
<script src="node_modules/angular-messages/angular-messages.js"></script>
<script src="node_modules/angular-material/angular-material.js"></script>
<script type="text/javascript">
angular.module('aloMundo', ['ngMaterial']);
</script>
</html>
Obteremos o resultado produzido conforme mostra a Figura 1.
Versão baseada no CDN
Você também pode implementar o mesmo exemplo usando os arquivos angular-material.min.css e angular-material.min.js do Google CDN, como mostra a Listagem 2. O resultado, por sua vez, continuará o mesmo.
<html lang="pt">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/
angular-material.min.css">
</head>
<body ng-app="aloMundo" ng-cloak>
<md-toolbar class="md-warn">
<div class="md-toolbar-tools">
<h2 class="md-flex">Alô Mundo Angular Material</h2>
</div>
</md-toolbar>
<md-content flex layout-padding>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. </p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. </p>
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum.</p>
</md-content>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular
.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular
-animate.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular
-aria.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular
-messages.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/
angular-material.min.js"></script>
<script type="text/javascript">
angular.module('aloMundo', ['ngMaterial']);
</script>
</html>
Diretivas
As diretivas são uma característica do núcleo do AngularJS. Como já falamos anteriormente, o Angular vem com várias diretivas que usamos todo o tempo, como ng-model ou ng-repeat. Elas são uma parte muito importante do framework, pois disponibilizam ações customizadas e reaproveitamento de código.
Essa biblioteca possui um conjunto de diretivas que inspiram o design Material. Diretivas do Angular Material são tags HTML que começam com md (abreviação de material design), e são bem fáceis de usar. Por exemplo, vamos dar uma olhada em um exemplo de botão simples. Um botão HTML padrão poderia ser algo como:
<button>Clique aqui!</button>
Um botão do Angular Material se parece mais ou menos com isso:
<md-button>Clique aqui!</md-button>
E isso é tudo o que precisamos para fazer um botão Material. Agora, existem várias outras opções que estão disponíveis para essa diretiva, como theming e elevação a partir da superfície, para aumentar a sua importância:
<md-button class="md-raised md-primary md-hue-1">Clique aqui!</md-button>
A classe md-raised se encarrega de incutir o efeito elevado ao botão, com uma sombra no plano de fundo, tal como vemos na Figura 2.
Serviços
Os serviços são de igual modo fundamentais para a funcionalidade do Angular, utilizados para dividir o código através da aplicação. Um serviço comum de núcleo como o $http é usado e reutilizado para chamadas de dados em aplicações do Angular. Os serviços Angular são:
- Lazily instantiated (preguiçosamente instanciados) – O Angular apenas instancia um serviço quando um componente da aplicação depende dele;
- Singletons – cada componente dependente de um serviço obtém uma referência à única instância gerada pela fábrica de serviços.
O Angular Material vem embalado com alguns serviços que fornecem funcionalidades extras para nossas aplicações. Eles também contribuem para o desempenho de algumas das diretivas. Um grande exemplo de um desses serviços é o toast. Um toast é uma pequena notificação que desliza a partir da parte inferior da tela e desaparece após alguns segundos. Veja na Listagem 3 como implementar esse tipo de serviço no JavaScript. Para o recurso funcionar, precisamos incorporar o módulo do $mdToast ao controller do AngularJS, assim ele estará disponível para instanciar a função show() e exibir a mensagem. Para o exemplo funcionar, você precisa adicionar também um ng-controller à tag body da página, uma vez que ele será mapeado pela função controller() na declaração do módulo do Angular:
<body ng-app="aloMundo" ng-controller="ToastEx" ng-cloak>
angular.module('aloMundo', ['ngMaterial']).controller('ToastEx', function($scope, $mdToast) {
$mdToast.show(
$mdToast.simple('Alô Mundo Toast!')
.position('left bottom')
.hideDelay(3000)
);
});
O resultado pode ser visualizado na Figura 3. Este exemplo mostra apenas um toast simples que aparece no canto inferior esquerdo da tela e é removido após três segundos.
Theming
O Material Design é uma linguagem visual onde os temas transmitem significados através de cores, tons e contraste. Esses temas são expressos ao longo dos componentes do aplicativo para proporcionar uma sensação mais unificada.
De acordo com as diretrizes do Material Design, você deve limitar a seleção de cores, escolhendo três tonalidades de cores da paleta primária e uma cor de destaque da paleta secundária. Ele segue essa simples diretriz usando o JavaScript para configurar o tema. Mas qual a diferença entre uma paleta e uma tonalidade? A tonalidade é uma única cor em uma paleta, já a paleta é um conjunto de tonalidades. Por exemplo uma paleta seria verde e uma tonalidade é um determinado tom de verde.
Configurando o tema
Tematizar o seu projeto é uma opção muito útil para impor um design profissional sem grandes esforços no Angular Material. No arquivo app.js (crie-o no diretório /js para organizar melhor a distribuição dos arquivos do projeto), é preciso definir suas paletas e tonalidades usando o serviço de provedor de theming, conforme podemos ver na Listagem 4. Nela, temos algumas funções importantes, a saber:
- A função config() define a função que lidará com as definições do tema a ser usado pelo Angular no design do módulo myApp. Veja que ela recebe o parâmetro mdThemingProvider, o qual se responsabiliza por fornecer os métodos da API do Material para customizar todos os componentes do framework;
- A função theme() auxilia nesse processo como um todo recebendo um parâmetro com o tipo do tema a ser usado (valor “default” configura o padrão);
- Já a função primaryPalette() define o início das nossas customizações: a cor primária será o azul, com matizes distintas definidas através dos elementos de prefixo hue, os quais se encarregam de aumentar/diminuir ligeiramente a tonalidade da cor em questão;
- Por último, as funções accentPalette(), warnPalette() e backgroundPalette() recebem as strings referentes às cores que configuram a paleta, mensagens de alerta e plano de fundo, respectivamente.
angular.module('myApp', ['ngMaterial'])
.config(function($mdThemingProvider) {
$mdThemingProvider.theme('default')
.primaryPalette('blue', {
'default': '400',
'hue-1': '100',
'hue-2': '600',
'hue-3': 'A100'
})
.accentPalette('orange')
.warnPalette('yellow')
.backgroundPalette('grey');
});
Na Listagem 5 aplicamos o tema nos componentes definindo a classe do elemento para a paleta e tonalidade desejada. Veja que precisamos referenciar cada uma das paletas criadas exatamente pelo seu nome (warn, accent, etc.). Veja o resultado de tal ação na Figura 4.
<md-button class="md-primary">Clique aqui!</md-button>
<md-button class="md-primary md-hue-1">Clique aqui!</md-button>
<md-button class="md-primary md-hue-2">Clique aqui!</md-button>
<md-button class="md-accent">ou talvez aqui</md-button>
<md-button class="md-warn">Cuidado</md-button>
Layouts
O flexbox é a maior e mais recente adição de design ao Angular Material. Trata-se do sistema nativo do CSS3 que lida com a organização automática dos elementos de uma página quando exibida em dispositivos de diferentes tamanhos, dispensando a propriedade float, antes largamente usada como solução para impor responsividade. Se você estiver familiarizado com o sistema de grids do Bootstrap, terá mais facilidade ao utilizá-lo, pois na verdade o Bootstrap já está migrando para o flexbox em seu próximo lançamento. Ele é baseado no sistema de layout de linhas e colunas (geralmente em máximo de 12), porém com muito mais recursos.
A diretiva de layout é um elemento de container usado para especificar a direção do layout para os elementos filhos. A seguir estão os valores atribuíveis à mesma:
- row – os itens são dispostos horizontalmente, com max-height = 100% e max-width igual à largura dos itens no recipiente;
- column – os itens são dispostos verticalmente, com max-width = 100% e max-height igual à altura dos itens no recipiente.
De acordo com as diretivas do design responsivo, com a forma como o layout será alterado e dependendo do tamanho da tela do dispositivo, as seguintes definições de layout definidas na Tabela 1 podem ser usadas para definir a direção nos dispositivos com larguras de diferentes dimensões.
Layout | Dimensões |
---|---|
layout-xs | width < 600px |
layout-gt-xs | width >= 600px |
layout-sm | 600px <= width < 960px |
layout-gt-sm | width >= 960px |
layout-md | 960px <= width < 1280px |
layout-gt-md | width >= 1280px |
layout-lg | 1280px <= width < 1920px |
layout-gt-lg | width >= 1920px |
layout-xl | width >= 1920px |
A Listagem 6, por sua vez, mostra como usar essa diretiva para exibir o layout padrão.
<html lang="pt">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/angular_material/
1.0.0/angular-material.min.css">
<style>
.box {
color:white;
padding:10px;
text-align:center;
border-style: inset;
}
.red {
background:red;
}
.orange {
background:orange;
}
</style>
</head>
<body ng-app="firstApplication">
<div id="layoutContainer" ng-controller="layoutController as ctrl"
style="height:100px;" ng-cloak>
<div layout="row" layout-xs="column">
<div flex class="red box">Linha 1: Item 1</div>
<div flex="20" class="orange box">Linha 1: Item 2</div>
</div>
<div layout="column" layout-xs="column">
<div flex="33" class="red box">Coluna 1: item 1</div>
<div flex="66" class="orange box">Coluna 1: item 2</div>
</div>
</div>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-animate.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-aria.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-messages.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/
angular-material.min.js"></script>
<script type="text/javascript">
angular.module('firstApplication', ['ngMaterial'])
.controller('layoutController', layoutController);
function layoutController ($scope) {
}
</script>
</html>
Veja na Figura 5 o resultado. Observe que na listagem estamos fazendo uso dos atributos layout definidos nas tags para determinar se as mesmas adotarão o estilo row ou column, conforme mencionamos. A função layoutController() está vazia já que não configuraremos nada para o controller ainda e o Angular não permite inicializar um controle sem uma função associada. Veja como o Angular Material trabalha o resultado final em detrimento das concepções de agrupamento que configurarmos.
A diretiva flex em um elemento de container é usada para personalizar o tamanho e a posição dos elementos, ela define a maneira como o elemento deve ajustar o seu tamanho em relação ao seu recipiente pai e aos outros elementos dentro do container. A seguir estão os valores atribuíveis:
- Múltiplos de 5. Ex: 5, 10, 15 ... 100
- 33 – equivalente a 33%
- 66 – equivalente a 66%
A Listagem 7 mostra o uso dessa diretiva demonstrando como atribuir o flex às nossas páginas.
<html lang="pt">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/
angular-material.min.css">
<style>
.box {
color:white;
padding:10px;
text-align:center;
border-style: inset;
}
.red {
background:red;
}
.orange {
background:orange;
}
</style>
</head>
<body ng-app="firstApplication">
<div id="layoutContainer" ng-controller="layoutController as ctrl" layout="row"
style="height:100px;"
ng-cloak layout-wrap>
<div flex="30" class="red box">
[flex="30"]
</div>
<div flex="45" class="orange box">
[flex="45"]
</div>
<div flex="25" class="red box">
[flex="25"]
</div>
<div flex="33" class="red box">
[flex="33"]
</div>
<div flex="66" class="orange box">
[flex="66"]
</div>
<div flex="50" class="orange box">
[flex="50"]
</div>
<div flex class="red box">
[flex]
</div>
</div>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-animate.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-aria.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-messages.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/
angular-material.min.js"></script>
<script type="text/javascript">
angular
.module('firstApplication', ['ngMaterial'])
.controller('layoutController', layoutController);
function layoutController ($scope) {
}
</script>
</html>
Veja na Figura 6 o resultado. Observe que as definições se mantiveram em relação à configuração do controller e das diretivas padrão do Angular, porém apenas acrescentamos o atributo flex aos nossos elementos de
definindo seus valores divididos tal como demonstramos. Bem simples, não acha?A seguir vamos conhecer algumas das melhores diretivas do Angular Material, onde teremos alguns exemplos práticos de como aplicá-las em nosso código.
Autocomplete
O Autocomplete fornece uma experiência agradável ao usuário, o auxiliando na escolha de uma opção. É o que faz o motor de busca do Google, o melhor do mercado. A diretiva Autocomplete adiciona essa funcionalidade ao seu aplicativo através da exibição do preenchimento das palavras via sugestões à medida em que o usuário as escreve. Mas a melhor parte dessa diretiva é a personalização. Ao preencher o seu autocomplete com o atributo md-item-template você pode dar mais sentido às sugestões. Por exemplo, se um usuário estava à procura de nomes em uma empresa, o autocomplete poderia mostrar os nomes correspondentes com a imagem da sua empresa (assim como vemos ao buscar alguém no Facebook ou no LinkedIn), dando ao usuário uma experiência mais robusta. A tag > pode ser usada para fornecer resultados de pesquisa a partir de dados locais, remotos ou em cache através do código sources.md-autocomplete. Após a primeira chamada, ele usa os resultados em cache para eliminar solicitações desnecessárias do servidor bem como lógicas de pesquisa que podem ser desativadas.
Vejamos uma série de atributos e suas descrições que usaremos no nosso exemplo de autocomplete:
- md-no-cache - desativar o cache interno que acontece no autocomplete;
- md-items - uma expressão que recebe a lista de itens a serem iterados no componente de pesquisa;
- md-selected-item-change - uma expressão a ser executada cada vez que um novo item é selecionado;
- md-search-text-change - uma expressão a ser executada ao atualizar os textos da pesquisa;
- md-search-text - um objeto model do Angular para vincular ao texto de consulta da pesquisa, caso deseje usá-lo mais à frente dentro do código JavaScript do AngularJS;
- md-selected-item - um model do Angular para vincular ao item selecionado, caso deseje usá-lo mais à frente dentro do código JavaScript do AngularJS;
- md-item-text - uma expressão que irá converter o seu objeto em uma string única;
- ng-disabled - determina sim ou não para desativar o campo de entrada;
- placeholder - texto que será exibido no campo de input sob a forma de dica;
- md-min-length: especifica o comprimento mínimo do texto antes do preenchimento automático das sugestões.
O exemplo da Listagem 8 (retirado da seção “Demos” da documentação oficial do Angular Material – vide seção Links) mostra como usar a diretiva md-autocomplete. Nele, veremos como criar um exemplo de caixa de pesquisa que buscará por um dos estados brasileiros (configurados em uma lista estática dentro do código JavaScript, criado mais adiante), com opções para habilitar um efeito de carregamento com uma barra de progresso, usando o recurso nativo de cache do próprio Angular, além habilitar ou desabilitar o campo de busca em si. Vejamos algumas de suas principais partes:
- As definições de imports, diretivas de aplicação e controller se mantêm aqui nessa listagem.
- Na linha 11 declaramos a diretiva com vários parâmetros configurados para a mesma, a saber:
- ng-disabled: configura o estado de campo habilitado ou não, tal valor será pego da checkbox mais abaixo;
- md-no-cache: define se os dados do campo de busca serão salvos em cache no browser ou não;
- md-selected-item: define qual o item que foi selecionado e, portanto, o value do campo em si;
- md-search-text-change: define a função JavaScript a ser chamada quando o texto do campo for alterado (a mesma está definida na linha 29 da Listagem 9 e apenas imprime em console o valor atual);
- md-search-text: define o valor do texto no campo de busca;
- md-selected-item-change: define a função JavaScript que será chamada quando um novo item for selecionado entre as demais opções na lista (cuja declaração foi feita na linha 32 da Listagem 9 e também só imprime o valor no console);
- md-items: define a iteração sobre os itens da lista que será retornada pela função queryBusca() a serem disponibilizados no mesmo campo;
- md-item-text: define o texto de cada item a ser exibido na listagem de opções;
- md-min-length: define o tamanho mínimo da lista de itens a ser exibida;
- placeholder: a dica que aparecerá no campo antes que o usuário digite qualquer coisa.
- Na linha 22 definimos um elemento que usaremos para dizer ao AngularJS qual a fonte de dados e como ele deve exibir cada elemento da listagem;
- Na linha 25 definimos via tag o texto padrão a ser exibido quando nenhum item for encontrado para o texto informado no campo de busca;
- Nas linhas 31 a 34 definimos as opções dos checkboxes com suas respectivas ações para exibir ou não a barra de progresso que vimos, desabilitar o cache ou o respectivo campo de busca;
<html lang="pt">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/
angular_material/1.0.0/angular-material.min.css">
</head>
<body ng-app="estadosApp" ng-cloak>
<div ng-controller="autoCompleteController as estApp" layout="column" ng-cloak>
<md-content class="md-padding">
<form ng-submit="$event.preventDefault()">
<md-autocomplete
ng-disabled="estApp.isDisabled"
md-no-cache="estApp.noCache"
md-selected-item="estApp.selectedItem"
md-search-text-change="estApp.searchTextChange(estApp.searchText)"
md-search-text="estApp.searchText"
md-selected-item-change="estApp.selectedItemChange(item)"
md-items="item in estApp.queryBusca(estApp.searchText)"
md-item-text="item.display"
md-min-length="0"
placeholder="Digite o nome do estado...">
<md-item-template>
<span md-highlight-text="estApp.searchText" md-highlight-
flags="^i">{{item.display}}</span>
</md-item-template>
<md-not-found>
Nenhum resultado encontrado para "{{estApp.searchText}}".
<a ng-click="estApp.novoEstado(estApp.searchText)">Criar um novo!</a>
</md-not-found>
</md-autocomplete>
<br/>
<md-checkbox ng-model="estApp.simularQuery">Exibir barra de progresso?
</md-checkbox>
<md-checkbox ng-model="estApp.noCache">Desabilitar cache?</md-checkbox>
<md-checkbox ng-model="estApp.isDisabled">Desabilitar campo de busca?
</md-checkbox>
</form>
</md-content>
</div>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-animate.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-aria.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-messages.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/
angular-material.min.js"></script>
<script type="text/javascript">
// Código do autocomplete
</script>
</html>
A segunda parte do código encontra-se na Listagem 9. O mesmo trecho deve ser inserido entre as tags das linhas 44 e 46 da listagem anterior, pois trata-se do código que dinamizará a busca via Angular Material.
Na linha 4 temos a função que mapeia os principais fluxos de execução. Nas primeiras linhas declaramos as variáveis a serem usadas ao longo do código (note que a função novoEstado não tem implementação, é apenas uma referência para que possamos implementar essa adição dinâmica em um exemplo mais formal). A função queryBusca (linha 17) se encarrega de receber o objeto de query e fazer uma filtragem inicial para saber se já temos no nosso próprio cache (variável self). Veja que o código faz uso também de promises (linha 24) para lidar com o processamento assíncrono da listagem de itens. As promises disponibilizam meios de processamento assíncrono dentro do JavaScript que, por padrão, não suporta tal recurso. O Angular faz uso delas para tornar o componente de autocomplete assíncrono, uma vez que não é possível saber quanto tempo pode demorar uma requisição a uma lista de valores remota a ser exibida pelo mesmo. A função loadEstados (linha 36), por sua vez, se encarrega de criar uma string com todos os valores separados por vírgula para que possamos quebrá-la (split) e mapeá-la (map) com chaves-valores. A função createFilterFor (linha 46) mascara todos os valores para minúsculo para que possamos assim não ter diferenças entre o que está na lista de estados e o que está de fato na tela.
angular.module('estadosApp', ['ngMaterial'])
.controller('autoCompleteController', autoCompleteController);
function autoCompleteController ($timeout, $q, $log) {
var self = this;
self.simularQuery = false;
self.isDisabled = false;
// lista de estados a serem exibidos
self.estados = loadEstados();
self.queryBusca = queryBusca;
self.selectedItemChange = selectedItemChange;
self.searchTextChange = searchTextChange;
self.novoEstado = novoEstado;
function novoEstado(state) {
alert("A implementar...");
}
function queryBusca (query) {
var results = query ? self.estados.filter( createFilterFor(query) ) :
self.estados, deferred;
if (self.simularQuery) {
deferred = $q.defer();
$timeout(function () {
deferred.resolve( results );
}, Math.random() * 1000, false);
return deferred.promise;
} else {
return results;
}
}
function searchTextChange(text) {
$log.info('Texto modificado para: ' + text);
}
function selectedItemChange(item) {
$log.info('Item modificado para: ' + JSON.stringify(item));
}
// constrói uma lista de estados como map de pares de chave-valor
function loadEstados() {
var allEstados = 'Acre (AC), Alagoas (AL), Amapá (AP), Amazonas (AM), Bahia (BA),
Ceará (CE), Distrito Federal (DF), Espírito Santo (ES), Goiás (GO), Maranhão (MA),
Mato Grosso (MT), Mato Grosso do Sul (MS), Minas Gerais (MG), Pará (PA) ,
Paraíba (PB), Paraná (PR), Pernambuco (PE), Piauí (PI), Rio de Janeiro (RJ),
Rio Grande do Norte (RN), Rio Grande do Sul (RS), Rondônia (RO), Roraima (RR),
Santa Catarina (SC), São Paulo (SP), Sergipe (SE), Tocantins (TO)';
return allEstados.split(/, +/g).map( function (state) {
return {
value: state.toLowerCase(),
display: state
};
});
}
// filtra pela query de busca
function createFilterFor(query) {
var lowercaseQuery = angular.lowercase(query);
return function filterFn(state) {
return (state.value.indexOf(lowercaseQuery) === 0);
};
}
}
Veja na Figura 7 como ficará nosso código final na tela.
O fato de o md-autocomplete exibir resultados em cache ao executar uma consulta permite que, após a primeira chamada, ele use os resultados em cache para eliminar solicitações ou lógicas de servidor desnecessárias, trazendo os resultados de imediato, como podemos ver na Figura 8.
Ele também pode ser desativado como mostra a Figura 9, onde o box fica bloqueado para uma nova digitação.
Botom Sheet
O botom sheet é um pequeno menu que desliza para cima a partir da parte inferior da tela, cobrindo o conteúdo e mantendo o foco. Originalmente destinado a ser utilizado exclusivamente para dispositivos móveis, o botom sheet vem ganhando popularidade em telas maiores. Para usá-lo, temos que criar um template com a diretiva md-bottom-sheet, que contém tanto uma classe md-grid como uma md-list, as quais são usadas para criar elementos de grade e lista no layout do Material, respectivamente. Em seguida, chamá-lo com o serviço do botom sheet: o $mdBottomSheet.show(). A Listagem 10 traz um exemplo prático de uso para essa diretiva. Veja que a nível de HTML nenhuma mudança significativa foi efetuada, apenas no JavaScript. A função abrirBottomSheet() se encarregou de receber o módulo do $mdBottomSheet e chamar a sua função show(), passando o template com uma string em HTML a ser exibida (veja que é nela onde devem estar inseridas as tags ).
<html lang="pt">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/angular_material/
1.0.0/angular-material.min.css">
</head>
<body ng-app="bottomSheetApp">
<div ng-controller="bottomSheetController as ctrl" layout="column">
<md-content class="md-padding">
<form ng-submit="$event.preventDefault()">
<md-button class="md-raised md-primary" ng-click="abrirBottomSheet()">
Abrir Bottom Sheet!
</md-button>
</form>
</md-content>
</div>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-animate.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-aria.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-messages.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/
angular-material.min.js"></script>
<script type="text/javascript">
angular
.module('bottomSheetApp', ['ngMaterial'])
.controller('bottomSheetController', bottomSheetController);
function bottomSheetController ($scope, $mdBottomSheet) {
$scope.abrirBottomSheet = function() {
$mdBottomSheet.show({
template: '<md-bottom-sheet>Aprender <b>
Angular Material</b>!!</md-bottom-sheet>'
});
};
}
</script>
</html>
A Figura 10 mostra como ficará o nosso botão criado através dessa diretiva, já na Figura 11 podemos ver como ficará a tela após o click no botão. Veja que a nova tela cobre o antigo conteúdo, mas ainda o exibe em plano de fundo.
Input
O md-input-container é um componente recipiente para conter qualquer elemento ou como um filho. O md-input-container também suporta a manipulação de erros usando o padrão do AngularJS, via diretivas ng-messages, e anima as mensagens usando os eventos de ngEnter/ngLeave ou os eventos de ngShow/ngHide.
Insira em alguns dos seus campos de texto a diretiva de input. Envolva sua tag de input com a diretiva md-input-container e você verá isso acontecer. A diretiva de input lida com um tipo de elemento no HTML que sempre foi tido como entediante, mas no fim proporciona uma agradável surpresa pela capacidade que o Angular Material fornece.
Como em várias diretivas apresentadas, o input também possui alguns atributos, a seguir iremos conhecer alguns e, logo mais na Listagem 11, iremos criar um formulário com essa diretiva.
- md-maxlength - limita o número máximo de caracteres permitidos na entrada. Se isso for especificado, um contador de caracteres será exibido na parte inferior da entrada. O objetivo do md-maxlength é exatamente mostrar o contador do comprimento máximo do texto. Se você só precisa de uma validação simples, pode usar os atributos ng-maxlength ou maxlenght;
- aria-label - é necessária quando nenhuma label está presente (uma mensagem de aviso será registrada no console quando isso ocorrer);
- placeholder - uma alternativa ao aria-label quando a label não está presente. O texto do placeholder é copiado para o atributo aria-label;
- md-no-autogrow: quando presente, áreas de texto não vão crescer automaticamente;
- md-detect-hidden - quando presente, as áreas de texto serão dimensionadas adequadamente quando forem reveladas após terem se escondido. Esse é desativado por padrão em vista de motivos de desempenho, pois garante um refluxo ao digerir cada ciclo.
<html lang="pt">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/angular_material
/1.0.0/angular-material.min.css">
</head>
<body ng-app="inputApp">
<div id="inputContainer" ng-controller="inputController as ctrl" ng-cloak>
<md-content layout-padding>
<form name="formProjeto">
<md-input-container class="md-block">
<label>Usuário</label>
<input required name="usuario" ng-model="project.usuario">
<div ng-messages="formProjeto.usuario.$error">
<div ng-message="required">Obrigatório.</div>
</div>
</md-input-container>
<md-input-container class="md-block">
<label>Email</label>
<input required type="email" name="emailUsuario" ng-model="
.emailUsuario"
minlength="10" maxlength="100" ng-pattern="/^.+@.+\..+$/" />
<div ng-messages="formProjeto.emailUsuario.$error" role="alert">
<div ng-message-exp="['required', 'minlength', 'maxlength',
'pattern']">
Seu email deve conter entre 10 e 100 caracteres de tamanho e
deve ser um endereço válido.
</div>
</div>
</md-input-container>
<md-input-container class="md-block">
<label>Comentários</label>
<input md-maxlength="300" required name="comentarios"
ng-model="project.comentarios">
<div ng-messages="formProjeto.comentarios.$error">
<div ng-message="required">Obrigatório.</div>
<div ng-message="md-maxlength">Os comentários devem ter menos de
300 caracteres.</div>
</div>
</md-input-container>
</form>
</md-content>
</div>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular
.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular
-animate.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular
-aria.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular
-messages.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/
angular-material.min.js"></script>
<script type="text/javascript">
angular
.module('inputApp', ['ngMaterial'])
.controller('inputController', inputController);
function inputController($scope) {
$scope.project = {
comentarios: 'Comentários',
};
}
</script>
</html>
Note que o próprio AngularJS já se encarrega de validar os atributos base da HTML5, como o required (para campo obrigatório) e o maxlength, por exemplo. Para cada campo devemos definir uma div com a diretiva ng-messages, que irá receber também um role (um papel específico dentro do Angular) referente ao tipo de mensagem que ali será exibida (o valor alert confere a cor amarela bem como configurações específicas do Angular Material). Na tag interna de diretiva ng-message-exp definimos quais validações deverão ser consideradas por padrão naquele campo em si e dentro dela a mensagem que deverá ser exibida caso alguma retorne falso (linhas 21 a 24). Caso seja necessário exibir uma mensagem para cada regra de validação, podemos definir várias divs com uma ng-message para cada uma (linhas 31 e 32).
No JavaScript da linha 51, contudo, apenas definimos o texto que já deve vir preenchido no campo por padrão.
As Figuras 12 a 14 mostram como ficará o formulário que acabamos de implementar.
Grid Lists
São uma alternativa às listas padrão. Uma grid list é melhor para a apresentação de imagens e é otimizada para uma melhor compreensão visual dos elementos. Ela funciona através da fixação de diferentes tiles (blocos de divs HTML) feitos sob medida em uma grid, dando uma sensação eclética à implementação. Vejamos na Listagem 12 um exemplo simples fornecido pela documentação do Angular Material de tilesets (conjunto de blocos tiles) que geram vários quadros dinâmicos na página, com cores aleatórias. As propriedades:
- md-cols* definem valores para as larguras dos tiles;
- md-row* definem valores para as alturas dos tiles;
- md-gutter* definem valores para as margens entre cada um dos tiles;
- md-colspan* definem quantas colunas cada tile ocupará na página (semelhante à abordagem com tabelas).
No JavaScript apenas criamos um vetor com várias cores em formato hexadecimal e as randomizamos via função randomColor (linha 42), da mesma forma que definimos rowspan’s e colspan’s distintos e aleatórios via função randomSpan (linha 46). Veja também que a função colorTiles da linha 29 faz uso dessas funções randômicas para gerar e atribuir as cores/dimensões referentes a cada tile. Se o leitor desejar fazer testes com mais tiles basta incrementar o valor que está sendo iterado no loop for.
O tamanho do tile e layout, então, corresponde ao tamanho da tela conforme ilustrado na Figura 15.
<html lang="pt">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/
angular-material.min.css">
</head>
<body ng-app="gridApp">
<div ng-controller="GridListCtrl as appCtrl" ng-cloak=""
class="gridListdemoResponsiveUsage">
<md-content layout-padding="">
<md-grid-list md-cols-gt-md="12" md-cols-sm="3" md-cols-md="8"
md-row-height-gt-md="1:1"
md-row-height="4:3" md-gutter-gt-md="16px" md-gutter-gt-sm="8px"
md-gutter="4px">
<md-grid-tile ng-repeat="tile in appCtrl.colorTiles" ng-style="{
'background': tile.color
}" md-colspan-gt-sm="{{tile.colspan}}" md-rowspan-gt-sm="{{tile.rowspan}}">
</md-grid-tile>
</md-grid-list>
</md-content>
</div>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-animate.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-aria.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/
angular-messages.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/
angular-material.min.js"></script>
<script type="text/javascript">angular
.module('gridApp', ['ngMaterial'])
.controller('GridListCtrl', function ($scope) {
var COLORS = ['#ffebee', '#ffcdd2', '#ef9a9a', '#e57373', '#ef5350', '#f44336',
'#e53935', '#d32f2f', '#c62828', '#b71c1c', '#ff8a80', '#ff5252', '#ff1744',
'#d50000', '#f8bbd0', '#f48fb1', '#f06292', '#ec407a', '#e91e63',
'#d81b60', '#c2185b', '#ad1457', '#880e4f', '#ff80ab',
'#ff4081', '#f50057', '#c51162', '#e1bee7', '#ce93d8', '#ba68c8',
'#ab47bc', '#9c27b0', '#8e24aa', '#7b1fa2', '#4a148c',
'#ea80fc', '#e040fb', '#d500f9', '#aa00ff', '#ede7f6', '#d1c4e9',
'#b39ddb', '#9575cd', '#7e57c2', '#673ab7', '#5e35b1',
'#4527a0', '#311b92', '#b388ff', '#7c4dff', '#651fff', '#6200ea',
'#c5cae9', '#9fa8da', '#7986cb', '#5c6bc0', '#3f51b5',
'#3949ab', '#303f9f', '#283593', '#1a237e', '#8c9eff', '#536dfe',
'#3d5afe', '#304ffe', '#e3f2fd', '#bbdefb', '#90caf9',
'#64b5f6', '#42a5f5', '#2196f3', '#1e88e5', '#1976d2', '#1565c0',
'#0d47a1', '#82b1ff', '#448aff', '#2979ff', '#2962ff',
'#b3e5fc', '#81d4fa', '#4fc3f7', '#29b6f6', '#03a9f4', '#039be5',
'#0288d1', '#0277bd', '#01579b', '#80d8ff', '#40c4ff',
'#00b0ff', '#0091ea', '#e0f7fa', '#b2ebf2', '#80deea', '#4dd0e1',
'#26c6da', '#00bcd4', '#00acc1', '#0097a7', '#00838f',
'#006064', '#84ffff', '#18ffff', '#00e5ff', '#00b8d4', '#e0f2f1',
'#b2dfdb', '#80cbc4', '#4db6ac', '#26a69a', '#009688',
'#00897b', '#00796b', '#00695c', '#a7ffeb', '#64ffda', '#1de9b6',
'#00bfa5', '#e8f5e9', '#c8e6c9', '#a5d6a7', '#81c784',
'#66bb6a', '#4caf50', '#43a047', '#388e3c', '#2e7d32', '#1b5e20',
'#b9f6ca', '#69f0ae', '#00e676', '#00c853', '#f1f8e9',
'#dcedc8', '#c5e1a5', '#aed581', '#9ccc65', '#8bc34a', '#7cb342',
'#689f38', '#558b2f', '#33691e', '#ccff90', '#b2ff59',
'#76ff03', '#64dd17', '#f9fbe7', '#f0f4c3', '#e6ee9c', '#dce775',
'#d4e157', '#cddc39', '#c0ca33', '#afb42b', '#9e9d24',
'#827717', '#f4ff81', '#eeff41', '#c6ff00', '#aeea00', '#fffde7',
'#fff9c4', '#fff59d', '#fff176', '#ffee58', '#ffeb3b',
'#fdd835', '#fbc02d', '#f9a825', '#f57f17', '#ffff8d', '#ffff00',
'#ffea00', '#ffd600', '#fff8e1', '#ffecb3', '#ffe082',
'#ffd54f', '#ffca28', '#ffc107', '#ffb300', '#ffa000', '#ff8f00',
'#ff6f00', '#ffe57f', '#ffd740', '#ffc400', '#ffab00',
'#fff3e0', '#ffe0b2', '#ffcc80', '#ffb74d', '#ffa726', '#ff9800',
'#fb8c00', '#f57c00', '#ef6c00', '#e65100', '#ffd180',
'#ffab40', '#ff9100', '#ff6d00', '#fbe9e7', '#ffccbc', '#ffab91',
'#ff8a65', '#ff7043', '#ff5722', '#f4511e', '#e64a19',
'#d84315', '#bf360c', '#ff9e80', '#ff6e40', '#ff3d00', '#dd2c00',
'#d7ccc8', '#bcaaa4', '#795548', '#d7ccc8', '#bcaaa4',
'#8d6e63', '#eceff1', '#cfd8dc', '#b0bec5', '#90a4ae', '#78909c',
'#607d8b', '#546e7a', '#cfd8dc', '#b0bec5', '#78909c'];
this.colorTiles = (function () {
var tiles = [];
for (var i = 0; i < 46; i++) {
tiles.push({
color: randomColor(),
colspan: randomSpan(),
rowspan: randomSpan()
});
}
return tiles;
})();
function randomColor() {
return COLORS[Math.floor(Math.random() * COLORS.length)];
}
function randomSpan() {
var r = Math.random();
if (r < 0.8) {
return 1;
} else if (r < 0.9) {
return 2;
} else {
return 3;
}
}
});
</script>
</html>
O Google está convertendo suas aplicações mais populares para o Material Design. Agora eles estão trabalhando no desenvolvimento do Angular Material, uma implementação do Material Design escrito em AngularJS acrescentando sempre novos recursos e funcionalidades. Dessa forma você terá sempre em mãos o poder desses dois poderosos frameworks: um totalmente focado nos componentes/estilos da página e outro na dinamização desses de modo a não necessitarmos de todo um aparato de servidor como era feito antigamente.
Podemos notar nesse artigo que o Material Design e o Angular Material são uma fantástica maneira de aplicar as especificações de design para suas aplicações single-page. Se você quiser criar seu próprio aplicativo com o Angular Material, não perca tempo começando do zero. Em vez disso, comece com um aplicativo totalmente funcional conforme as demonstrações das diretivas configuradas. E claro, você pode aprender tudo sobre Angular Material visitando a documentação oficial na seção Links.