web-dev-qa-db-fra.com

Comment rendre le menu avec un élément actif avec DRY?

Je voudrais rendre des constructions comme:

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

Communauté est l'élément de menu sélectionné. J'ai un menu avec les mêmes options pour plusieurs modèles, mais je ne voudrais pas créer de combinaisons pour chaque modèle:

<!-- 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>

Nous avons une liste permanente d’éléments de menu, ce qui peut être un moyen plus efficace de créer une seule structure généralisée de menu puis de rendre le menu avec l’option requise pour le modèle.

Par exemple, cela pourrait être une balise qui permet de le faire.

34
sergzach

J'ai trouvé une solution simple et élégante DRY.

C'est l'extrait de code: 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' %}
26
sergzach

Trouvez une autre façon de le faire, assez élégante grâce à cette réponse: https://stackoverflow.com/a/17614086/34871

Étant donné un modèle d'URL tel que:

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

my_view_name est disponible pour le modèle via request (n'oubliez pas que vous devez utiliser un RequestContext - ce qui est implicite lorsque vous utilisez render_to_response)

Ensuite, les éléments de menu peuvent ressembler à:

<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>

etc.

De cette façon, l'URL peut changer et cela fonctionne toujours si les paramètres d'URL varient, et vous n'avez pas besoin de conserver une liste d'éléments de menu ailleurs.

57
vincent

Utiliser la balise template

Vous pouvez simplement utiliser la balise de modèle suivante:

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

from Django import template
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 ''

Donc, en vous votre modèle:

{% 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>

Utiliser uniquement HTML et CSS

Il existe une autre approche, utilisant uniquement HTML et CSS, que vous pouvez utiliser dans n’importe quel framework ou site statique.

Considérant que vous avez un menu de navigation comme celui-ci:

<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>

Créez des modèles de base, un pour chaque session de votre site, comme par exemple:

home.html
base_blog.html
base_contact.html

Tous ces modèles étendant base.html avec une "section" block, comme par exemple:

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

Ensuite, en prenant le base_blog.html comme exemple, vous devez avoir les éléments suivants:

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

Maintenant, il est facile de définir l'élément de menu activé en utilisant uniquement CSS:

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

Vous pouvez créer une variable de contexte links avec le nom, l'URL et s'il s'agit d'un élément actif:

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

Si ce menu est présent sur toutes les pages, vous pouvez utiliser un processeur de contexte:

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

Ensuite, dans votre fichier de paramètres, ajoutez cette fonction à TEMPLATE_CONTEXT_PROCESSORS comme suit: path.to.where.that.function.is.located.menu_links. Cela signifie que la fonction menu_links sera appelée pour chaque modèle et que la variable links est disponible dans chaque modèle.

4
Simeon Visser

En supposant que l'élément de navigation soit un lien avec la même URL que la page actuelle, vous pouvez simplement utiliser JavaScript. Voici une méthode annotée que j'utilise pour ajouter un class="active" à une li dans un menu de navigation avec 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';

Cette méthode signifie que vous pouvez simplement l'insérer une fois dans votre modèle de base et l'oublier. Aucune répétition et aucune spécification manuelle de l'URL de la page dans chaque modèle. 

Une mise en garde: cela ne fonctionne évidemment que si la url trouvée correspond à un lien de navigation href. En outre, il serait possible de spécifier quelques cas d'utilisation spéciaux dans le SC, ou de cibler un élément parent différent selon les besoins.

Voici un exemple qui peut être exécuté (n'oubliez pas que des extraits sont exécutés sur 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>

1
rnevius

Voici ma solution:

{% 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>
0
Tobias Ernst

J'ai rencontré ce défi aujourd'hui avec la manière d'activer dynamiquement une "catégorie" dans une barre latérale. Les catégories ont des slugs qui proviennent de la base de données.

Je l'ai résolu en vérifiant que la catégorie était dans le chemin actuel. Les limaces sont uniques (pratique standard) donc je pense que cela devrait fonctionner sans aucun conflit.

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

Exemple de code complet de la boucle pour obtenir les catégories et activer celle en cours. 

{% 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 %}
0
Alex Phelps

Basé sur la réponse de @vincent, il existe un moyen plus simple de le faire sans gâcher les modèles d'URL de Django.

Le chemin de demande actuel peut être vérifié par rapport au chemin de l'élément de menu rendu. S'ils correspondent, il s'agit de l'élément actif.

Dans l'exemple suivant, j'utilise Django-mptt pour rendre le menu, mais on peut remplacer node.path par chaque chemin d'élément de menu.

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

J'ai trouvé un moyen d'utiliser des balises de bloc dans un modèle parent contenant des menus pour réaliser quelque chose comme ceci.

base.html - le modèle parent:

<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 - modèle pour une page spécifique:

{% extends "base.html" %}

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

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

Comme vous pouvez le constater, le nom du bloc contenant active varie selon les modèles de page. contact.html utiliserait menu_contact_class, et ainsi de suite.

Un avantage de cette approche est que vous pouvez avoir plusieurs sous-pages avec le même élément de menu actif. Par exemple, une page à propos peut avoir des sous-pages donnant des informations sur chaque membre de l'équipe d'une entreprise. Il pourrait être judicieux que l’élément de menu À propos reste actif pour chacune de ces sous-pages.

0
Magnus Teekivi

J'utilise une solution CSS simple et plus simple. Il a ses limites, que je connais et peux vivre, mais il évite les sélecteurs de classe CSS maladroits, comme ceci:

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

Comme il manque un caractère d'espacement avant active, le sélecteur de classe s'appelle itemactive au lieu de item active et il n'est pas trop difficile de se tromper de cette façon.

Pour moi, cette solution CSS pure fonctionne beaucoup mieux:

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;
}

Remarque: cela fonctionne même si l'URL contient des composants de chemin supplémentaires, car href correspond également partiellement. Cela pourrait éventuellement causer des "collisions" avec plus d'une correspondance, mais cela suffit souvent, car sur des sites Web bien structurés, un "sous-répertoire" d'URL est généralement un enfant de l'élément de menu sélectionné.

0
MaxC