Material als WebComponents

Wie ich bereits in einem früheren Artikel erwähnt habe , entwickeln sich WebComponents von Wrappern für gängige Frameworks, die die Verwendung über die Browser-API ermöglichen, kürzlich selbst aktiv. Dies bedeutet, dass Sie das Projekt nicht bereitstellen und zusammenstellen müssen, wenn Sie vorgefertigte Komponenten verwenden möchten, die in einem bestimmten Framework erstellt wurden. Dies bedeutet auch, dass Sie die Entwicklung auf verschiedenen Frameworks verwenden können, indem Sie sie durch Interaktion über die Browser-API miteinander verknüpfen.

Vor nicht allzu langer Zeit habe ich versucht, ein anständiges Raster für Webkomponenten zu finden, damals ein so vollwertiges, aber gleichzeitig war ich nicht verpflichtet, ein Framework zu verwenden, insbesondere wenn es so etwas wie Polymer nicht war. In der jüngeren Vergangenheit hatte ich zuvor ziemlich erfolgreiche Erfahrungen mit Material / CDK. Dann war es für mich relativ einfach, die Filter und den Pager für die Tabelle ernsthaft anzupassen, die Hinweise zu lokalisieren und all dies, ohne den Bibliothekscode oder düstere Hooks mithilfe von Neudefinitionsmechanismen neu zu schreiben. Zum Zeitpunkt der Überprüfung stellte sich heraus, dass noch keine Bindemittel speziell für die Tabellenkomponente erstellt wurden. Vor einigen Wochen stellte ich jedoch fest, dass zu diesem Thema etwas im Repository angezeigt wurde, und beschloss, sie im Rahmen des Experiments als Webkomponenten zu verbinden.

Um mit der Verwendung der Materialkomponenten zu beginnen, verbinden Sie einfach das Bundle mit dem Code und einer anderen Ressource mit allen Stilen, genau wie bei VueJS-Fans. Sie können beispielsweise ein Eingabefeld erstellen, das wie folgt durch Winkel / Material gesteuert wird:

<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> 

Und wir können eine Tabelle zur Anzeige von Daten genauso einfach direkt aus der Beispieldokumentation auf dem Link verbinden .

 <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> 

Bei der Verbindung von Unpkg-Bundles mit Unpkg, die wir bereits zusammengestellt haben, verwenden wir leider nicht die nativen modularen Funktionen des WHATWG-Browsers Diese Bundles binden Komponenten an den globalen Namespace und sein mdc-Objekt, anstatt sie mit dem modularen ES6-Standard zu exportieren. Aber eine solche Option wird konservativ denkenden Spezialisten wahrscheinlich vertrauter sein und kann in älteren Browsern ohne Transpiler funktionieren.



Die Liste der implementierten Komponenten finden Sie in diesem Repository .

Leider ist derzeit nur eine Art Interaktion mit den Kontrollkästchen und dem Inhalt bereits gerenderter Zeilen für Tabellen in einer von außerhalb zugänglichen API festgelegt.

Es ist mir jedoch gelungen, ein Beispiel zu googeln, mit dem Sie durch Vererbung auf die bisher verborgene API zugreifen können. Darüber hinaus sollten Sie sich darüber im Klaren sein, dass Sie mithilfe des Angular Elements- Projekts Komponenten in der Framework-Infrastruktur entwickeln und diese unabhängig voneinander der Browser-API und denselben CustomElements zugänglich machen können.



Ich nahm das ganze Beispiel und nahm einige Korrekturen vor, um am ursprünglichen Layout zu „arbeiten“, und es war klar, wo ich nach dem Lesen des Artikels auswählen sollte. Es gibt viel Code und jetzt ist er aufgerollt.

Tabellencode
 <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> 


In diesem Beispiel wird ein bestimmtes Minimum für die Neudefinition implementiert und Interaktionsmethoden mit API verfügbar gemacht, die in API fehlen. Dank dessen können wir die Tabellenkomponente in den Darm des Frameworks mit einer anderen Komponente des Eingabefelds integrieren und unsere eigene Filterfunktion hinzufügen.

 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(); } }; 

Es ist konzeptionell nicht sehr richtig, die Filterung direkt im Ereignishandler zu implementieren. Dafür haben wir jetzt eine Tabellenklasse und Strukturen einer Grundlage und eines Datenadapters, die eine engere Bedeutung haben. Unsere heutige Aufgabe ist es jedoch sicherzustellen, dass es möglich ist, die Interaktion von Komponenten zu organisieren. Und wir haben es gerade geschafft, zwei Komponenten, die keine gemeinsame Implementierungslogik haben, mit Code im Kontext der Browserausführung zu verbinden.


Nach Eingabe der Daten in das Feld wird der Inhalt gefiltert


Dieses Beispiel kann, insbesondere nachdem Sie die gesamte Javascript-Logik in separate Klassendateien eingefügt haben, wie wir es im ersten Artikel der Serie getan haben, ein Ausgangspunkt für Sie sein, um Komponenten von Angular / Material CDK oder einem anderen Toolkit für Ihre eigene Entwicklung wiederzuverwenden und das Verhalten neu zu definieren "Collective Farm" alles von Grund auf neu oder durch die Integration des neuen Codes in die vorhandene Infrastruktur, aufhören, den Monolithen zu erhöhen, weil Webkomponenten bieten die beste Möglichkeit, die Entwicklung modular zu organisieren.

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


All Articles