PG支持array类型,但simple form中却没有 array input 组件,试着custom一个,效果还不错。
效果图
最终效果:
具体步骤
这里以诗词Poem的content类型为array为例,基于rails5.2,webpacker4.0,给出具体的实现步骤,
新增component array_input
添加
app/inputs/array_input.rb
文件,内容如下:class ArrayInput < SimpleForm::Inputs::Base def input(wrapper_options = nil) existing_values = Array(object.public_send(attribute_name)) template.content_tag(:div) do existing_values.map do |array_item| template.concat builder_input_with_remove_btn(array_item) end if existing_values.empty? template.concat builder_input_with_remove_btn(nil) end template.concat add_item_btn end end def input_html_options super.merge({ class: 'form-control rounded', type: :text }) end def builder_input_with_remove_btn(element) template.content_tag(:div, class: 'input-group mb-3') do template.concat @builder.text_field(nil, input_html_options.merge(value: element, name: "#{object_name}[#{attribute_name}][]")) template.concat remove_item_btn end end def add_item_btn content = <<~HTML <button type="button" class="btn btn-success btn-sm array-add-item">新增 +</button> HTML content.html_safe end def remove_item_btn content = <<~HTML <div class="input-group-append ml-2"> <button type="button" class="btn btn-secondary btn-sm rounded array-remove-item">移除 -</button> </div> HTML content.html_safe end end
这里,需要注意的是,当content 为空时,只会有一行input,此时的移除按钮应该是disabled才合适。
我这里给出的解决方法有点蠢,直接在build_input_with_remove_btn中,添加了参数来控制,可以看到效果图中,第一个移除按钮是disabled。
修改builder_input_with_remove_btn 这个method:
## 给 builder_input_with_remove_btn 添加 disabled 参数 def builder_input_with_remove_btn(element, disabled = false) template.content_tag(:div, class: 'input-group mb-3') do template.concat @builder.text_field(nil, input_html_options.merge(value: element, name: "#{object_name}[#{attribute_name}][]")) disabled ? template.concat(disabled_remove_item_btn) : template.concat(remove_item_btn) end end ## 添加新的 disabled_remove_item_btn def disabled_remove_item_btn content = <<~HTML <div class="input-group-append ml-2"> <button type="button" class="btn btn-secondary btn-sm rounded array-remove-item" disabled>移除 -</button> </div> HTML content.html_safe end ## 修改 input ,调用新的 builder_input_with_remove_btn def input(wrapper_options = nil) existing_values = Array(object.public_send(attribute_name)) template.content_tag(:div) do existing_values.each_with_index do |array_el, index| disabled = index.zero? ? true : false template.concat builder_input_with_remove_btn(array_el, disabled) end if existing_values.empty? template.concat builder_input_with_remove_btn(nil, true) end template.concat add_item_btn end end
给移除和新增按钮添加click监听事件
在app/javascripts中,添加文件
src/array_input.jsx
:import $ from 'jquery'; $(document).on('content:loaded', function(event) { $(event.target).find('.array-add-item').each(function() { $(this).on('click', e => { e.preventDefault() const inputList = this.parentElement.querySelectorAll('.input-group') const lastLineField = inputList[inputList.length - 1] const $cloneField = $(lastLineField).clone() $cloneField.find('input').val("") $cloneField.find('button').removeAttr('disabled') $(lastLineField).after($cloneField) }) }) $(event.target).on('click', '.array-remove-item', e => { e.preventDefault() e.currentTarget.parentElement.parentElement.remove() }) })
这里针对移除按钮的 click 事件,需要使用
on('click', '.array-remove-item',e => {....})
来全局监听,因为新增事件中,clone出来的input-group 并没有将事件监听也一并clone了。「踩过这个坑的都懂」在packs/application.js中,引入刚刚新增的文件:
import '../src/array_input.jsx';
修改使用content的地方,确保值正确传递
修改文件的表单部分:
<%= f.input :content, as: :array %>
在controller中,针对传过来的params,设定content为array:
params.require(:poem).permit(:title, :dynasty, :author, content: [])
在poem.rb中,清除content中的空行:
before_save :remove_blank_item def remove_blank_item self.content.reject!(&:blank?) end
OK,收工。