美文网首页
React16.13 简单封装一个select组件

React16.13 简单封装一个select组件

作者: IamaStupid | 来源:发表于2020-08-06 14:11 被阅读0次

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

相关文章

网友评论

      本文标题:React16.13 简单封装一个select组件

      本文链接:https://www.haomeiwen.com/subject/nkrzrktx.html