Day24. form_tag 与 simple_form_for 的用法 - 表单 part2

前一天,我们使用了simple_form_for提到了新增表单写法,而今天要讲一个上传情境。这个上传想要做的事情在特殊新增页签中新增或更新id=1,2,3,4的资料,上传後转址回首页,并且存在的的id: 1-4 的值会被填写在表单上面。

form_for / simple_form_for 的情境为一般crud上传,而上述情境的特殊上传并不是遵照惯例的上传法,所以我们用form_tag的方式,写出我们想要的功能。这里讲一下 form_for / simple_form_for / form_tag的差别

  • form_for:Rails 内建的表单结构,适合写新增、更新单笔资料用。
  • form_tag:Rails提供的helper,但使用方式为普通的form。写法比较自由,但写的行数比较多
  • simple_form_for:使用前先需安装simple_form,并且做一些基本的设定。这是基於 form_for 所延伸的更简易的用法,是我爱用的用法

form_tag

以下为关於 form_tag 的雷:

#====== 错误用法
form_tag(controller: "people", action: "search", method: "get", class: "nifty_form")
#=> '<form accept-charset="UTF-8" action="/people/search?class=nifty_form&method=get" method="post">'

#====== 正确用法
form_tag({ controller: "people", action: "search" }, method: "get", class: "nifty_form")
#=> '<form accept-charset="UTF-8" action="/people/search" class="nifty_form" method="get">'

上方的错误用法为form_tagmethod: "get", class: "nifty_form" 当作网址的参数,因此我们必须要将:controller,:action用大括号包住。

这里很容易被雷到,所以一定要注意!而由上述的例子我们也可以知道,当我们要传get参数,可以用上面用法进行参数传递

css & inline element

不免俗的讲解一下样式

= tab_content(blog_genre.second[:id]) do
  - form_tag({ controller: 'admin/blogs', action: 'special_update' }, 
               method: "post", class: "nifty_form")
    - @ten_blogs.each do |blog|
      = text_field(:blog, :title, multiple: true)
      = text_field(:blog, :content, multiple: true)

由於 input element 为行内排列,因此做简单的排版後,输入框会排成一列。

https://ithelp.ithome.com.tw/upload/images/20210919/20115854oTc8HxsHtO.png

Inline element 无法用区块的方式控制,因此<input>无法用padding, margin 控制。

另外加了multiple: true後,可以允许传复数值,因此name属性会从blog[title] 转为 blog[title][],但不过这也不是我们想要的样子。因此我们直接暴力的将name属性写在上面,并且仿照simple_form刻画画面的方式做样式的撰写,最後版本如下:

- form_tag({ controller: 'admin/blogs', action: 'special_update' }, 
           method: "post", class: "nifty_form")
  = grid_div(1, 1, style: "column-gap: 10px;") do
    - @ten_blogs.each.with_index(1) do |blog, index|
      = card do
        = card_header(title: "第#{index}笔资料")
        = card_body do
          = tag.div class: 'form-group blog__title'
            = label_tag :blog__title, "标题"
            = text_field_tag "blog[][title]", nil, class: 'form-control string required'
          = tag.div class: 'form-group string required blog__content'
            = label_tag :blog__content, "内文"
            = text_field_tag "blog[][content]", nil, class: 'form-control string required'
          = grid_div(1, 10) do
            = label_tag nil, "种类"
            = tag.div do
              - [["生活", :life], ["休闲", :casual], ["科技", :technology]].each do |e|
                = tag.div class: 'form-check form-check-inline'
                  = radio_button_tag "blog[][genre]", e.second, false
                  = label_tag :blog__genre, e.first

https://ithelp.ithome.com.tw/upload/images/20210919/201158543Jz6V5jyin.png

再来我们开始填写预设值进去

- form_tag({ controller: 'admin/blogs', action: 'special_update' }, 
           method: "post", class: "nifty_form")
  = grid_div(1, 1, style: "column-gap: 10px;") do
    - @ten_blogs.each.with_index(1) do |blog, index|
      = card do
        = card_header(title: "第 #{index} 笔资料")
        = card_body do
          = tag.div class: 'form-group blog__title'
            = label_tag :blog__title, "标题"
            = text_field_tag "blog[][title]", blog.title, 
                             class: 'form-control string required'
          = tag.div class: 'form-group string required blog__content'
            = label_tag :blog__content, "内文"
            = text_field_tag "blog[][content]", blog.content, 
                             class: 'form-control string required'
          = grid_div(1, 10) do
            = label_tag nil, "种类"
            = tag.div do
              - [["生活", :life], ["休闲", :casual], ["科技", :technology]].each do |e|
                = tag.div class: 'form-check form-check-inline'
                  = radio_button_tag "blog[][genre]", 
                    e.second, e.second === blog.genre&.to_sym
                  = label_tag :blog__genre, e.first

下列的文字是我们设定的预设文字

https://ithelp.ithome.com.tw/upload/images/20210919/20115854hBF0xrWy9E.png

上述情境并非依照惯例撰写,因此程序码会写得比较多行。不过由此我们可以细细品察form_tag的用法

radio_button in simple_form

接着,我们回到Day23的例子,我们看一下在simple_form中到底帮忙radio button做多少事情,以至於我们不用像刚刚在form_tag时写得那麽多

= f.input :genre, as: :radio_buttons, label: tag.strong('分类'),
          collection: [["生活", :life], ["休闲", :casual], ["科技", :technology]],
          selected: :casual, item_wrapper_class: 'form-check form-check-inline'

上面的slim所对应的html 如下

<fieldset class="form-group radio_buttons required blog_genre">
  <legend class="col-form-label pt-0">
    <strong>分类</strong> <abbr title="required">*</abbr>
  </legend>
  <input type="hidden" name="blog[genre]" value="" />
  <div class="form-check form-check-inline">
    <input
      class="form-check-input radio_buttons required"
      type="radio"
      value="life"
      name="blog[genre]"
      id="blog_genre_life"
    /><label
      class="form-check-label collection_radio_buttons"
      for="blog_genre_life"
      >生活</label>
  </div>
  <div class="form-check form-check-inline">
    <input
      class="form-check-input radio_buttons required"
      selected="selected"
      type="radio"
      value="casual"
      name="blog[genre]"
      id="blog_genre_casual"
    /><label
      class="form-check-label collection_radio_buttons"
      for="blog_genre_casual"
      >休闲</label>
  </div>
  <div class="form-check form-check-inline">
    <input
      class="form-check-input radio_buttons required"
      type="radio"
      value="technology"
      name="blog[genre]"
      id="blog_genre_technology" /><label
      class="form-check-label collection_radio_buttons"
      for="blog_genre_technology"
      >科技</label>
  </div>
</fieldset>

下面一行为隐藏区块,这是rails的贴心设计。当没有填值时,会传一个空字串。

<input type="hidden" name="blog[genre]" value="" />

接着我们来看看 controller 的逻辑

Controller - 新增单笔文章

在资料库只创建到第二笔的情况下,当我们传了新的一笔,该笔的id会等於3。当我们表单送出时,会在第3笔资料显示资料。

以下为新增表单按下送出按钮时,从画面传过来的值

params
#=> <ActionController::Parameters {"authenticity_token"=>"an8nw1/cmx7KOMq42araGAwTHwA894VnMDszcCBKTbBlxHQGj1l8nmCs79wgh55aoUSOG4/Ta9VxupUmSENi2Q==", "blog"=><ActionController::Parameters {"title"=>"哈罗", "content"=>"哈罗", "genre"=>"casual"} permitted: false>, "button"=>"", "controller"=>"admin/blogs", "action"=>"create"} permitted: false>

blog_params
#=> <ActionController::Parameters {"title"=>"哈罗", "content"=>"哈罗", "genre"=>"casual"} permitted: true>

按下送出後,就会触发controller#create 逻辑。以下为送出表单的 controller#create 逻辑

module Admin
  class BlogsController < ApplicationController    
    def index
      # 单笔资料新增一笔待储存的纪录
      @blog = current_user.blogs.new
    end
    
    # 创建一笔新的纪录
    def create
      if current_user.blogs.create(blog_params).persisted?
        flash[:notice] = '建立成功'
      else
        flash[:alert] = '建立失败'
      end
      
      redirect_to admin_blogs_path
    end
    
    private

    # 单笔资料用
    def blog_params
      params.require(:blog).permit(:title, :content, :genre)
    end
  end
end

Controller - 新增多笔文章

下列为上传 id: 1-4 笔,送出後重新导向原画面的动作

下列为从画面传过去的 params

params
#=> <ActionController::Parameters {"authenticity_token"=>"yG9dFBySr22K4cTR8AwBivWqn03ZYr6ZL/8HIEISBpvH1A7RzBdI7SB14bUJIUXIWP0OVmpGUCtufqF2Khsp8g==", "blog"=>[<ActionController::Parameters {"title"=>"Title 1", "content"=>"Content 1", "genre"=>"life"} permitted: false>, <ActionController::Parameters {"title"=>"cxzbdsb", "content"=>"hrahr"} permitted: false>, <ActionController::Parameters {"title"=>"", "content"=>""} permitted: false>, <ActionController::Parameters {"title"=>"", "content"=>""} permitted: false>], "commit"=>"送出", "controller"=>"admin/blogs", "action"=>"special_update"} permitted: false>

special_blog_params
#=> [<ActionController::Parameters {"title"=>"标题1", "content"=>"内文1"} permitted: true>,
#    <ActionController::Parameters {"title"=>"", "content"=>""} permitted: true>,
#    <ActionController::Parameters {"title"=>"标题2", "content"=>"6666", "genre"=>"life"} permitted: true>,
#    <ActionController::Parameters {"title"=>"", "content"=>""} permitted: true>]

按下送出後,就会触发controller#special_update 逻辑。以下为送出表单的 controller#special_update 逻辑

module Admin
  class BlogsController < ApplicationController    
    def index
      # 取得id: 1-4 的资料,若娶不到则会新增一笔待储存的纪录
      @ten_blogs = SPECIAL_ARTICLE.map { |n| Blog.find_or_initialize_by(id: n) }
    end
    
    # 创建或修改id: 1-4
    def special_update
      blogs = Blog.where(id: SPECIAL_ARTICLE)

      special_blog_params.each.with_index(1) do |params, index|
        next unless params.values.any?(&:present?)

        blog = blogs.find_or_initialize_by(id: index)

        blog.title = params[:title]
        blog.content = params[:content]
        blog.genre = params[:genre]
        blog.user = current_user

        blog.save
      end

      flash[:notice] = '建立成功'

      redirect_to admin_blogs_path
    end
    
    private

    # 多笔资料用
    def special_blog_params
      params.permit(blog: [:title, :content, :genre]).try(:[], :blog)
    end
  end
end

结尾语

今天主要讲的是表单的部分,以下整理出这章节讲到的几个重点

  • form_tag的使用

  • simple_form对於radio_button的使用

  • <input> 为行内元素,并非区块元素

    前几天我们也讲到<button>也为行内元素,要记住喔!

  • controller针对单笔新增 / 多笔新增的处理

目前尚未介绍的内容

  • Strong Parameter 的意义以及使用
  • flash 的应用

参考资料


<<:  Node.js安装

>>:  Day10:10 - 商品服务(1) - 後端 - 总商品资料API

Web服务器扫描工具-Nikto

前几天有练习了小蜘蛛和跳过鱼 今天还是持续练习Web的工具 透过这些工具可以辅助我们更顺利进行手动测...

Day 18. 阿咧?我记得我安装过XCode? Can't find Xcode install for Metal compiler | Unreal Engine

当我在Unreal Engine 4.27.0下载好试图启动软件时,跳出了下面这个视窗,     U...

【PHP Telegram Bot】Day18 - 基础(7):逻辑运算与流程控制

前面介绍的程序都只有单一路径,今天要来讲更复杂的多路径的情况 运算子 比较运算子 说明 用法 ==...

IT铁人DAY 1-进入物件导向世界前的心理准备

  在开始之前,还是很惊讶自己有天可以在这里写文章,分享自身所学的IT技术,提供给大家参考。那其实我...

Day 46 (Node.js)

1.NPM版本 无须更新到最新,怕错误 2.制作专案package.json npm init np...