diff --git a/imgsrc/srv/caret-down.svg b/imgsrc/srv/caret-down.svg
new file mode 100644
index 0000000000..38a1a25364
--- /dev/null
+++ b/imgsrc/srv/caret-down.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/imgsrc/srv/caret-right.svg b/imgsrc/srv/caret-right.svg
new file mode 100644
index 0000000000..02a55b7df5
--- /dev/null
+++ b/imgsrc/srv/caret-right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/pyj/dom.pyj b/src/pyj/dom.pyj
index 7911d16f90..3337887425 100644
--- a/src/pyj/dom.pyj
+++ b/src/pyj/dom.pyj
@@ -71,12 +71,19 @@ def create_keyframes(animation_name, *frames):
ans.push('}')
return ans.join('\n') + '\n'
+def change_icon_image(icon_element, new_name):
+ if new_name:
+ icon_element.firstChild.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#icon-' + new_name)
+ else:
+ icon_element.firstChild.removeAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href')
+
def svgicon(name, height, width):
ans = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
ans.setAttribute('style', 'fill: currentColor; height: {}; width: {}; vertical-align: text-top'.format(height ? '2ex', width ? '2ex'))
u = document.createElementNS('http://www.w3.org/2000/svg', 'use')
- u.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#icon-' + name)
ans.appendChild(u)
+ if name:
+ ans.firstChild.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#icon-' + name)
return ans
def element(elem_id, child_selector):
diff --git a/src/pyj/widgets.pyj b/src/pyj/widgets.pyj
index 635d5b5631..9a67414616 100644
--- a/src/pyj/widgets.pyj
+++ b/src/pyj/widgets.pyj
@@ -2,7 +2,7 @@
# License: GPL v3 Copyright: 2015, Kovid Goyal
from __python__ import hash_literals
-from dom import build_rule, clear, svgicon, create_keyframes, set_css
+from dom import build_rule, clear, svgicon, create_keyframes, set_css, change_icon_image
from elementmaker import E
from book_list.theme import get_color
@@ -94,6 +94,53 @@ class Breadcrumbs:
self.container.appendChild(li)
return li
+def create_tree(root, populate_data, onclick):
+ container = E.div()
+ set_css(container, overflow='auto')
+
+ def toggle_node(li):
+ if li.dataset.treeState is 'closed':
+ li.dataset.treeState = 'open'
+ li.lastChild.style.display = 'block'
+ change_icon_image(li.firstChild.firstChild, 'caret-down')
+ else:
+ li.dataset.treeState = 'closed'
+ li.lastChild.style.display = 'none'
+ change_icon_image(li.firstChild.firstChild, 'caret-right')
+
+ def process_node(node, parent_container, level):
+ if node.children?.length:
+ ul = E.div()
+ parent_container.appendChild(ul)
+ for child in node.children:
+ icon = 'caret-right' if child.children?.length else None
+ li = E.div(style='display:flex; flex-direction:column; margin: 1ex 1em; margin-left: {}em'.format(level+1),
+ E.div(style='display:flex; align-items: center',
+ svgicon(icon),
+ E.span('\xa0'),
+ E.a(
+ href='javascript: void(0)',
+ onclick=def (event):
+ if onclick:
+ onclick(event.currentTarget.parentNode.parentNode)
+ ),
+ ),
+ E.div(style='display:none'),
+ data_tree_state='closed',
+ )
+ ul.appendChild(li)
+ populate_data(child, li, li.firstChild.lastChild)
+ if icon:
+ set_css(li.firstChild.firstChild, cursor='pointer')
+ li.firstChild.firstChild.addEventListener('click', def(event):
+ toggle_node(event.currentTarget.parentNode.parentNode)
+ )
+ process_node(child, li.lastChild, level + 1)
+
+ if root:
+ process_node(root, container, 0)
+ return container
+
def get_widget_css():
ans = 'a, button:focus { outline: none }; a, button::-moz-focus-inner { border: 0 }\n'
ans += create_button.style