使用React.js、jQuery-ui和Autocomplete实现候选词下拉菜单

Screenshot 2015-07-10 22.15.40.png

总结

React.jsの入力フォームでjQuery-ui/Autocompleteを利用し、利用者の文字列入力に応じて語句の候補をドロップダウンで表示する機能を追加する。
Rails, HAML, CoffeeScriptで書いた。

环境
– Environment.

ruby 2.2.1
Rails 4.2.1

#(以Rails为例的)准备工作

将’jquery-ui-rails’添加到Gemfile中。

#jQuery-ui/Autocompleteを使うため。
gem 'jquery-ui-rails', '~> 5.0.5'

#React.jsを使うため。
gem 'react-rails',     '~> 1.0.0'

# ...他

将jquery-ui/autocomplete添加到application.js中。

// 例
//= require jquery
//= require jquery_ujs
//= require jquery.turbolinks
//= require bootstrap
//= require turbolinks
//= require jquery-ui/autocomplete
//= require react
//= require react_ujs
//= require growl
//= require components
//= requre_tree .

新数据输入表格的处理流程

    • 表作成用のデータとともに、カテゴリー名称と部屋名称のデータをデータベースから取り出し、配列として部品レンダリング時に渡しておく。

 

    • ユーザーが入力した内容に反応してその文字を含む語句を候補として表示する。

 

    • 語句をクリックすると、それが入力データとして取り扱われる。

 

    • データを検証し、OKであれば、提出ボタンが有効になり、提出可能になる。

 

    • データはXHRでサーバーに送信され、データベースに保存される。

 

    部品のUIも状態データを元に更新される。(新規データ)が表に追加される。

1. 主要的零件


@RecordsApp = React.createClass
  # レンダリング時に受け取ったデータ(表を作成するデータ)を覚えておく。
  getInitialState: ->
    records: @props.data

  getDefaultProps: ->
    records: []

  # 新規作成用フォームに入力された内容でデータを追加する。そしてUIを更新。
  addRecord: (record) ->
    records = React.addons.update(@state.records, { $unshift: [record] })
    @setState records: records

  #(中略)

  render: ->
    # React.DOM記述を省略する目的の一時変数。
    R = React.DOM

    R.div
      R.h2 null, "Add a new item"

      # 新規作成フォーム
      React.createElement NewMovingRecordForm,
        # 新規データ処理用のメソッドを渡す。
        handleNewRecord: @addRecord
        # 語句候補のデータを渡す。
        roomSuggestions: @props.roomSuggestions
        categorySuggestions: @props.categorySuggestions
      R.hr null

      # 表
      React.createElement Records,
        records: @state.records,

2. 创建新的表单组件

