我正在尝试使用react.js激活shuffle.js组件功能(搜索,过滤和排序)。但是,网站上的文档非常有限。我知道我需要添加搜索输入和一些按钮来完成我想要的操作,但是我不确定如何连接搜索框输入和其他按钮事件来操纵光电栅格(或容器中的其他元素),即被反应渲染。
我已经导入shuffle.js作为节点模块,并在react页面上对其进行了初始化。他们提供的基本代码似乎可以正常工作并显示照片网格,但是,仅此而已。我也想实现搜索,过滤和排序功能,但是在react.js中没有有关如何执行此操作的文档。下面的代码显示了photogrid的实现,但仅此而已。
import React, {Component} from "react";
import Shuffle from 'shufflejs';
class PhotoGrid extends React.Component {
constructor(props) {
super(props);
const grayPixel = 'data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==';
const blackPixel = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=';
const greenPixel = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mO02Vz4HwAE9AJhcLBN6AAAAABJRU5ErkJggg==';
this.state = {
photos: [{
id: 4,
src: grayPixel
},
{
id: 5,
src: blackPixel
},
{
id: 6,
src: greenPixel
},
],
searchTerm: '',
sortByTitle: '',
sortByDate: '',
sortByPopularity: '',
filterCategory: ''
};
this.filters = {
cat1: [],
cat2: [],
};
this.wb = this.props.dataWB;
this.element = React.createRef();
this.sizer = React.createRef();
this._handleSearchKeyup = this._handleSearchKeyup.bind(this);
this._handleSortChange = this._handleSortChange.bind(this);
this._handleCategory1Change = this._handleCategory1Change.bind(this);
this._handleCategory2Change = this._handleCategory2Change.bind(this);
this._getCurrentCat1Filters = this._getCurrentCat1Filters.bind(this);
this._getCurrentCat2Filters = this._getCurrentCat2Filters.bind(this);
}
/**
* Fake and API request for a set of images.
* @return {Promise<Object[]>} A promise which resolves with an array of objects.
*/
_fetchPhotos() {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{
id: 4,
username: '@stickermule',
title:'puss',
date_created: '2003-09-01',
popularity: '233',
category1:'animal',
category2:'mammals',
name: 'Sticker Mule',
src: 'https://images.unsplash.com/photo-1484244233201-29892afe6a2c?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=800&h=600&fit=crop&s=14d236624576109b51e85bd5d7ebfbfc'
},
{
id: 5,
username: '@prostoroman',
date_created: '2003-09-02',
popularity: '232',
category1:'industry',
category2:'mammals',
title:'city',
name: 'Roman Logov',
src: 'https://images.unsplash.com/photo-1465414829459-d228b58caf6e?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=800&h=600&fit=crop&s=7a7080fc0699869b1921cb1e7047c5b3'
},
{
id: 6,
username: '@richienolan',
date_created: '2003-09-03',
popularity: '231',
title:'nature',
category1:'art',
category2:'insect',
name: 'Richard Nolan',
src: 'https://images.unsplash.com/photo-1478033394151-c931d5a4bdd6?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=800&h=600&fit=crop&s=3c74d594a86e26c5a319f4e17b36146e'
}
]);
}, 300);
});
}
_whenPhotosLoaded(photos) {
return Promise.all(photos.map(photo => new Promise((resolve) => {
const image = document.createElement('img');
image.src = photo.src;
if (image.naturalWidth > 0 || image.complete) {
resolve(photo);
} else {
image.onload = () => {
resolve(photo);
};
}
})));
}
_handleSortChange(evt) {
var value = evt.target.value.toLowerCase();
function sortByDate(element) {
return element.getAttribute('data-created');
}
function sortByPopularity(element) {
return element.getAttribute('data-popularity');
}
function sortByTitle(element) {
return element.getAttribute('data-title').toLowerCase();
}
let options;
if (value == 'date-created') {
options = {
reverse: true,
by: sortByDate,
};
} else if (value == 'title') {
options = {
by: sortByTitle,
};
} else if (value == 'popularity') {
options = {
reverse: true,
by: sortByPopularity,
};
} else if (value == 'default') {
this.shuffle.filter('all');
} else {
options = {};
}
this.shuffle.sort(options);
};
_getCurrentCat1Filters = function () {
return this.filters.cat1.filter(function (button) {
return button.classList.contains('active');
}).map(function (button) {
console.log('button value: '+button.getAttribute('data-value'))
return button.getAttribute('data-value');
});
};
_getCurrentCat2Filters = function () {
return this.filters.cat2.filter(function (button) {
return button.classList.contains('active');
}).map(function (button) {
console.log('button value: '+button.getAttribute('data-value'))
// console.log('button value: '+button.getAttribute('data-value'))
return button.getAttribute('data-value');
});
};
_handleCategory1Change = function (evt) {
var button = evt.currentTarget;
console.log(button)
// Treat these buttons like radio buttons where only 1 can be selected.
if (button.classList.contains('active')) {
button.classList.remove('active');
} else {
this.filters.cat1.forEach(function (btn) {
btn.classList.remove('active');
});
button.classList.add('active');
}
this.filters.cat1 = this._getCurrentCat1Filters();
console.log('current cat contains : '+this.filters.cat1);
this.filter();
};
/**
* A color button was clicked. Update filters and display.
* @param {Event} evt Click event object.
*/
_handleCategory2Change = function (evt) {
var button = evt.currentTarget;
// Treat these buttons like radio buttons where only 1 can be selected.
if (button.classList.contains('active')) {
button.classList.remove('active');
} else {
this.filters.cat2.forEach(function (btn) {
btn.classList.remove('active');
});
button.classList.add('active');
}
this.filters.cat2 = this._getCurrentCat2Filters();
console.log('current cat contains : '+this.filters.cat2);
this.filter();
};
filter = function () {
if (this.hasActiveFilters()) {
this.shuffle.filter(this.itemPassesFilters.bind(this));
} else {
this.shuffle.filter(Shuffle.ALL_ITEMS);
}
};
itemPassesFilters = function (element) {
var cat1 = this.filters.cat1;
var cat2 = this.filters.cat2;
var cat1 = element.getAttribute('data-category1');
var cat2 = element.getAttribute('data-category2');
// If there are active shape filters and this shape is not in that array.
if (cat1.length > 0 && !cat1.includes(cat1)) {
return false;
}
// If there are active color filters and this color is not in that array.
if (cat2.length > 0 && !cat2.includes(cat2)) {
return false;
}
return true;
};
/**
* If any of the arrays in the `filters` property have a length of more than zero,
* that means there is an active filter.
* @return {boolean}
*/
hasActiveFilters = function () {
return Object.keys(this.filters).some(function (key) {
return this.filters[key].length > 0;
}, this);
};
_handleSearchKeyup(event) {
this.setState({
searchTerm: event.target.value.toLowerCase()
}, () => {
this.shuffle.filter((element) => {
return element.dataset.name.toLowerCase().includes(this.state.searchTerm) || element.dataset.username.toLowerCase().includes(this.state.searchTerm);
})
})
}
componentDidMount() {
// The elements are in the DOM, initialize a shuffle instance.
this.shuffle = new Shuffle(this.element.current, {
itemSelector: '.js-item',
sizer: this.sizer.current,
});
// Kick off the network request and update the state once it returns.
this._fetchPhotos()
.then(this._whenPhotosLoaded.bind(this))
.then((photos) => {
this.setState({
photos
});
});
}
componentDidUpdate() {
// Notify shuffle to dump the elements it's currently holding and consider
// all elements matching the `itemSelector` as new.
this.shuffle.resetItems();
}
componentWillUnmount() {
// Dispose of shuffle when it will be removed from the DOM.
this.shuffle.destroy();
this.shuffle = null;
}
render() {
return (
<div>
<div id='searchBar'>
<input type="text" className='js-shuffle-search' onChange={ this._handleSearchKeyup } value={ this.state.searchTerm } />
</div>
<div id='gridActions'>
<h2>Filter By cat 1</h2>
<button onClick={ this._handleCategory1Change } value='all'>All</button>
<button onClick={ this._handleCategory1Change } value='art'>Art</button>
<button onClick={ this._handleCategory1Change } value='industry'>Industry</button>
<button onClick={ this._handleCategory1Change } value='animal'>Animal</button>
<h2>Filter By cat 2</h2>
<button onClick={ this._handleCategory2Change } value='all'>All</button>
<button onClick={ this._getCurrentCat1Filters } value='mammals'>Mammals</button>
<button onClick={ this._getCurrentCat2Filters } value='insects'>Insects</button>
<h2>Sort By</h2>
<button onClick={ this._handleSortChange } value='default'>Default</button>
<button onClick={ this._handleSortChange } value='date-created'>By Date</button>
<button onClick={ this._handleSortChange } value='title'>By Title</button>
<button onClick={ this._handleSortChange } value='popularity'>By Popularity</button>
</div>
<div ref={ this.element } id='grid' className="row my-shuffle-container shuffle"> {
this.state.photos.map(image =>
<PhotoItem { ...image } />)}
<div ref={ this.sizer } className="col-1@xs col-1@sm photo-grid__sizer"></div>
</div>
</div>
);
}
}
function PhotoItem({id, src, category1, category2, date_created, popularity, title, name, username }) {
return (
<div key={id}
className="col-lg-3 js-item"
data-name={name}
data-title={title}
data-date-created={date_created}
data-popularity={popularity}
data-category1={category1}
data-cetagory2={category2}
data-username={username}>
<img src={src} style={{width : "100%",height :"100%"}}/>
</div>
)
}
export default PhotoGrid;
照片网格现在什么也不做,只显示无法搜索,过滤和排序的照片。
答案 0 :(得分:0)
仅从文档来看,我还没有尝试过,但是应该可以。
Shuffle
的实例具有一种filter
方法,该方法采用字符串或字符串数组来按“组”过滤元素,或者使用回调函数来执行更复杂的搜索。更新组件状态后,您应该调用this.shuffle.filter
,即:
_handleSearchKeyup(event){
this.setState({searchTerm : event.target.value}, () => {
this.shuffle.filter((element) => { /* use this.state.searchTerm to return matching elements */ } );
})
}
在构建fiddle之后进行编辑。
回调函数查看data-name
和data-username
属性,以检查它们是否包含搜索字符串
_handleSearchKeyup(event){
this.setState({searchTerm : event.target.value.toLowerCase()}, () => {
this.shuffle.filter((element) => {
return (
element.dataset.name.toLowerCase().includes(this.state.searchTerm) ||
element.dataset.username.toLowerCase().includes(this.state.searchTerm)
);
})
})
}
要使上述方法起作用,还需要将这些属性添加到DOM节点,因此更新PhotoItem
组件:
function PhotoItem({ id, src, name, username }) {
return (
<div key={id}
className="col-md-3 photo-item"
data-name={name}
data-username={username}>
<img src={src} style={{width : "100%",height :"100%"}}/>
</div>
)
}
答案 1 :(得分:0)
与pawel的回答相反,我认为该库在DOM上运行。 。
经典输入处理程序使用setState
方法在状态内保存值。作为状态更改的结果,React刷新/更新了虚拟DOM中的视图(使用render()
方法)。在此之后,更新实际DOM使其与虚拟DOM保持同步。
在这种情况下,lib在真实的DOM元素上进行操作-调用render()
(由setState()
强制执行)将覆盖Shuffle先前所做的更改。为了避免这种情况,我们应该避免使用setState
。
只需将过滤器和排序参数直接保存在组件实例中(使用this
):
_handleSearchKeyup(event){
this.searchTerm = event.target.value;
this.shuffle.filter((element) => { /* use this.searchTerm to return matching elements */ } );
}
在构造函数中初始化所有参数(fe filterCategories
,searchTerm
,sortBy
和sortOrder
),并在一个this.shuffle.filter()
调用中使用它们(第二个参数用于排序对象)。准备一些帮助程序以创建组合的过滤功能(过滤和搜索的混合),排序要容易得多。
setState
可用于clear all filters
按钮-强制重新渲染-记住清除处理程序中的所有参数。
用于排序顺序声明
this.reverse = true; // in constructor
this.orderBy = null;
处理程序
_handleSortOrderChange = () => {
this.reverse = !this.reverse
// call common sorting function
// extracted from _handleSortChange
// this._commonSortingFunction()
}
_handleSortByChange = (evt) => {
this.orderBy = evt.target.value.toLowerCase();
// call common sorting function
// extracted from _handleSortChange
// this._commonSortingFunction()
}
_commonSortingFunction = () => {
// you can declare sorting functions in main/component scope
let options = { reverse: this.reverse }
const value = this.orderBy;
if (value == 'date-created') {
options.by = sortByDate // or this.sortByDate
} else if (value == 'title') {
options.by = sortByTitle
//...
//this.shuffle.sort(options);
您还可以将就绪的options
排序对象存储在由处理程序更新的组件实例(this.options
)中。 _commonSortingFunction()
可以使用此值来调用this.shuffle.sort
,也可以通过过滤函数(第二个参数)来使用此值。
后退按钮(无需绑定)
<button onClick={this._handleSortOrder}>Reverse order</button>
如果您想使用“正常”反应,setState
可以将所有过滤(searchBar,gridActions)移动(封装)到单独的组件中。
状态更新将仅强制重新渲染“工具”,而不影响通过重排(父级未重新渲染)在实际DOM中管理的元素。这样,您可以通过使用条件渲染来避免手动的css操作(“活动”)(还有更多的可能性-分别列出活动过滤器,显示顺序asc / desc,仅在更改某项时显示重置等)。
通过传递this.shuffle
作为道具,您可以简单地在父级中调用搜索/过滤器/排序。