如何使用 DRY 渲染带有一个活动项目的菜单?

Posted

技术标签:

【中文标题】如何使用 DRY 渲染带有一个活动项目的菜单?【英文标题】:How to render menu with one active item with DRY? 【发布时间】:2012-04-05 07:40:45 【问题描述】:

我想渲染如下结构:

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

您可以创建一个上下文变量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在每个模板中都可用。

【讨论】:

for 循环中的 3 个变量是什么意思?对于 nameurlactive。使用 3 个变量是否正确? name 是链接的名称(例如,主页),url 是链接所在的 URL,active 是页面当前是否处于活动状态。你可以为每个页面计算这个,你会得到一个链接列表,比如[("Home", "/home", True), ("Community", "/community", False), ("About", "/about", False)] 确实可以使用三个变量。您可以在此处阅读 Django 中 for 循环的文档:docs.djangoproject.com/en/1.3/ref/templates/builtins/#for(请参阅他们的示例)。 这正是我想出的解决方案。当涉及到模板时,python 的优雅和上下文处理器的强大功能似乎经常被忽略,所以我喜欢这个解决方案。【参考方案2】:

我找到了简单而优雅的 DRY 解决方案。

这是sn-p: 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' %

【讨论】:

如果我使用% extends "includes/tabs.html" % 创建所需的布局,如何结合这种方法? 为什么我们应该在每个页面模板中包含标签?我认为这不是 DRY 解决方案...... 这完全有道理。例如,从包含的模板中,您甚至可以按页面选择不同的菜单模板。 @EvgenyBobkin 是否要指定菜单(使用extend)?如果您使用extend,那么可能一个菜单已经在基本模板中,它不应该被称为“tabs.html”。在您的基本模板中,您可以通过分配给变量来处理active_tabextend是指定一个父模板。【参考方案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的模板都带有一个block“部分”,例如:

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

那么,以base_blog.html 为例,你必须具备以下条件:

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

现在可以很容易地仅使用 CSS 来定义激活的菜单项:

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

【讨论】:

我想要完全匹配,所以我将模式修改为:pattern = '^' + reverse(pattern_or_urlname) + '$' @daigorocub 所以你打破了下面的例子。您可以在% active '^/regex/' % 中提供一个 ^。我认为我们必须恢复您的编辑。 ;) 好吧,这篇文章确实帮助了我,但我必须添加 $ 符号才能使我的代码工作,我相信它会为答案添加一些东西,不是吗?我没有使用整个示例 - 只需要第一部分。谢谢。 感谢您的启发。我已经修改了您的代码,即使使用具有语言前缀路径的 I18N 模式也可以使用。如果您愿意,您可以编辑您的评论,以便其他人可以从中受益。 pastebin.com/8hegST6b 模板标签法不错!我在active 标记中添加了一个exact 参数,在正则表达式中添加了一个尾随$,因为当您链接到索引网址“/”时,该链接将始终处于活动状态,因为所有网址都以/【参考方案4】:

想出了另一种方法,由于这个答案足够优雅:https://***.com/a/17614086/34871

给定一个 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 参数变化时仍然有效,并且您不需要在其他地方保留菜单项列表。

【讨论】:

只是想说这是完美的,因为它允许您在单个“全局”模板中使用它,而不是在单个模板文件中多次实现相同的东西。谢谢 request.resolver_match.url_name 不返回命名空间名称。 使用 request.resolver_match.view_name 作为命名空间的名称 这是一个很好的解决方案。比其他人更容易、更优雅。 这个答案是最干净的实现。我在论坛上阅读了很多帖子,他们提出了复杂的解决方案,但对于我们大多数不打算开发非常大的应用程序的人来说,这就像一个魅力。我想添加一件事:最有可能的是,一个在 html 中需要有 2 个类名,一个用于设置未选择菜单项的样式,另一个用于选择/激活它们的情况。我在下一条评论中举个例子。【参考方案5】:

假设导航项是与当前页面具有相同 URL 的链接,您可以只使用 javascript。这是一个带注释的方法,我用它在带有class="nav" 的导航菜单中将class="active" 添加到li

// 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 中指定几个特殊用例,或根据需要定位不同的父元素。

这是一个可运行的示例(请记住,sn-ps 在 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://***.com/">***</a>
  </li>
</ul>

【讨论】:

【参考方案6】:

根据@vincent 的回答,有一种更简单的方法可以做到这一点,而不会弄乱 django url 模式。

可以根据呈现的菜单项路径检查当前请求路径,如果它们匹配,则这是活动项。

在以下示例中,我使用 django-mptt 来呈现菜单,但可以将 node.path 替换为每个菜单项路径。

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

【讨论】:

【参考方案7】:

我今天遇到了如何在侧边栏中动态激活“类别”的挑战。这些类别有来自数据库的 slug。

我通过查看类别 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 %

【讨论】:

我可以看到这出了问题。例如,一个蛞蝓是amazing,另一个是amazing_stuff。满足唯一约束条件,但 category.slug in request.pathamazing 的两个页面上都为 true。【参考方案8】:

我正在使用更简单、更纯粹的 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 的“子目录”通常是所选菜单项的子项。

【讨论】:

【参考方案9】:

我想出了一种方法来利用包含菜单的父模板中的块标签来实现这样的目标。

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,以此类推。

这种方法的一个好处是您可以拥有多个具有相同活动菜单项的子页面。例如,关于页面可能有子页面,提供有关公司每个团队成员的信息。让每个子页面的“关于”菜单项保持活动状态可能是有意义的。

【讨论】:

【参考方案10】:

这是我的解决方案:

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

【讨论】:

【参考方案11】:

我个人认为最简单的方法是为每个链接创建块,如下所示:

    # base.py
...
<a href="% url 'home:index' %" class=% block tab1_active %% endblock %>
...
<a href="% url 'home:form' %" class=% block tab2_active %% endblock %>
...

然后在每个相关模板中将该链接声明为“活动”,例如:

tab1 模板:

% block tab1_active %"active"% endblock %

tab2 模板:

% block tab2_active %"active"% endblock %

【讨论】:

【参考方案12】:

只需使用模板标签

# app/templatetags/cores.py

from django import template
from django.shortcuts import reverse

register = template.Library()

@register.simple_tag
def active(request, url, classname):
    if request.path == reverse(url):
        return classname
    return ""

在你的模板中这样做

% load cores %
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Example</title>
</head>
<body>
    <div>
        <a href="% url 'myUrl' %" class="% active request 'myUrl' 'activeClass' %">myUrl</a>
    </div>
</body>
</html>

玩得开心?

【讨论】:

以上是关于如何使用 DRY 渲染带有一个活动项目的菜单?的主要内容,如果未能解决你的问题,请参考以下文章

带有子菜单的大量性能下降渲染菜单

React Native:如何隐藏条件渲染以外的元素?

如何以 DRY 方式基于 current_page 将“活动”类应用于我的导航? - 导轨 3

VUE项目实战17通过接口获取菜单并渲染

如何使用 Vuetify 创建带有子菜单的菜单?

在菜单选择上渲染部分视图