Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

未完成

動機

  • クライアント側でインクリメンタルにバリデーションしたい
  • 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の自由化
kppn
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away