Make checklist entries sortable by d&d
This commit is contained in:
parent
1e1d80a2c3
commit
7acc0559ae
10 changed files with 98 additions and 20 deletions
|
|
@ -33,8 +33,13 @@ class ChecklistEntriesController < ApplicationController
|
||||||
# PATCH/PUT /checklist_entries/1
|
# PATCH/PUT /checklist_entries/1
|
||||||
def update
|
def update
|
||||||
if @checklist_entry.update(checklist_entry_params)
|
if @checklist_entry.update(checklist_entry_params)
|
||||||
|
respond_to do |format|
|
||||||
|
format.turbo_stream
|
||||||
|
format.html do
|
||||||
redirect_to @checklist_entry.checklist, notice: "Checklist entry was successfully updated.",
|
redirect_to @checklist_entry.checklist, notice: "Checklist entry was successfully updated.",
|
||||||
status: :see_other
|
status: :see_other
|
||||||
|
end
|
||||||
|
end
|
||||||
else
|
else
|
||||||
render :edit, status: :unprocessable_entity
|
render :edit, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
|
||||||
38
app/javascript/controllers/drag_controller.js
Normal file
38
app/javascript/controllers/drag_controller.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
import Sortable from "sortablejs"
|
||||||
|
import { put } from "@rails/request.js";
|
||||||
|
|
||||||
|
// Connects to data-controller="drag"
|
||||||
|
export default class extends Controller {
|
||||||
|
static values = {
|
||||||
|
group: String
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.element.style.cursor = "grab"
|
||||||
|
new Sortable(this.element, {
|
||||||
|
group: this.groupValue,
|
||||||
|
onEnd: this.onEnd,
|
||||||
|
animation: 150,
|
||||||
|
swap: true,
|
||||||
|
swapThreshold: 0.65,
|
||||||
|
ghostClass: 'dragFrom',
|
||||||
|
swapClass: 'dragTo',
|
||||||
|
forceFallback: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onEnd(event) {
|
||||||
|
const position = event.newIndex + 1
|
||||||
|
const url = event.item.dataset["sortableUrl"]
|
||||||
|
// Expect backend to update list items via turbo if necessary
|
||||||
|
put(url, {
|
||||||
|
body: JSON.stringify({ checklist_entry: { position: position }}),
|
||||||
|
contentType: "application/json",
|
||||||
|
headers: {
|
||||||
|
"Accept": "text/vnd.turbo-stream.html, text/html, application/xhtml+xml"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,9 @@ application.register("check-link", CheckLinkController)
|
||||||
import CollapseChevronTogglerController from "./collapse_chevron_toggler_controller"
|
import CollapseChevronTogglerController from "./collapse_chevron_toggler_controller"
|
||||||
application.register("collapse-chevron-toggler", CollapseChevronTogglerController)
|
application.register("collapse-chevron-toggler", CollapseChevronTogglerController)
|
||||||
|
|
||||||
|
import DragController from "./drag_controller"
|
||||||
|
application.register("drag", DragController)
|
||||||
|
|
||||||
import HelloController from "./hello_controller"
|
import HelloController from "./hello_controller"
|
||||||
application.register("hello", HelloController)
|
application.register("hello", HelloController)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,14 +105,24 @@ class Check < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def display_target_disabilities
|
def display_target_disabilities
|
||||||
%i[visual auditory physical cognitive].select { |d| send(:"#{d}?") }.map { |d| I18n.t("disability.#{d}") }.join(", ")
|
%i[visual auditory physical cognitive].select { |d| send(:"#{d}?") }
|
||||||
|
.map { |d| I18n.t("disability.#{d}") }
|
||||||
|
.sort
|
||||||
|
.join(", ")
|
||||||
end
|
end
|
||||||
|
|
||||||
def display_applicabilities
|
def display_applicabilities
|
||||||
%i[applicable_to_analogue applicable_to_app applicable_to_document applicable_to_non_web applicable_to_web].select { |a| send(:"#{a}?") }.map { |a| I18n.t("applicability.#{a}") }.join(", ")
|
%i[applicable_to_analogue
|
||||||
|
applicable_to_app
|
||||||
|
applicable_to_document
|
||||||
|
applicable_to_non_web
|
||||||
|
applicable_to_web].select { |a| send(:"#{a}?") }
|
||||||
|
.map { |a| I18n.t("applicability.#{a}") }
|
||||||
|
.sort
|
||||||
|
.join(", ")
|
||||||
end
|
end
|
||||||
|
|
||||||
def display_label
|
def display_label
|
||||||
"#{name_de} (#{[number, external_number].compact_blank.join("/")})"
|
[external_number, name_de].compact_blank.join(" ")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,18 @@
|
||||||
|
|
||||||
<div id="<%= dom_id checklist_entry %>" class="hover-row py-1">
|
<div id="<%= dom_id checklist_entry %>" class="hover-row py-1">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div style="width: 56px">
|
<i class="bi bi-grip-vertical text-secondary mt-2"></i>
|
||||||
<%= button_to tag.i(class: "bi bi-arrow-down"), checklist_entry_path(checklist_entry), method: :patch, class: "btn btn-link p-0 #{"pe-3" if is_last } float-start", data: { turbo_frame: "checklist_entries" }, params: { checklist_entry: { position: checklist_entry.position + 1 }} unless is_last %>
|
<div style="">
|
||||||
<%= button_to tag.i(class: "bi bi-arrow-up"), checklist_entry_path(checklist_entry), method: :patch, class: "btn btn-link p-0 pe-3 float-start", data: { turbo_frame: "checklist_entries" }, params: { checklist_entry: { position: checklist_entry.position - 1 }} unless is_first %>
|
<% button_to tag.i(class: "bi bi-arrow-down"), checklist_entry_path(checklist_entry), method: :patch, class: "btn btn-link p-0 #{"pe-3" if is_last } float-start", data: { turbo_frame: "checklist_entries" }, params: { checklist_entry: { position: checklist_entry.position + 1 }} unless is_last %>
|
||||||
|
<% button_to tag.i(class: "bi bi-arrow-up"), checklist_entry_path(checklist_entry), method: :patch, class: "btn btn-link p-0 pe-3 float-start", data: { turbo_frame: "checklist_entries" }, params: { checklist_entry: { position: checklist_entry.position - 1 }} unless is_first %>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-inline-flex flex-grow-1">
|
<div class="d-inline-flex flex-grow-1">
|
||||||
<%# checklist_entry.position %>
|
<%# checklist_entry.position %>
|
||||||
<%= link_to([checklist_entry.position, checklist_entry.check.display_label].join(" "), checklist_entry.check, data: { turbo_frame: "_top" }, class: "flex-grow-1") %>
|
|
||||||
|
<span class="flex-grow-1 mt-2">
|
||||||
|
<%= checklist_entry.check.display_label %>
|
||||||
|
</span>
|
||||||
|
<%= link_to(tag.id(class: "bi bi-search"), checklist_entry.check, data: { turbo_frame: "_top" }, class: "btn btn-link") %>
|
||||||
<%= button_to tag.i(class: "bi bi-trash"), checklist_entry_path(checklist_entry), method: :delete, class: "btn btn-link" %>
|
<%= button_to tag.i(class: "bi bi-trash"), checklist_entry_path(checklist_entry), method: :delete, class: "btn btn-link" %>
|
||||||
<%= link_to tag.i(class: "bi bi-pencil"), edit_checklist_entry_path(checklist_entry), class: "btn btn-link" %>
|
<%= link_to tag.i(class: "bi bi-pencil"), edit_checklist_entry_path(checklist_entry), class: "btn btn-link" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
<div id="<%= dom_id checklist_entry %>">
|
||||||
<%= bootstrap_form_with(model: checklist_entry, layout: :horizontal) do |form| %>
|
<%= bootstrap_form_with(model: checklist_entry, layout: :horizontal) do |form| %>
|
||||||
<%= link_to "Abbrechen", checklist_entry.persisted? ? checklist_entry : checklist_entry.checklist, class: "btn btn-outline-secondary float-end", data: { turbo_frame: "checklist_entries" } %>
|
<%= link_to "Abbrechen", checklist_entry.persisted? ? checklist_entry : checklist_entry.checklist, class: "btn btn-outline-secondary float-end", data: { turbo_frame: "checklist_entries" } %>
|
||||||
<%= form.submit class: "btn btn-secondary float-end me-2" %>
|
<%= form.submit class: "btn btn-secondary float-end me-2" %>
|
||||||
<%= form.hidden_field :checklist_id %>
|
<%= form.hidden_field :checklist_id %>
|
||||||
<%= form.collection_select :check_id, Check.all.order(:name_de), :id, :name_de %>
|
<%= form.collection_select :check_id, Check.all.order(:external_number), :id, :display_label %>
|
||||||
<%# form.number_field :position %>
|
<%# form.number_field :position %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</div>
|
||||||
1
app/views/checklist_entries/update.turbo_stream.erb
Normal file
1
app/views/checklist_entries/update.turbo_stream.erb
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<%== @checklist_entry.checklist.checklist_entries.map { turbo_stream.update _1 }.join %>
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
<h1><i class="bi bi-card-checklist me-2"></i><%= "#{@checklist.name}" %></h1>
|
<h1><i class="bi bi-card-checklist me-2"></i><%= "#{@checklist.name}" %></h1>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-12">
|
<div class="col col-12 col-lg-8">
|
||||||
<%= render @checklist %>
|
|
||||||
</div>
|
|
||||||
<div class="col col-md-12">
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<%= link_to tag.i(class: "bi bi-plus-lg"), new_checklist_entry_path(checklist_id: @checklist.id), class: "btn btn-primary float-end", data: { turbo_frame: "checklist_entries" } %>
|
<%= link_to tag.i(class: "bi bi-plus-lg"), new_checklist_entry_path(checklist_id: @checklist.id), class: "btn btn-primary float-end", data: { turbo_frame: "checklist_entries" } %>
|
||||||
<h2>Checks</h2>
|
<h2>Checks</h2>
|
||||||
|
|
@ -14,14 +11,19 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= turbo_frame_tag "checklist_entries" do %>
|
<%= turbo_frame_tag "checklist_entries" do %>
|
||||||
|
<div data-controller="drag">
|
||||||
<% @checklist.checklist_entries.each do |entry| %>
|
<% @checklist.checklist_entries.each do |entry| %>
|
||||||
<%= turbo_frame_tag dom_id(entry, :frame) do %>
|
<%= turbo_frame_tag dom_id(entry, :frame), data: { id: entry.id, "sortable-url": url_for(entry) } do %>
|
||||||
<%= render entry %>
|
<%= render entry %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= tag.i "Es sind keine Checks zugewiesen." if @checklist.empty? %>
|
<%= tag.i "Es sind keine Checks zugewiesen." if @checklist.empty? %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col col-12 col-lg-4">
|
||||||
|
<%= render @checklist %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-row">
|
<div class="action-row">
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
"@hotwired/turbo-rails": "^8.0.4",
|
"@hotwired/turbo-rails": "^8.0.4",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@rails/actiontext": "^7.1.3-4",
|
"@rails/actiontext": "^7.1.3-4",
|
||||||
|
"@rails/request.js": "^0.0.11",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
|
|
@ -14,6 +15,7 @@
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.39",
|
||||||
"postcss-cli": "^11.0.0",
|
"postcss-cli": "^11.0.0",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
|
"sortablejs": "^1.15.3",
|
||||||
"trix": "^2.1.3"
|
"trix": "^2.1.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
10
yarn.lock
10
yarn.lock
|
|
@ -185,6 +185,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
spark-md5 "^3.0.1"
|
spark-md5 "^3.0.1"
|
||||||
|
|
||||||
|
"@rails/request.js@^0.0.11":
|
||||||
|
version "0.0.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rails/request.js/-/request.js-0.0.11.tgz#4d9be25a49d97911c64ccd0f00b79d57fca4c3b4"
|
||||||
|
integrity sha512-2U3uYS0kbljt+pAstN+LIlZOl7xmOKig5N6FrvtUWO1wq0zR1Hf90fHfD2SYiyV8yH1nyKpoTmbLqWT0xe1zDg==
|
||||||
|
|
||||||
"@sindresorhus/merge-streams@^2.1.0":
|
"@sindresorhus/merge-streams@^2.1.0":
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz"
|
resolved "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz"
|
||||||
|
|
@ -712,6 +717,11 @@ slash@^5.0.0, slash@^5.1.0:
|
||||||
resolved "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz"
|
resolved "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz"
|
||||||
integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==
|
integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==
|
||||||
|
|
||||||
|
sortablejs@^1.15.3:
|
||||||
|
version "1.15.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.3.tgz#033668db5ebfb11167d1249ab88e748f27959e29"
|
||||||
|
integrity sha512-zdK3/kwwAK1cJgy1rwl1YtNTbRmc8qW/+vgXf75A7NHag5of4pyI6uK86ktmQETyWRH7IGaE73uZOOBcGxgqZg==
|
||||||
|
|
||||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0:
|
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz"
|
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue