como dividir os dados ng-repeat com três colunas usando o bootstrap


122

Estou usando ng-repeat com meu código e tenho um número de caixa de texto com base em ng-repeat. Eu quero alinhar a caixa de texto com três colunas.

esse é o meu código

<div class="control-group" ng-repeat="oneExt in configAddr.ext">
    {{$index+1}}. 
    <input type="text" name="macAdr{{$index+1}}" 
           id="macAddress" ng-model="oneExt.newValue" value=""/>
</div>

1
supondo que você tenha nove (9) que deseja 1,2,3 em uma linha ou em uma coluna?
Gabriele Petrioli

Respostas:


254

A abordagem mais confiável e tecnicamente correta é transformar os dados no controlador. Aqui está uma função e uso simples de um pedaço.

function chunk(arr, size) {
  var newArr = [];
  for (var i=0; i<arr.length; i+=size) {
    newArr.push(arr.slice(i, i+size));
  }
  return newArr;
}

$scope.chunkedData = chunk(myData, 3);

Então, sua visão seria assim:

<div class="row" ng-repeat="rows in chunkedData">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>

Se você tiver alguma entrada dentro do ng-repeat, provavelmente desejará desmarcar / juntar as matrizes à medida que os dados são modificados ou enviados. Veja como isso ficaria em um $watch, para que os dados estejam sempre disponíveis no formato mesclado original:

$scope.$watch('chunkedData', function(val) {
  $scope.data = [].concat.apply([], val);
}, true); // deep watch

Muitas pessoas preferem fazer isso na visualização com um filtro. Isso é possível, mas só deve ser usado para fins de exibição! Se você adicionar entradas nessa exibição filtrada, isso causará problemas que podem ser resolvidos, mas não são bonitos ou confiáveis .

O problema com esse filtro é que ele retorna novas matrizes aninhadas a cada vez. Angular está observando o valor de retorno do filtro. Na primeira vez em que o filtro é executado, o Angular conhece o valor e o executa novamente para garantir a alteração. Se os dois valores forem iguais, o ciclo será encerrado. Caso contrário, o filtro será acionado repetidamente até que sejam iguais, ou o Angular perceberá que um loop de digestão infinito está ocorrendo e será desligado. Como novas matrizes / objetos aninhados não foram rastreados anteriormente pelo Angular, ele sempre vê o valor de retorno diferente do anterior. Para corrigir esses filtros "instáveis", você deve agrupar o filtro em uma memoizefunção. lodashtem uma memoizefunção e a versão mais recente do lodash também inclui uma chunkfunção, para que possamos criar esse filtro de maneira muito simples usandonpmmódulos e compilando o script com browserifyou webpack.

Lembre-se: somente exibição! Filtre no controlador se você estiver usando entradas!

Instale o lodash:

npm install lodash-node

Crie o filtro:

var chunk = require('lodash-node/modern/array/chunk');
var memoize = require('lodash-node/modern/function/memoize');

angular.module('myModule', [])
.filter('chunk', function() {
  return memoize(chunk);
});

E aqui está uma amostra com este filtro:

<div ng-repeat="row in ['a','b','c','d','e','f'] | chunk:3">
  <div class="column" ng-repeat="item in row">
    {{($parent.$index*row.length)+$index+1}}. {{item}}
  </div>
</div>

Encomende itens verticalmente

1  4
2  5
3  6

Em relação às colunas verticais (listar de cima para baixo) em vez de horizontais (da esquerda para a direita), a implementação exata depende da semântica desejada. Listas que se dividem desigualmente podem ser distribuídas de maneiras diferentes. Aqui está uma maneira:

<div ng-repeat="row in columns">
  <div class="column" ng-repeat="item in row">
    {{item}}
  </div>
</div>
var data = ['a','b','c','d','e','f','g'];
$scope.columns = columnize(data, 3);
function columnize(input, cols) {
  var arr = [];
  for(i = 0; i < input.length; i++) {
    var colIdx = i % cols;
    arr[colIdx] = arr[colIdx] || [];
    arr[colIdx].push(input[i]);
  }
  return arr;
}

