Material como WebComponents

Recentemente, como observei em um artigo anterior, os WebComponents de wrappers para estruturas populares que permitem usá-los através da API do navegador estão se desenvolvendo ativamente. Isso significa que, se você deseja usar componentes prontos criados em uma estrutura específica, não é necessário implantar o projeto e montá-lo. Isso também significa que você pode usar o desenvolvimento em diferentes estruturas vinculando-as por meio da interação por meio da API do navegador.

Há não muito tempo atrás, eu estava tentando encontrar uma grade decente para componentes da Web, na época tão completa, mas ao mesmo tempo não obrigando a usar qualquer estrutura, especialmente se fosse algo como o Polymer não era. No passado recente, eu tive uma experiência bastante bem-sucedida com o material / cdk antes. Foi relativamente fácil para mim personalizar seriamente os filtros e o pager da tabela, localizar as dicas e tudo isso sem reescrever o código da biblioteca ou os ganchos sombrios, usando mecanismos de redefinição. No momento da revisão, verificou-se que os fichários especificamente para o componente da tabela ainda não haviam sido concluídos, mas há algumas semanas notei que algo apareceu no repositório sobre esse tópico e decidi tentar conectá-los como componentes da Web como parte do experimento.

Para começar a usar os componentes do material, basta conectar o pacote com o código e outro recurso com todos os estilos, assim como os fãs do VueJS. Por exemplo, você pode criar um campo de entrada controlado por angular / material da seguinte maneira:

<link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css"> <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script> <div class="mdc-text-field"> <input type="text" id="my-text-field" class="mdc-text-field__input"> <label class="mdc-floating-label" for="my-text-field">Label</label> <div class="mdc-line-ripple"></div> </div> <script> mdc.textField.MDCTextField.attachTo(document.querySelector('.mdc-text-field')); </script> 

E podemos conectar uma tabela para exibir dados com a mesma facilidade, diretamente da documentação de amostra no link .

 <div class="mdc-text-field"> <input type="text" id="my-text-field" class="mdc-text-field__input"> <label class="mdc-floating-label" for="my-text-field">Label</label> <div class="mdc-line-ripple"></div> </div> <div class="mdc-data-table"> <table class="mdc-data-table__table" aria-label="Dessert calories"> <thead> <tr class="mdc-data-table__header-row"> <th class="mdc-data-table__header-cell" role="columnheader" scope="col">Dessert</th> <th class="mdc-data-table__header-cell" role="columnheader" scope="col">Carbs (g)</th> <th class="mdc-data-table__header-cell" role="columnheader" scope="col">Protein (g)</th> <th class="mdc-data-table__header-cell" role="columnheader" scope="col">Comments</th> </tr> </thead> <tbody class="mdc-data-table__content"> <tr class="mdc-data-table__row"> <td class="mdc-data-table__cell">Frozen yogurt</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">24</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">4.0</td> <td class="mdc-data-table__cell">Super tasty</td> </tr> <tr class="mdc-data-table__row"> <td class="mdc-data-table__cell">Ice cream sandwich</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">37</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">4.3</td> <td class="mdc-data-table__cell">I like ice cream more</td> </tr> <tr class="mdc-data-table__row"> <td class="mdc-data-table__cell">Eclair</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">24</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">6.0</td> <td class="mdc-data-table__cell">New filing flavor</td> </tr> </tbody> </table> </div> <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script> <script type="module"> let filterField = mdc.textField.MDCTextField.attachTo(document.querySelector('.mdc-text-field')); let dataTable = new mdc.dataTable.MDCDataTable(document.querySelector('.mdc-data-table')); </script> 

Ao conectar pacotes configuráveis ​​unpkg com unpkg que já montamos, infelizmente não usamos os recursos modulares nativos do navegador WHATWG, como esses pacotes configuráveis ​​vinculam componentes ao espaço para nome global e seu objeto mdc, em vez de exportá-lo usando o padrão modular ES6. Mas essa opção provavelmente será mais familiar para especialistas conservadores e pode funcionar sem transpilers em navegadores herdados.



A lista de componentes implementados pode ser encontrada neste repositório .

Infelizmente, no momento, apenas algum tipo de interação com as caixas de seleção e o conteúdo das linhas já renderizadas é definido para tabelas em uma API acessível a partir de fora.

No entanto, consegui pesquisar no Google um exemplo que permite acessar a API até agora escondida de nós, por herança. Além disso, você também deve estar ciente de que, com a ajuda do projeto Angular Elements , é possível desenvolver componentes na infraestrutura da estrutura e expô-los independentemente à API do navegador e aos mesmos CustomElements.



Peguei o exemplo inteiro, fazendo algumas correções, “para trabalhar” no layout original e ficou claro onde escolher depois de ler o artigo. Há muito código e agora ele é agregado.

