This guide will be deprecated soon!

CommCare HQ is currently undergoing a migration to Bootstrap 5. You can find the updated style guide here.

Templates structure free-floating molecules and organisms into a context: a specific page.

Navigation

Users should always know where they are and how to get back where they came from.

Standard navigation

These navigation elements should be present on almost every page. The following assumes that the page descends from hqwebapp/bootstrap3/base_section.html, which virtually all pages should.

Top navigation
The top navigation should always be visible to users. Individual tabs are defined in tabclasses.py. Which tab is highlighted depends on the url_prefix_formats defined for each tab. Which items appear in a tab's dropdown is determined by dropdown_items.
Side navigation

The side navigation appears almost everywhere. Major exceptions are the dashboard, which has no navigation, and app manager, which has a custom sidebar corresponding to the app's structure. Other pages should almost certainly have a sidebar.

The sidebar is also defined in tabclasses.py, controlled by the sidebar_items functions.

Temporary sub-items that appear only when on a specific page, such as the "Create Form Data Export" item that appears under the main "Export Form Data" item only when you're creating a new export, can be added using the subpages property.

Dynamic item names, such as displaying the username when editing a web user, can be enabled by passing a function to the menu item's title.

Breadcrumbs

Breadcrumbs are defined by the parent_pages in the template context (see below for how to populate). Standard pages should have a breadcrumb for the top nav section (e.g., "Data"), for the left sidebar section if there is one (e.g., "Edit Data"), and for the page itself. Multi-step workflows such as case import should also be reflected in breadcrumbs. All items in the breadcrumbs should be links, except for the last (the current page).

A few pages override the page_breadcrumbs block defined in hqwebapp/bootstrap3/base_section.html. This should only be done in exceptional circumstances.

Page headers

A page header at the top of the page's main content helps the user stay oriented, and this is a good place to briefly introduce the user to the page and link to documentation.

This is an area where the technical implementation isn't well-standardized. When possible, set page_title in the template's context. Some pages do create the header by hand.

Page header

Mobile Workers

Mobile workers' activity and form submissions can be monitored in the Reports section of this CommCare HQ project space.
Read more about managing mobile workers on our Help Site .

<p class="lead">
  Mobile Workers
</p>
<p class="help-block">
  Mobile workers' activity and form submissions can be monitored in the Reports section of this CommCare HQ project space.
  <br>
  Read more about managing mobile workers on our
  <a href="https://confluence.dimagi.com/display/commcarepublic/Create+and+Manage+CommCare+Mobile+Workers" target="_blank">
    Help Site
  </a>.
</p>

In-page navigation

If a page has multiple sections, you have a few options to visually separate them:

  • Split into multiple pages.
  • Enclose each section in a panel. Note that app manager has its own panel styling, .panel-appmanager.
  • Set each section up as a tab. Avoid using pills for navigation, because they look similar to the toggles used in forms.

Panels

Winter

December, January, February

Spring

March, April, May

Summer

June, July, August

Autumn

September, October, November
<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">Winter</h3>
  </div>
  <div class="panel-body">
    December, January, February
  </div>
</div>

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">Spring</h3>
  </div>
  <div class="panel-body">
    March, April, May
  </div>
</div>

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">Summer</h3>
  </div>
  <div class="panel-body">
    June, July, August
  </div>
</div>

<div class="panel panel-default">
  <div class="panel-heading">
    <h3 class="panel-title">Autumn</h3>
  </div>
  <div class="panel-body">
    September, October, November
  </div>
</div>

Tabs for in-page navigation


December, January, February

March, April, May

June, July, August

September, October, November
<ul class="nav nav-tabs">
  <li class="active"><a data-toggle="tab" href="#tabs-winter">Winter</a></li>
  <li><a data-toggle="tab" href="#tabs-spring">Spring</a></li>
  <li><a data-toggle="tab" href="#tabs-summer">Summer</a></li>
  <li><a data-toggle="tab" href="#tabs-autumn">Autumn</a></li>
</ul>

<div class="tab-content">
  <div class="tab-pane fade in active" id="tabs-winter">
    <br>December, January, February
  </div>

  <div class="tab-pane fade" id="tabs-spring">
    <br>March, April, May
  </div>

  <div class="tab-pane fade" id="tabs-summer">
    <br>June, July, August
  </div>

  <div class="tab-pane fade" id="tabs-autumn">
    <br>September, October, November
  </div>
</div>

Class-Based Views

Ultimately all views are based on django's TemplateView class, if the view has a user-facing UI.

If not, then inheriting from a simple View will probably be enough.

The BaseDomainView

If your view has something to do with the domain, then it will probably inherit from this view.

What benefits do you have from using this view?

  • The permission login_and_domain_required is already in effect established.
  • self.domain (the name of the domain) is already available as an instance variable for your class and is automatically inserted into the template's context.
  • self.domain_object is already available as an instance variable (the Domain instance with the domain name given by self.domain.
  • The self.page_url property is already avaiable, provided that your page doesn't require other args beyond domain. If it does (for instance if your url looks like /a/[domain]/my/page/[unique-id], then you'll have to re-implement self.page_url with the required arguments.

Here is an example of a class inheriting from the BaseDomainView.

class MyView(BaseDomainView):
    urlname = 'my_view'

(Required) urlname will be the string referenced when you do the url mapping for this view in it's application's urls.py.
(e.g. url(r'^myview/$', MyView.as_view(), name=MyView.urlname).

   template_name = 'all_the_views/my_view.html'
    page_title = gettext_lazy("My View")
    section_title = gettext_lazy("All the Views")

    @property
    def section_url(self):
        return reverse(MySectionPageRoot.urlnale, args=[self.domain])

(Required) section_title and section_url are the title and url for the "tab" or major content area that the view is inside. For instance, Settings, Users, Data, Reports, etc. (See Creating a Base View for my Section for best practices on abstracting this out so that every View in the same Section doesn't have to define this each time).

Both the section_url and page_title are used in rendering the page's breadcrumbs (it's the first crumb).

    @property
    def parent_pages(self):
        return [
            {
                'title': MyParentPageView.page_title,
                'url': reverse(MyParentPageView.urlname, args=[self.domain]),
            }
        ]

(Optional) This is what shows up as the hierarchy in the Breadcrumbs for the page, and is what's in between the first crumb (the section) and the last crumb (the current page).

    @property
    def page_name(self):
        return "{domain}'s {page_title}".format(
            domain=self.domain,
            page_title=self.page_title,
        )

(Optional) page_name by default returns page_title. It's a way of adding request-specific context to the page_title, but still having access to the page_title from a class level for general referencing of the page.

    @property
    def page_context(self):
        return {
            'form': MyForm(),
        }

(Optional) While page_context is optional, it's very likely you'll implement it, as that's where you return all the variables specific to that page. If you are starting to create a hierarchy where you're then adding to the page_context via.

    # note, this is an example of what NOT to do
    @property
    def page_context(self):
        context = super(MyView, self).page_context
        context.update({
            'form': MyForm(),
        })
        return context

Then, first, ask why you need that specific hierarchy. Perhaps you want to just inherit from a Base Section view? Second, this should never happen with page_context. That's what main_context is for, as that covers the context for more than one page (or in the next example, a whole section).

Creating a Base View for My Section

The best practice is to create a Base Section View for all of the views that will be part of that section (or tab) so that section_title section_url and any section-specific context can be added to the main_context.

class BaseMySectionView(BaseDomainView):
    section_name = "My Section"

    @property
    def section_url(self):
        return reverse(TheRootOfMySection.urlname, args=[self.domain]

    @property
    def main_context(self):
        context = super(BaseMySectionView, self).main_context
        context.update({
            'my_section_emoji': "bears",
        })
        return context

main_context here is optional but a good place to insert any section-specific template contexts in.

    def dispatch(self, request, *args, **kwargs):
        return super(MyView, self).dispatch(request, *args, **kwargs)

Functional Views

When using functional views, "fake" the same context that class-based views generate.

Manually mimic the context provided by BasePageView.main_context and rendered by hqwebapp/bootstrap3/base_section.html. This is an area where we could use better standards / shared code.

context.update({
    'current_page': {
        'title': page_name,
        'page_name': page_name,
        'parents': [
            {
                'title': grandparent_title,
                'page_name': grandparent_name,
                'url': grandparent_url,
            },
            {
                'title': parent_title,
                'page_name': parent_name,
                'url': parent_url,
            },
       ],
    },
    'section': {
        'page_name': MySection.section_name,
        'url': reverse("section_url_name"),
    },
});