No entanto, a maneira mais direta e simples de obter colunas é usar colunas CSS :

.columns {
  columns: 3;
}
<div class="columns">
  <div ng-repeat="item in ['a','b','c','d','e','f','g']">
    {{item}}
  </div>
</div>

Obrigado pela ajuda. Porém, o uso do carrossel com o ui-chart em angular não pode desenhar o gráfico bt carrossel funciona bem. meu código é <intervalo de carrossel = "myInterval"> <slide ng-repeat = "etapa em mileStone | split: 4" active = "slide.active"> <div class = "col-md-3" ng-repeat = " milestone1 no marco "> <div style =" alinhamento de texto: centro; "> <div ui-chart =" data "chart-options =" ​​chartOptions "> </div> </div> </div> </ slide > </carousel> E eu inicializei minha variável de escopo como dados JSON de marcos.
Kuppu # 9/14

1
@ m59 Neste momento, esta função exibe dados na primeira linha a b csegunda linha d e f. Posso exibir a b na primeira coluna c dna segunda e e fna terceira coluna? Graças

1
@ Ifch0o1 Você estaria correto se fosse [].concat.call([], val). Você deve considerar o que applyfaz. valé uma matriz de matrizes e queremos passar cada uma dessas matrizes como argumentos para concat. Eu acho que você está se concentrando no contexto de [], que não é o ponto aqui. O que queremos é [].concat(val[1], val[2], val[3], etc), por isso precisamosapply([], val)
M59

1
@bahadir Atualizei a resposta para ficar mais claro. Veja o que acontece se você criar um filtro que retorne apenas []vs [[]]e [{}]. A matriz / objeto aninhado faz com que o Angular veja um resultado diferente a cada filtere continua repetindo, procurando que o resultado permaneça o mesmo (estabilizar).
M59

1
Você está realmente enviando spam aos comentários :) Experimente você mesmo. var x = [1,2,3]; var y = [1,2,3]; console.log(x === y);A verificação profunda significa a stringização e a comparação de strings, o que é arriscado porque um objeto pode não ser passível de streabilidade ou a verificação recursiva de cada propriedade individualmente. Acho que a Angular poderia implementar isso para filtros, mas tenho certeza de que há uma boa razão para que eles não o tenham feito. Provavelmente porque eles pretendiam principalmente filtros para modificação simples de um conjunto de dados, não uma revisão completa com alterações na estrutura.
M59

64

Esta solução é muito simples:

JSON:

[{id:"1",name:"testA"},{id:"2",name:"test B"},{id:"3",name:"test C"},{id:"4",name:"test D"},{id:"5",name:"test E"}]

HTML:

<div ng-controller="MyCtrl">
  <table>
    <tr ng-repeat="item in items" ng-switch on="$index % 3">
      <td ng-switch-when="0">
        {{items[$index].id}} {{items[$index].name}}
      </td>
      <td ng-switch-when="0">
        <span ng-show="items[$index+1]">
          {{items[$index+1].id}} {{items[$index+1].name}}
        </span>
      </td>
      <td ng-switch-when="0">
        <span ng-show="items[$index+2]">    
          {{items[$index+2].id}} {{items[$index+2].name}}
        </span>
      </td>
    </tr>
  </table>
</div>

DEMO em FIDDLE

insira a descrição da imagem aqui


3
Melhor resposta! E faça o que for necessário com diretivas nativas! Congratz!
Renan Franca

1
Esta solução funciona, mas, tanto quanto as melhores práticas ir, você provavelmente não deveria estar usando tabelas para definir o layout em sua marcação :)
Dave Cooper

2
Como o @DaveCooper menciona, aqui está um layout alternativo sem tabelas: plnkr.co/edit/81oj8mHaNyfQpCmRBWO4?p=preview . Considere tabelas para dados tabulados, mas, caso contrário, esse é um layout melhor para dispositivos móveis e tamanhos de tela variáveis.
Zymotik

1
Esta é de longe a melhor solução. Leva tempo para encontrar todas as peculiaridades quando se utiliza um novo quadro, mas ng-switch é uma dádiva de Deus
Zoran

