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 else null # Renders a list only if it has items renderOptions: -> if @state.optionItems.length 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