Skip to content

Instantly share code, notes, and snippets.

@stephendolan
Last active July 11, 2022 15:07
Show Gist options
  • Save stephendolan/35dc716f060729b866c4ed6b11722def to your computer and use it in GitHub Desktop.
Save stephendolan/35dc716f060729b866c4ed6b11722def to your computer and use it in GitHub Desktop.
Saving array of values to the DB in a Lucky operation
class Videos::FormFields < BaseComponent
needs operation : SaveVideo
div data_controller: "select" do
mount Shared::FieldLabel, operation.topic_ids, "Topics"
text_input operation.topic_ids, class: "hidden", data_select_target: "input"
tag "select", multiple: true, data_select_target: "select" do
render_topic_options
end
end
private def render_topic_options
existing_topics = if (persisted_record = operation.record)
persisted_record.topics
else
[] of Topic
end
TopicQuery.new.each do |topic|
attributes = existing_topics.includes?(topic) ? [:selected] : [] of Symbol
option topic.label, value: topic.id, attrs: attributes
end
end
end
# The operation to save a video
class SaveVideo < Video::SaveOperation
permit_columns title, description
attribute topic_ids : String
after_save associate_topics
private def associate_topics(video : Video)
return unless (topic_ids_string = topic_ids.value)
submitted_topics = topic_ids_string.split(",").map { |topic| UUID.new(topic) }
existing_topics = video.topics.map &.id
common_topics = existing_topics & submitted_topics
additional_topics = submitted_topics - existing_topics
# Remove topics that were not found in the submitted topics
VideoTopicQuery.new.video_id(video.id).topic_id.not.in(common_topics).delete
# Associate newly-added topics
additional_topics.each do |topic_id|
SaveVideoTopic.create!(video_id: video.id, topic_id: topic_id)
end
end
end
import { Controller } from "@hotwired/stimulus";
import Choices from "choices.js";
import "choices.js/public/assets/styles/choices.min.css";
// Given some 'select' target, a Choices.js select will be created for it
// and values will be populated into the 'input' target's value field.
export default class extends Controller {
static targets = ["select", "input"];
readonly selectTarget!: HTMLSelectElement;
readonly inputTarget!: HTMLInputElement;
connect(): void {
const choicesSelect = new Choices(this.selectTarget as HTMLSelectElement, {
allowHTML: false,
removeItemButton: true,
duplicateItemsAllowed: false,
});
this.element.addEventListener("change", (event) => {
// We force this because the TypeScript definition doesn't account for the value-only Boolean paramter.
let currentValue = choicesSelect.getValue(true) as string | string[];
if (!(currentValue instanceof Array)) {
currentValue = [currentValue];
}
this.inputTarget.value = currentValue.join(",");
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment