Это почти наверняка не лучший способ сделать это, , но он работает .Пожалуйста, предлагайте предложения, и я буду обновлять это, или, если кто-то добавит лучший ответ, я с радостью отмечу его как правильный.Это решение не требует особых изменений в контроллере / модели и в основном выполняется с помощью (сравнительно) короткого бита jquery / JS, поэтому его можно легко повторить в проекте.
Мне удалось заставить и автозаполнение, и микросхемы работать с Ruby on Rails, используя помощников формы simple_form, где это возможно.
По сути, я сохраняю JSON в свой атрибут для каждого случая, а затем анализирую его с помощью некоторого jquery/ javascript, когда представление загружается перед использованием этого для инициализации autocomplete
или chips
.
Значения автозаполнения преобразуются из имени в id в контроллере.
Значения микросхем распознаются на стороне клиента с некоторыми JS, а входы создаются с правильными name
и id
для простой формы вавтоматически сохраняйте значения в виде массива в хэш.
Полное объяснение и код приведены ниже.
Спасибо Тому за его полезные комментарии и ввод.
autocomplete
Требуется создать вход в поле переменная _name , а затем добавить дополнительные функции в модельперевести имя в идентификатор для сохранения.Эффективно следуя этому учебнику .
<%= f.input :profession_name, input_html: { data: { autocomplete: @professions_json } } %>
Как вы можете видеть выше, единственное реальное отличие от добавления типичной ассоциации simple_form заключается в следующем:
f.input
вместо f.association
- обеспечивает отображение текстового поля, а не раскрывающегося списка :model_name
вместо :model
- гарантирует, что контроллер распознает, что это имя необходимо преобразовать в объект input_html: { data: { autocomplete: @model_json } }
- это добавляет настраиваемый атрибут со всеми вашими данными JSON, это разбирается на
Вы должны убедиться, что имена вашей модели уникальны.
chips
Это немного сложнее, требуя дополнительных функций JavaScript.Код прикрепляет обратный вызов к событию добавления или удаления микросхемы перед циклическим просмотром каждого из них и добавлением скрытого input
.Каждый вход имеет атрибут name, который соответствует тому, что ожидает simple_form, поэтому он корректно добавляется в параметры хеша перед отправкой в контроллер.Я не смог заставить его переводить несколько имен в массиве, поэтому просто заставил его перечитать идентификатор из исходного JSON и добавить его в качестве значения ввода.
<div id="team_ids" placeholder="Add a team" name="setting[team_ids]" class="chips" data-autocomplete="<%= @teams_json %>"></div>
Сверху выможно увидеть, что существуют следующие отклонения от соглашения simple_form:
<div>
, а не <% f.input %>
, так как фишки Materialise должны вызываться на div placeholder="..."
этот текстиспользуется в качестве заполнителя после инициализации чипов, его можно оставить пустым / не включать name="setting[team_ids]"
помогает simple_form понять, к какой модели это относится class="chips"
гарантирует, что наш JavaScript позжезнает, что нужно инициализировать chips
для этого элемента data-autocomplete="<%= @teams_json %>"
сохраняет данные JSON как атрибут div для последующего анализа
В настоящее время код re- разбирает исходный атрибут JSON , можно ссылаться на данные JSON, созданные при инициализации чипов, это, вероятно, лучше, но я не смог заставить его работать.
Custom Элемент ввода - кто-то, обладающий большим опытом, чем я, мог бы поиграть с этим и создать собственный элемент для simple_form ... это, к сожалению, было мне непонятно.
Код 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
setting.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 NB это частичное, как обозначено предыдущим подчеркиванием
<%= 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>