gpt4 book ai didi

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

转载 作者:数据小太阳 更新时间:2023-10-29 04:16:31 24 4
gpt4 key购买 nike

我正在尝试创建一个表单,以便用户可以保存 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 - 我一直在努力适应这个tutorial并相信第 11 步会在模型中有效地转化它,但我显然不理解某些东西......
  2. "setting"=>{"team_name"=>"", "profession_name"=>"Consultant Doctor"} - team_name尝试保存记录时未识别值(即 chips)。我有一些讨厌的 jquery 可以传输 id来自 div到生成的 input我希望这会起作用...

之前也查了很多关于Stack Overflow的问题(有的好像和这个问题很像,一般都是用jqueryui的),但是想不通怎么修改答案。

如何在实体化中使用模型中的名称 chipautocomplete按相关联的 id 输入并保存选择进入记录?

如有任何帮助或指导,我们将不胜感激。


设置.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

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

views/settings/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 %>

$("*[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,
});

}

});
.prefix~.chips {
margin-top: 0px;
}
<!-- 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>

gem 和版本

  • ruby '2.5.0'
  • gem 'rails', '~> 5.2.1'
  • gem 'materialize-sass'
  • gem 'material_icons'
  • gem '物化形式'
  • gem 'simple_form', '>= 4.0.1'
  • gem 'client_side_validations'
  • gem 'client_side_validations-simple_form'

最佳答案

This is almost certainly not the best way of doing this, but it does work. Please offer suggestions and I will update this, or if someone adds a better answer I will happily mark it as correct. This solution doesn't require much in the way of controller/model changes and is largely done with a (comparatively) short bit of jquery/JS so can be easily repeated within a project.


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

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

自动完成值在 Controller 中从名称转换为 ID。

Chip 值被一些 JS 识别为客户端,并使用正确的 name 创建输入和 id对于 simpleform 自动将值作为数组保存到散列中。

完整的解释和代码如下。

感谢Tom感谢他的有益评论和意见。


autocomplete

要求您在variable_name 下创建一个输入,然后在模型中添加额外的函数以将名称转换为id 进行保存。有效地遵循此 tutorial .

<%= f.input :profession_name,  input_html: { data: { autocomplete: @professions_json  } } %>

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

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

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


chips

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

  <div id="team_ids" placeholder="Add a team" name="setting[team_ids]" class="chips" data-autocomplete="<%=  @teams_json %>"></div>

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

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

Currently the code re-parses the original JSON attribute, it is possible to reference the JSON data that is created on initialisation of the chips, this is likely better but I could not get it to work.

Custom Input Element - someone more experience than myself might be able to play around with this and create a custom element for simple_form... it was beyond me unfortunately.


Ruby on Rails 代码

settings_controller.rb

class SettingsController < ApplicationController

...

def new

@user = current_user
@setting = Setting.new
@professions = Profession.select(:name)
@teams = Team.select(:id, :name)

# Prepare JSON for autocomplete and chips
@teams_json = @teams.to_json(:only => [:id, :name] )
@professions_json = @professions.to_json(:only => [:name] )

end


....

private
def setting_params
params.require(:setting).permit( :profession_name, :user_id, :profession_id, team_ids: [])
end

设置.rb

class Setting < ApplicationRecord

has_and_belongs_to_many :teams, optional: true
belongs_to :user
belongs_to :profession, optional: true

def profession_name
profession.try(:name)
end

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

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

<%= simple_form_for @setting, validate: true, remote: true  do |f| %>

<%= f.input :profession_name, input_html: { data: { autocomplete: @professions_json } } %>

<div id="team_ids" placeholder="Add a team" name="setting[team_ids]" class="chips" data-autocomplete="<%= @teams_json %>"></div>

<%= f.submit %>

<% end %>

演示

