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 23, 2020
1 parent b6151b4 commit c9877e2
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 20 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) {
console.log(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.id, el.dataset.folded)
} else {
leftIconArea.innerHTML = '&nbsp;'
}
});
};

Alchemy.NodeSorter = function() {
Alchemy.DisplayNodeFolders();

var onFinishDragging = function (evt) {
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
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)
}

document.querySelectorAll('.nodes_tree ul.children').forEach(function (el) {
new Sortable(el, {
group: 'nodes',
animation: 150,
fallbackOnBody: true,
swapThreshold: 0.65,
onEnd: onFinishDragging
});
});
}
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 %>
4 changes: 3 additions & 1 deletion app/views/alchemy/admin/nodes/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@
$('.nodes_tree').on('click', '.node_folder', function() {
var $this = $(this)
var node_id = $this.data('node-id')
var menu_item = this.closest('li.menu-item')
var url = '<%= alchemy.toggle_admin_node_path(id: ":id") %>'.replace(':id', node_id)
var $children = $this.closest('li').find('> .children')
var $children = $(menu_item).find('> .children')
$this.find('> i').
toggleClass('fa-plus-square').
toggleClass('fa-minus-square')
menu_item.dataset.folded = menu_item.dataset.folded == 'false'
$children.toggleClass('hidden')
$.ajax(url, { method: 'PATCH' }).then(function (nodes) {
if ($children.children().length === 0) {
Expand Down

0 comments on commit c9877e2

Please sign in to comment.