Código da tabela
 <link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css"> <div class="mdc-text-field"> <input type="text" id="my-text-field" class="mdc-text-field__input"> <label class="mdc-floating-label" for="my-text-field">Label</label> <div class="mdc-line-ripple"></div> </div> <div class="mdc-data-table"> <table class="mdc-data-table__table" aria-label="Dessert calories"> <thead> <tr class="mdc-data-table__header-row"> <th class="mdc-data-table__header-cell" role="columnheader" scope="col">Dessert</th> <th class="mdc-data-table__header-cell" role="columnheader" scope="col">Carbs (g)</th> <th class="mdc-data-table__header-cell" role="columnheader" scope="col">Protein (g)</th> <th class="mdc-data-table__header-cell" role="columnheader" scope="col">Comments</th> </tr> </thead> <tbody class="mdc-data-table__content"> <tr class="mdc-data-table__row"> <td class="mdc-data-table__cell">Frozen yogurt</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">24</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">4.0</td> <td class="mdc-data-table__cell">Super tasty</td> </tr> <tr class="mdc-data-table__row"> <td class="mdc-data-table__cell">Ice cream sandwich</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">37</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">4.3</td> <td class="mdc-data-table__cell">I like ice cream more</td> </tr> <tr class="mdc-data-table__row"> <td class="mdc-data-table__cell">Eclair</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">24</td> <td class="mdc-data-table__cell mdc-data-table__cell--numeric">6.0</td> <td class="mdc-data-table__cell">New filing flavor</td> </tr> </tbody> </table> </div> <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.js"></script> <script type="module"> let filterField = mdc.textField.MDCTextField.attachTo(document.querySelector('.mdc-text-field')); const DATATABLE_COLUMNS_SELECTOR = `.mdc-data-table thead`, DATATABLE_DATA_SELECTOR = `tbody.mdc-data-table__content`, DATATABLE_SORTABLE_SELECTOR = `.mdc-data-table--sortable`, DATATABLE_COLUMNS_NUMERIC = `mdc-data-table--numeric`, DATATABLE_COLUMNS_SORTABLE = `mdc-data-table--sortable`, DATATABLE_COLUMNS_SORT_ASC = `mdc-data-table--sort-asc`, DATATABLE_COLUMNS_SORT_DESC = `mdc-data-table--sort-desc`; class MyDataTable extends mdc.dataTable.MDCDataTable { get data() { return this.foundation_.data; } set data(data) { if (Array.isArray(data)) { this.foundation_.setData(data); } else { throw new Error(`Expected an array`); } } layout() { if (this.foundation_.layout) { this.foundation_.layout(); } } getDefaultFoundation() { const getHeaderRow = () => { let thead = this.root_.querySelector(DATATABLE_COLUMNS_SELECTOR), row = thead.querySelector(`tr`); if (!row) { row = document.createElement(`tr`); row.setAttribute(`role`, `rowheader`); thead.appendChild(row); } return row; }, getHeaderColumns = () => { return getHeaderRow().querySelectorAll(`th`); }, emptyHeaderColumns = () => { getHeaderRow().remove(); }, getData = () => { return this.root_.querySelector(DATATABLE_DATA_SELECTOR); }, getDataRows = () => { return getData().querySelectorAll(`tr`); }, emptyData = () => { Array.prototype.map.call(getDataRows(), row => { row.remove(); }); }; return new MyDataTableFoundation({ registerSortClickHandler: (handler) => this.root_.addEventListener(`click`, handler), deregisterSortClickHandler: (handler) => this.root_.removeEventListener(`click`, handler), // Reads the columns list readColumns: () => { var cols = getHeaderColumns(); return Array.prototype.map.call(cols, col => { return { text: col.textContent, description: col.getAttribute(`aria-label`), numeric: col.classList.contains(DATATABLE_COLUMNS_NUMERIC), sortable: col.classList.contains(DATATABLE_COLUMNS_SORTABLE), sort: col.classList.contains(DATATABLE_COLUMNS_SORT_ASC) ? 1 : col.classList.contains(DATATABLE_COLUMNS_SORT_DESC) ? -1 : 0 }; }); }, // Edit the columns setColumns: (cols) => { emptyHeaderColumns(); let row = getHeaderRow(); cols.forEach(col => { let column = document.createElement(`th`); column.setAttribute(`role`, `columnheader`); // Add text column.textContent = col.text; column.setAttribute(`aria-label`, col.description); // Numeric if (col.numeric) { column.classList.add(DATATABLE_COLUMNS_NUMERIC); } // Sort if (col.sortable) { let ariaSort = `none`; column.classList.add(DATATABLE_COLUMNS_SORTABLE); if (col.sort === `asc` || col.sort === 1) { ariaSort = `ascending`; column.classList.add(DATATABLE_COLUMNS_SORT_ASC); } else if (col.sort === `desc` || col.sort === -1) { ariaSort = `descending`; column.classList.add(DATATABLE_COLUMNS_SORT_DESC); } column.setAttribute(`aria-sort`, ariaSort); } // Add to cols row.appendChild(column); }); }, // Read data readData: () => { var rows = getDataRows(); return Array.prototype.map.call(rows, row => { let cells = row.querySelectorAll(`td`); return Array.prototype.map.call(cells, cell => cell.textContent); }); }, // Edit the data setData: (data) => { emptyData(); let element = getData(); // Sorting data let column = this.columns.find(el => el.sort); if (column) { let index = this.columns.indexOf(column); if (column.sortable) { let f = (params => { if (params.sort === `desc` || params.sort === -1) { return params.numeric ? (a, b) => b[index] - a[index] : (a, b) => b[index].localeCompare(a[index]); } else { return params.numeric ? (a, b) => a[index] - b[index] : (a, b) => a[index].localeCompare(b[index]); } })(column); data.sort(f); } } // For each data data.forEach(d => { // Create a new row let row = document.createElement(`tr`); row.setAttribute(`role`, `row`); // For each values d.forEach((val, i) => { // Create a new cell let cell = document.createElement(`td`); cell.setAttribute(`role`, `gridcell`); // Add numeric if needed if (this.columns[i].numeric) { cell.classList.add(DATATABLE_COLUMNS_NUMERIC); } // Add content if (val instanceof Element) { cell.appendChild(val); } else { cell.textContent = val; } row.appendChild(cell); }); // Add to cols element.appendChild(row); }); }, // Redraw data table after edit redraw: () => { this.foundation_.adapter_.setColumns(this.columns); this.foundation_.adapter_.setData(this.data); } }); } } mdc.autoInit.register(`MDCDataTable`, MyDataTable); class MyDataTableFoundation extends mdc.base.MDCFoundation { static get defaultAdapter() { return { registerSortClickHandler: ( /* handler: EventListener */ ) => {}, deregisterSortClickHandler: ( /* handler: EventListener */ ) => {}, readColumns: () => {}, setColumns: () => {}, readData: () => {}, setData: () => {}, redraw: () => {} }; } constructor(adapter) { super(Object.assign(MyDataTableFoundation.defaultAdapter, adapter)); // Attributes this.columns = []; this.data = []; // Methods // On sort this.sortClickHandler_ = (e) => { let target = e.target.closest(DATATABLE_SORTABLE_SELECTOR); if (target) { let index = Array.prototype.indexOf.call(target.parentElement.children, target); this.columns.forEach((col, i) => { if (i !== index) { col.sort = 0; } else { if (col.sort === `asc` || col.sort === 1) { col.sort = `desc`; } else { col.sort = `asc`; } } }); this.adapter_.redraw(); } }; } init() { // Read columns this.columns = this.adapter_.readColumns(); // Read data this.data = this.adapter_.readData(); // Click this.adapter_.registerSortClickHandler(this.sortClickHandler_); } destroy() { // Click this.adapter_.deregisterSortClickHandler(this.sortClickHandler_); } setColumns(cols) { this.adapter_.setColumns(cols); } setData(data) { this.adapter_.setData(data); } } let dataTable = new MyDataTable(document.querySelector('.mdc-data-table')); </script> 


Este exemplo implementa um certo mínimo de redefinição e expõe métodos de interação com a API que estão ausentes na API. Graças a isso, podemos integrar o componente da tabela nas entranhas da estrutura com outro componente do campo de entrada, adicionando nossa própria funcionalidade de filtragem.

 let filterField = mdc.textField.MDCTextField.attachTo(document.querySelector('.mdc-text-field')); filterField.input_.oninput = (event) => { dataTable.origData = dataTable.origData || dataTable.data.slice(); if (event.target.value == '') { dataTable.data = dataTable.origData.slice(); } else { let data = dataTable.origData.filter((row) => { let rowIsOk = false; for (let item of row) { if (item.indexOf(event.target.value) > 0) { rowIsOk = true; } } return rowIsOk; }) || []; dataTable.data = data; dataTable.getDefaultFoundation().redraw(); } }; 

Conceitualmente, não é muito correto implementar a filtragem diretamente no manipulador de eventos, pois agora temos uma classe de tabela e estruturas de uma base e adaptador de dados com significado mais próximo, no entanto, nossa tarefa hoje é garantir que seja possível organizar a interação dos componentes. E acabamos de conectar dois componentes que não possuem lógica de implementação comum com código no contexto da execução do navegador.


depois de inserir dados no campo, o conteúdo será filtrado


Este exemplo, especialmente depois de colocar toda a lógica javascript em arquivos de classe separados, como fizemos no primeiro artigo da série , pode ser um ponto de partida para você reutilizar componentes do CDK angular / material ou outro kit de ferramentas para seu próprio desenvolvimento, redefinindo o comportamento para que “Farm coletivo” tudo do zero ou integrando o novo código à infraestrutura existente, deixando de aumentar o monólito, porque Os componentes da Web fornecem a melhor maneira de organizar o desenvolvimento de forma modular.

Source: https://habr.com/ru/post/pt462695/


All Articles