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.