-
Notifications
You must be signed in to change notification settings - Fork 229
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Zachary Hickman
committed
Mar 31, 2016
1 parent
df1aac7
commit dd0fda8
Showing
7 changed files
with
848 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/** | ||
* PolyFills make me sad | ||
*/ | ||
var KeyEvent = KeyEvent || {}; | ||
KeyEvent.DOM_VK_UP = KeyEvent.DOM_VK_UP || 38; | ||
KeyEvent.DOM_VK_DOWN = KeyEvent.DOM_VK_DOWN || 40; | ||
KeyEvent.DOM_VK_BACK_SPACE = KeyEvent.DOM_VK_BACK_SPACE || 8; | ||
KeyEvent.DOM_VK_RETURN = KeyEvent.DOM_VK_RETURN || 13; | ||
KeyEvent.DOM_VK_ENTER = KeyEvent.DOM_VK_ENTER || 14; | ||
KeyEvent.DOM_VK_ESCAPE = KeyEvent.DOM_VK_ESCAPE || 27; | ||
KeyEvent.DOM_VK_TAB = KeyEvent.DOM_VK_TAB || 9; | ||
|
||
module.exports = KeyEvent; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
var Typeahead = require('./typeahead'); | ||
var Tokenizer = require('./tokenizer'); | ||
|
||
module.exports = { | ||
Typeahead: Typeahead, | ||
Tokenizer: Tokenizer | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
/** | ||
* @jsx React.DOM | ||
*/ | ||
|
||
var React = require('react'); | ||
var Token = require('./token'); | ||
var KeyEvent = require('../keyevent'); | ||
var Typeahead = require('../typeahead'); | ||
var classNames = require('classnames'); | ||
|
||
function _arraysAreDifferent(array1, array2) { | ||
if (array1.length != array2.length){ | ||
return true; | ||
} | ||
for (var i = array2.length - 1; i >= 0; i--) { | ||
if (array2[i] !== array1[i]){ | ||
return true; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* A typeahead that, when an option is selected, instead of simply filling | ||
* the text entry widget, prepends a renderable "token", that may be deleted | ||
* by pressing backspace on the beginning of the line with the keyboard. | ||
*/ | ||
var TypeaheadTokenizer = React.createClass({displayName: "TypeaheadTokenizer", | ||
propTypes: { | ||
name: React.PropTypes.string, | ||
options: React.PropTypes.array, | ||
customClasses: React.PropTypes.object, | ||
allowCustomValues: React.PropTypes.number, | ||
defaultSelected: React.PropTypes.array, | ||
defaultValue: React.PropTypes.string, | ||
placeholder: React.PropTypes.string, | ||
disabled: React.PropTypes.bool, | ||
inputProps: React.PropTypes.object, | ||
onTokenRemove: React.PropTypes.func, | ||
onKeyDown: React.PropTypes.func, | ||
onKeyPress: React.PropTypes.func, | ||
onKeyUp: React.PropTypes.func, | ||
onTokenAdd: React.PropTypes.func, | ||
onFocus: React.PropTypes.func, | ||
onBlur: React.PropTypes.func, | ||
filterOption: React.PropTypes.oneOfType([ | ||
React.PropTypes.string, | ||
React.PropTypes.func | ||
]), | ||
displayOption: React.PropTypes.oneOfType([ | ||
React.PropTypes.string, | ||
React.PropTypes.func | ||
]), | ||
maxVisible: React.PropTypes.number, | ||
defaultClassNames: React.PropTypes.bool | ||
}, | ||
|
||
getInitialState: function() { | ||
return { | ||
// We need to copy this to avoid incorrect sharing | ||
// of state across instances (e.g., via getDefaultProps()) | ||
selected: this.props.defaultSelected.slice(0) | ||
}; | ||
}, | ||
|
||
getDefaultProps: function() { | ||
return { | ||
options: [], | ||
defaultSelected: [], | ||
customClasses: {}, | ||
allowCustomValues: 0, | ||
defaultValue: "", | ||
placeholder: "", | ||
disabled: false, | ||
inputProps: {}, | ||
defaultClassNames: true, | ||
filterOption: null, | ||
displayOption: function(token){return token }, | ||
onKeyDown: function(event) {}, | ||
onKeyPress: function(event) {}, | ||
onKeyUp: function(event) {}, | ||
onFocus: function(event) {}, | ||
onBlur: function(event) {}, | ||
onTokenAdd: function() {}, | ||
onTokenRemove: function() {} | ||
}; | ||
}, | ||
|
||
componentWillReceiveProps: function(nextProps){ | ||
// if we get new defaultProps, update selected | ||
if (_arraysAreDifferent(this.props.defaultSelected, nextProps.defaultSelected)){ | ||
this.setState({selected: nextProps.defaultSelected.slice(0)}) | ||
} | ||
}, | ||
|
||
focus: function(){ | ||
this.refs.typeahead.focus(); | ||
}, | ||
|
||
getSelectedTokens: function(){ | ||
return this.state.selected; | ||
}, | ||
|
||
// TODO: Support initialized tokens | ||
// | ||
_renderTokens: function() { | ||
var tokenClasses = {}; | ||
tokenClasses[this.props.customClasses.token] = !!this.props.customClasses.token; | ||
var classList = classNames(tokenClasses); | ||
var result = this.state.selected.map(function(selected) { | ||
var displayString = this.props.displayOption(selected); | ||
return ( | ||
React.createElement(Token, {key: displayString, className: classList, | ||
onRemove: this._removeTokenForValue, | ||
object: selected, | ||
name: this.props.name}, | ||
displayString | ||
) | ||
); | ||
}, this); | ||
return result; | ||
}, | ||
|
||
_getOptionsForTypeahead: function() { | ||
// return this.props.options without this.selected | ||
return this.props.options; | ||
}, | ||
|
||
_onKeyDown: function(event) { | ||
// We only care about intercepting backspaces | ||
if (event.keyCode === KeyEvent.DOM_VK_BACK_SPACE) { | ||
return this._handleBackspace(event); | ||
} | ||
this.props.onKeyDown(event); | ||
}, | ||
|
||
_handleBackspace: function(event){ | ||
// No tokens | ||
if (!this.state.selected.length) { | ||
return; | ||
} | ||
|
||
// Remove token ONLY when bksp pressed at beginning of line | ||
// without a selection | ||
var entry = this.refs.typeahead.refs.entry; | ||
if (entry.selectionStart == entry.selectionEnd && | ||
entry.selectionStart == 0) { | ||
this._removeTokenForValue( | ||
this.state.selected[this.state.selected.length - 1]); | ||
event.preventDefault(); | ||
} | ||
}, | ||
|
||
_removeTokenForValue: function(value) { | ||
var index = this.state.selected.indexOf(value); | ||
if (index == -1) { | ||
return; | ||
} | ||
|
||
this.state.selected.splice(index, 1); | ||
this.setState({selected: this.state.selected}); | ||
this.props.onTokenRemove(value); | ||
return; | ||
}, | ||
|
||
_addTokenForValue: function(value) { | ||
if (this.state.selected.indexOf(value) != -1) { | ||
return; | ||
} | ||
this.state.selected.push(value); | ||
this.setState({selected: this.state.selected}); | ||
this.refs.typeahead.setEntryText(""); | ||
this.props.onTokenAdd(value); | ||
}, | ||
|
||
render: function() { | ||
var classes = {}; | ||
classes[this.props.customClasses.typeahead] = !!this.props.customClasses.typeahead; | ||
var classList = classNames(classes); | ||
var tokenizerClasses = [this.props.defaultClassNames && "typeahead-tokenizer"]; | ||
tokenizerClasses[this.props.className] = !!this.props.className; | ||
var tokenizerClassList = classNames(tokenizerClasses) | ||
|
||
return ( | ||
React.createElement("div", {className: tokenizerClassList}, | ||
this._renderTokens(), | ||
React.createElement(Typeahead, {ref: "typeahead", | ||
className: classList, | ||
placeholder: this.props.placeholder, | ||
disabled: this.props.disabled, | ||
inputProps: this.props.inputProps, | ||
allowCustomValues: this.props.allowCustomValues, | ||
customClasses: this.props.customClasses, | ||
options: this._getOptionsForTypeahead(), | ||
defaultValue: this.props.defaultValue, | ||
maxVisible: this.props.maxVisible, | ||
onOptionSelected: this._addTokenForValue, | ||
onKeyDown: this._onKeyDown, | ||
onKeyPress: this.props.onKeyPress, | ||
onKeyUp: this.props.onKeyUp, | ||
onFocus: this.props.onFocus, | ||
onBlur: this.props.onBlur, | ||
displayOption: this.props.displayOption, | ||
defaultClassNames: this.props.defaultClassNames, | ||
filterOption: this.props.filterOption}) | ||
) | ||
); | ||
} | ||
}); | ||
|
||
module.exports = TypeaheadTokenizer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/** | ||
* @jsx React.DOM | ||
*/ | ||
|
||
var React = require('react'); | ||
var classNames = require('classnames'); | ||
|
||
/** | ||
* Encapsulates the rendering of an option that has been "selected" in a | ||
* TypeaheadTokenizer | ||
*/ | ||
var Token = React.createClass({displayName: "Token", | ||
propTypes: { | ||
className: React.PropTypes.string, | ||
name: React.PropTypes.string, | ||
children: React.PropTypes.string, | ||
object: React.PropTypes.oneOfType([ | ||
React.PropTypes.string, | ||
React.PropTypes.object, | ||
]), | ||
onRemove: React.PropTypes.func | ||
}, | ||
|
||
render: function() { | ||
var className = classNames([ | ||
"typeahead-token", | ||
this.props.className | ||
]); | ||
|
||
return ( | ||
React.createElement("div", {className: className}, | ||
this._renderHiddenInput(), | ||
this.props.children, | ||
this._renderCloseButton() | ||
) | ||
); | ||
}, | ||
|
||
_renderHiddenInput: function() { | ||
// If no name was set, don't create a hidden input | ||
if (!this.props.name) { | ||
return null; | ||
} | ||
|
||
return ( | ||
React.createElement("input", { | ||
type: "hidden", | ||
name: this.props.name + '[]', | ||
value: this.props.object} | ||
) | ||
); | ||
}, | ||
|
||
_renderCloseButton: function() { | ||
if (!this.props.onRemove) { | ||
return ""; | ||
} | ||
return ( | ||
React.createElement("a", {className: "typeahead-token-close", href: "#", onClick: function(event) { | ||
this.props.onRemove(this.props.object); | ||
event.preventDefault(); | ||
}.bind(this)}, "×") | ||
); | ||
} | ||
}); | ||
|
||
module.exports = Token; |
Oops, something went wrong.