材质作为WebComponents

最近,正如我在上一篇文章中提到的那样,流行框架的WebComponents允许通过浏览器API使用它们,它们正在积极地开发自己。 这意味着,如果要使用在特定框架上创建的现成组件,则不必部署项目并进行组装。 这也意味着您可以通过通过浏览器API进行交互将它们链接在一起,从而在不同的框架上使用开发。

不久前,我试图为Web组件找到一个像样的网格,但当时并没有强制使用任何框架,尤其是在没有Polymer的情况下。 在最近的过去,我之前在material / cdk方面有相当成功的经验。 然后,对于我来说,使用重定义机制来认真地自定义表的过滤器和分页器,本地化提示以及所有这些都相对容易,而无需重写库代码或令人沮丧的钩子。 在审查时,事实证明尚未完成专门用于表格组件的活页夹,但几周前,我注意到该主题的存储库中出现了一些东西,并决定尝试将它们作为Web组件进行连接作为实验的一部分。

为了开始使用材料的组件,只需将捆绑包与代码和具有所有样式的另一资源连接起来,就像VueJS粉丝一样。 例如,您可以创建一个由角度/材质控制的输入字段,如下所示:

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

而且,我们可以直接从链接上的样本文档连接一个表来轻松显示数据。

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

从unpkg捆绑包与已经组装的unpkg连接时,很遗憾,我们没有使用WHATWG浏览器的本机模块化功能,因为 这些捆绑将组件绑定到全局名称空间及其mdc对象,而不是使用ES6模块化标准将其导出。 但是,保守的专家可能会更熟悉这种选择,并且无需在旧版浏览器中使用转译器就可以工作。



可以在此存储库中找到已实现组件的列表。

不幸的是,目前,对于可从外部API访问的表,仅设置了与复选框和已渲染行的内容的某种交互。

但是,我设法在Google上找到了一个示例,该示例使您可以通过继承访问迄今为止对我们隐藏的API。 此外,您还应该意识到,借助Angular Elements项目,您可以在框架基础结构中开发组件,并将它们独立地暴露给浏览器API和相同的CustomElements。



我以整个示例为例,进行了一些更正,使它们“可以”在原始布局上使用,并且在阅读本文后很清楚可以从何处选择。 有很多代码,现在可以汇总了。

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


本示例实现了重新定义的最低要求,并公开了api中缺少的与api交互的方法。 由于这一点,我们可以将表格组件与其他字段集成到框架的内部,从而添加我们自己的过滤功能。

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

从概念上讲,直接在事件处理程序中实现过滤不是很正确,为此,我们现在有了一个表类以及基础和数据适配器的结构,它们在含义上更接近,但是今天的任务是确保可以组织组件的交互。 而且,我们只是设法在浏览器执行的上下文中将不具有通用实现逻辑的两个组件与代码连接起来。


在字段中输入数据后,内容将被过滤


本示例,尤其是在将所有JavaScript逻辑放入单独的类文件中之后(如本系列第一篇文章所述) ,可以成为您重新使用angular / material cdk组件或其他工具包进行自己的开发,重新定义行为以便“集体农场”从头开始或通过将新代码集成到现有基础架构中来停止一切,不再增加整体,因为 Web组件提供了模块化组织开发的最佳方法。

Source: https://habr.com/ru/post/zh-CN462695/


All Articles