Skip to content

Commit

Permalink
Use Sortable.js to sort Menus
Browse files Browse the repository at this point in the history
This commit allows sorting of menus. We instantiate for each element in
the tree one instance of Sortable.js, but - through the `group` option
- allow dragging and dropping between each element. For this to work,
  all `ul` elements need to be present (otherwise we cannot drop a node
below a list that does not have nodes yet).

A little tricky was the correct behaviour of the `folded` button: It
should only be displayed if there are any pages to fold. This is now
accomplished through rendering the `+` and `-` buttons in Javascript,
depending on how many elements are in each list after sorting.
  • Loading branch information
mamhoff committed Mar 22, 2020
1 parent 439bf63 commit 4165b5a
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 19 deletions.
1 change: 1 addition & 0 deletions app/assets/javascripts/alchemy/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
//= require alchemy/alchemy.link_dialog
//= require alchemy/alchemy.list_filter
//= require alchemy/alchemy.initializer
//= require alchemy/alchemy.node_sorter
//= require alchemy/alchemy.page_sorter
//= require alchemy/alchemy.uploader
//= require alchemy/alchemy.preview_window
Expand Down
2 changes: 2 additions & 0 deletions app/assets/javascripts/alchemy/alchemy.initializer.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Alchemy.Initializer = ->
tagName = (event.target || event.srcElement).tagName
key.isPressed('esc') || !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA')

Alchemy.NodeSorter()

# Enabling the Turbolinks Progress Bar for v2.5
Turbolinks.enableProgressBar() if Turbolinks.enableProgressBar

Expand Down
60 changes: 60 additions & 0 deletions app/assets/javascripts/alchemy/alchemy.node_sorter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
Alchemy.DisplayNodeFolders = function() {
var generate_link = function(node_id, folded) {
var icon = folded === "true" ? 'plus' : 'minus';
return '<a class="node_folder" data-node-id="' + node_id + '"><i class="far fa-' + icon + '-square fa-fw"></i></a>'
}
document.querySelectorAll('li.menu-item').forEach(function (el) {
var leftIconArea = el.querySelector('.nodes_tree-left_images')
if (el.dataset.childrenCount > 0) {
leftIconArea.innerHTML = generate_link(el.dataset.nodeId, el.dataset.folded)
} else {
leftIconArea.innerHTML = '&nbsp;'
}
});
};

Alchemy.NodeSorter = function() {
Alchemy.DisplayNodeFolders();
document.querySelectorAll('#archive_all ul.children').forEach(function (el) {
new Sortable(
el,
{
group: 'nodes',
animation: 150,
fallbackOnBody: true,
swapThreshold: 0.65,
onEnd: function (/**Event*/evt) {
var itemEl = evt.item; // dragged HTMLElement
var url = '/api/nodes/' + evt.item.dataset.id + '/move.json'
var xhr = new XMLHttpRequest()
var token = document.querySelector('meta[name="csrf-token"]').attributes.content.textContent
var data = {
target_parent_id: evt.to.dataset.nodeId,
new_position: evt.newIndex
};
var json = JSON.stringify(data)

xhr.open("PATCH", url);
xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
xhr.setRequestHeader('X-CSRF-Token', token)

xhr.onload = function () {
response_json = JSON.parse(xhr.responseText)
if (xhr.readyState == 4 && xhr.status == "200") {
// Update DOM element for list we remove elements from
var list = evt.from.parentElement
evt.from.parentElement.dataset.childrenCount = evt.from.children.length
evt.to.parentElement.dataset.childrenCount = evt.to.children.length
evt.item.dataset.left = response_json.lft
evt.item.dataset.right = response_json.rgt
Alchemy.DisplayNodeFolders()
} else {
Alchemy.growl(response_json.error, 'error');
}
}
xhr.send(json)
},
}
)
});
}
26 changes: 7 additions & 19 deletions app/views/alchemy/admin/nodes/_node.html.erb
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
<li>
<%= content_tag :li, class: 'menu-item', data: { id: node.id, parent_id: node.parent_id, left: node.lft, right: node.rgt, children_count: node.children.length, folded: node.folded? } do %>
<%= content_tag :div, class: [
'sitemap_node',
node.external? ? 'external' : 'internal',
"sitemap_node-level_#{node.depth}"
] do %>
<span class="nodes_tree-left_images">
<% if node.children.any? %>
<a class="node_folder" data-node-id="<%= node.id %>">
<% if node.folded? %>
<i class="far fa-plus-square fa-fw"></i>
<% else %>
<i class="far fa-minus-square fa-fw"></i>
<% end %>
</a>
<% else %>
&nbsp;
<% end %>
&nbsp;
</span>
<span class="nodes_tree-right_tools">
<% if can?(:edit, node) %>
Expand Down Expand Up @@ -81,11 +71,9 @@
<% end %>
</div>
<% end %>
<% if node.children.any? %>
<ul class="children<%= node.folded? ? ' hidden' : nil %>">
<% unless node.folded? %>
<%= render partial: 'node', collection: node.children.includes(:page, :children) %>
<% end %>
</ul>
<%= content_tag :ul, class: "children#{' hidden' if node.folded?}", data: { node_id: node.id } do %>
<% unless node.folded? %>
<%= render partial: 'node', collection: node.children.includes(:page, :children) %>
<% end %>
<% end %>
</li>
<% end %>

0 comments on commit 4165b5a

Please sign in to comment.