Rev.registerComponent 'Tokenizer',
KEY_TAB: 9
KEY_ENTER: 13
KEY_ESC: 27
getDefaultProps: ->
props =
placeholder: ""
remoteOptionsUrl: null
queryParam: 'q'
selectedItems: []
inputClassName: 'RevTokenizer-input'
optionListClassName: 'RevTokenizer-options'
optionClassName: 'RevTokenizer-option'
tokenListClassName: 'RevTokenizer-tokens'
tokenClassName: 'RevTokenizer-token'
removeButtonClassName: 'RevTokenizer-remove'
leaveOpen: false
getInitialState: ->
state =
ignoreKeyUp: false
optionItems: []
selectedItems: @props.selectedItems
# Default is item.id
getItemValue: (item) ->
(@props.getItemValue || (x) => x.id)(item)
getSelectedValues: ->
_.map @state.selectedItems, _(@getItemValue).bind(this)
# Defaults to whatever getTokenComponent returns
getOptionComponent: (item) ->
(@props.getOptionComponent or @getTokenComponent)(item)
# Defaults to JSON
getTokenComponent: (item) ->
(@props.getTokenComponent or JSON.stringify)(item)
# Default for is
getInputName: (item) ->
(@props.getInputName || () => "#{@props.name}")(item)
# List item that contains @getTokenComponent(item) and a remove button
renderToken: (item) ->
{ @getTokenComponent item }
@onRemove(item)).bind(this) }>
Remove
getInput: ->
@refs.input.getDOMNode()
# List item that contains @getOptionComponent(item)
renderOption: (item) ->
@onSelect(item)).bind(this) }>
{ @getOptionComponent item }
# Renders a list only if it has items
renderTokens: ->
if @state.selectedItems.length
{ _(@state.selectedItems).map @renderToken }
else
null
# Renders a list only if it has items
renderOptions: ->
if @state.optionItems.length
{ _(@state.optionItems).map @renderOption }
else
null
render: ->
{ @renderTokens() }
{ @props.children }
{ @renderOptions() }
isSelectKey: (e) ->
e.keyCode == @KEY_TAB || e.keyCode == @KEY_ENTER
onKeyDown: (e) ->
# prevent blur, form submit, etc.
# unless there's no text in the box, in which case we don't worry about it
@state.optionItems.length == 0 or not @isSelectKey(e)
onKeyUp: (e) ->
if @isSelectKey(e)
# Select the first item on special keypresses
@onSelect @state.optionItems[0] if @state.optionItems.length > 0
else if e.keyCode == @KEY_ESC
# Esc to make the option go away
@getInput().blur()
else
@fetchOptions()
@setState
ignoreKeyUp: false
onBlur: ->
return if @props.leaveOpen
# If you don't delay this a little you can't click any of the options
clearOptions = () -> @setState(optionItems: [])
setTimeout _(clearOptions).bind(this), 200
onFocus: -> @fetchOptions()
clearOptions: ->
@xhr.abort if @xhr?
@setState(optionItems: [])
setOptions: (items) ->
selectedItemsValues = @getSelectedValues()
rejectItem = (item) -> _.contains selectedItemsValues, @getItemValue(item)
@setState
optionItems: _.reject items, _(rejectItem).bind(this)
fetchOptions: ->
if @getInput().value == ""
@clearOptions()
else
queryData = {}
queryData[@props.queryParam] = @getInput().value
@xhr = $.get @props.remoteOptionsUrl, queryData
@xhr.done _(@setOptions).bind(this)
onSelect: (item) ->
items = @state.selectedItems
items.push item
items = _.uniq items, null, _((x) -> @getItemValue(x)).bind(this)
input = @getInput()
input.value = ""
input.focus()
@clearOptions()
@setState
selectedItems: items
false
onRemove: (item) ->
items = @state.selectedItems
items = _.reject items, _((x) -> @getItemValue(x) == @getItemValue(item)).bind(this)
@setState
selectedItems: items
false