Reactjs - 同位素布局 - 使用数据属性 filter/sort
Reactjs - Isotope layout - using data-attributes to filter/sort
我正在尝试简化 React 组件中的同位素处理程序——我想创建类似 catergory[metal] 或 catergory[transition] 的过滤器选项——以及类似 catergory[metal, transition] 的组合过滤器.
这个沙箱。
https://codesandbox.io/s/brave-sea-tnih7
就像 filterFns[ filterValue, "param2" ] -- 如何将 2 个参数推入过滤器函数 - 而不是这些固定的 greaterThan50 -- "greaterThan(50)", "greaterThan(5) -- 这个处理程序的动态过滤类型“
最新的沙箱
https://codesandbox.io/s/brave-sea-tnih7?file=/src/IstotopeWrapper.js
import React, { Component } from 'react';
import Isotope from 'isotope-layout';
import Button from '@material-ui/core/Button';
import ButtonGroup from '@material-ui/core/ButtonGroup';
import GenericForm from '../GenericForm/GenericForm';
import './IsotopeHandler.scss';
class IsotopeHandler extends Component {
constructor() {
super();
this.myRef = React.createRef();
let items = [{
"name": "Mercury",
"category": "transition",
"weight": "122",
"symbol": "Hg",
"number": 12,
"custom": "a",
"isgas": false
}, {
"name": "Tellurium",
"category": "metal",
"weight": "12",
"symbol": "Te",
"number": 232,
"custom": "b",
"isgas": false
}, {
"name": "Bismuth",
"category": "rock",
"weight": "2200.59",
"symbol": "Bi",
"number": 1666,
"custom": "c",
"isgas": false
}, {
"name": "Cadmium",
"category": "metal",
"weight": "1200",
"symbol": "Cd",
"number": 454,
"custom": "d",
"isgas": false
}, {
"name": "Bosim",
"category": "gas",
"weight": "100",
"symbol": "Gs",
"number": 454,
"custom": "e",
"isgas": true
}, {
"name": "xosim",
"category": "gas",
"weight": "100",
"symbol": "xs",
"number": 44,
"custom": "f",
"isgas": true
}]
this.state = { sortBy: 'original-order', items: items };
this.onFilterHandler = this.onFilterHandler.bind(this);
this.onSortHandler = this.onSortHandler.bind(this);
}
onSortHandler(sortValue){
if(sortValue){
console.log("sortValue", sortValue);
let bool = this.state.bool;
let options = { sortBy: sortValue };
options = Object.assign(options, {
sortAscending: (bool = !bool),
});
this.setState({ bool: bool });
this.setState({ sortBy: sortValue });
this.state.iso.arrange(options);
}
}
onFilterHandler(filterName, filterField, filterValue){
var filterFns = {
//match
matches: function(itemElem) {
var values = itemElem.getAttribute('data-'+filterField);
return values.match(filterValue);
},
//greaterThan
greaterThan: function(itemElem) {
var number = itemElem.getAttribute('data-'+filterField);
return parseInt(number, 10) > filterValue;
},
//lessThan
lessThan: function(itemElem) {
var number = itemElem.getAttribute('data-'+filterField);
return parseInt(number, 10) < filterValue;
},
//Between
between: function(itemElem) {
var number = itemElem.getAttribute('data-'+filterField);
return parseInt(number, 10) > parseInt(filterValue.split(",")[0], 10) && parseInt(number, 10) < parseInt(filterValue.split(",")[1], 10);
},
};
// use matching filter function
let val = filterFns[filterName] || filterValue;
this.state.iso.arrange({ filter: val });
}
submitFilterFormHandler(data){
console.log("data now with parent", data);
if(data){
//open dialog box
//console.log("this", this);
}
}
componentDidMount(){
// init Isotope
var iso = new Isotope(this.myRef.current, {
itemSelector: '.grid-item',
layoutMode: 'fitRows',
getSortData: this.getCustomSortAttributes(this.state.items[0])
});
this.setState({ iso: iso });
console.log("iso",iso);
}
getCustomSortAttributes(item){
let keys = Object.keys(item);
let dataObj = {};
for (let i = 0; i < keys.length; i++) {
var obj = {
[keys[i]]: '[data-'+keys[i]+']'
}
dataObj = {...obj, ...dataObj}
}
return dataObj;
}
attributeGeneration(item){
let keys = Object.keys(item);
let dataObj = {};
for (let i = 0; i < keys.length; i++) {
var obj = {
["data-"+keys[i]]: item[keys[i]]
}
dataObj = {...obj, ...dataObj}
}
return dataObj;
}
render() {
var sorts = [{
"label": "Original Order",
"value": "original-order"
},{
"label": "Name",
"value": "name"
},{
"label": "Symbol",
"value": "symbol"
},{
"label": "Number",
"value": "number"
},{
"label": "Weight",
"value": "weight"
},{
"label": "Category",
"value": "category"
},{
"label": "Custom",
"value": "custom"
}]
let filters = [{
"label": "show all",
"params": {
"filter": "showAll",
"field": "",
"value": "*"
}
},{
"label": "number > 50",
"params": {
"filter": "greaterThan",
"field": "number",
"value": 50
}
},{
"label": "number < 350",
"params": {
"filter": "lessThan",
"field": "number",
"value": 350
}
},{
"label": "between- number < 350 && number < 600",
"params": {
"filter": "between",
"field": "number",
"value": "350,600"
}
},{
"label": "metal category",
"params": {
"filter": "matches",
"field": "category",
"value": "metal"
}
},{
"label": "metal transition",
"params": {
"filter": "matches",
"field": "category",
"value": "transition"
}
},{
"label": "metal gas and transition",
"params": {
"filter": "matches",
"field": "category",
"value": "gas|transition"
}
},{
"label": "isGas",
"params": {
"filter": "matches",
"field": "isgas",
"value": true
}
}]
let initialFilterFormValues = {
"manual": "type to filter"
}
let fieldsFilterForm = [
{
"type": "text",
"label": "Manual Entry",
"name": ["manual"],
"options": []
},
{
"type": "buttons",
"label": "Sweets2",
"name": ["sweets2"],
"options": [
{
"label": "Sherbert",
"value": "0"
},
{
"label": "Peach Jam",
"value": "1"
},
{
"label": "Almond Butter",
"value": "2"
}
],
//"validate": ["required"],
}
];
let buttonsFilterForm = [
/*{
"label": "Submit3",
"variant": "contained",
"color": "primary",
"type": "submit",
"disabled": ["submitting"],
"onClick" : null
}*/
];
return (
<div
className={"isotope"}
>
<div className="button-group filters-button-group">
<ButtonGroup variant="outlined" color="primary" aria-label="outlined primary button group">
{
filters.map((item, j) => {
return(
<Button
key={j}
className="button"
onClick={() => this.onFilterHandler(item.params.filter, item.params.field, item.params.value)}
>
{item.label}
</Button>
)
})
}
{/*
<Button className="button" data-filter=".alkali, .alkaline-earth">alkali and alkaline-earth</Button>
<Button className="button" data-filter=":not(.transition)">not transition</Button>
<Button className="button" data-filter=".metal:not(.transition)">metal but not transition</Button>
*/}
</ButtonGroup>
</div>
<div className="button-group sort-by-button-group">
<ButtonGroup variant="outlined" color="primary" aria-label="outlined primary button group">
{
sorts.map((item, j) => {
return(
<Button
key={j}
className="button"
onClick={() => this.onSortHandler(item.value)}
>
{item.label}
</Button>
)
})
}
</ButtonGroup>
</div>
{/*
<GenericForm
initialValues={initialFilterFormValues}
fields={fieldsFilterForm}
buttons={buttonsFilterForm}
submitHandler={this.submitFilterFormHandler}
/>
*/}
<div
className="grid"
ref={this.myRef}
>
{
this.state.items.map((item, j) => {
return(
<div
key={j}
className="grid-item"
{...this.attributeGeneration(item)}
>
<h3>{item.name}</h3>
<p>{item.weight}</p>
<p>{item.number}</p>
<p>{item.symbol}</p>
<p>{item.category}</p>
</div>
)
})
}
</div>
</div>
);
}
}
export default IsotopeHandler;
在您的应用程序中,您可以将过滤器存储在数组中并将过滤器数组更新为新过滤器(单一过滤器模式)或将新选择的过滤器推到现有过滤器列表的顶部。
在此处查看更新后的 codesandbox。
首先我们用同位素注册我们的过滤方法:
componentDidMount() {
// init Isotope
var iso = new Isotope(this.myRef.current, {
itemSelector: ".grid-item",
layoutMode: "fitRows",
getSortData: this.getCustomSortAttributes(this.state.items[0]),
filter: this.filterData // <- custom filter method
});
this.setState({ iso: iso });
console.log("iso", iso);
}
已更新 onFilterHandler
并且需要 3 个参数
- addFilter:布尔标志,指示是应添加新的过滤条件还是应删除现有的过滤条件
- filterCondition:字符串条件或条件数组
rule|field|value
- type: string value will be wither
AND
or OR
来判断是匹配type中的所有条件还是匹配任何一个条件
基于参数函数添加新过滤器或搜索现有过滤器并将其从全局 filters
数组中删除。
getFilterObject(condition) {
const conditionItems = condition.split("|");
const filterName = conditionItems[0];
const filterField = conditionItems[1];
const filterValue = conditionItems[2];
return {
condition: condition,
name: filterName,
field: filterField,
value: filterValue,
filterFunc: this.filterFns[filterName]
};
}
onFilterHandler(addFilter, filterCondition, type = "AND") {
// use matching filter function
// let val = filterFns[filterName] || filterValue;
const filterObj = {
type: type,
filters: Array.isArray(filterCondition)
? filterCondition.map((cnd) => this.getFilterObject(cnd))
: [this.getFilterObject(filterCondition)]
};
const existingFilters = this.filters.filter(
(f) =>
f.filters.length === filterObj.filters.length &&
f.filters.every((f) =>
filterObj.filters.some(
(fObjFilter) => fObjFilter.condition === f.condition
)
)
);
if (addFilter && existingFilters.length === 0) {
this.filters = [...this.filters, filterObj];
} else if (!addFilter) {
this.filters = this.filters.filter((f) => !existingFilters.includes(f));
}
this.state.iso.arrange();
}
新增filterData
函数将负责根据当前过滤器列表过滤数据:
filterData(item) {
if (this.filters.length <= 0) {
return true;
}
const filterPredicate = (filterObj) => {
return typeof filterObj.filterFunc === "function"
? filterObj.filterFunc(item, filterObj.field, filterObj.value)
: filterObj.value === "*"
? true
: item.classList.contains(filterObj.value);
};
for (let idx = 0; idx < this.filters.length; idx++) {
const filterObj = this.filters[idx];
const retVal =
filterObj.type === "OR"
? filterObj.filters.some((fObjFilter) => filterPredicate(fObjFilter))
: filterObj.filters.every((fObjFilter) =>
filterPredicate(fObjFilter)
);
if (retVal) {
return retVal;
}
}
return false;
}
我正在尝试简化 React 组件中的同位素处理程序——我想创建类似 catergory[metal] 或 catergory[transition] 的过滤器选项——以及类似 catergory[metal, transition] 的组合过滤器.
这个沙箱。 https://codesandbox.io/s/brave-sea-tnih7
就像 filterFns[ filterValue, "param2" ] -- 如何将 2 个参数推入过滤器函数 - 而不是这些固定的 greaterThan50 -- "greaterThan(50)", "greaterThan(5) -- 这个处理程序的动态过滤类型“
最新的沙箱
https://codesandbox.io/s/brave-sea-tnih7?file=/src/IstotopeWrapper.js
import React, { Component } from 'react';
import Isotope from 'isotope-layout';
import Button from '@material-ui/core/Button';
import ButtonGroup from '@material-ui/core/ButtonGroup';
import GenericForm from '../GenericForm/GenericForm';
import './IsotopeHandler.scss';
class IsotopeHandler extends Component {
constructor() {
super();
this.myRef = React.createRef();
let items = [{
"name": "Mercury",
"category": "transition",
"weight": "122",
"symbol": "Hg",
"number": 12,
"custom": "a",
"isgas": false
}, {
"name": "Tellurium",
"category": "metal",
"weight": "12",
"symbol": "Te",
"number": 232,
"custom": "b",
"isgas": false
}, {
"name": "Bismuth",
"category": "rock",
"weight": "2200.59",
"symbol": "Bi",
"number": 1666,
"custom": "c",
"isgas": false
}, {
"name": "Cadmium",
"category": "metal",
"weight": "1200",
"symbol": "Cd",
"number": 454,
"custom": "d",
"isgas": false
}, {
"name": "Bosim",
"category": "gas",
"weight": "100",
"symbol": "Gs",
"number": 454,
"custom": "e",
"isgas": true
}, {
"name": "xosim",
"category": "gas",
"weight": "100",
"symbol": "xs",
"number": 44,
"custom": "f",
"isgas": true
}]
this.state = { sortBy: 'original-order', items: items };
this.onFilterHandler = this.onFilterHandler.bind(this);
this.onSortHandler = this.onSortHandler.bind(this);
}
onSortHandler(sortValue){
if(sortValue){
console.log("sortValue", sortValue);
let bool = this.state.bool;
let options = { sortBy: sortValue };
options = Object.assign(options, {
sortAscending: (bool = !bool),
});
this.setState({ bool: bool });
this.setState({ sortBy: sortValue });
this.state.iso.arrange(options);
}
}
onFilterHandler(filterName, filterField, filterValue){
var filterFns = {
//match
matches: function(itemElem) {
var values = itemElem.getAttribute('data-'+filterField);
return values.match(filterValue);
},
//greaterThan
greaterThan: function(itemElem) {
var number = itemElem.getAttribute('data-'+filterField);
return parseInt(number, 10) > filterValue;
},
//lessThan
lessThan: function(itemElem) {
var number = itemElem.getAttribute('data-'+filterField);
return parseInt(number, 10) < filterValue;
},
//Between
between: function(itemElem) {
var number = itemElem.getAttribute('data-'+filterField);
return parseInt(number, 10) > parseInt(filterValue.split(",")[0], 10) && parseInt(number, 10) < parseInt(filterValue.split(",")[1], 10);
},
};
// use matching filter function
let val = filterFns[filterName] || filterValue;
this.state.iso.arrange({ filter: val });
}
submitFilterFormHandler(data){
console.log("data now with parent", data);
if(data){
//open dialog box
//console.log("this", this);
}
}
componentDidMount(){
// init Isotope
var iso = new Isotope(this.myRef.current, {
itemSelector: '.grid-item',
layoutMode: 'fitRows',
getSortData: this.getCustomSortAttributes(this.state.items[0])
});
this.setState({ iso: iso });
console.log("iso",iso);
}
getCustomSortAttributes(item){
let keys = Object.keys(item);
let dataObj = {};
for (let i = 0; i < keys.length; i++) {
var obj = {
[keys[i]]: '[data-'+keys[i]+']'
}
dataObj = {...obj, ...dataObj}
}
return dataObj;
}
attributeGeneration(item){
let keys = Object.keys(item);
let dataObj = {};
for (let i = 0; i < keys.length; i++) {
var obj = {
["data-"+keys[i]]: item[keys[i]]
}
dataObj = {...obj, ...dataObj}
}
return dataObj;
}
render() {
var sorts = [{
"label": "Original Order",
"value": "original-order"
},{
"label": "Name",
"value": "name"
},{
"label": "Symbol",
"value": "symbol"
},{
"label": "Number",
"value": "number"
},{
"label": "Weight",
"value": "weight"
},{
"label": "Category",
"value": "category"
},{
"label": "Custom",
"value": "custom"
}]
let filters = [{
"label": "show all",
"params": {
"filter": "showAll",
"field": "",
"value": "*"
}
},{
"label": "number > 50",
"params": {
"filter": "greaterThan",
"field": "number",
"value": 50
}
},{
"label": "number < 350",
"params": {
"filter": "lessThan",
"field": "number",
"value": 350
}
},{
"label": "between- number < 350 && number < 600",
"params": {
"filter": "between",
"field": "number",
"value": "350,600"
}
},{
"label": "metal category",
"params": {
"filter": "matches",
"field": "category",
"value": "metal"
}
},{
"label": "metal transition",
"params": {
"filter": "matches",
"field": "category",
"value": "transition"
}
},{
"label": "metal gas and transition",
"params": {
"filter": "matches",
"field": "category",
"value": "gas|transition"
}
},{
"label": "isGas",
"params": {
"filter": "matches",
"field": "isgas",
"value": true
}
}]
let initialFilterFormValues = {
"manual": "type to filter"
}
let fieldsFilterForm = [
{
"type": "text",
"label": "Manual Entry",
"name": ["manual"],
"options": []
},
{
"type": "buttons",
"label": "Sweets2",
"name": ["sweets2"],
"options": [
{
"label": "Sherbert",
"value": "0"
},
{
"label": "Peach Jam",
"value": "1"
},
{
"label": "Almond Butter",
"value": "2"
}
],
//"validate": ["required"],
}
];
let buttonsFilterForm = [
/*{
"label": "Submit3",
"variant": "contained",
"color": "primary",
"type": "submit",
"disabled": ["submitting"],
"onClick" : null
}*/
];
return (
<div
className={"isotope"}
>
<div className="button-group filters-button-group">
<ButtonGroup variant="outlined" color="primary" aria-label="outlined primary button group">
{
filters.map((item, j) => {
return(
<Button
key={j}
className="button"
onClick={() => this.onFilterHandler(item.params.filter, item.params.field, item.params.value)}
>
{item.label}
</Button>
)
})
}
{/*
<Button className="button" data-filter=".alkali, .alkaline-earth">alkali and alkaline-earth</Button>
<Button className="button" data-filter=":not(.transition)">not transition</Button>
<Button className="button" data-filter=".metal:not(.transition)">metal but not transition</Button>
*/}
</ButtonGroup>
</div>
<div className="button-group sort-by-button-group">
<ButtonGroup variant="outlined" color="primary" aria-label="outlined primary button group">
{
sorts.map((item, j) => {
return(
<Button
key={j}
className="button"
onClick={() => this.onSortHandler(item.value)}
>
{item.label}
</Button>
)
})
}
</ButtonGroup>
</div>
{/*
<GenericForm
initialValues={initialFilterFormValues}
fields={fieldsFilterForm}
buttons={buttonsFilterForm}
submitHandler={this.submitFilterFormHandler}
/>
*/}
<div
className="grid"
ref={this.myRef}
>
{
this.state.items.map((item, j) => {
return(
<div
key={j}
className="grid-item"
{...this.attributeGeneration(item)}
>
<h3>{item.name}</h3>
<p>{item.weight}</p>
<p>{item.number}</p>
<p>{item.symbol}</p>
<p>{item.category}</p>
</div>
)
})
}
</div>
</div>
);
}
}
export default IsotopeHandler;
在您的应用程序中,您可以将过滤器存储在数组中并将过滤器数组更新为新过滤器(单一过滤器模式)或将新选择的过滤器推到现有过滤器列表的顶部。
在此处查看更新后的 codesandbox。
首先我们用同位素注册我们的过滤方法:
componentDidMount() {
// init Isotope
var iso = new Isotope(this.myRef.current, {
itemSelector: ".grid-item",
layoutMode: "fitRows",
getSortData: this.getCustomSortAttributes(this.state.items[0]),
filter: this.filterData // <- custom filter method
});
this.setState({ iso: iso });
console.log("iso", iso);
}
已更新 onFilterHandler
并且需要 3 个参数
- addFilter:布尔标志,指示是应添加新的过滤条件还是应删除现有的过滤条件
- filterCondition:字符串条件或条件数组
rule|field|value
- type: string value will be wither
AND
orOR
来判断是匹配type中的所有条件还是匹配任何一个条件
基于参数函数添加新过滤器或搜索现有过滤器并将其从全局 filters
数组中删除。
getFilterObject(condition) {
const conditionItems = condition.split("|");
const filterName = conditionItems[0];
const filterField = conditionItems[1];
const filterValue = conditionItems[2];
return {
condition: condition,
name: filterName,
field: filterField,
value: filterValue,
filterFunc: this.filterFns[filterName]
};
}
onFilterHandler(addFilter, filterCondition, type = "AND") {
// use matching filter function
// let val = filterFns[filterName] || filterValue;
const filterObj = {
type: type,
filters: Array.isArray(filterCondition)
? filterCondition.map((cnd) => this.getFilterObject(cnd))
: [this.getFilterObject(filterCondition)]
};
const existingFilters = this.filters.filter(
(f) =>
f.filters.length === filterObj.filters.length &&
f.filters.every((f) =>
filterObj.filters.some(
(fObjFilter) => fObjFilter.condition === f.condition
)
)
);
if (addFilter && existingFilters.length === 0) {
this.filters = [...this.filters, filterObj];
} else if (!addFilter) {
this.filters = this.filters.filter((f) => !existingFilters.includes(f));
}
this.state.iso.arrange();
}
新增filterData
函数将负责根据当前过滤器列表过滤数据:
filterData(item) {
if (this.filters.length <= 0) {
return true;
}
const filterPredicate = (filterObj) => {
return typeof filterObj.filterFunc === "function"
? filterObj.filterFunc(item, filterObj.field, filterObj.value)
: filterObj.value === "*"
? true
: item.classList.contains(filterObj.value);
};
for (let idx = 0; idx < this.filters.length; idx++) {
const filterObj = this.filters[idx];
const retVal =
filterObj.type === "OR"
? filterObj.filters.some((fObjFilter) => filterPredicate(fObjFilter))
: filterObj.filters.every((fObjFilter) =>
filterPredicate(fObjFilter)
);
if (retVal) {
return retVal;
}
}
return false;
}