未完成
動機
- クライアント側でインクリメンタルにバリデーションしたい
- Opalで
- バリデーション自体はサーバ側に任せたい
- でもWEB Socketは使いたくない
利用
- クライアント側
opal
require 'opal-jquery'
Document.ready? do
# 前提
# フォームのinputはidが "#{model_name}_#{cur_attr_name}" となっている
# 送受信のI/F
# フォーマットはJSON
# リクエスト:
# Parameters: {"user"=>{"name"=>"a", "age"=>"", "password"=>"", "password_confirmation"=>""}}
# レスポンス:
# respone.jsonで以下の形式が取れる
# {"messages"=>{"password"=>[""], "name"=>[""], "age"=>[""], "password_confirmation"=>[""]}}
ClientValidator.register('/users/validate') do
model 'user'
attrs 'name', 'age', 'password', 'password_confirmation'
# 実行前。共通
before do |elem|
end
# 実行後、valid時。アトリビュート共通
all_success do |elem, response|
# 全てのアトリビュートがvalidならsubmitボタン有効
Element[':submit'].prop('disabled', false)
end
# 実行後、invalid時。アトリビュート共通
any_error do |elem, response|
# 何れかのアトリビュートがinvalidならsubmitボタン無効
Element[':submit'].prop('disabled', true)
end
# 実行後、valid時。アトリビュート毎
attr_success do |elem, attr_name, response|
# アトリビュート毎のvalidationメッセージを消す
Element["##{elem.id}_field"].remove_class('has-error')
Element["#user_#{attr_name}_error"].text = "\u00a0"
end
# 実行後、invalid時。アトリビュート毎
attr_error do |elem, attr_name, response|
# アトリビュート毎のvalidationメッセージを表示
Element["##{elem.id}_field"].add_class('has-error')
Element["#user_#{attr_name}_error"].text = response.json[:messages][attr_name]
end
# 実行後。アトリビュート共通
after do |elem, response|
end
end
- サーバ側
def validate
user = User.new(user_params)
if user.valid?
render json: { notice: '' }
else
render json: { notice: user.errors.messages }, status: 400
end
end
クラス
class ClientValidator
def self.register(url, &block)
n = new
n.instance_variable_set(:@actions, {})
n.instance_variable_set(:@url, "#{url}.json")
n.instance_eval(&block)
n.done
end
private
def model(model_name)
@model_name = model_name
end
def attrs(*attr_names)
@attr_names = attr_names
end
def before(&block); @actions[:before] = block; end
def after(&block); @actions[:after] = block; end
def all_success(&block); @actions[:all_success] = block; end
def any_error(&block); @actions[:any_error] = block; end
def attr_success(&block); @actions[:attr_success] = block; end
def attr_error(&block); @actions[:attr_error] = block; end
def after(&block); @actions[:after] = block; end
def done
validate(@url, @model_name, @attr_names, @actions)
end
def validate(url, model_name, attr_names, actions)
attr_names.each do |attr_name|
cur_attr_name, *other_attr_names = *attr_names.partition{|a| a == attr_name}.flatten
register_event(url, model_name, cur_attr_name, other_attr_names, actions)
end
end
def form_ajax_data(model_name, attrs)
attr_hash = attrs.map{|attr|
[attr, Element["##{model_name}_#{attr}"].value]
}.to_h
{model_name.to_sym => attr_hash}
end
def register_event(url, model_name, cur_attr_name, other_attr_names, actions)
Element["##{model_name}_#{cur_attr_name}"].on :keyup do |evt|
data = form_ajax_data(model_name, [cur_attr_name] + other_attr_names)
send_ajax(url, model_name, cur_attr_name, data, actions)
end
end
def send_ajax(url, model_name, cur_attr_name, data, actions)
elem = Element["##{model_name}_#{cur_attr_name}"]
actions[:before] && actions[:before].call(elem)
HTTP.post(url, payload: data) do |res|
if res.ok?
actions[:all_success] && actions[:all_success].call(elem, cur_attr_name, res)
else
actions[:any_error] && actions[:any_error].call(elem, cur_attr_name, res)
end
unless res.json[:messages][cur_attr_name.to_sym]
actions[:attr_success] && actions[:attr_success].call(elem, cur_attr_name, res)
else
actions[:attr_error] && actions[:attr_error].call(elem, cur_attr_name, res)
end
actions[:after] && actions[:after].call(elem, res)
end
end
end
ダメでしょ
- 車輪の再発明
- I/Fの前提が多すぎるオレオレ仕様。ほぼRails専用
- keyupイベントで検知しているためタッチデバイス非対応
- 以下、対応が必要
- リクエスト・レスポンスのフォーマット
- フォームのinputの自由化