From bd399d9c92b0b4ad2456b3549c615bd73207f1df Mon Sep 17 00:00:00 2001 From: jquense Date: Tue, 3 Nov 2015 00:43:57 +0700 Subject: [PATCH] [added] grouping SelectList items --- docs/components/demos/MultiSelect.jsx | 4 +- docs/components/demos/dropdownlist.jsx | 5 +- docs/components/examples/disabled.js | 16 +++++-- docs/components/pages/Multiselect.api.md | 24 +++++----- docs/components/pages/SelectList.api.md | 51 +++++++++++++++----- src/SelectList.jsx | 59 +++++++++--------------- src/less/selectlist.less | 9 ++-- src/util/interaction.js | 2 +- 8 files changed, 95 insertions(+), 75 deletions(-) diff --git a/docs/components/demos/MultiSelect.jsx b/docs/components/demos/MultiSelect.jsx index 5ca9aa732..9590230b3 100644 --- a/docs/components/demos/MultiSelect.jsx +++ b/docs/components/demos/MultiSelect.jsx @@ -47,8 +47,8 @@ module.exports = React.createClass({ id: list.length + 1, name: tag, first: parts[0], - last: parts[1], - }), + last: parts[1] + }) }) } diff --git a/docs/components/demos/dropdownlist.jsx b/docs/components/demos/dropdownlist.jsx index 3e4135699..f87c9180b 100644 --- a/docs/components/demos/dropdownlist.jsx +++ b/docs/components/demos/dropdownlist.jsx @@ -39,7 +39,7 @@ var DropdownApi = React.createClass({ getInitialState: function(){ return { - duration: 250, + duration: 250 } }, @@ -59,6 +59,8 @@ var DropdownApi = React.createClass({ textField: 'name' } + //let disabled = this.state.disabled === true || Array.isArray(this.state.disabled); + return (
@@ -153,4 +155,3 @@ var DropdownApi = React.createClass({ }); module.exports = DropdownApi; - diff --git a/docs/components/examples/disabled.js b/docs/components/examples/disabled.js index 3b37052df..d7c94019d 100644 --- a/docs/components/examples/disabled.js +++ b/docs/components/examples/disabled.js @@ -1,5 +1,6 @@ 'use strict'; -module.exports = function(widgetName, prop){ +module.exports = function(widgetName, prop, isArray=true){ + var value = !isArray ? '"orange"' : '["orange", "red"]' var code = ` var ${widgetName} = ReactWidgets.${widgetName} @@ -9,10 +10,15 @@ var Example = React.createClass({ render() { return (
- <${widgetName} ${prop} data={colors}/> - <${widgetName} ${prop}={colors.slice(1,2)} + <${widgetName} ${prop} data={colors} - defaultValue={colors.slice(0,2)}/> + defaultValue={${value}} + /> + <${widgetName} + ${prop}={colors.slice(1,2)} + data={colors} + defaultValue={${value}} + />
) } }); @@ -20,4 +26,4 @@ var Example = React.createClass({ ReactDOM.render(, mountNode);` return code -} \ No newline at end of file +} diff --git a/docs/components/pages/Multiselect.api.md b/docs/components/pages/Multiselect.api.md index 5b2d91faf..2edc7fa68 100644 --- a/docs/components/pages/Multiselect.api.md +++ b/docs/components/pages/Multiselect.api.md @@ -18,12 +18,12 @@ change event Handler that is called when the value is changed. The handler is ca ### onSelect?{ type:'Function(Any value)'} -This handler fires when an item has been selected from the list. It fires before the `onChange` handler, and fires +This handler fires when an item has been selected from the list. It fires before the `onChange` handler, and fires regardless of whether the value has actually changed. ### onCreate?{ type: 'Function(String searchTerm)'} -This handler fires when the user chooses to create a new tag, not in the data list. It is up to the widget parent to implement creation logic, +This handler fires when the user chooses to create a new tag, not in the data list. It is up to the widget parent to implement creation logic, a common implementation is shown below, where the new tag is selected and added to the data list. @@ -36,18 +36,18 @@ properties comprise the value field (such as an id) and the field used to label ### valueField?{ type: 'String' } -A dataItem field name for uniquely identifying items in the `data` list. A `valueField` is required +A dataItem field name for uniquely identifying items in the `data` list. A `valueField` is required when the `value` prop is not itself a dataItem. A `valueField` is useful when specifying the selected item, by its `id` instead of using the model as the value. -When a `valueField` is not provided, the {widgetName} will use strict equality checks (`===`) to locate +When a `valueField` is not provided, the {widgetName} will use strict equality checks (`===`) to locate the `value` in the `data` list. ### textField?{ type: 'String | Function(dataItem)' } -Specify which data item field to display in the ${widgetName} and selected item. The `textField` prop +Specify which data item field to display in the ${widgetName} and selected item. The `textField` prop may also also used as to find an item in the list as you type. Providing an accessor function allows for computed text values @@ -68,14 +68,14 @@ renders the text of the selected item (specified by `textfield`) ### groupBy?{ type: 'String | Function(Any dataItem)' } -Determines how to group the {widgetName} dropdown list. Providing a `string` will group +Determines how to group the {widgetName} values. Providing a `string` will group the `data` array by that property. You can also provide a 'function' which should return the group value. ### groupComponent?{ type: 'Component' } -This component is used to render each option group, when `groupBy` is specified. By +This component is used to render each option group, when `groupBy` is specified. By default the `groupBy` value will be used. @@ -93,7 +93,7 @@ The `defaultSearchTerm` prop can be used to set an initialization value for unco ### onSearch?{ type: 'Function(String searchTerm)' } -Called when the value of the text box changes either from typing or a pasted value. `onSearch` should +Called when the value of the text box changes either from typing or a pasted value. `onSearch` should be used when the `searchTerm` prop is set. ### open?{ type: 'Boolean' } @@ -110,8 +110,8 @@ when the `open` prop is set otherwise the widget will never open or close. ### filter?{ type: '[String, Function(dataItem, searchTerm)]', default: 'startsWith' } -Specify a filtering method used to reduce the items in the dropdown as you type. There are a few prebuilt filtering -methods that can be specified by passing the `String` name. You can explicitly opt out of filtering by +Specify a filtering method used to reduce the items in the dropdown as you type. There are a few built-in filtering +methods that can be specified by passing the `String` name. You can explicitly opt out of filtering by setting filter to `false` To handle custom filtering techniques provide a `function` that returns `true` or `false` for each passed in item @@ -150,11 +150,11 @@ Place the widget in a readonly mode, If an `Array` of values is passed in only t ### isRtl?{ type: 'Boolean', default:"false" } -mark whether the widget should render right-to-left. This property can also be implicitly passed to the widget through +mark whether the widget should render right-to-left. This property can also be implicitly passed to the widget through a `childContext` prop (`isRtl`) this allows higher level application components to specify the direction. -### messages?{ type: 'Object' } +### messages?{ type: 'Object' } Object hash containing display text and/or text for screen readers. Use the `messages` object to localize widget text and increase accessibility. diff --git a/docs/components/pages/SelectList.api.md b/docs/components/pages/SelectList.api.md index 221bf39f3..125127bad 100644 --- a/docs/components/pages/SelectList.api.md +++ b/docs/components/pages/SelectList.api.md @@ -12,7 +12,7 @@ the `data` array; widgets can have values that are not in their list. ### onChange?{ type: 'Function(Array|Any values)' } -Change event handler that is called when the value is changed. `values` will be an array +Change event handler that is called when the value is changed. `values` will be an array when `multiple` prop is set. @@ -25,21 +25,27 @@ properties comprise the value field (such as an id) and the field used to label ### valueField?{ type: 'String' } -A dataItem field name for uniquely identifying items in the `data` list. A `valueField` is required +A dataItem field name for uniquely identifying items in the `data` list. A `valueField` is required when the `value` prop is not itself a dataItem. A `valueField` is useful when specifying the selected item, by its `id` instead of using the model as the value. -When a `valueField` is not provided, the {widgetName} will use strict equality checks (`===`) to locate +When a `valueField` is not provided, the {widgetName} will use strict equality checks (`===`) to locate the `value` in the `data` list. ### textField?{ type: 'String | Function(dataItem)' } -Specify which data item field to display in the ${widgetName} and selected item. The `textField` prop may also also used as to find an item in the list as you type. Providing an accessor function allows for computed text values +Specify which data item field to display in the ${widgetName} and selected item. The `textField` prop may also also +used as to find an item in the list as you type. Providing an accessor function allows for computed text values +### multiple?{ type: 'Boolean' } + +Whether or not the {widgetName} allows multiple selection or not. when `false` the {widgetName} will +render as a list of radio buttons, and checkboxes when `true`. + ### itemComponent?{ type: 'Component' } This component is used to render each item in the {widgetName}. The default component @@ -47,17 +53,26 @@ renders the text of the selected item (specified by `textfield`) -### multiple?{ type: 'Boolean' } +### groupBy?{ type: 'String | Function(Any dataItem)' } -Whether or not the {widgetName} allows multiple selection or not. when `false` the {widgetName} will -render as a list of radio buttons, and checkboxes when `true`. +Determines how to group the {widgetName} dropdown list. Providing a `string` will group +the `data` array by that property. You can also provide a 'function' which should return the group value. + + + +### groupComponent?{ type: 'Component' } + +This component is used to render each option group, when `groupBy` is specified. By +default the `groupBy` value will be used. + + ### onMove?{ type: 'Function(HTMLElement list, HTMLElement focusedNode, Any focusedItem)' } A handler called when focus shifts on the {widgetName}. Internally this is used to ensure the focused item is in view. -If you want to define your own "scrollTo" behavior or just disable the default one specify an `onMove` handler. -The handler is called with the relevant DOM nodes needed to implement scroll behavior: the list element, +If you want to define your own "scrollTo" behavior or just disable the default one specify an `onMove` handler. +The handler is called with the relevant DOM nodes needed to implement scroll behavior: the list element, the element that is currently focused, and a focused value. @@ -74,15 +89,29 @@ Disable the widget, if an `Array` of values is passed in only those values will ### readOnly?{ type: '[Boolean, Array]' } -Place the {widgetName} in a readonly mode, If an `Array` of values is passed in only those values will be readonly. +Place the {widgetName} in a read-only mode, If an `Array` of values is passed in only those values will be read-only. +### groupBy?{ type: 'String | Function(Any dataItem)' } + +Determines how to group the {widgetName}. Providing a `string` will group +the `data` array by that property. You can also provide a function which should return the group value. + + + +### groupComponent?{ type: 'Component' } + +This component is used to render each option group, when `groupBy` is specified. By +default the `groupBy` value will be used. + + + ### isRtl?{ type: 'Boolean', default:"false" } mark whether the {widgetName} should render right-to-left. This property can also be implicitly passed to the widget through a `childContext` prop (`isRtl`) this allows higher level application components to specify the direction. -### messages?{ type: 'Object' } +### messages?{ type: 'Object' } Object hash containing display text and/or text for screen readers. Use the `messages` object to localize widget text and increase accessibility. diff --git a/src/SelectList.jsx b/src/SelectList.jsx index 217566225..dda23fd19 100644 --- a/src/SelectList.jsx +++ b/src/SelectList.jsx @@ -6,6 +6,8 @@ import compat from './util/compat'; import CustomPropTypes from './util/propTypes'; import PlainList from './List'; +import GroupableList from './ListGroupable'; +import ListOption from './ListOption'; import validateList from './util/validateListInterface'; import scrollTo from 'dom-helpers/util/scrollTo'; @@ -14,10 +16,7 @@ import { dataItem } from './util/dataHelpers'; import { widgetEditable, widgetEnabled } from './util/interaction'; import { instanceId, notify } from './util/widgetHelpers'; -import { - move, contains - , isDisabled, isReadOnly - , isDisabledItem, isReadOnlyItem } from './util/interaction'; +import { isDisabled, isReadOnly, contains } from './util/interaction'; let { omit, pick } = _; @@ -53,6 +52,7 @@ let propTypes = { } + var SelectList = React.createClass({ propTypes: propTypes, @@ -109,10 +109,10 @@ var SelectList = React.createClass({ render() { let { - className, tabIndex, busy + className, tabIndex, busy, groupBy , listComponent: List } = this.props; - List = List || PlainList + List = List || (groupBy && GroupableList) || PlainList let elementProps = omit(this.props, Object.keys(propTypes)); let listProps = pick(this.props, Object.keys(List.propTypes)); @@ -178,11 +178,8 @@ var SelectList = React.createClass({ var key = e.key , { valueField, multiple } = this.props , list = this.refs.list - , focusedItem = this.state.focusedItem - , props = this.props; - + , focusedItem = this.state.focusedItem; - let moveItem = (dir, item)=> move(dir, item, props, list); let change = (item) => { if (item) this._change(item, multiple @@ -198,14 +195,14 @@ var SelectList = React.createClass({ if (key === 'End') { e.preventDefault() - if (multiple) this.setState({ focusedItem: moveItem('prev', null) }) - else change(moveItem('prev', null)) + if (multiple) this.setState({ focusedItem: list.last() }) + else change(list.last()) } else if (key === 'Home' ) { e.preventDefault() - if (multiple) this.setState({ focusedItem: moveItem('next', null) }) - else change(moveItem('next', null)) + if (multiple) this.setState({ focusedItem: list.first() }) + else change(list.first()) } else if (key === 'Enter' || key === ' ' ) { e.preventDefault() @@ -214,14 +211,14 @@ var SelectList = React.createClass({ else if (key === 'ArrowDown' || key === 'ArrowRight' ) { e.preventDefault() - if (multiple) this.setState({ focusedItem: moveItem('next', focusedItem) }) - else change(moveItem('next', focusedItem)) + if (multiple) this.setState({ focusedItem: list.next(focusedItem) }) + else change(list.next(focusedItem)) } else if (key === 'ArrowUp' || key === 'ArrowLeft' ) { e.preventDefault() - if (multiple) this.setState({ focusedItem: moveItem('prev', focusedItem) }) - else change(moveItem('prev', focusedItem)) + if (multiple) this.setState({ focusedItem: list.prev(focusedItem) }) + else change(list.prev(focusedItem)) } else if (multiple && e.keyCode === 65 && e.ctrlKey ) { e.preventDefault() @@ -318,31 +315,21 @@ function getListItem(parent){ render() { let { - children, focused, selected - , dataItem: item - , ...props } = this.props; + children + , disabled, readonly + , dataItem: item } = this.props; let { multiple, name = instanceId(parent, '_name') } = parent.props; - let checked = contains(item, parent._values(), parent.props.valueField) - , change = parent._change.bind(null, item) - , disabled = isDisabledItem(item, parent.props) - , readonly = isReadOnlyItem(item, parent.props) + let checked = contains(item, parent._values(), parent.props.valueField) + , change = parent._change.bind(null, item) , type = multiple ? 'checkbox' : 'radio'; return ( -
  • -
  • + ); function onChange(e){ diff --git a/src/less/selectlist.less b/src/less/selectlist.less index a2b002f23..c84cda5fb 100644 --- a/src/less/selectlist.less +++ b/src/less/selectlist.less @@ -11,7 +11,7 @@ overflow: auto; > li { - + &.rw-list-option { position: relative; min-height: 27px; @@ -22,7 +22,7 @@ position: absolute; margin: 4px 0 0 -20px; } - + > label { padding-left: 20px; line-height: 1.423em; @@ -62,14 +62,11 @@ } .rw-selectlist { - &.rw-state-disabled, &.rw-state-readonly { - > ul > li:hover { background: none; border-color: transparent; } } - -} \ No newline at end of file +} diff --git a/src/util/interaction.js b/src/util/interaction.js index a95f5564a..37f6ac679 100644 --- a/src/util/interaction.js +++ b/src/util/interaction.js @@ -29,7 +29,7 @@ export function move(dir, item, props, list) { , stop = dir === 'next' ? list.last() : list.first() , next = list[dir](item); - while( next !== stop && isDisabledOrReadonly(next)) + while (next !== stop && isDisabledOrReadonly(next)) next = list[dir](next) return isDisabledOrReadonly(next) ? item : next