From 7acc0559aebaf8df89ec606a2f2b101a99afdc61 Mon Sep 17 00:00:00 2001 From: david Date: Sun, 27 Oct 2024 22:37:11 +0100 Subject: [PATCH] Make checklist entries sortable by d&d --- .../checklist_entries_controller.rb | 9 ++++- app/javascript/controllers/drag_controller.js | 38 +++++++++++++++++++ app/javascript/controllers/index.js | 3 ++ app/models/check.rb | 16 ++++++-- .../_checklist_entry.html.erb | 13 +++++-- app/views/checklist_entries/_form.html.erb | 6 ++- .../checklist_entries/update.turbo_stream.erb | 1 + app/views/checklists/show.html.erb | 20 +++++----- package.json | 2 + yarn.lock | 10 +++++ 10 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 app/javascript/controllers/drag_controller.js create mode 100644 app/views/checklist_entries/update.turbo_stream.erb diff --git a/app/controllers/checklist_entries_controller.rb b/app/controllers/checklist_entries_controller.rb index 10ac787..1091922 100644 --- a/app/controllers/checklist_entries_controller.rb +++ b/app/controllers/checklist_entries_controller.rb @@ -33,8 +33,13 @@ class ChecklistEntriesController < ApplicationController # PATCH/PUT /checklist_entries/1 def update if @checklist_entry.update(checklist_entry_params) - redirect_to @checklist_entry.checklist, notice: "Checklist entry was successfully updated.", - status: :see_other + respond_to do |format| + format.turbo_stream + format.html do + redirect_to @checklist_entry.checklist, notice: "Checklist entry was successfully updated.", + status: :see_other + end + end else render :edit, status: :unprocessable_entity end diff --git a/app/javascript/controllers/drag_controller.js b/app/javascript/controllers/drag_controller.js new file mode 100644 index 0000000..d3f7af8 --- /dev/null +++ b/app/javascript/controllers/drag_controller.js @@ -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" + } + }) + } +} diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js index a76470e..9d6b531 100644 --- a/app/javascript/controllers/index.js +++ b/app/javascript/controllers/index.js @@ -13,6 +13,9 @@ application.register("check-link", CheckLinkController) import CollapseChevronTogglerController from "./collapse_chevron_toggler_controller" application.register("collapse-chevron-toggler", CollapseChevronTogglerController) +import DragController from "./drag_controller" +application.register("drag", DragController) + import HelloController from "./hello_controller" application.register("hello", HelloController) diff --git a/app/models/check.rb b/app/models/check.rb index 815c08f..a85fc11 100644 --- a/app/models/check.rb +++ b/app/models/check.rb @@ -105,14 +105,24 @@ class Check < ApplicationRecord end 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 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 def display_label - "#{name_de} (#{[number, external_number].compact_blank.join("/")})" + [external_number, name_de].compact_blank.join(" ") end end diff --git a/app/views/checklist_entries/_checklist_entry.html.erb b/app/views/checklist_entries/_checklist_entry.html.erb index 96f2cc8..658c63e 100644 --- a/app/views/checklist_entries/_checklist_entry.html.erb +++ b/app/views/checklist_entries/_checklist_entry.html.erb @@ -3,13 +3,18 @@
-
- <%= 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 %> + +
+ <% 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 %>
<%# 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") %> + + + <%= checklist_entry.check.display_label %> + + <%= 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" %> <%= link_to tag.i(class: "bi bi-pencil"), edit_checklist_entry_path(checklist_entry), class: "btn btn-link" %>
diff --git a/app/views/checklist_entries/_form.html.erb b/app/views/checklist_entries/_form.html.erb index 238de36..8ff01f5 100644 --- a/app/views/checklist_entries/_form.html.erb +++ b/app/views/checklist_entries/_form.html.erb @@ -1,7 +1,9 @@ +
<%= 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" } %> <%= form.submit class: "btn btn-secondary float-end me-2" %> <%= 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 %> -<% end %> \ No newline at end of file +<% end %> +
\ No newline at end of file diff --git a/app/views/checklist_entries/update.turbo_stream.erb b/app/views/checklist_entries/update.turbo_stream.erb new file mode 100644 index 0000000..bdfcdae --- /dev/null +++ b/app/views/checklist_entries/update.turbo_stream.erb @@ -0,0 +1 @@ +<%== @checklist_entry.checklist.checklist_entries.map { turbo_stream.update _1 }.join %> \ No newline at end of file diff --git a/app/views/checklists/show.html.erb b/app/views/checklists/show.html.erb index 987a7a3..7dac49c 100644 --- a/app/views/checklists/show.html.erb +++ b/app/views/checklists/show.html.erb @@ -1,10 +1,7 @@

<%= "#{@checklist.name}" %>

-
- <%= render @checklist %> -
-
+
<%= 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" } %>

Checks

@@ -14,13 +11,18 @@ <% end %>
<%= turbo_frame_tag "checklist_entries" do %> - <% @checklist.checklist_entries.each do |entry| %> - <%= turbo_frame_tag dom_id(entry, :frame) do %> - <%= render entry %> +
+ <% @checklist.checklist_entries.each do |entry| %> + <%= turbo_frame_tag dom_id(entry, :frame), data: { id: entry.id, "sortable-url": url_for(entry) } do %> + <%= render entry %> + <% end %> <% end %> + <%= tag.i "Es sind keine Checks zugewiesen." if @checklist.empty? %> <% end %> - <%= tag.i "Es sind keine Checks zugewiesen." if @checklist.empty? %> - <% end %> +
+
+
+ <%= render @checklist %>
diff --git a/package.json b/package.json index 94d542f..10b7d4b 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@hotwired/turbo-rails": "^8.0.4", "@popperjs/core": "^2.11.8", "@rails/actiontext": "^7.1.3-4", + "@rails/request.js": "^0.0.11", "autoprefixer": "^10.4.19", "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", @@ -14,6 +15,7 @@ "postcss": "^8.4.39", "postcss-cli": "^11.0.0", "sass": "^1.77.8", + "sortablejs": "^1.15.3", "trix": "^2.1.3" }, "scripts": { diff --git a/yarn.lock b/yarn.lock index 48ee517..a027858 100644 --- a/yarn.lock +++ b/yarn.lock @@ -185,6 +185,11 @@ dependencies: 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": version "2.3.0" 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" 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: version "1.2.0" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz"