Skip to content

Commit

Permalink
Merge 9413743 into a19c081
Browse files Browse the repository at this point in the history
  • Loading branch information
melvyn-sopacua committed Jan 27, 2017
2 parents a19c081 + 9413743 commit 7183be2
Show file tree
Hide file tree
Showing 3 changed files with 321 additions and 1 deletion.
9 changes: 9 additions & 0 deletions bootstrap3/templates/bootstrap3/tabs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% spaceless %}
<ul class="nav {{ nav_tabs }}{{ nav_justified }}" role="tablist">
{% for name in names %}
<li role="presentation"{% if active == name %} class="active"{% endif %}{% if name in disabled_tabs %} class="disabled"{% endif %}>
<a href="#{{ name|lower }}" aria-controls="{{ name|lower }}" role="tab" data-toggle="{{ tab_toggle }}">{{ name }}</a>
</li>
{% endfor %}
</ul>
{% endspaceless %}
187 changes: 186 additions & 1 deletion bootstrap3/templatetags/bootstrap3.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
DEFAULT_MESSAGE_LEVELS.WARNING: "alert alert-warning",
DEFAULT_MESSAGE_LEVELS.ERROR: "alert alert-danger",
}

BOOTSTRAP_TABS_JS = """
$('ul[role="tablist"]').click(function (e) {
e.preventDefault();
$(this).tab("show");
});
"""
register = template.Library()


Expand Down Expand Up @@ -921,3 +926,183 @@ def get_pagination_context(page, pages_to_show=11,
'pagination_css_classes': ' '.join(pagination_css_classes),
'parameter_name': parameter_name,
}


@register.inclusion_tag('bootstrap3/tabs.html')
def bootstrap_tabs(*names, **kwargs):
"""
Render bootstrap tabs
**Tag name**::
bootstrap_tabs
**Parameters**:
names
One or more names for the tabs as strings
active
Name corresponding to the tab that should be marked active
:default: The first name in the provided names
justified
Should the tabs be justified?
Does nothing if combined with ``vertical``
:default: ``False``
pills
Should we render pills instead of tabs?
:default: ``False``
vertical
Should the pills be rendered vertically?
Does nothing if pills is ``False``
:default: ``False``
disabled_tabs
Comma separated list of tab names that should be rendered disabled
:default: ``None``
**Usage**::
{% bootstrap_tabs "Home" "Profile" "Messages" "Settings" %}
**Example**::
{% bootstrap_tabs "View" "Edit" "Delete" disabled_tabs="Edit, Delete" %}
**See also**::
``bootstrap_tabpanel``, ``bootstrap_tabs_js``
"""
tabs_kwargs = {'names': list(names)}
tabs_kwargs.update(kwargs)
return get_tabs_context(**tabs_kwargs)


def get_tabs_context(names=None, active=None, justified=False, pills=False,
vertical=False, disabled_tabs=None):
if not names:
raise ValueError('Must provide at least one name for the tabs')
active = active or names[0]
nav_tabs = 'nav-tabs'
tab_toggle = 'tab'
if pills:
nav_tabs = 'nav-pills'
tab_toggle = 'pill'
if vertical:
nav_tabs += ' nav-stacked'
nav_justified = ''
if justified and not vertical:
nav_justified = ' nav-justified'
disabled = []
if disabled_tabs:
import re
tmp = re.split(',\s?', disabled_tabs)
for name in tmp:
if name in names and name != active:
disabled.append(name)
return {
'names': names,
'active': active,
'nav_tabs': nav_tabs,
'tab_toggle': tab_toggle,
'nav_justified': nav_justified,
'disabled_tabs': disabled,
}


@register.tag('bootstrap_tabpanel')
def bootstrap_tabpanel(parser, token):
"""
Render a tabpanel for bootstrap tabs
**Tag name**:
bootstrap_tabpanel
**Parameters**:
name
Name of the tab to be rendered. Keep consistent with the names
provided to ``bootstrap_tabs``.
active
Whether this tab panel is active
:default: ``False``
**Usage**::
{% bootstrap_tabpanel "Home"%}
**Example**::
{% bootstrap_tabpanel "Home" active=True %}
**See also**::
``bootstrap_tabs``, ``bootstrap_tabs_js``
"""
kwargs = parse_token_contents(parser, token)
kwargs['nodelist'] = parser.parse(('endbootstrap_tabpanel',))
parser.delete_first_token()
return TabPanelNode(**kwargs)


class TabPanelNode(ButtonsNode):
def render(self, context):
css_classes = ['tab-pane']
id_name = self.args[0].var.lower()
active = self.kwargs.pop('active', False)
if active:
css_classes.append('active')
return mark_safe(
render_tag(
'div',
attrs={
'role': 'tabpanel',
'class': ' '.join(css_classes),
'id': id_name,
},
content=self.nodelist.render(context)
)
)


