• 动态嵌套form,使用Stimulus Js库(前后端不分离)


    我的git代码:https://github.com/chentianwei411/nested_form-Stimulus-

    Stimulus:     https://www.cnblogs.com/chentianwei/p/9806875.html

    开始:

    rails new -m ../jumpstart, Gorails视频(创建一个rails模版)/template.rb -d postgresql nested_forms

    rails webpacker:install:stimulus

    在_header.html.erb内添加:

    使用javascript_pack_tag方法添加JS pack到Rails views

    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

    然后修改文件名:

     mv app/javascript/controllers/{hello.nested_form}_controller.js

    创建手脚架和模型:

    rails g scaffold Project name description

    rails g model Task description project:belongs_to

    rails db:migrate

    产生:

      create_table "projects", force: :cascade do |t|
        t.string "name"
        t.string "description"
        t.datetime "created_at", null: false
        t.datetime "updated_at", null: false
      end
    
      create_table "tasks", force: :cascade do |t|
        t.string "description"
        t.datetime "created_at", null: false
        t.datetime "updated_at", null: false
        t.bigint "project_id"
        t.index ["project_id"], name: "index_tasks_on_project_id"
      end

    嵌套结构的类方法使用:

    class Project < ApplicationRecord
      has_many :tasks, inverse_of: :project
      accepts_nested_attributes_for :tasks, reject_if: :all_blank, allow_destroy: true
    end

    具体解释见博客:

    ActiveRecord Nested Atrributes 关联记录,对嵌套属性进行CURD
    Rails-Treasure chest2 嵌套表单;

    controller内添加属性白名单:

        def project_params
          params.require(:project).permit(:name, :description, tasks_attributes: [:id, :description, :_destroy])
        end

    然后在view视图_form.html.erb上添加一个嵌套的form builder:

      <h4>Tasks</h4>
    
      <%= form.fields_for :tasks do |task| %>
        <div class="form-group">
          <%= task.label :description %>
          <%= task.text_field :description, class: 'form-control'%>
        </div>
      <% end %>

    使用#fields_for(record_name, record_object = nil, &block),创建一个scope在一个指定的model对象如form_for,但是不创建自身的form tags。它用于在一个form内创建额外的model对象。(具体见api文档)

    修改project_controller,添加语句@project.tasks.new。或者设置视图中的#fields_for方法的第2个参数record_object为Task.new

      def new
        @project = Project.new
        @project.tasks.new
      end

    ⚠️:重构视图,可以把fields_for方法的块block提取出来。_nest.html.erb

    使用template标签,让JavaScript决定是否显示在页面。配合使用stimulus.js。

      <template>
        <%= form.fields_for :tasks, Task.new, child_index: 'New_RECORD' do |task| %>
          <%= render 'nest', form: task%>
        <% end %>
      </template>

    child_index: 'New_RECORD' 即子索引的名字,会在label,input标签的for,name,  id属性上使用到:

    <div class="form-group">
      <label for="project_tasks_attributes_New_RECORD_description">Description</label>
      <input class="form-control" type="text" name="project[tasks_attributes][New_RECORD][description]" id="project_tasks_attributes_New_RECORD_description">
    </div>

    视图的最终代码:

    • data-controller调用js文件中的类对象实例化的对象
    • data-target, 用于取对应元素。
    • data-action,用于绑定事件。这里使用了link_to视图方法。
      <div data-controller="nested-form">
        <template data-target="nested-form.template">
          <%= form.fields_for :tasks, Task.new, child_index: 'New_RECORD' do |task| %>
            <%= render 'nest', form: task%>
          <% end %>
        </template>
    
        <%= form.fields_for :tasks do |task| %>
          <%= render 'nest', form: task%>
        <% end %>
    
        <div class="mb-3" data-target='nested-form.links'>
          <%= link_to 'Add Task', '#', class: 'btn btn-outline-primary', data: {action: 'click->nested-form#add_association'} %>
        </div>
      </div>

    nested_form_controller.rb

    export default class extends Controller {
      static targets = [ "links", "template" ]
    
      connect() {
      }
      add_association(event) {
        event.preventDefault()
    
        var content = this.templateTarget.innerHTML.replace(/New_RECORD/g, new Date().getTime())
        this.linksTargets.insertAdjacentHTML('beforebegin', content)
      }
    }

    ⚠️这里用到js库的2个方法replace和insertAdjacentHTML用于对元素内容操作和元素节点的插入。
    视图上的最终代码:
    <input class="form-control" type="text" name="project[tasks_attributes][1554196211709][description]" id="project_tasks_attributes_1554196211709_description">

    功能:增加task,还可以删除task。

    在_nest.html.erb表格内增加一个"Remove"连接按钮。

    <%= content_tag :div, class:'nested-fields', data: {new_record: form.object.new_record?}  do %>  
      <div class="form-group">
        <%= form.label :description %>
        <%= form.text_field :description, class: 'form-control'%>
        <small><%= link_to "REMOVE", "#", data: {action: "click->nested-form#remove_association"}%></small>
        <!-- <%= form.hidden_field :_destroy%> -->
      </div>
    <% end %>

    data-new-record用于判断是不是新建数据。

    添加一个data-action绑定事件remove_association。

    再看nested_form_controller.js,添加事件:

      remove_association(event) {
        event.preventDefault()
    
        let wrapper = event.target.closest(".nested-fields")   #有nest-fileds类的元素
        if (wrapper.dataset.newRecord == "true") {
          wrapper.remove()
        } else {
          console.log("不能删除")
        }
      }

    注意:

    dataset属性提供了读写所有客制化的data属性 data-*

    考虑到edit界面,有已经添加的task和新增的task,所以要区分,新增的直接从nom树移除,已经添加的则要发出删除请求。

    修改上面的代码:

    _nest.html.erb内添加一个input标签,并隐藏。它的用途是标记作用!

    在js中添加2行代码:

    1. 找到这个input标签,并设置value等于1或者true, 这样更新时,会自动判断是否删除。
    2. 从节点树上隐藏这个元素。CSS#display属性设置none。
      remove_association(event) {
        event.preventDefault()
    
        let wrapper = event.target.closest(".nested-fields")
        if (wrapper.dataset.newRecord == "true") {
          wrapper.remove()
        } else {
          wrapper.querySelector("input[name*='_destroy']").value = true
          wrapper.style.display = 'none'
        }
      }
  • 相关阅读:
    (一)SAPI简述
    一、初识T4引擎
    (二)语音合成测试案例
    (三)语音合成器实例
    四、分离T4引擎
    二、T4模板
    三、T4模板与实体生成
    禁用浏览器缓存
    js_function
    asp.net(C#)读取文件夹和子文件夹下所有文件,绑定到GRIDVIEW并排序 .
  • 原文地址:https://www.cnblogs.com/chentianwei/p/10640812.html
Copyright © 2020-2023  润新知