Как выделить активную ссылку с помощью Snap?

Может ли кто-нибудь привести пример выделения «активной» ссылки в меню на сайте с помощью Snap? Или, по крайней мере, скажите мне, как бы вы это сделали — я понятия не имею.

В других веб-фреймворках я обычно устанавливаю переменную контекста с именем active на то, какой должна быть активная страница, а затем мой html просто проверяет это:

<ul> 
   {% ifequal active "home" %}
   <li class="active">
   {% else %}
   <li>
   {% endifqual %}
   <a href="/">Home</a>
   </li>

   {% ifequal active "about" %}
   <li class="active">
   {% else %}
   <li>
   {% endifequal %}
   <a href="/about">About Us</a>
   </li>

 </ul>

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

Мое решение

Благодаря @mightybyte и @Adam Bergmark я остановился на следующем:

Хаскель-код:

menuenuEntrySplice :: MonadSnap m => HeistT m Template
menuEntrySplice = do
               requestPath <- lift $ withRequest (return . rqURI)
               node <- getParamNode
               let setActive n = if getAttribute "href" node == Just (decodeUtf8 requestPath)
                                    then setAttribute "class" "active" n
                                    else n


               let aNode = Element "a" [("href", fromMaybe "/" $ getAttribute "href" node)] $ [TextNode (nodeText node)]
               let liNode = setActive $ Element "li" [] [aNode]

               return [liNode]


app :: SnapletInit App App
app = makeSnaplet "app" "An snaplet example application." Nothing $ do
    ....
    addSplices [ ("menuEntry", liftHeist menuEntrySplice) ]
    return $ App h s a

И вот теперь это используется в HTML:

<ul class="nav">
      <menuEntry href="/">Home</menuEntry>
          <menuEntry href="/contact">Contact</menuEntry>
</ul>

который производит:

<ul class="nav">
     <li class="active"> <a href="/">Home</a> </li>
     <li> <a href="/contact">Contact</a> </li>
</ul>

person Andriy Drozdyuk    schedule 29.07.2012    source источник
comment
Как насчет того, чтобы дать body и вашим li id, а затем сделать #page-home #nav-home, #page-about #nav-about?   -  person    schedule 30.07.2012
comment
Спасибо, но я бы предпочел сохранить логику в коде, а не в css/html :-) (Да, я понимаю, что мое решение основано на логике в html, но я не говорил, что оно лучшее :-)   -  person Andriy Drozdyuk    schedule 31.07.2012


Ответы (2)


На сайте snapframework.com мы делаем это с помощью javascript. В конце site.js вы найдете этот код, который добавляет соответствующий класс в соответствующую ссылку.

if ($.url.segment(0)) {
  $('.nav li.'+$.url.segment(0)).addClass('active');
} else {
  $('.nav .home').addClass('active');
}

Если вы хотите сделать это с помощью Heist, вам нужно будет использовать парадигму, немного отличающуюся от той, что используется в вашем шаблоне выше. Весь смысл Heist (при поддержке сильной статической системы типов Haskell) состоит в том, чтобы обеспечить максимально возможное разделение между представлением и бизнес-логикой, чтобы вы не могли использовать конструкции Haskell, такие как циклы или условные операторы, непосредственно в своих шаблонах. Ответ заключается в создании нового тега (реализованного с помощью соединения), который делает именно то, что вы хотите. В своем шаблоне вы будете использовать его следующим образом:

<ul>
  <menuLink href="/">Home</menuLink>
  <menuLink href="/about">About Us</menuLink>
</ul>

Прежде всего, обратите внимание, насколько это стало чище. В вашем подходе вам приходилось повторять один и тот же код для каждой ссылки. Здесь мы можем сохранить шаблон СУХИМ, и он довольно хорошо читается.

Для реализации тега menuLink вам понадобится сплайс, который выглядит примерно так:

import Control.Monad.Trans.Class (lift) -- transformers 
import Data.Text.Encoding (decodeUtf8)
import Text.XmlHtml (getAttribute, setAttribute, elementTag)

menuLinkSplice :: MonadSnap m => HeistT m Template
menuLinkSplice = do
    requestPath <- lift $ withRequest (return . rqURI)
    node <- getParamNode
    let addActive n = if getAttribute "href" n == Just (decodeUtf8 requestPath)
                           then setAttribute "class" "active" n
                           else n
    return [addActive (node { elementTag = "a" })]

Затем, чтобы собрать все это вместе, вам нужно привязать этот сплайс к тегу menuLink. В приложении Snap вы сделаете это с помощью:

addSplices [ ("menuLink", liftHeist menuLinkSplice) ]

Вы также можете найти мою запись в блоге Цикл и поток управления in Heist полезно (а также некоторые другие мои посты с тегом "heist").

person mightybyte    schedule 30.07.2012
comment
Отличный ответ! Как раз то, что я искал. Извините за глупый вопрос, но в let addActive node =... что такое addActive? - person Andriy Drozdyuk; 30.07.2012
comment
И как я могу сделать вывод <li><a href="...">Blah</a></li>, а не только тег a? Я не совсем правильно понимаю это: return [addActive (node (node {elementTag ="li" {elementTag="a"} ... ??? - person Andriy Drozdyuk; 30.07.2012
comment
addActive — это просто функция, которую он определяет там локально, вопрос форматирования. - person Adam Bergmark; 31.07.2012
comment
@AdamBergmark Аааа! Спасибо! Одинаковые имена переменных узла действительно меня смутили. - person Andriy Drozdyuk; 31.07.2012
comment
И если вы пропустили мой комментарий IRC, проверьте определение узла в hackage.haskell.org/packages/archive/xmlhtml/0.2.0.2/doc/html/ для вложения элементов. - person Adam Bergmark; 31.07.2012
comment
О да, это был плохой выбор имени с моей стороны. Я отредактирую, чтобы было понятнее. - person mightybyte; 31.07.2012

Насколько я могу судить, Heist требует, чтобы вы поместили даже такую ​​логику в Haskell, а не в Heist. См., например, этот пример из документы для Text.Templating.Heist:

link :: Text -> Text -> X.Node
link target text = X.Element "a" [("href", target)] [X.TextNode text]

loginLink :: X.Node
loginLink = link "/login" "Login"

logoutLink :: Text -> X.Node
logoutLink user = link "/logout" (T.append "Logout " user)

loginLogoutSplice :: Splice MyAppMonad
loginLogoutSplice = do
    user <- lift getUser
    return [maybe loginLink logoutLink user]

Здесь вместо того, чтобы передавать Bool в шаблон и позволять шаблону выполнять логику if/then, вся логика выполняется на уровне Haskell. Это лучший ответ, который я могу дать. Я не знаком с Heist; Я знаю, что вы можете привести аргументы для сращивания, но мне непонятно, как выполнить вашу конкретную задачу.

person Dan Burton    schedule 30.07.2012
comment
Спасибо, но я вообще не понимаю, как этим пользоваться. Более того, первоначальный проект, который Snap сгенерирует для вас, имеет функции, которые совсем не похожи на документацию Snap или их примеры, и я не знаю, что куда поместить. Раздражающий... :-( - person Andriy Drozdyuk; 30.07.2012
comment
Да, документации ограбления, кажется, не хватает довольно много. Попробуйте #snapframework на freenode irc. - person Dan Burton; 30.07.2012