@register.simple_tag
def bootstrap_tabs_js():
"""
Renders the javascript to invoke the actions when a tab is clicked
This implementation activates the click handler for all ul elements with
role attribute set to 'tablist'. It is a surrounded by a script tag.
**Tag name**::
bootstrap_tabs_js
**Usage**::
{% bootstrap_tabs_js %}
**Example**::
{% bootstrap_tabs_js %}
**See also**::
``bootstrap_tabs``, ``bootstrap_tabpanel``
"""
return mark_safe(
render_tag(
'script',
attrs={'type': 'text/javascript'},
content=mark_safe(BOOTSTRAP_TABS_JS)
)
)
126 changes: 126 additions & 0 deletions bootstrap3/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,132 @@ def test_alert(self):
'&times;</button>content</div>'
)

def test_tabs(self):
res = render_template_with_form(
'{% bootstrap_tabs "Home" "Profile" "Messages" "Settings" %}'
)
self.assertEqual(
'<ul class="nav nav-tabs" role="tablist">' +
'<li role="presentation" class="active">' +
'<a href="#home" aria-controls="home" role="tab" ' +
'data-toggle="tab">Home</a></li>' +
'<li role="presentation">' +
'<a href="#profile" aria-controls="profile" role="tab" ' +
'data-toggle="tab">Profile</a></li>' +
'<li role="presentation">' +
'<a href="#messages" aria-controls="messages" role="tab" ' +
'data-toggle="tab">Messages</a></li>' +
'<li role="presentation">' +
'<a href="#settings" aria-controls="settings" role="tab" ' +
'data-toggle="tab">Settings</a></li>' +
'</ul>'
,
res.strip()
)
# save for reuse
tabs_expect = res.strip()
res = render_template_with_form(
'{% bootstrap_tabs "Home" "Profile" "Messages" "Settings"' +
' disabled_tabs="Messages, Settings" %}',
)
self.assertEqual(
'<ul class="nav nav-tabs" role="tablist">' +
'<li role="presentation" class="active">' +
'<a href="#home" aria-controls="home" role="tab" ' +
'data-toggle="tab">Home</a></li>' +
'<li role="presentation">' +
'<a href="#profile" aria-controls="profile" role="tab" ' +
'data-toggle="tab">Profile</a></li>' +
'<li role="presentation" class="disabled">' +
'<a href="#messages" aria-controls="messages" role="tab" ' +
'data-toggle="tab">Messages</a></li>' +
'<li role="presentation" class="disabled">' +
'<a href="#settings" aria-controls="settings" role="tab" ' +
'data-toggle="tab">Settings</a></li>' +
'</ul>'
,
res.strip()
)
disabled_expect = res.strip()
# adding active element to disabled should not work
# sneakily also testing missing whitespace
res = render_template_with_form(
'{% bootstrap_tabs "Home" "Profile" "Messages" "Settings"' +
' disabled_tabs="Home,Messages, Settings" %}',
)
self.assertEqual(
disabled_expect,
res.strip()
)
# Vertical should do nothing on nav-tabs
res = render_template_with_form(
'{% bootstrap_tabs "Home" "Profile" "Messages" "Settings"' +
' vertical=True %}',
)
self.assertEqual(
tabs_expect,
res.strip()
)
res = render_template_with_form(
'{% bootstrap_tabs "Home" "Profile" "Messages" "Settings"' +
' justified=True %}',
)
self.assertEqual(
tabs_expect.replace('nav-tabs"', 'nav-tabs nav-justified"'),
res.strip()
)
# intentionally not refreshing tabs_expect
res = render_template_with_form(
'{% bootstrap_tabs "Home" "Profile" "Messages" "Settings"' +
' pills=True %}'
)
self.assertEqual(
tabs_expect.replace('nav-tabs', 'nav-pills').replace(
'data-toggle="tab"', 'data-toggle="pill"'
),
res.strip()
)
pills_expect = res.strip()
res = render_template_with_form(
'{% bootstrap_tabs "Home" "Profile" "Messages" "Settings"' +
' pills=True vertical=True %}',
)
self.assertEqual(
pills_expect.replace('nav-pills"', 'nav-pills nav-stacked"'),
res.strip()
)
pills_expect = res.strip()
# justified should do nothing with vertical
res = render_template_with_form(
'{% bootstrap_tabs "Home" "Profile" "Messages" "Settings"' +
' pills=True vertical=True justified=True %}',
)
self.assertEqual(
pills_expect,
res.strip()
)
res = render_template_with_form(
'{% bootstrap_tabpanel "Home" active=True %}' +
'<p>Home content</p>' +
'{% endbootstrap_tabpanel %}'
)
self.assertEqual(
'<div class="tab-pane active" id="home" role="tabpanel">' +
'<p>Home content</p></div>',
res.strip()
)
res = render_template_with_form(
'{% bootstrap_tabs_js %}'
)
self.assertEqual(
'<script type="text/javascript">\n' +
' $(\'ul[role="tablist"]\').click(function (e) {\n' +
' e.preventDefault();\n' +
' $(this).tab("show");\n' +
' });\n</script>',
res.strip()
)


class MessagesTest(TestCase):
def test_messages(self):
Expand Down

0 comments on commit 7183be2

Please sign in to comment.