在Ruby on Rails表单中使用Materialise`chip`和`autocomplete`与关联模型

Oli*_*ure 8 javascript ruby jquery ruby-on-rails materialize

我正在尝试创建一个表单,以便用户可以保存setting具有默认teams(多个)和他们professions(单个)的表单.我可以使用simple_form以下代码行完成此操作,但我尝试使用自动完成功能,因为下拉列表与我的设计不兼容.

  • <%= f.association :profession %>
  • <%= f.association :team, input_html: { multiple: true } %>

我正在将一个集合中的JSON加载到data-autocomplete-source我的一个属性中inputs,jquery然后稍微循环一下这些属性,然后初始化实现.autocomplete,我还需要.chips为许多关联做这个.

UI元素正在按照我的意愿工作,但我无法弄清楚如何保存新记录.我有两个问题:

  1. Unpermitted parameters: :team_name, :profession_name- 我一直在努力调整本教程并相信Step 11会在模型中有效地转换它,但显然不理解某些东西......
  2. "setting"=>{"team_name"=>"", "profession_name"=>"Consultant Doctor"}- 尝试保存记录时未识别team_name值(即chips).我有一些讨厌的jQuery转移了对iddiv所生成的input,我希望会工作...

我还检查过很多关于Stack Overflow的问题(其中一些似乎与这个问题类似,通常使用jqueryui),但无法弄清楚如何调整答案.

如何在实体化chipautocomplete输入中使用模型中的名称并将它们的关联保存id到记录中?

任何帮助或指导将不胜感激.


setting.rb

class Setting < ApplicationRecord

  has_and_belongs_to_many :team, optional: true

  belongs_to :user
  belongs_to :profession

  def team_name
    team.try(:name)
  end

  def team_name=(name)
    self.team = Team.find_by(name: name) if name.present?
  end

  def profession_name
    profession.try(:name)
  end

  def profession_name=(name)
    self.profession = Profession.find_by(name: name) if name.present?
  end


end
Run Code Online (Sandbox Code Playgroud)

settings_controller.rb

  def new

    @user = current_user
    @professions = Profession.all
    @teams = Team.all
    @setting = Setting.new

    @teams_json = @teams.map(&:name)
    @professions_json = @professions.map(&:name)

    render layout: "modal"

  end


  def create

    @user = current_user
    @setting = @user.settings.create(setting_params)

    if @setting.save 
      redirect_to action: "index"
    else
      flash[:success] = "Failed to save settings"
      render "new"   
    end

  end


  private

    def setting_params
      params.require(:setting).permit(:user_id, :contact, :view, :taketime, :sortname, :sortlocation, :sortteam, :sortnameorder, :sortlocationorder, :sortteamorder, :location_id, :profession_id, :department_id, team_ids: [])
    end
Run Code Online (Sandbox Code Playgroud)

查看/设置/ new.html.erb

<%= simple_form_for @setting do |f| %>



<div class="row">
  <div class="col s12">
    <div class="row">
      <div class="input-field autocomplete_dynamic col s12">
        <i class="material-icons prefix">group</i>

        <div data-autocomplete-source='<%= @teams_json %>' class="string optional chips" type="text" name="setting[team_name]" id="setting_team_name"></div>

      </div>
    </div>
  </div>
</div>




<div class="row">
  <div class="col s12">
    <div class="row">
      <div class="input-field autocomplete_dynamic col s12">
        <i class="material-icons prefix">group</i>

          <%= f.input :profession_name, wrapper: false, label: false, as: :search, input_html: {:data => {autocomplete_source: @professions_json} } %>

        <label for="autocomplete-input">Select your role</label>
      </div>
    </div>
  </div>
</div>



  <%= f.submit %>

<% end %>
Run Code Online (Sandbox Code Playgroud)

