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

总结
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