#概要
- React.jsの入力フォームでjQuery-ui/Autocompleteを利用し、利用者の文字列入力に応じて語句の候補をドロップダウンで表示する機能を追加する。
- Rails, HAML, CoffeeScriptで書いた。
#環境
ruby 2.2.1
Rails 4.2.1
#準備(Rails場合のー例)
##'jquery-ui-rails'をGemfileに追加
主なGem
#jQuery-ui/Autocompleteを使うため。
gem 'jquery-ui-rails', '~> 5.0.5'
#React.jsを使うため。
gem 'react-rails', '~> 1.0.0'
# ...他
##application.jsに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.coffee
@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. 新規作成フォーム部品
NewRecordForm.js.coffee
@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. ドロップダウンメニューのスタイリング
自分の好みでスタイリングする。
autocomplete.scss
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を介してアクセスする。
show.html.haml
%h1.page-header Sample template
= react_component 'RecordsApp', { data: @items,
roomSuggestions: Room.all.pluck("name"),
categorySuggestions: @moving.moving_items.pluck("category").uniq! }
#参考資料