$("*[data-autocomplete-source]").each(function() {

  var items = [];
  var dataJSON = JSON.parse($(this).attr("data-autocomplete-source"));

  var i;
  for (i = 0; i < dataJSON.length; ++i) {
    items[dataJSON[i]] = null;
  }

  if ($(this).hasClass("chips")) {

    $(this).chips({
      placeholder: $(this).attr("placeholder"),
      autocompleteOptions: {
        data: items,
        limit: Infinity,
        minLength: 1
      }
    });


    // Ugly jquery to give the generated input the correct id and name
    idStore = $(this).attr("id");
    $(this).attr("id", idStore + "_wrapper");
    nameStore = $(this).attr("name");
    $(this).attr("name", nameStore + "_wrapper");

    $(this).find("input").each(function() {
      $(this).attr("id", idStore);
      $(this).attr("name", nameStore);
    });


  } else {

    $(this).autocomplete({
      data: items,
    });

  }

});
Run Code Online (Sandbox Code Playgroud)
.prefix~.chips {
  margin-top: 0px;
}
Run Code Online (Sandbox Code Playgroud)
<!-- jquery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<!-- Materialize CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">

<!-- Materialize JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>

<!-- Material Icon Webfont -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">




<div class="row">
  <div class="col s12">
    <div class="row">
      <div class="input-field autocomplete_dynamic col s12">
        <i class="material-icons prefix">group</i>

        <div data-autocomplete-source='["Miss T","Mr C","Mr D","Medicine Take","Surgery Take"]' class="string optional chips" type="text" name="setting[team_name]" id="setting_team_name"></div>


      </div>
    </div>
  </div>
</div>




<div class="row">
  <div class="col s12">
    <div class="row">
      <div class="input-field autocomplete_dynamic col s12">
        <i class="material-icons prefix">group</i>

        <input class="string optional input-field" data-autocomplete-source='["Consultant Doctor","Ward Clerk","Nurse","Foundation Doctor (FY1)","Foundation Doctor (FY2)","Core Trainee Doctor (CT2)","Core Trainee Doctor (CT1)"]' type="text" name="setting[profession_name]"
          id="setting_profession_name">


        <label for="autocomplete-input">Select your role</label>
      </div>
    </div>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

宝石和版本

  • 红宝石'2.5.0'
  • 宝石'轨道','〜> 5.2.1'
  • 宝石'物化 - 萨斯'
  • 宝石'material_icons'
  • 宝石'物化形式'
  • gem'simple_form','> = 4.0.1'
  • gem'client_side_validations'
  • gem'client_side_validations-simple_form'

Oli*_*ure 4

\n

几乎可以肯定这不是最好的方法,但它确实有效。请提供建议,我会更新此内容,或者如果有人添加更好的答案,我会很乐意将其标记为正确。该解决方案不需要太多控制器/模型更改,并且很大程度上是通过(相对)较短的 jquery/JS 完成的,因此可以在项目中轻松重复。

\n
\n\n
\n\n

我已经设法让自动完成和芯片与 Ruby on Rails 一起使用,并尽可能利用 simple_form 表单助手。

\n\n

实际上,我将 JSON 存储到每种情况的自定义属性中,然后在加载视图时使用一些 jquery/javascript 对其进行解析,然后再使用它来初始化autocompletechips

\n\n

自动完成值在控制器内从名称转换为 ID。

\n\n

客户端使用一些 JS 来识别芯片值,并使用正确name和正确的方式创建输入id形式,以自动将值作为数组保存到哈希中。

\n\n

完整的解释和代码如下。

\n\n

谢谢你汤姆的有用评论和意见。

\n\n
\n\n

autocomplete

\n\n

要求您在变量_name下创建输入,然后在模型中添加其他函数以将名称转换为 id 进行保存。有效地遵循本教程

\n\n
<%= f.input :profession_name,  input_html: { data: { autocomplete: @professions_json  } } %>\n
Run Code Online (Sandbox Code Playgroud)\n\n

正如您在上面看到的,与添加典型的 simple_form 关联的唯一真正区别如下:

\n\n
    \n
  • f.input而不是f.association- 确保呈现文本框而不是下拉列表
  • \n
  • :model_name而不是:model- 确保控制器识别出这是一个需要转换为对象的名称
  • \n
  • input_html: { data: { autocomplete: @model_json } }- 这会添加一个包含所有 JSON 数据的自定义属性,这是由
  • \n
\n\n

您需要确保模型的名称是唯一的。

\n\n
\n\n

chips

\n\n

这有点复杂,需要额外的 JavaScript 函数。该代码将回调附加到添加或删除芯片的事件,然后循环遍历每个芯片并添加隐藏的input. 每个输入都有一个与 simple_form 期望的名称属性相匹配的名称属性,因此在提交到控制器之前它会被正确添加到哈希参数中。我无法让它翻译数组中的多个名称,所以只是让它重新读取原始 JSON 中的 id 并将其添加为输入的值。

\n\n
  <div id="team_ids" placeholder="Add a team" name="setting[team_ids]" class="chips" data-autocomplete="<%=  @teams_json %>"></div>\n
Run Code Online (Sandbox Code Playgroud)\n\n

从上面你可以看到与 simple_form 约定有以下偏差:

\n\n
    \n
  • <div>而不是 a,<% f.input %>因为需要在 div 上调用 Materialise 芯片
  • \n
  • placeholder="..."一旦芯片初始化,此文本将用作占位符,可以留空/不包含
  • \n
  • name="setting[team_ids]"帮助 simple_form 了解这适用于哪个模型
  • \n
  • class="chips"确保我们的 JavaScript 稍后知道要chips在此元素上进行初始化
  • \n
  • data-autocomplete="<%= @teams_json %>"将JSON数据保存为div的属性以供稍后解析
  • \n
\n\n
\n

目前,代码重新解析原始 JSON 属性,可以引用在芯片初始化时创建的 JSON 数据,这可能更好,但我无法让它工作。

\n\n

自定义输入元素- 比我更有经验的人可能能够尝试这个并为 simple_form 创建自定义元素......不幸的是,这超出了我的范围。

\n
\n\n


\n\n

Ruby on Rails 代码

\n\n

设置_控制器.rb

\n\n
<%= f.input :profession_name,  input_html: { data: { autocomplete: @professions_json  } } %>\n
Run Code Online (Sandbox Code Playgroud)\n\n

设置.rb

\n\n
  <div id="team_ids" placeholder="Add a team" name="setting[team_ids]" class="chips" data-autocomplete="<%=  @teams_json %>"></div>\n
Run Code Online (Sandbox Code Playgroud)\n\n

_form.html.erb 注意 这是部分内容,如前面的下划线所示

\n\n
class SettingsController < ApplicationController\n\n  ...\n\n  def new\n\n    @user = current_user\n    @setting = Setting.new\n    @professions = Profession.select(:name)\n    @teams = Team.select(:id, :name)\n\n    # Prepare JSON for autocomplete and chips\n    @teams_json = @teams.to_json(:only => [:id, :name] )\n    @professions_json = @professions.to_json(:only => [:name] )\n\n  end\n\n\n  ....\n\n private\n    def setting_params\n      params.require(:setting).permit( :profession_name, :user_id,  :profession_id, team_ids: [])\n    end\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

演示

\n\n

\r\n
\r\n
class Setting < ApplicationRecord\n\n  has_and_belongs_to_many :teams, optional: true    \n  belongs_to :user\n  belongs_to :profession, optional: true\n\n  def profession_name\n    profession.try(:name)\n  end\n\n  def profession_name=(name)\n    self.profession = Profession.find_by(name: name) if name.present?\n  end\n
Run Code Online (Sandbox Code Playgroud)\r\n
<%= simple_form_for @setting, validate: true, remote: true  do |f| %>\n\n  <%= f.input :profession_name,  input_html: { data: { autocomplete: @professions_json  } } %>\n\n  <div id="team_ids" placeholder="Add a team" name="setting[team_ids]" class="chips" data-autocomplete="<%=  @teams_json %>"></div>\n\n  <%= f.submit %>\n\n<% end %>\n
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n