@NewMovingRecordForm = React.createClass
  # 元の状態
  getInitialState: ->
    name:        ""
    volume:      ""
    quantity:    ""
    room:        ""
    category:    ""
    description: ""

  # 一字一句入力時に状態データを更新する。
  handleChange: (e) ->
    name = e.target.name
    @setState "#{ name }": e.target.value

  # 提出ボタンが押された時の処理をここに記述する。
  handleSubmit: (e) ->

  # UIを元の状態(空フォーム)に戻す。
  handleClear: (e) ->
    @setState @getInitialState()

  # 入力内容の検証
  valid: ->
    @validName() && @validVolume() && @validQuantity() &&
    @validRoom() && @validCategory() && @validDescription()
  validName: ->
    @state.name && @state.name.length <= 50
  validVolume: ->
    @state.volume && @state.volume.length <= 10
  validQuantity: ->
    @state.quantity && @state.quantity.length <= 10
  validRoom: ->
    @state.room && @state.room.length <= 50
  validCategory: ->
    @state.category && @state.category.length <= 50
  validDescription: ->
    @state.description.length <= 200

  # Reactによりマークアップが生成された直後に呼ばれる。ここでjQueryが実際のDOMに対してAutocompleteを初期化する。(Reactは知らない)
  componentDidMount: ->
    @updateAutocomplete()

  # Reactによりマークアップが更新された直後に呼ばれる。ここでAutocompleteを更新する。
  componentDidUpdate: ->
    @updateAutocomplete()

  # jQueryが作ったAutocompleteを消去する。
  componentWillUnmount: ->
    $(React.findDOMNode(@refs.room)).autocomplete('destroy')
    $(React.findDOMNode(@refs.category)).autocomplete('destroy')

  # Autocomplete初期化・更新の処理。
  updateAutocomplete: ->
    $(React.findDOMNode(@refs.room)).autocomplete
      source: @props.roomSuggestions
      select: (e, ui) =>
        @setState room: ui.item.value

    $(React.findDOMNode(@refs.category)).autocomplete
      source: @props.categorySuggestions
      select: (e, ui) =>
        @setState category: ui.item.value

  render: ->
    R = React.DOM

    R.form
      onSubmit:  @handleSubmit
      R.div
        className: 'form-group'
        R.div
          className: "form-group col-sm-12"
          R.input
            type:        'text'
            className:   'form-control'
            placeholder: 'Item name'
            name:        'name'
            value:       @state.name
            onChange:    @handleChange
        R.div
          className: "form-group col-sm-6"
          R.input
            type:        'number'
            min:         "0"
            className:   'form-control'
            placeholder: 'Volume'
            name:        'volume'
            value:       @state.volume
            onChange:    @handleChange
        R.div
          className: "form-group col-sm-6"
          R.input
            type:        'number'
            min:         "0"
            className:   'form-control'
            placeholder: 'Quantity'
            name:        'quantity'
            value:       @state.quantity
            onChange:    @handleChange
        R.div
          className: "form-group col-sm-6"
          R.input
            type:        'text'
            className:   'form-control'
            ref:         'category'  # AutocompleteがDOMアクセスに使用
            name:        'category'  # @handleChange処理時に使用
            placeholder: 'Category'
            value:       @state.category
            onChange:    @handleChange
        R.div
          className: "form-group col-sm-6"
          R.input
            type:        'text'
            className:   'form-control'
            ref:         'room'  # AutocompleteがDOMアクセスに使用
            name:        'room'  # @handleChange処理時に使用
            placeholder: 'Room'
            value:       @state.room
            onChange:    @handleChange
        R.div
          className: "form-group col-sm-6"
          R.textarea
            rows:        '3'
            className:   'form-control'
            placeholder: 'Description'
            name:        'description(optional)'
            value:       @state.description
            onChange:    @handleChange

        R.div
          className: 'col-sm-6'
          R.div
            className: 'form-group col-sm-8'
            R.button
              type:      'submit'
              className: if @valid() then 'btn btn-success btn-block' else 'btn btn-default btn-block'
              disabled:  not @valid()
              'Add item'
          R.div
            className: 'form-group col-sm-4'
            R.button
              type:      'submit'
              className: "btn btn-default btn-block"
              onClick: @handleClear
              'Clear'
        R.div
          className: 'form-group col-sm-6'
          R.span
            id: "helpBlock"
            className: "help-block text-center"
            "Please fill in all the required fields"
      R.div className: "clearfix"

3. 下拉菜单的样式设计

按照个人喜好来进行造型。

ul.ui-autocomplete {
  position: absolute;
  list-style: none;
  margin: 0;
  padding: 0;
  border: solid 1px #999;
  cursor: default;
  li {
    background-color: #FFF;
    border-top: solid 1px #DDD;
    margin: 0;
    padding: 2px 15px;
    a {
      color: #000;
      display: block;
      padding: 3px;
    }
    a.ui-state-hover, a.ui-state-active {
      background-color: #FFFCB2;
    }
  }
}

4. 传递语句候选数据并呈现渲染

使用react_rails的react_component方法来展示以下例子。
提前传递候选语句数据进行渲染。在组件内部通过@props进行访问。

%h1.page-header Sample template

= react_component 'RecordsApp', { data: @items,
                   roomSuggestions: Room.all.pluck("name"),
                   categorySuggestions: @moving.moving_items.pluck("category").uniq! }

# 参考材料

    • https://jqueryui.com/autocomplete/

 

    • https://github.com/railscasts/102-auto-complete-association-revised

 

    • http://ludovf.net/reactbook/blog/reactjs-jquery-ui-autocomplete.html

 

    https://facebook.github.io/react/docs/more-about-refs.html
bannerAds