0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

クライアント側でインクリメンタルバリデーション(Opal使用)

Last updated at Posted at 2016-07-17

未完成

動機

  • クライアント側でインクリメンタルにバリデーションしたい
  • 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の自由化
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?