Se você estiver indo para divs em vez de uma tabela, poderá colocar ng-switch-when="0"em uma div externa uma vez em vez de em todos os 3 itens.
Hans Wouters 28/03

19

Uma solução limpa e adaptável que não requer manipulação de dados :

O HTML:

<div class="control-group" class="label"
    ng-repeat="oneExt in configAddr.ext"
    ng-class="{'new-row': startNewRow($index, columnBreak) }">
    {{$index+1}}. 
    <input type="text" name="macAdr{{$index+1}}" 
       id="macAddress{{$index}}" ng-model="oneExt.newValue" />
</div>

O CSS:

.label {
    float: left;
    text-align: left;
}
.new-row {
    clear: left;
}

O JavaScript:

$scope.columnBreak = 3; //Max number of colunms
$scope.startNewRow = function (index, count) {
    return ((index) % count) === 0;
};

Essa é uma solução simples para renderizar dados em linhas e colunas dinamicamente, sem a necessidade de manipular a matriz de dados que você está tentando exibir. Além disso, se você tentar redimensionar a janela do navegador, poderá ver a grade se adaptar dinamicamente ao tamanho da tela / div.

(Também adicionei um sufixo {{index}} ao seu ID, pois ng-repeat tentará criar vários elementos com o mesmo ID, se você não o fizer.

Um exemplo de trabalho semelhante


1
Acredito que o OP queria a marcação para linhas / colunas do Twitter Bootstrap, mas isso pode ser útil para alguém. No entanto, você precisa colocar o código e a explicação na sua resposta. As respostas apenas ao link serão removidas.
m59 24/01

@ m59 Resposta atualizada. Obrigado pela sugestão
Cumulo Nimbus

1
Big +1 para não requer manipulação de dados . Isso funcionou perfeitamente para minhas necessidades de querer separar dados em um intervalo usando ng-repeat em 2 colunas, para que meu modal Bootstrap não demorasse tanto! Fiquei surpreso com o quão simples isso foi implementar! <span ng-repeat="z in waiverModalLinks" ng-class="{'new-row':startNewRow($index, columnBreak)}" class="{{z.id}}"><a href="{{z.link}}{{z.title}}">{{z.title}}</a></span>Isso está dentro do `modal-body '.
Christine268

15

A resposta da m59 é muito boa. A única coisa que eu não gosto é que ele usa divs para o que poderia ser potencialmente dados para uma tabela.

Portanto, em conjunto com o filtro do m59 (responda em algum lugar acima), veja como mostrá-lo em uma tabela.

<table>
    <tr class="" ng-repeat="rows in foos | chunk:2">
        <td ng-repeat="item in rows">{{item}}</td>
    </tr>
</table>

7

A seguir, é uma maneira mais simples:

<table>
    <tr ng-repeat="item in lists" ng-hide="$index%2!==0">
        <td>
            <label>{{ lists[$index].name}}</label>
        </td>
        <td ng-hide="!lists[$index+1]">
            <label>{{ lists[$index+1].name}}</label>
        </td>
    </tr>
</table>

A resposta de Cumulo Nimbus é útil para mim, mas eu quero essa grade envolvida por uma div que possa mostrar a barra de rolagem quando a lista for muito longa.

Para conseguir isso, adicionei style="height:200px; overflow:auto"uma div ao redor da tabela que faz com que ela apareça como uma única coluna.

Agora funciona para um comprimento de matriz de um.


quando houver apenas 1 item na matriz, ele não exibirá a primeira linha. para corrigir esta mudança a ng-hide="$index%2!==0"
Anuj Pandey

4

Eu tenho uma função e a armazenei em um serviço para que eu possa usá-la em todo o meu aplicativo:

Serviço:

app.service('SplitArrayService', function () {
return {
    SplitArray: function (array, columns) {
        if (array.length <= columns) {
            return [array];
        };

        var rowsNum = Math.ceil(array.length / columns);

        var rowsArray = new Array(rowsNum);

        for (var i = 0; i < rowsNum; i++) {
            var columnsArray = new Array(columns);
            for (j = 0; j < columns; j++) {
                var index = i * columns + j;

                if (index < array.length) {
                    columnsArray[j] = array[index];
                } else {
                    break;
                }
            }
            rowsArray[i] = columnsArray;
        }
        return rowsArray;
    }
}

});

Controlador:

$scope.rows   = SplitArrayService.SplitArray($scope.images, 3); //im splitting an array of images into 3 columns

Marcação:

 <div class="col-sm-12" ng-repeat="row in imageRows">
     <div class="col-sm-4" ng-repeat="image in row">
         <img class="img-responsive" ng-src="{{image.src}}">
     </div>
 </div>

Enquanto isso é legal, não ajuda ao usar outros filtros, como orderBy ou filter.
Robbie Smith

2

Minha abordagem foi uma mistura de coisas.

Meu objetivo era ter uma grade adaptada ao tamanho da tela. Eu queria 3 colunas para lg, 2 colunas para sme mde 1 coluna para xs.

Primeiro, criei a seguinte função de escopo, usando o $windowserviço angular :

$scope.findColNumberPerRow = function() {
    var nCols;
    var width = $window.innerWidth;

    if (width < 768) {
        // xs
        nCols = 1;
    }
    else if(width >= 768 && width < 1200) {
         // sm and md
         nCols = 2
    } else if (width >= 1200) {
        // lg
        nCols = 3;
    }
    return nCols;
};

Então, usei a classe proposta por @Cumulo Nimbus:

.new-row {
    clear: left;
}

Na div que contém o ng-repeat, adicionei a resizablediretiva, conforme explicado nesta página , para que toda vez que a janela seja redimensionada, o $windowserviço angular seja atualizado com os novos valores.

Por fim, na div repetida, tenho:

ng-repeat="user in users" ng-class="{'new-row': ($index % findColNumberPerRow() === 0) }"

Por favor, deixe-me saber quaisquer falhas nessa abordagem.

Espero que possa ser útil.


2

Um truque simples com CSS "clearfix" recomendado pelo Bootstrap:

<div class="row">
      <div ng-repeat-start="value in values" class="col-md-4">
        {{value}}
      </div>
      <div ng-repeat-end ng-if="$index % 3 == 0" class="clearfix"></div>
</div>

Muitas vantagens: Eficiente, rápido, usando as recomendações do Boostrap, evitando possíveis problemas de digestão e não alterando o modelo Angular.


1

Este exemplo produz um repetidor aninhado em que os dados externos incluem uma matriz interna que eu queria listar em duas colunas. O conceito seria válido para três ou mais colunas.

Na coluna interna, repito a "linha" até que o limite seja atingido. O limite é determinado dividindo o comprimento da matriz de itens pelo número de colunas desejadas, neste caso por duas. O método de divisão fica no controlador e recebe o comprimento atual da matriz como parâmetro. A função de fatia do JavaScript (0, array.length / columnCount) aplicou o limite ao repetidor.

O segundo repetidor da coluna é chamado e repete a fatia (array.length / columnCount, array.length) que produz a segunda metade do array na coluna dois.

<div class="row" ng-repeat="GroupAccess in UsersController.SelectedUser.Access" ng-class="{even: $even, odd: $odd}">
    <div class="col-md-12 col-xs-12" style=" padding-left:15px;">
        <label style="margin-bottom:2px;"><input type="checkbox" ng-model="GroupAccess.isset" />{{GroupAccess.groupname}}</label>
    </div>
    <div class="row" style=" padding-left:15px;">
        <div class="col-md-1 col-xs-0"></div>
        <div class="col-md-3 col-xs-2">
            <div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice(0, state.DivideBy2(GroupAccess.features.length))">
                <span class="GrpFeature">{{Feature.featurename}}</span>
            </div>
        </div>
        <div class="col-md-3 col-xs-2">
            <div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice( state.DivideBy2(GroupAccess.features.length), GroupAccess.features.length )">
                <span class="GrpFeature">{{Feature.featurename}}</span>
            </div>
        </div>
        <div class="col-md-5 col-xs-8">
        </div>
    </div>
</div>


// called to format two columns
state.DivideBy2 = function(nValue) {
    return Math.ceil(nValue /2);
};

Espero que isso ajude a olhar para a solução de outra maneira. (PS, este é o meu primeiro post aqui! :-))


1

Eu conserto sem .row

<div class="col col-33 left" ng-repeat="photo in photos">
   Content here...
</div>

e css

.left {
  float: left;
}

1

Aqui está uma maneira fácil e fácil de fazer isso. É mais manual e acaba com uma marcação confusa. Eu não recomendo isso, mas há situações em que isso pode ser útil.

Aqui está um link de violino http://jsfiddle.net/m0nk3y/9wcbpydq/

HTML:

<div ng-controller="myController">
    <div class="row">
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3)">
                {{ person.name }}
                </div>
            </div>
        </div>
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)">
                {{ person.name }}
                </div>
            </div>
        </div>
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)*2">
                {{ person.name }}
                </div>
            </div>
        </div>
    </div>
</div>

JS:

var app = angular.module('myApp', []);

app.controller('myController', function($scope) {

    $scope.Math = Math;
    $scope.data = [
        {"name":"Person A"},
        ...
    ];
});

Essa configuração requer que usemos um pouco de matemática na marcação: /, portanto, você precisará injetar o matemática adicionando esta linha: $scope.Math = Math;


1

Todas essas respostas parecem maciças demais.

De longe, o método mais simples seria configurar as divs de entrada em um bootstrap de coluna col-md-4; o bootstrap o formatará automaticamente em 3 colunas devido à natureza de 12 colunas do bootstrap:

<div class="col-md-12">
    <div class="control-group" ng-repeat="oneExt in configAddr.ext">
        <div class="col-md-4">
            <input type="text" name="macAdr{{$index}}"
                   id="macAddress" ng-model="oneExt.newValue" value="" />
        </div>
    </div>
</div>

1
<div class="row">
  <div class="col-md-4" ng-repeat="remainder in [0,1,2]">
    <ul>
      <li ng-repeat="item in items" ng-if="$index % 3 == remainder">{{item}}</li>
    </ul>
  </div>
</div>

0

Baseando-se na resposta muito boa do m59. Eu descobri que as entradas do modelo seriam borradas se elas mudassem, então você só pode alterar um caractere de cada vez. Este é um novo para listas de objetos para quem precisa:

EDIT Atualizado para lidar com vários filtros em uma página

app.filter('partition', function() {
  var cache = {}; // holds old arrays for difference repeat scopes
  var filter = function(newArr, size, scope) {
    var i,
      oldLength = 0,
      newLength = 0,
      arr = [],
      id = scope.$id,
      currentArr = cache[id];
    if (!newArr) return;

    if (currentArr) {
      for (i = 0; i < currentArr.length; i++) {
        oldLength += currentArr[i].length;
      }
    }
    if (newArr.length == oldLength) {
      return currentArr; // so we keep the old object and prevent rebuild (it blurs inputs)
    } else {
      for (i = 0; i < newArr.length; i += size) {
        arr.push(newArr.slice(i, i + size));
      }
      cache[id] = arr;
      return arr;
    }
  };
  return filter;
}); 

E este seria o uso:

<div ng-repeat="row in items | partition:3:this">
  <span class="span4">
    {{ $index }}
  </span>
</div>

Até onde eu sei, isso deve funcionar a maior parte do tempo, mas essa lógica de cache não parece totalmente confiável e a passagem no escopo é estranha. Eu realmente recomendo apenas filtrar dentro do controlador quando forem necessárias entradas. É a abordagem tecnicamente correta de qualquer maneira.
M59

0

Eu sou novo no bootstrap e no angularjs, mas isso também pode gerar Array por 4 itens como um grupo, o resultado será quase três colunas. Este truque usa o princípio de linha de interrupção de inicialização.

<div class="row">
 <div class="col-sm-4" data-ng-repeat="item in items">
  <div class="some-special-class">
   {{item.XX}}
  </div>
 </div>
</div>

0

Agora, o Lodash possui um método chunk que eu pessoalmente uso: https://lodash.com/docs#chunk

Com base nisso, o código do controlador pode se parecer com o seguinte:

$scope.groupedUsers = _.chunk( $scope.users, 3 )

Ver código:

<div class="row" ng-repeat="rows in groupedUsers">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>

0

Outra maneira é definida width:33.33%; float:leftcomo uma div wrapper como esta:

<div ng-repeat="right in rights" style="width: 33.33%;float: left;">
    <span style="width:60px;display:inline-block;text-align:right">{{$index}}</span>
    <input type="number" style="width:50px;display:inline-block" ">
</div>

0

Eu me encontrei em um caso semelhante, desejando gerar grupos de exibição de 3 colunas cada. No entanto, embora eu estivesse usando o bootstrap, estava tentando separar esses grupos em diferentes divs pai. Eu também queria fazer algo genericamente útil.

Eu me aproximei com 2 ng-repeatcomo abaixo:

<div ng-repeat="items in quotes" ng-if="!($index % 3)">
  <div ng-repeat="quote in quotes" ng-if="$index <= $parent.$index + 2 && $index >= $parent.$index">
                ... some content ...
  </div>
</div>

Isso facilita muito a mudança para um número diferente de colunas e separadas em várias divs pai.


0

Caso alguém queira um Angular 7 (e uma versão superior), aqui está um exemplo que eu usei em um dos meus aplicativos:

HTML:

                   <div class="container-fluid">
                    <div class="row" *ngFor="let reports of chunkData; index as i">
                        <div *ngFor="let report of reports" class="col-4 col-sm-4"
                            style="border-style:solid;background-color: antiquewhite">{{report}}</div>
                    </div>
                </div>

.TS FILE

export class ConfirmationPageComponent implements OnInit {
  chunkData = [];
  reportsArray = ["item 1", "item 2", "item 3", "item 4", "item 5", "item 6"];
  
  constructor() {}

  ngOnInit() {
    this.chunkData = this.chunk(this.reportsArray, 3);
  }

  chunk(arr, size) {
    var newArr = [];
    for (var i = 0; i < arr.length; i += size) {
      newArr.push(arr.slice(i, i + size));
    }
    return newArr;
  }
}

Esta é uma solução incrível para criar dinamicamente novas colunas / linhas, dependendo da quantidade de itens que você itera no banco de dados. Obrigado!


-1

isso responde à pergunta original, que é como obter 1,2,3 em uma coluna. Pergunta feita por kuppu Feb 8 '14 às 13:47

código angularjs:

function GetStaffForFloor(floor) {
    var promiseGet = Directory_Service.getAllStaff(floor);
    promiseGet.then(function (pl) {
        $scope.staffList = chunk(pl.data, 3); //pl.data; //
    },
    function (errorOD) {
        $log.error('Errored while getting staff list.', errorOD);
    });
}
function chunk(array, columns) {
    var numberOfRows = Math.ceil(array.length / columns);

    //puts 1, 2, 3 into column
    var newRow = []; //array is row-based.
    for (var i = 0; i < array.length; i++) {
        var columnData = new Array(columns);
        if (i == numberOfRows) break;
        for (j = 0; j < columns; j++)
        {
            columnData[j] = array[i + numberOfRows * j];
        }
        newRow.push(columnData); 
    }
    return newRow;

    ////this works but 1, 2, 3 is in row
    //var newRow = [];
    //for (var i = 0; i < array.length; i += columns) {
    //    newRow.push(array.slice(i, i + columns)); //push effectively does the pivot. array is row-based.
    //}
    //return newRow;
};

Exibir código (nota: usando o bootstrap 3):

<div class="staffContainer">
    <div class="row" ng-repeat="staff in staffList">
        <div class="col-md-4" ng-repeat="item in staff">{{item.FullName.length > 0 ? item.FullName + ": Rm " + item.RoomNumber : ""}}</div>
    </div>
</div>

-1

Esse código ajudará a alinhar os elementos com três colunas no modo lg e md, duas colunas no modo sm e a coluna única no modo xs

<div class="row">
<div ng-repeat="oneExt in configAddr.ext">
    <div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
        {$index+1}}. 
        <input type="text" name="macAdr{{$index+1}}" 
       id="macAddress" ng-model="oneExt.newValue" value=""/>
    </div>
</div>

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.