Как визуализировать меню с одним активным элементом с DRY?

Я хотел бы сделать такие конструкции, как:

<a href='/home'>Home</a>
<span class='active'>Community</span>
<a href='/about'>About</a>

Где Сообщество выбрано пункт меню. У меня есть меню с одинаковыми параметрами для нескольких шаблонов, но я бы не хотел создавать комбинации для каждого шаблона:

<!-- for Home template-->
        <span class='active'>Home</span>
        <a href='/comminuty'>Community</a>
        <a href='/about'>About</a>
    ...
<!-- for Community template-->
        <a href='/home'>Home</a>
        <span class='active'>Community</span>
        <a href='/about'>About</a>
    ...
<!-- for About template-->
        <a href='/home'>Home</a>
        <a href='/community'>Community</a>
        <span class='active'>About</span>

У нас есть постоянный список пунктов меню, поэтому он может быть более эффективным способом - создать только одну обобщенную структуру меню, а затем отобразить меню с требуемой опцией для шаблона.

Например, это может быть тег, который позволяет это сделать.

Ответ 1

Я нашел легкое и элегантное решение DRY.

Это фрагмент: http://djangosnippets.org/snippets/2421/

**Placed in templates/includes/tabs.html**

<ul class="tab-menu">
    <li class="{% if active_tab == 'tab1' %} active{% endif %}"><a href="#">Tab 1</a></li>
    <li class="{% if active_tab == 'tab2' %} active{% endif %}"><a href="#">Tab 2</a></li>
    <li class="{% if active_tab == 'tab3' %} active{% endif %}"><a href="#">Tab 3</a></li>
</ul>

**Placed in your page template**

{% include "includes/tabs.html" with active_tab='tab1' %}

Ответ 2

Выяснил другой способ сделать это, достаточно изящный благодаря этому ответу: fooobar.com/questions/87067/...

Учитывая шаблон URL, например:

url(r'^some-url', "myapp.myview", name='my_view_name'),

my_view_name доступно шаблону через request (помните, что вам нужно использовать RequestContext - который неявна при использовании render_to_response)

Затем пункты меню могут выглядеть так:

<li class="{% if request.resolver_match.url_name == "my_view_name" %}active{% endif %}"><a href="{% url "my_view_name" %}">Shortcut1</a></li>
<li class="{% if request.resolver_match.url_name == "my_view_name2" %}active{% endif %}"><a href="{% url "my_view_name2" %}">Shortcut2</a></li>

и т.п.

Таким образом, URL-адрес может измениться, и он все еще работает, если параметры URL-адреса меняются, и вам не нужно сохранять список пунктов меню в другом месте.

Ответ 3

Использование тега шаблона

Вы можете просто использовать следующий тег шаблона:

# path/to/templatetags/mytags.py
import re

from django import template
try:
    from django.urls import reverse, NoReverseMatch
except ImportError:
    from django.core.urlresolvers import reverse, NoReverseMatch

register = template.Library()


@register.simple_tag(takes_context=True)
def active(context, pattern_or_urlname):
    try:
        pattern = '^' + reverse(pattern_or_urlname)
    except NoReverseMatch:
        pattern = pattern_or_urlname
    path = context['request'].path
    if re.search(pattern, path):
        return 'active'
    return ''

Итак, у вас ваш шаблон:

{% load mytags %}
<nav><ul>
  <li class="nav-home {% active 'url-name' %}"><a href="#">Home</a></li>
  <li class="nav-blog {% active '^/regex/' %}"><a href="#">Blog</a></li>
</ul></nav>

Использование только HTML и CSS

Существует другой подход, использующий только HTML и CSS, который вы можете использовать в любом фреймворке или статических сайтах.

Учитывая, что у вас есть меню навигации, подобное этому:

<nav><ul>
  <li class="nav-home"><a href="#">Home</a></li>
  <li class="nav-blog"><a href="#">Blog</a></li>
  <li class="nav-contact"><a href="#">Contact</a></li>
</ul></nav>

Создайте несколько базовых шаблонов, по одному для каждой сессии вашего сайта, например:

home.html
base_blog.html
base_contact.html

Все эти шаблоны расширяют base.html блоком "section", например:

...
<body id="{% block section %}section-generic{% endblock %}">
...

Затем, взяв в качестве примера base_blog.html, вы должны иметь следующее:

{% extends "base.html" %}
{% block section %}section-blog{% endblock %}

Теперь легко определить пункт меню actived, используя только CSS:

#section-home .nav-home,
  #section-blog .nav-blog,
  #section-contact .nav-contact { background-color: #ccc; }

Ответ 4

Вы можете создать links контекстной переменной с именем, URL-адресом и активным ли элементом:

{% for name, url, active in links %}
    {% if active %}
<span class='active'>{{ name }}</span>
    {% else %}
<a href='{{ url }}'>{{ name }}</a>
    {% endif %}
{% endfor %}

Если это меню присутствует на всех страницах, вы можете использовать обработчик контекста:

def menu_links(request):
    links = []
    # write code here to construct links
    return { 'links': links }

Затем в вашем файле настроек добавьте эту функцию в TEMPLATE_CONTEXT_PROCESSORS следующим образом: path.to.where.that.function.is.located.menu_links. Это означает, что функция menu_links будет вызываться для каждого шаблона, а это означает, что переменные links доступны в каждом шаблоне.

Ответ 5

Я придумал способ использования блочных тегов в родительском шаблоне, содержащем меню, для достижения чего-то подобного.

base.html - родительский шаблон:

<a href="/" class="{% block menu_home_class %}{% endblock %}">Home</a>
<a href="/about" class="{% block menu_about_class %}{% endblock %}">About</a>
<a href="/contact" class="{% block menu_contact_class %}{% endblock %}">Contact</a>

{% block content %}{% endblock %}

about.html - шаблон для определенной страницы:

{% extends "base.html" %}

{% block menu_about_class %}active{% endblock %}

{% block content %}
    About page content...
{% endblock %}

Как вы можете видеть, то, что меняется между разными шаблонами страниц, - это имя блока, содержащего active. contact.html будет использовать menu_contact_class и т.д.

Одним из преимуществ этого подхода является то, что вы можете иметь несколько подстраниц с одним и тем же активным элементом меню. Например, на странице о странице могут быть подстраницы, предоставляющие информацию о каждой команде членов компании. Было бы разумно, чтобы элемент меню "О программе" оставался активным для каждой из этих подстраниц.

Ответ 6

Предполагая, что элемент nav - это ссылка с тем же URL-адресом, что и текущая страница, вы можете просто использовать JavaScript. Здесь аннотированный метод, который я использую для добавления class="active" в li в меню навигации с class="nav":

// Get the path name (the part directly after the URL) and append a trailing slash
// For example, 'http://www.example.com/subpage1/sub-subpage/' 
//     would become '/subpage1/'
var pathName = '/' + window.location.pathname.split('/')[1];
if ( pathName != '/' ) { pathName = pathName + '/'; }

// Form the rest of the URL, so that we now have 'http://www.example.com/subpage1/'
// This returns a top-level nav item
var url = window.location.protocol + '//' +
          window.location.host +
          pathName;
console.log(url);

// Add an 'active' class to the navigation list item that contains this url
var $links = document.querySelectorAll('.nav a');
$link = Array.prototype.filter.call( $links, function(el) {
    return el.href === url;
})[0];
$link.parentNode.className += ' active';

Этот метод означает, что вы можете просто вставить его в шаблон базы один раз и забыть об этом. Нет повторений, и никаких ручных указаний URL страницы в каждом шаблоне.

Одно предостережение: это, очевидно, работает только в том случае, если найденный url соответствует ссылке навигации href. Кроме того, было бы возможно указать несколько особых случаев использования в JS или указать другой родительский элемент по мере необходимости.

Вот пример выполнения (помните, фрагменты выполняются на StackSnippets):

// Get the path name (the part directly after the URL) and append a trailing slash
// For example, 'http://www.example.com/subpage1/sub-subpage/' 
//     would become '/subpage1/'
var pathName = '/' + window.location.pathname.split('/')[1];
if ( pathName != '/' ) { pathName = pathName + '/'; }

// Form the rest of the URL, so that we now have 'http://www.example.com/subpage1/'
// This returns a top-level nav item
var url = window.location.protocol + '//' +
          window.location.host +
          pathName;
console.log(url);

// Add an 'active' class to the navigation list item that contains this url
var $links = document.querySelectorAll('.nav a');
$link = Array.prototype.filter.call( $links, function(el) {
    return el.href === url;
})[0];
$link.parentNode.className += ' active';
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: black;
  text-decoration: none;
}
.active a {
  color: red;
}
<ul class="nav">
  <li>
    <a href="http://example.com/">Example Link</a>
  </li>
  <li>
    <a href="http://stacksnippets.net/js/">This Snippet</a>
  </li>
  <li>
    <a href="https://google.com/">Google</a>
  </li>
  <li>
    <a href="http://stackoverflow.com/">StackOverflow</a>
  </li>
</ul>

Ответ 7

Вот мое решение:

{% url 'module:list' as list_url %}
{% url 'module:create' as create_url %}

<ul>
    <li><a href="{% url 'module:list' %}" class="{% if request.path == list_url %}active{% endif %}">List Page</a></li>
    <li><a href="{% url 'module:create' %}" class="{% if request.path == create_url %}active{% endif %}">Creation Page</a></li>
</ul>

Ответ 8

Основываясь на ответе @vincent, существует более простой способ сделать это, не испортив шаблоны url django.

Текущий путь запроса может быть проверен на пути отображаемого элемента меню, и если они совпадают, то это активный элемент.

В следующем примере я использую django-mptt для рендеринга меню, но каждый путь к нему можно заменить node.path.

<li class="{% if node.path == request.path %}active{% endif %}">
    <a href="node.path">node.title</a>
</li>

Ответ 9

Сегодня я столкнулся с этой проблемой, как динамически активировать "категорию" на боковой панели. В категориях есть слизни, которые из БД.

Я решил это, проверив, чтобы категория slug находилась в текущем пути. Пули уникальны (стандартная практика), поэтому я думаю, что это должно работать без каких-либо конфликтов.

{% if category.slug in request.path %}active{% endif %}

Полный пример кода цикла, чтобы получить категории и активировать текущий.

{% for category in categories %}
<a class="list-group-item {% if category.slug in request.path %}active{% endif %}" href="{% url 'help:category_index' category.slug %}">
  <span class="badge">{{ category.article_set.count }}</span>
  {{ category.title }}
</a>
{% endfor %}

Ответ 10

Я использую более простое и чистое решение CSS. Он имеет свои ограничения, о которых я знаю и могу жить, но он избегает неуклюжих селекторов классов CSS, например:

<a href="index.html" class="item{% if url == request.path %}active{% endif %}">index</a>

Поскольку пробельный символ перед active отсутствует, селектор класса получает имя itemactive вместо item active и это не так уж сложно, чтобы ошибиться.

Для меня это чистое решение CSS работает намного лучше:

a.item /* all menu items are of this class */
{
    color: black;
    text-decoration: none;
}

a.item[href~="{{ request.path }}"] /* just the one which is selected matches */
{
    color: red;
    text-decoration: underline;
}

Примечание. Это даже работает, если URL имеет дополнительные компоненты пути, потому что тогда href также частично совпадает. В конечном итоге это может привести к "столкновениям" с более чем одним совпадением, но часто это достаточно просто, потому что на хорошо структурированных веб-сайтах "подкаталог" URL-адреса обычно является дочерним элементом выбранного элемента меню.

Ответ 11

Здесь много хороших ответов, но они довольно жесткие или не очень СУХИЕ. Это позволит вам установить/получить активную навигацию во всех шаблонах, не прибегая к повсеместному включению включений и не полагаясь на совпадения только по URL.

Ваш "активный" элемент может быть представлением, пространственным представлением имен или любым произвольным текстом, который вы хотите проверить, и вы можете передать дополнительные имена классов для атрибутов DRY HTML. Поскольку мы устанавливаем/получаем из контекста шаблона, вы также можете установить активные элементы из представления (с помощью рендера) или процессора контекста.

Мне нравятся представления в пространстве имен, поэтому приведу пример их использования в базовом шаблоне с двумя ссылками.

templatetags/navigation.py:

from django import template
from django.utils.safestring import mark_safe


register = template.Library()


@register.simple_tag(takes_context=True)
def nav_is_active(context, url_pattern, classes=''):
    active_class = ''

    try:
        if context['nav_active'] == url_pattern:
            active_class = 'is-active'
    except:
        pass

    return mark_safe('class="{} {}"'.format(classes, active_class))


@register.simple_tag(takes_context=True)
def nav_set_active(context, url_pattern):
    context['nav_active'] = url_pattern
    return ''

base.html:

<a {% nav_is_active 'dashboard:index' 'menu-link' %} href="{% url 'dashboard:index' %}">Dashboard</a>
<a {% nav_is_active 'account:index' 'menu-link' %} href="{% url 'account:index' %}">Account</a>

dashboard.html:

{% extends "base.html" %}

{% load navigation %}
{% nav_set_active 'dashboard:index' %}

<h1>Dashboard!</h1>