React16.13 简单封装一个select组件
GInput.css
html, body {
height: 100%; padding: 0; margin: 0;
}
/*GInput*/
.g-input-box > .g-input {
border: 1px solid #666;
}
.g-input-box.error > .g-input {
border: 1px solid #f00;
}
.g-input-box.error > .error-msg, .g-input-box.error > .g-input {
color: #f00;
}
/*GSelect*/
.hidden {
display: none;
}
.g-input {
display: block; box-sizing: border-box; width: 100%;
}
.select-outer {
border: 1px solid #666;
}
.select-box, .select-outer {
position: relative; display: inline-block;cursor: pointer; width: 100%;
}
.select-box > .g-input-box > .g-input {
cursor: pointer; border: 0; line-height: 22px; height: 22px; padding: 0; margin: 0;
}
.select-box > .g-input-box{
padding-right: 22px;
}
.sel-down-arrow {
width: 18px; height: 18px; position: absolute; right: 0; top: 50%; margin-top: -9px;
background-color: brown;
}
/* 禁止选中文本,双击arrow的时候,有时候会把下拉框中的文本选中,禁止arrow文本选中,可以避免这种发生这种情况 */
.sel-down-arrow {
-moz-user-select:none; /*火狐*/
-webkit-user-select:none; /*webkit浏览器*/
-ms-user-select:none; /*IE10*/
-khtml-user-select:none; /*早期浏览器*/
user-select:none;
}
.down-list-box {
position: absolute;left: -1px; width: 100%; z-index: 999;
background-color: #fff; border: 1px solid #666;
}
.down-list-box > p {
line-height: 32px; padding: 0; text-align: center; margin: 0;
}
.data-ul {
line-height: 30px; max-height: 150px; overflow-y: auto;
}
.data-ul > li:hover, .data-ul > li.checked {
background-color: rgb(211, 208, 208);
}
.select-outer.error {
border: 1px solid #f00;
}
.select-outer.error .g-input {
color: #f00;
}
.select-outer > .error-msg {
position: absolute; top: 100%; left: 0; width: 100%; line-height: 20px; color: #f00; font-size: small; z-index: 9;
}
GSelect.jsx
import React, { Component } from 'react';
import './GInput.css'
// props 参数:
// --props
// ------可选:source:Array [{val,text,checked:boolean}] | noDataDes className
// ------可选:onChange事件,通过事件回调获取select选择的值
// ------可选:rule:{ regx:RegExp | function, [callback: function, errorMsg: '', requiredRuleInit: boolean] } 验证规则
//------------------regx function返回true,或者正则test()返回true,则表示当前值符合规则,如果为false,则表示当前值有错误
//------------------requiredRuleInit 初始化数据的时候是否校验,默认true校验
class GSelect extends Component {
constructor(props) {
super();
if (!window.GEelementIndex) {
window.GEelementIndex = 1
}
let selectInitData = this.getInitData(props)
let errMsg = ''
let hasErrorFirst = false
if (props.rule) {
errMsg = props.rule.errorMsg || ''
if (!(typeof props.rule.requiredRuleInit === 'boolean' && !props.rule.requiredRuleInit)) {
// requiredRuleInit: 非false 进行校验
hasErrorFirst = this.regxRule(props, selectInitData.value)
}
}
this.state = {
GEelementIndex: window.GEelementIndex++,
source: props.source || [],
noDataDes: props.noDataDes || '暂无数据',
isDownHidden: true,
onChange: props.onChange || null,
valueText: selectInitData.valueText,
value: selectInitData.value,
checkIndex: selectInitData.checkIndex,
stylePosDownListBox: {
bottom: ''
},
hasError: hasErrorFirst,
errorMsg: errMsg
}
this.handleSelectDown = this.handleSelectDownFn.bind(this)
this.handleDropSelectClick = this.handleDropSelectClickFn.bind(this)
}
UNSAFE_componentWillMount () {}
componentDidMount () {
if(!window.cancelSelectPopFn) {
window.cancelSelectPopFn = (event) => {
let $target = event.target
let classStr = $target.className
if (classStr.indexOf('sel-down-arrow') > -1 || classStr.indexOf('select-box') > -1) {
return false
}
else if (classStr.indexOf('g-input') > -1 || classStr.indexOf('g-input-box') > -1) {
if($target.getAttribute('data-select')||($target.firstChild && $target.firstChild.getAttribute('data-select'))) {
return false
}
}
console.log('触发了GSelect监听的body点击事件。')
this.closeDropDown()
}
// false是冒泡监听事件,true是捕获监听
document.body.addEventListener('click', window.cancelSelectPopFn, false);
}
}
closeDropDown ($downListBoxSelf) {
let selectDownWrap = document.querySelectorAll('.down-list-box:not(.hidden)')
if(selectDownWrap && selectDownWrap.length > 0) {
for (let i = 0; i < selectDownWrap.length; i++) {
if (selectDownWrap[i].className.indexOf('hidden') < 0 && selectDownWrap[i] !== $downListBoxSelf) {
selectDownWrap[i].classList.add('hidden')
// todo
// 这种情况也应该初始化下拉框位置的,但是不能使用this太麻烦,暂时不处理
// this.initDownBoxPos()
}
}
}
}
handleSelectDownFn (event) {
console.log('触发了Gselect点击事件。')
event = event || window.event;
let dropDownUl = document.querySelector(`#select-${this.state.GEelementIndex}>.down-list-box`);
// 隐藏其他打开的下拉框
this.closeDropDown(dropDownUl)
//
if (dropDownUl && dropDownUl.className.indexOf('hidden') > -1) {
// 下拉框隐藏, 切换成显示
if (event) {
// 展示下拉框
if (this.state.isDownHidden) {
this.setState({
isDownHidden: false
})
} else {
// isDownHidden已经是false,但是下拉框却显示了,说明是body点击事件触发的 dropDownUl hidden
dropDownUl.classList.remove('hidden')
}
// 计算下拉框位置
let selectHeight = document.getElementById(`select-${this.state.GEelementIndex}`).clientHeight
let sizeObj = {
selectHeight: selectHeight,
topDistanceY: event.clientY - selectHeight,
downDistanceY: window.innerHeight - event.clientY - selectHeight
}
let timer = setTimeout(() => {
clearTimeout(timer)
timer = null
this.caculateDownBoxPos(sizeObj, dropDownUl)
}, 0)
}
} else {
// 下拉框显示, 切换成隐藏
this.setState({
isDownHidden: true
})
this.initDownBoxPos()
}
event.preventDefault();
event.stopPropagation();
}
renderDownList () {
return this.state.source.map((item, i) => {
return (
<li key={'sel' + window.GEelementIndex + '_' + i} data-i={i} data-val={item.val} className={i == this.state.checkIndex ? 'checked' : ''}>
{item.text}
</li>
)
})
}
getInitData(props) {
let obj = {}
let arr = props.source
if(Array.isArray(arr)) {
if (arr.length > 0) {
let check = 0
let hasCheck = false
for (let i = 0; i < arr.length; i++) {
if (arr[i].val && arr[i].text && arr['checked']) {
check = i
hasCheck = true
break
}
}
obj = {
valueText: arr[check].text || '',
value: arr[check].val || '',
checkIndex: hasCheck ? check : -1
}
}
} else {
console.error('GSelect source data is Array, yours wrong.')
}
return obj
}
handleDropSelectClickFn (event) {
event = event || window.event
let tar = event.target
if (tar.className.indexOf('checked') > -1) {
return false
}
let obj = {
valueText: tar.textContent || '',
value: tar.getAttribute('data-val') || '',
checkIndex: tar.getAttribute('data-i')
}
this.setState({
valueText: obj.valueText,
value: obj.value,
checkIndex: obj.checkIndex
})
if(typeof this.state.onChange === 'function') {
this.state.onChange(obj)
}
}
initDownBoxPos () {
if (this.state.stylePosDownListBox.bottom) {
this.setState({
stylePosDownListBox: {
bottom: ''
}
})
}
}
caculateDownBoxPos (sizeObj, dropDownBox) {
dropDownBox = dropDownBox ? dropDownBox : document.querySelector(`#select-${this.state.GEelementIndex}>.down-list-box`);
sizeObj.dropDownHeight = dropDownBox.clientHeight
if (sizeObj.downDistanceY >= sizeObj.dropDownHeight) {
return false
} else if (sizeObj.topDistanceY >= sizeObj.dropDownHeight) {
this.setState({
stylePosDownListBox: {
bottom: sizeObj.selectHeight + 'px'
}
})
}
}
hasRule (props) {
let flag = ''
if (props.rule) {
if (props.rule.regx instanceof RegExp) {
flag = 'RegExp'
}
else if (typeof props.rule.regx === 'function') {
flag = 'function'
}
}
return flag
}
regxRule (props, expValue) {
expValue = expValue || ''
let error = false
let ruleFlag = this.hasRule(props)
if (ruleFlag) {
let flag;
if(ruleFlag === 'RegExp') {
flag = new RegExp(props.rule.regx).test(expValue)
}
else if (ruleFlag === 'function') {
flag = props.rule.regx(expValue)
}
if (!flag) {
error = true
}
if (typeof props.rule.callback === 'function') {
props.rule.callback(flag)
}
}
return error
}
render () {
return (
<div className={'select-outer ' + this.props.className + (this.state.hasError? ' error': '')} id={'select-' + this.state.GEelementIndex}>
<div className='select-box' onClick={this.handleSelectDown}>
<div className='g-input-box'>
<input type='text' className='g-input' readOnly data-select='select' value={this.state.valueText}/>
</div>
<div className='sel-down-arrow'></div>
</div>
<div className={this.state.isDownHidden ? 'down-list-box hidden' : 'down-list-box'}
style={this.state.stylePosDownListBox.bottom ? this.state.stylePosDownListBox : {}}>
{
this.state.source.length > 0 ?
(
<ul className='data-ul' onClick={this.handleDropSelectClick}>
{ this.renderDownList() }
</ul>
)
:
(
<p>{this.state.noDataDes}</p>
)
}
</div>
{
this.state.hasError && (
<div className='error-msg'>
{this.state.errorMsg}
</div>
)
}
</div>
)
}
}
export default GSelect;
UseSelectPage.jsx
import React, { Component } from "react";
import GSelect from './components/form/GSelect.jsx'
import '../static/main.css'
import GInput from "./components/form/GInput.jsx";
class UseSelectPage extends Component {
constructor(props) {
super();
this.state={
form: {
listArr: [
{val: 'abc1', text: 'abc输入法1'},
{val: 'abc2', text: 'abc输入法2'},
{val: 'abc3', text: 'abc输入法3'},
{val: 'abc4', text: 'abc输入法4'},
{val: 'abc5', text: 'abc输入法5'},
{val: 'abc6', text: 'abc输入法6'},
{val: 'abc7', text: 'abc输入法7'},
{val: 'abc8', text: 'abc输入法8'}
]
},
rules: {
selectRule: {
regx: function (val) {
if (val !== null && val !== '' && val !== undefined) {
return true
} else {
return false
}
},
callback: function (flag) {
console.log('pwdRule:', flag)
},
errorMsg: '该项为必选项'
}
}
}
this.handleInput123ChangeFn = this.handleInput123Change.bind(this)
}
UNSAFE_componentWillMount () {}
componentDidMount () {}
handleInput123Change (val) {
console.log('handleInput123Change:', val)
}
render () {
return (
<div style={{'paddingTop': '300px'}}>
<h1>ctx page</h1>
<GInput></GInput>
<div style={{position: 'absolute', zIndex: 999, width: '100%'}}>
<div className="sel-1">
<GSelect className="abc-sel" source={this.state.form.listArr}
rule={this.state.rules.selectRule}
onChange={this.handleInput123ChangeFn}></GSelect>
</div>
<div className="sel-1">
<GSelect className="abc-sel" source={this.state.form.listArr} onChange={this.handleInput123ChangeFn}></GSelect>
</div>
<div className="sel-1">
<GSelect className="abc-sel" source={[]}
rule={this.state.rules.selectRule}
onChange={this.handleInput123ChangeFn}></GSelect>
</div>
</div>
</div>
)
}
}
export default UseSelectPage;
效果图:
image.png
网友评论