12/08/2018, 13:57

Thêm và xóa field với Rails Nested Forms and AngularJS (Phần 2)

Giới thiệu Ở phần trước chúng ta đã thực hiện bược tạo Plan có các field con là Poll bằng AngularJs và Rails. Giả sử khi tạo ta gặp lỗi cần render lại trang new hoặc ta muốn update Plan vừa tạo thì làm sao để các field vẫn được điền đúng giá trị. Chúng ta cùng tìm hiểu trong phần này. Validate ...

Giới thiệu

Ở phần trước chúng ta đã thực hiện bược tạo Plan có các field con là Poll bằng AngularJs và Rails. Giả sử khi tạo ta gặp lỗi cần render lại trang new hoặc ta muốn update Plan vừa tạo thì làm sao để các field vẫn được điền đúng giá trị. Chúng ta cùng tìm hiểu trong phần này.

Validate Model

Thêm validate cho Poll

# app/models/poll.rb
class Poll < ActiveRecord::Base
  belongs_to :plan
  validate :title, presence: true, length: {maximum: 20}
  validate :content, presence: true, length: {maximum: 200}
end

Khi tạo poll mà chưa điền đầy đủ title hoặc điền quá maxlength thì sẽ không tạo được.

Render

# app/controllers/plans_controller.rb
class PlansController < ApplicationController
  def new
    @ plan = Plan.new
    @ poll = @ plan.polls.build
  end

  def create
    @ plan = Plan.new plan_params
    if @ plan.save
      riderect_to root_path
    else
      render :new
    end
  end
end

Khi tạo lỗi sé render về trang new. Vậy làm cách nào để những thông tin mình điền trước đấy vẫn còn trên form.

Truyền dữ liệu ngược lại từ controller

Biến lưu dữ liệu của cả trang là vm.polls. Nhưng vì đây là biến của angularJS nên khi render hoặc load lại trang thì vm.polls không còn lưu được giá trị. Cách giải quyết là truyền dữ liệu cho vm.polls từ một biến trong controller

# app/controllers/plans_controller.rb
class PlansController < ApplicationController
  def new
    @ plan = Plan.new
    @ poll = @ plan.polls.build
    @ render_polls = [{title: "", content: ""}]
  end

  def create
    @ plan = Plan.new plan_params
    if @ plan.save
      riderect_to root_path
    else
      @ render_polls = @ plan.polls.map do |poll|
        {
         title: poll.title,
         content: poll.content
        }
      end
      render :new
    end
  end
end

Tạo biến @ render_polls chứa toàn bộ dữ liệu về các polls của plan. Sau đấy sẽ truyền sang view và gán giá trị cho vm.polls Bên cạnh đó ta cũng cần sửa lại hàm new, để truyền vào giá trị mặc định cho @ render_polls khi mà plan chưa có poll con nào.

# app/views/plans/new.html.erb
<div ng-app>
  <%= form_for @plan do |form| %>
    <div ng-controller="PlanCtrl as vm" ng-init="vm.polls = <%=  @render_polls.to_json %>">
      <div ng-repeat="poll in vm.polls">
        <%= form.fields_for :polls, @poll, child_index: '{{$index}}' do |poll_form| %>
          <%= poll_form.text_field :title, id: 'plan_poll_{{$index}}', "ng-model": "poll.title" %>
          <%= poll_form.text_field :content, id: 'plan_poll_{{$index}}', "ng-model": "poll.content" %>
        <% end %>
        <a href="#" ng-click="vm.remove($index)" ng-show="vm.isRemovable()">Remove</a>
      </div>
      <a href="#" ng-click="vm.add()">Add</a>
    </div>
  <% end %>
</div>

Khi này vm.polls đã có đủ dữ liệu của các poll con của plan.

Hiện error messages

Ngoài việc truyền dữ liệu từ controller ra, ta có thể truyền thêm error messages để người dùng có thể biết mình nhập sai thông tin nào.

# app/controllers/plans_controller.rb
class PlansController < ApplicationController
  def new
    @ plan = Plan.new
    @ poll = @ plan.polls.build
  end

  def create
    @ plan = Plan.new plan_params
    if @ plan.save
      riderect_to root_path
    else
      @ render_polls = @ plan.polls.map do |poll|
        {
         title: poll.title,
         content: poll.content,
         title_err_mes: poll.errors.messages[:title].try(:first),
         content_err_mes: poll.errors.messages[:content].try(:first)
        }
      end
      render :new
    end
  end
end

title_err_mes và content_err_mes lưu error message của title và content.

Ngoài view, ta sẽ hiển thị error message nếu có.

# app/views/plans/new.html.erb
<div ng-app>
  <%= form_for @plan do |form| %>
    <div ng-controller="PlanCtrl as vm" ng-init="vm.polls = <%=  @render_polls.to_json %>">
      <div ng-repeat="poll in vm.polls">
        <%= form.fields_for :polls, @poll, child_index: '{{$index}}' do |poll_form| %>
          <%= poll_form.text_field :title, id: 'plan_poll_{{$index}}', "ng-model": "poll.title" %>
          <div class="error" ng-bind="poll.title_err_mes"></div>
          <%= poll_form.text_field :content, id: 'plan_poll_{{$index}}', "ng-model": "poll.content" %>
          <div class="error" ng-bind="poll.content_err_mes"></div>
        <% end %>
        <a href="#" ng-click="vm.remove($index)" ng-show="vm.isRemovable()">Remove</a>
      </div>
      <a href="#" ng-click="vm.add()">Add</a>
    </div>
  <% end %>
</div>

Update Plan

Trong trường hợp muốn chỉnh sửa 1 plan, thì cách làm vẫn tương tự là ta truyền dữ liệu đã có cho vm.polls trong hàm edit.

# app/controllers/plans_controller.rb
class PlansController < ApplicationController
  def new
    @ plan = Plan.new
    @ poll = @ plan.polls.build
  end

  def edit
    @ plan = Plan.find params[:id]
    @ poll = @ plan.polls.build
    @ render_polls = @ plan.polls.map do |poll|
      {
      title: poll.title,
      content: poll.content
      }
    end
  end

  def create
    @ plan = Plan.new plan_params
    if @ plan.save
      riderect_to root_path
    else
      @ render_polls = @ plan.polls.map do |poll|
        {
         title: poll.title,
         content: poll.content,
         title_err_mes: poll.errors.messages[:title].try(:first),
         content_err_mes: poll.errors.messages[:content].try(:first)
        }
      end
      render :new
    end
  end

  def update
    @ plan = Plan.find params[:id]
    if @ plan.update_attributes plan_params
      riderect_to root_path
    else
      @ render_polls = @ plan.polls.map do |poll|
        {
         title: poll.title,
         content: poll.content,
         title_err_mes: poll.errors.messages[:title].try(:first),
         content_err_mes: poll.errors.messages[:content].try(:first)
        }
      end
      render :edit
    end
  end
end

Còn view trang edit thì giống hoàn toàn trang new.

Tổng kết

Trên đây là toàn bộ bài viết về việc thêm và xóa field với Rails Nested Forms and AngularJS, bao gồm cả trường hợp tạo lỗi, update và hiển thị error messages.

0