nav.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. from hashlib import sha1
  2. from dominate import tags
  3. from visitor import Visitor
  4. class BootstrapRenderer(Visitor):
  5. def __init__(self, html5=True, id=None):
  6. self.html5 = html5
  7. self._in_dropdown = False
  8. self.id = id
  9. def visit_Navbar(self, node):
  10. # create a navbar id that is somewhat fixed, but do not leak any
  11. # information about memory contents to the outside
  12. node_id = self.id or sha1(str(id(node)).encode()).hexdigest()
  13. root = tags.nav() if self.html5 else tags.div(role='navigation')
  14. root['class'] = 'navbar navbar-default'
  15. cont = root.add(tags.div(_class='container-fluid'))
  16. # collapse button
  17. header = cont.add(tags.div(_class='navbar-header'))
  18. btn = header.add(tags.button())
  19. btn['type'] = 'button'
  20. btn['class'] = 'navbar-toggle collapsed'
  21. btn['data-toggle'] = 'collapse'
  22. btn['data-target'] = '#' + node_id
  23. btn['aria-expanded'] = 'false'
  24. btn['aria-controls'] = 'navbar'
  25. btn.add(tags.span('Toggle navigation', _class='sr-only'))
  26. btn.add(tags.span(_class='icon-bar'))
  27. btn.add(tags.span(_class='icon-bar'))
  28. btn.add(tags.span(_class='icon-bar'))
  29. # title may also have a 'get_url()' method, in which case we render
  30. # a brand-link
  31. if node.title is not None:
  32. if hasattr(node.title, 'get_url'):
  33. header.add(tags.a(node.title.text, _class='navbar-brand',
  34. href=node.title.get_url()))
  35. else:
  36. header.add(tags.span(node.title, _class='navbar-brand'))
  37. bar = cont.add(tags.div(
  38. _class='navbar-collapse collapse',
  39. id=node_id,
  40. ))
  41. bar_list = bar.add(tags.ul(_class='nav navbar-nav'))
  42. for item in node.items:
  43. bar_list.add(self.visit(item))
  44. return root
  45. def visit_Text(self, node):
  46. if not self._in_dropdown:
  47. return tags.p(node.text, _class='navbar-text')
  48. return tags.li(node.text, _class='dropdown-header')
  49. def visit_Link(self, node):
  50. item = tags.li()
  51. item.add(tags.a(node.text, href=node.get_url()))
  52. return item
  53. def visit_Separator(self, node):
  54. if not self._in_dropdown:
  55. raise RuntimeError('Cannot render separator outside Subgroup.')
  56. return tags.li(role='separator', _class='divider')
  57. def visit_Subgroup(self, node):
  58. if not self._in_dropdown:
  59. li = tags.li(_class='dropdown')
  60. if node.active:
  61. li['class'] = 'active'
  62. a = li.add(tags.a(node.title, href='#', _class='dropdown-toggle'))
  63. a['data-toggle'] = 'dropdown'
  64. a['role'] = 'button'
  65. a['aria-haspopup'] = 'true'
  66. a['aria-expanded'] = 'false'
  67. a.add(tags.span(_class='caret'))
  68. ul = li.add(tags.ul(_class='dropdown-menu'))
  69. self._in_dropdown = True
  70. for item in node.items:
  71. ul.add(self.visit(item))
  72. self._in_dropdown = False
  73. return li
  74. else:
  75. raise RuntimeError('Cannot render nested Subgroups')
  76. def visit_View(self, node):
  77. item = tags.li()
  78. item.add(tags.a(node.text, href=node.get_url(), title=node.text))
  79. if node.active:
  80. item['class'] = 'active'
  81. return item