$(document).ready(function() {

// Cycle through anything with an data-autocomplete attribute
// Cannot use 'input' as chips must be innitialised on a div
$("[data-autocomplete]").each(function() {

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

// Prepare array for items and add each
var items = [];
var i;
for (i = 0; i < dataJSON.length; ++i) {
items[dataJSON[i].name] = null; // Could assign id to image url and grab this later? dataJSON[i].id
}


// Check if component needs to be a chips
if ($(this).hasClass("chips")) {

// Initialise chips
// Documentation: https://materializecss.com/chips.html
$(this).chips({
placeholder: $(this).attr("placeholder"),
autocompleteOptions: {
data: items,
limit: Infinity,
minLength: 1
},
onChipAdd: () => {
chipChange($(this).attr("id")); // See below
},
onChipDelete: () => {
chipChange($(this).attr("id")); // See below
}
});


// Tweak the input names, etc
// This means we can style the code within the view as we would a simple_form input
$(this).attr("id", $(this).attr("id") + "_wrapper");

$(this).attr("name", $(this).attr("name") + "_wrapper");

} else {

// Autocomplete is much simpler! Just initialise with data
// Documentation: https://materializecss.com/autocomplete.html
$(this).autocomplete({
data: items,
});

}




});

});


function chipChange(elementID) {

// Get chip element from ID
var elem = $("#" + elementID);

// In theory you can get the data of the chips instance, rather than re-parsing it
var dataJSON = JSON.parse(elem.attr("data-autocomplete"));

// Remove any previous inputs (we are about to re-add them all)
elem.children("input[auto-chip-entry=true]").remove();

// Find the wrapping element
wrapElement = elem.closest("div[data-autocomplete].chips")

// Get the input name we need, [] tells Rails that this is an array
formInputName = wrapElement.attr("name").replace("_wrapper", "") + "[]";

// Start counting entries so we can add value to input
var i = 0;

// Cycle through each chip
elem.children(".chip").each(function() {

// Get text of chip (effectively just excluding material icons 'close' text)
chipText = $(this).ignore("*").text();

// Get id from original JSON array
// You should be able to check the initialised Materialize data array.... Not sure how to make that work
var chipID = findElement(dataJSON, "name", chipText);

// ?Check for undefined here, will be rejected by Rails anyway...?

// Add input with value of the selected model ID
$(this).parent().append('<input value="' + chipID + '" multiple="multiple" type="hidden" name="' + formInputName + '" auto-chip-entry="true">');


});

}


// Get object from array of objects using property name and value
function findElement(arr, propName, propValue) {
for (var i = 0; i < arr.length; i++)
if (arr[i][propName] == propValue)
return arr[i].id; // Return id only
// will return undefined if not found; you could return a default instead
}


// Remove text from children, etc
$.fn.ignore = function(sel) {
return this.clone().find(sel || ">*").remove().end();
};


// Print to console instead of posting
$(document).on("click", "input[type=submit]", function(event) {

// Prevent submission of form
event.preventDefault();

// Gather input values
var info = [];
$(this).closest("form").find("input").each(function() {
info.push($(this).attr("name") + ":" + $(this).val());
});

// Prepare hash in easy to read format
var outText = "<h6>Output</h6><p>" + info.join("</br>") + "</p>";

// Add to output if exists, or create if it does not
if ($("#output").length > 0) {
$("#output").html(outText);
} else {
$("form").append("<div id='output'>" + outText + "</div>");
}


});
<!-- 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">



<form class="simple_form new_setting" id="new_setting" novalidate="novalidate" data-client-side-validations="" action="/settings" accept-charset="UTF-8" data-remote="true" method="post"><input name="utf8" type="hidden" value="✓">


<div class="input-field col string optional setting_profession_name">
<input data-autocomplete='[{"id":1,"name":"Consultant Doctor"},{"id":2,"name":"Junior Doctor (FY1)"}]' class="string optional" type="text" name="setting[profession_name]" id="setting_profession_name"
data-target="autocomplete-options-30fe36f7-f61c-b2f3-e0ef-c513137b42f8" data-validate="true">
<label class="string optional" for="setting_profession_name">Profession name</label></div>

<div id="team_ids" name="setting[team_ids]" class="chips input-field" placeholder="Add a team" data-autocomplete='[{"id":1,"name":"Miss T"},{"id":2,"name":"Surgical Take"}]'></div>


<input type="submit" name="commit" value="Create Setting" data-disable-with="Create Setting">

</form>

关于javascript - 在关联模型的 Ruby on Rails 表单中使用 Materialise `chip` 和 `autocomplete`,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53839487/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com