Skip to content

Commit

Permalink
Feature/expanded nodes are stored in the page session so that the str…
Browse files Browse the repository at this point in the history
…ucture of the nav tree is retained after editing a menu (#153)

* feat: store expanded/collapsed nodes in JS session object

When editing a menu, the expanded/collapsed nodes are stored in a
session object that lasts until the tab is closed. This allows the user
to edit the menu, or delete a node, and when the menu reloads, they
are displayed the menu with the same nodes still open.

* feat: store expanded/collapsed for multiple menus

Updates the JS so that the ID of the menu is used when storing the
expanded/collapsed node ID's. This change means storing an array of IDs
for each menu as a json string. By storing session data unique for the
menu object it stops the session data being used for the wrong menu
object, and allows multiple menu states to be stored in a session.

* fix: update changelog

Co-authored-by: Michael Collins <[email protected]>
  • Loading branch information
michaeljcollinsuk and michaeljcollinsuk authored Oct 13, 2022
1 parent 13e39df commit 6464f9f
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Changelog

Unreleased
==========
* feat: store a list of expanded nodes in a page session object so after editing a menu the
previous state is displayed

1.7.0 (2022-09-21)
==================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,23 @@ Original code found in treebeard-admin.js
DRAG_LINE_COLOR = '#AA00AA';
RECENTLY_FADE_DURATION = 2000;

const EXPANDED_SESSION_KEY = 'expanded-';

// This is the basic Node class, which handles UI tree operations for each 'row'
var Node = function (elem) {
var $elem = $(elem);
var node_id = $elem.attr('node');
var parent_id = $elem.attr('parent');
var level = parseInt($elem.attr('level'));
var children_num = parseInt($elem.attr('children-num'));
var menu_content_id = $("#result_list").data("menuContentId");
return {
elem: elem,
$elem: $elem,
node_id: node_id,
parent_id: parent_id,
level: level,
expanded_key: EXPANDED_SESSION_KEY + menu_content_id,
has_children: function () {
return children_num > 0;
},
Expand Down Expand Up @@ -63,41 +67,77 @@ Original code found in treebeard-admin.js
}
}).show();
},
// collapse_all() and expand_all() show/hide the node + child nodes AND modifies classes:
// collapse_all() and expand_all() show/hide the node + child nodes AND modifies classes:
// (In practice these functions are only used with the root node)
collapse_all: function () {
this.$elem.find('a.collapse').removeClass('expanded').addClass('collapsed');
$.each(this.children(), function() {
let node = new Node(this);
node.collapse_all();
}).hide();
// clear storage so that on reload go back to default view
sessionStorage.clear()
},
expand_all: function () {
this.$elem.find('a.collapse').removeClass('collapsed').addClass('expanded');
$.each(this.children(), function() {
let node = new Node(this);
node.expand_all();
}).show();
// clear storage so that on reload go back to default view
sessionStorage.clear()
},
// Toggle show/hides the node (and child nodes), but does not modify child classes - this is so the 'state' can be perserved.
toggle: function () {
if (this.is_collapsed()) {
this.expand();
// Update classes just for this node:
this.$elem.find('a.collapse').removeClass('collapsed').addClass('expanded');
this.add_to_session()
} else {
this.collapse();
this.$elem.find('a.collapse').removeClass('expanded').addClass('collapsed');
this.remove_from_session()
}
},
clone: function () {
return $elem.clone();
},
add_to_session: function () {
// get or create an array of element ids that are expanded
let expanded = JSON.parse(sessionStorage.getItem(this.expanded_key)) || []
expanded.push(this.elem.id)
sessionStorage.setItem(this.expanded_key, JSON.stringify(expanded))
},
remove_from_session: function () {
let expanded = JSON.parse(sessionStorage.getItem(this.expanded_key)) || []
// filter the array to remove this element id
expanded = expanded.filter(elementId => elementId !== this.elem.id)
// also remove any child elements
if (this.has_children()) {
$.each(this.children(), function () {
expanded = expanded.filter(elementId => elementId !== this.id)
})
}
// update the session
sessionStorage.setItem(this.expanded_key, JSON.stringify(expanded))
}
}
};

$(document).ready(function () {

// check the session for a stored state of expanded nodes
const menuContentId = $("#result_list").data("menuContentId");
let expanded = JSON.parse(sessionStorage.getItem(EXPANDED_SESSION_KEY + menuContentId))
if (expanded) {
expanded.forEach(function (elementId) {
var node = new Node($.find('#' + elementId)[0])
node.expand()
node.$elem.find('a.collapse').removeClass('collapsed').addClass('expanded');
})
}

// begin csrf token code
// Taken from http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
$(document).ajaxSend(function (event, xhr, settings) {
Expand Down Expand Up @@ -344,7 +384,7 @@ Original code found in treebeard-admin.js
// Get root node:
let root_node = new Node($.find('tr[level=1]')[0]);

// Toggle expand / collapse all:
// Toggle expand / collapse all:
if (!this.hasAttribute('class') || $(this).hasClass('collapsed-all') ) {
root_node.expand_all();
$(this).addClass('expanded-all').removeClass('collapsed-all').text('-');
Expand All @@ -353,7 +393,7 @@ Original code found in treebeard-admin.js
$(this).addClass('collapsed-all').removeClass('expanded-all').text('+');
}
});

var hash = window.location.hash;

if (hash) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{% endif %}
{% if results %}
<div class="results">
<table cellspacing="0" id="result_list">
<table cellspacing="0" id="result_list" data-menu-content-id="{{ menu_content_id }}">
<thead>
<tr>
{% for header in result_headers %}
Expand Down
4 changes: 2 additions & 2 deletions djangocms_navigation/templatetags/navigation_admin_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ def result_tree(context, cl, request):
'result_headers': headers,
'results': list(results(cl)),
'disable_drag_drop': disable_drag_drop,
'move_node_message': move_node_message

'move_node_message': move_node_message,
'menu_content_id': context["menu_content"].pk
}


Expand Down
16 changes: 16 additions & 0 deletions tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,22 @@ def test_menuitem_move_message(self):
element = soup.find("tbody")
self.assertEqual(element.attrs["data-move-message"], "Are you sure you want to move menu item")

def test_menu_content_id_present(self):
"""
Check that the rendered template includes the menu content id as a data attribute so that it can be accessed by
the client side js
"""
menu_content = factories.MenuContentWithVersionFactory()
list_url = reverse(
"admin:djangocms_navigation_menuitem_list", args=(menu_content.id,)
)
response = self.client.get(list_url)

soup = BeautifulSoup(str(response.content), features="lxml")
result_list = soup.find(id="result_list")

self.assertEqual(result_list["data-menu-content-id"], str(menu_content.pk))


@override_settings(
CMS_PERMISSION=True,
Expand Down

0 comments on commit 6464f9f

Please sign in to comment.