react-widgets
Version:
282 lines (235 loc) • 8.24 kB
JavaScript
/** React.DOM */
var React = require('react')
, _ = require('lodash')
, cx = require('../util/cx')
, setter = require('../util/stateSetter')
, compose = require('../util/compose')
, mergeIntoProps = require('../util/transferProps').mergeIntoProps
, directions = require('../util/constants').directions
, DefaultValueItem = require('./value-item')
, Popup = require('../popup/popup')
, List = require('../common/list')
, $ = require('../util/dom');
var btn = require('../common/btn')
, ifShouldUpdate = compose.provided(function(props){
return !_.isEqual(props.value, this.props.value)
})
, ifValueChanges = compose.provided(function(data){
return !_.isEqual(data, this.props.value)
});
var propTypes = {
//main input interface
value: React.PropTypes.any,
onChange: React.PropTypes.func,
data: React.PropTypes.array,
valueField: React.PropTypes.string,
textField: React.PropTypes.string,
valueComponent: React.PropTypes.component,
itemComponent: React.PropTypes.component,
busy: React.PropTypes.bool,
delay: React.PropTypes.number,
duration: React.PropTypes.number, //popup
disabled: React.PropTypes.oneOfType([
React.PropTypes.bool,
React.PropTypes.oneOf(['disabled'])
]),
readOnly: React.PropTypes.oneOfType([
React.PropTypes.bool,
React.PropTypes.oneOf(['readOnly'])
]),
messages: React.PropTypes.shape({
open: React.PropTypes.string,
})
};
module.exports = React.createClass({
displayName: 'DropdownList',
mixins: [
require('../mixins/PureRenderMixin'),
require('../mixins/TextSearchMixin'),
require('../mixins/DataHelpersMixin'),
require('../mixins/RtlParentContextMixin'),
require('../mixins/DataIndexStateMixin')('focusedIndex'),
require('../mixins/DataIndexStateMixin')('selectedIndex')
],
propTypes: propTypes,
getInitialState: function(){
var initialIdx = this._dataIndexOf(this.props.data, this.props.value);
return {
open: false,
selectedIndex: initialIdx,
focusedIndex: initialIdx === -1 ? 0 : initialIdx,
}
},
getDefaultProps: function(){
return {
delay: 500,
value: null,
data: [],
messages: {
open: 'open dropdown'
}
}
},
componentWillReceiveProps: ifShouldUpdate(function(props){
var idx = this._dataIndexOf(props.data, props.value);
this.setSelectedIndex(idx)
this.setFocusedIndex(idx === -1 ? 0 : idx)
}),
// componentDidUpdate: function(){
// if( this.state.open)
// this.setWidth()
// },
render: function(){
var keys = _.keys(propTypes)
, valueItem = this._dataItem( this._data(), this.props.value )
, optID = this._id('_option');
return mergeIntoProps(
_.omit(this.props, keys),
React.DOM.div({ref: "element",
onKeyDown: this._maybeHandle(this._keyDown),
onClick: this._maybeHandle(this.toggle),
onFocus: this._maybeHandle(_.partial(this._focus, true), true),
onBlur: _.partial(this._focus, false),
'aria-expanded': this.state.open,
'aria-haspopup': true,
'aria-activedescendent': this.state.open ? optID : undefined,
'aria-disabled': this.props.disabled,
'aria-readonly': this.props.readOnly,
tabIndex: this.props.disabled ? '-1' : "0",
className: cx({
'rw-dropdown-list': true,
'rw-widget': true,
'rw-state-disabled': this.props.disabled,
'rw-state-readonly': this.props.readOnly,
'rw-state-focus': this.state.focused,
'rw-open': this.state.open,
'rw-rtl': this.isRtl()
})},
React.DOM.span({className: "rw-dropdownlist-picker rw-select rw-btn"},
React.DOM.i({className: "rw-i rw-i-caret-down" + (this.props.busy ? ' rw-loading' : "")},
React.DOM.span({className: "rw-sr"}, this.props.messages.open)
)
),
React.DOM.div({className: "rw-input"},
this.props.valueComponent
? this.props.valueComponent({ item: valueItem })
: this._dataText(valueItem)
),
Popup({open: this.state.open, onRequestClose: this.close, duration: this.props.duration},
React.DOM.div(null,
List({ref: "list",
optID: optID,
'aria-hidden': !this.state.open,
style: { maxHeight: 200, height: 'auto'},
data: this.props.data,
initialVisibleItems: this.props.initialBufferSize,
itemHeight: 18,
selectedIndex: this.state.selectedIndex,
focusedIndex: this.state.focusedIndex,
textField: this.props.textField,
valueField: this.props.valueField,
listItem: this.props.itemComponent,
onSelect: this._maybeHandle(this._onSelect)})
)
)
)
)
},
setWidth: function() {
var width = $.width(this.getDOMNode())
, changed = width !== this.state.width;
if ( changed )
this.setState({ width: width })
},
_focus: function(focused){
var self = this;
clearTimeout(self.timer)
self.timer = setTimeout(function(){
if(focused) self.getDOMNode().focus()
else self.close()
if( focused !== self.state.focused)
self.setState({ focused: focused })
}, 0)
},
_onSelect: function(data, idx, elem){
this.close()
this.change(data)
},
_keyDown: function(e){
var self = this
, key = e.key
, alt = e.altKey
, isOpen = this.state.open;
if ( key === 'End' ) {
if ( isOpen) this.setFocusedIndex(this._data().length - 1)
else change(this._data().length - 1)
e.preventDefault()
}
else if ( key === 'Home' ) {
if ( isOpen) this.setFocusedIndex(0)
else change(0)
e.preventDefault()
}
else if ( key === 'Escape' && isOpen ) {
this.close()
}
else if ( key === 'Enter' && isOpen ) {
change(this.state.focusedIndex)
}
else if ( key === 'ArrowDown' ) {
if ( alt ) this.open()
else if ( isOpen ) this.setFocusedIndex(this.nextFocusedIndex())
else change(this.nextSelectedIndex())
e.preventDefault()
}
else if ( key === 'ArrowUp' ) {
if ( alt ) this.close()
else if ( isOpen ) this.setFocusedIndex(this.prevFocusedIndex())
else change(this.prevSelectedIndex())
e.preventDefault()
}
else
this.search(
String.fromCharCode(e.keyCode)
, this._locate)
function change(idx){
self.change(self._data()[idx])
}
},
change: ifValueChanges(function(data){
var change = this.props.onChange
if ( change ) {
change(data)
this.close()
}
}),
_locate: function(word){
var key = this.state.open ? 'focusedIndex' : 'selectedIndex'
, idx = this.findNextWordIndex(word, this.state[key])
, setIndex = setter(key).bind(this);
if ( idx !== -1)
setIndex(idx)
},
_data: function(){
return this.props.data
},
_maybeHandle: function(handler, disabledOnly){
if ( !(this.props.disabled || (!disabledOnly && this.props.readOnly)))
return handler
},
open: function(){
this.setState({ open: true })
},
close: function(){
this.setState({ open: false })
},
toggle: function(e){
this.state.open
? this.close()
: this.open()
},
_id: function(suffix){
return (_id || (_id = (this.props.id || _.uniqueId('rw_')))) + suffix
}
})
var _id =''