Textpattern CMS support forum
You are not logged in. Register | Login | Help
- Topics: Active | Unanswered
#109 2012-09-24 07:14:19
Re: etc_query: all things Textpattern
I’m already in love with this plugin :)
But at the same time… sometimes… brain hurts when using it…
First, a quick tip: the (almost) Ultimate Navigation with Link for Current Page (and all parent links) Highlighted.
Some background:
You may have a website with a main navigation that could be made up of some static code (hardcoded links) and/or maybe some dynamic code (a mix of section_list, category_list, article_custom). It could even have more than one level: nested menus, to create dropdown navigations, or whatever other fancy trick you may have.
So, now, your client ask you to to highlight the current page on this main navigation.
You could go the CSS-way: put a class
or id
in the <body>
, and a class
or id
on every nav item, and add lotz of CSS rules to match them. Old-school, effective, but a PITA to maintain. It doesn’t scale.
You could the Txp conditional-tags way, but then, this 3-level menu is a mix of static (hardcoded) and dynamic (Txp-generated) code. It would be a nightmare of if_section
, if_category
, if_article_id
to test which is the current page. You will go crazy pretty quick..
And then, there is the etc_query way… Enter: Ultimate Navigation with Link for Current Page (and all parent links) Highlighted.
1) Create whatever navigation you desire, mixing static HTML and dynamically-generated HTML (section_list, category_list, article_custom, you name it). Also, for this example, we will store it in a form named main_nav
.
You may end up with something like this, once it’s rendered in the front-end:
<ul>
<li><a href="/section-a">Section A</a>
<ul>
<li><a href="/section-a/article-1">Article 1</li>
<li><a href="/section-a/article-2">Article 2</li>
<li><a href="/section-a/article-3">Article 3</li>
</ul>
</li>
<li><a href="/section-b">Section B</a>
<ul>
<li><a href="/section-b/article-1">Article 4</li>
<li><a href="/section-b/article-2">Article 5</li>
<li><a href="/section-b/article-3">Article 6</li>
</ul>
</li>
<li><a href="/section-c">Section C</a>
<ul>
<li><a href="/section-c/category-alfa">Category Alfa
<ul>
<li><a href="/section-c/category-alfa/article-7">Article 7</li>
<li><a href="/section-c/category-alfa/article-8">Article 8</li>
<li><a href="/section-c/category-alfa/article-9">Article 9</li>
</ul>
</li>
<li><a href="/section-c/category-beta">Category Beta
<ul>
<li><a href="/section-c/category-beta/article-7">Article 10</li>
<li><a href="/section-c/category-beta/article-8">Article 11</li>
<li><a href="/section-c/category-beta/article-9">Article 12</li>
</ul>
</li>
</ul>
</li>
</ul>
Believe: this trick will work even with more nested levels and whatever kind of URL structure you have.
2) Now, we create a url_path
that is just the path component of request_uri, with any query string removed.
<txp:php>
global $variable;
// This returns the URL path (like /some-section/ or /some-section/some-article or /some/crazy/custom/url/)
// without any query string and stores it in a txp:variable named "url_path"
$variable['url_path'] = parse_url(page_url(array()), PHP_URL_PATH);
</txp:php>
3) Finally, etc_query doing its magic:
<txp:etc_query data='<txp:output_form form="main_nav" />' replace='//a[@href="<txp:variable name="url_path" />"]/ancestor::li@@class={concat(@class," current")}' />
So, what will this code do? It will add the class current
(while keeping any other class) to all ancestor <li>
s (in the DOM tree) of a link for which the href
attribute matches the current url_path
.
So, if you were visiting the /section-c/category-alfa/
page, your nav menu markup will output like this:
<ul>
<li><a href="/section-a">Section A</a>
<ul>
<li><a href="/section-a/article-1">Article 1</li>
<li><a href="/section-a/article-2">Article 2</li>
<li><a href="/section-a/article-3">Article 3</li>
</ul>
</li>
<li><a href="/section-b">Section B</a>
<ul>
<li><a href="/section-b/article-1">Article 4</li>
<li><a href="/section-b/article-2">Article 5</li>
<li><a href="/section-b/article-3">Article 6</li>
</ul>
</li>
<li class="current"><a href="/section-c">Section C</a>
<ul>
<li class="current"><a href="/section-c/category-alfa">Category Alfa
<ul>
<li><a href="/section-c/category-alfa/article-7">Article 7</li>
<li><a href="/section-c/category-alfa/article-8">Article 8</li>
<li><a href="/section-c/category-alfa/article-9">Article 9</li>
</ul>
</li>
<li><a href="/section-c/category-beta">Category Beta
<ul>
<li><a href="/section-c/category-beta/article-7">Article 10</li>
<li><a href="/section-c/category-beta/article-8">Article 11</li>
<li><a href="/section-c/category-beta/article-9">Article 12</li>
</ul>
</li>
</ul>
</li>
</ul>
Spot the differences? The ancestor <li>s
for the matching links are marked up with a class="current"
attribute.
Combine it with the nav-bar style example of the Superfish menu and create your own Ultimate Navigation with Link for Current Page (and all parent links) Highlighted :)
Offline
#110 2012-09-24 07:27:41
Re: etc_query: all things Textpattern
Now, a question for Mr. etc…
I’d like to use the substring-before
Xpath function but I couldn’t come up with the proper syntax yet:
<txp:etc_query data='<txp:output_form form="main_nav" />' replace='//a[@href={substring-before("<txp:variable name="url_path" />", "<txp:article_url_title />")}]/ancestor::li@@class={concat(@class," current")}' />
Let me explain what I’m trying to do here:
//a[@href={substring-before("<txp:variable name="url_path" />", "<txp:article_url_title />")}]
In the nav menu I’m creating (half static, half dynamic), I’m only using links to particular sections and categories, and maybe to some other custom URLs, but not including any link to individual articles. But even when visiting an individual article, I’d like to keep “highlighted” the path to it. Problem is: as on the menu there isn’t any link to the individual article, the trick doesn’t work in this situation, as the Xpath selector doesn’t match any href
on the menu.
The solution is easy: just use the path to the individual article, but stripping the article_url_title
. But I can’t get it (yet) with just etc_query and its magic powers.
I could create a txp:variable
with the proper path (page_url minus articule_url_title) just before, and feed it to etc_query, as in the original tip.
But then… I prefer not to… It isn’t as funny as doing it with etc_query. :/
Thanks.
Offline
#111 2012-09-24 10:24:59
Re: etc_query: all things Textpattern
maniqui wrote:
I’m already in love with this plugin :)
But at the same time… sometimes… brain hurts when using it…
Oh how I know what you feel! Very pleased, thanks!
Waa, ::ancestor
! You have explored xpaths I have never taken, very nice to know it works, and many thanks for the tip.
Now, a question for Mr. etc…
Ready for a lesson? :) The etc_query replace
tokens are of the form xpath=string
, or xpath@@attr=string
, like in
//a[@href='#']@@class={concat(@class," current")}
Curly braces mean that etc_query
will give them some special treatment, which is “evaluate them as xpath” here. But etc_query
does not parse xpath
part for xpath, so
//a[@href={substring-before(...)}] ?
results in an invalid xpath expression. The reason for not parsing xpath
in xpath=string
tokens is that it is not necessary:
//a[@href=substring-before(...)]
looks like valid xpath expression (probably only for me). On the other hand, string
part gets parsed and evaluated, and it can include many {xpath}
and other chunks. So class={concat(@class, " current")}
is ok, but class=concat(@class, " current")
(without braces) would break parsing. Could someone teach me how to output @ in inline code?
This is the theory, awaiting experimental confirmation. :)
Cheers
Edit: enhanced the lesson with my new Textile skills.
Last edited by etc (2012-09-24 12:14:00)
Offline
#112 2012-09-24 11:26:04
Re: etc_query: all things Textpattern
etc wrote:
Could someone teach me how to output @ in inline code?
Like this you mean? code@code
. Works by combining both escaping and inline code syntax:
@ ==code@code== @
Leaves two visible spaces, but what can you do. Bracket tricks and nesting do not seem to work within inline code tags (for a reason most likely).
Last edited by Gocom (2012-09-24 11:28:48)
Offline
#113 2012-09-24 12:10:10
Offline
#114 2012-09-24 18:13:44
Re: etc_query: all things Textpattern
etc wrote:
Oh how I know what you feel! Very pleased, thanks!
You are welcome.
Waa,
::ancestor
! You have explored xpaths I have never taken, very nice to know it works, and many thanks for the tip.
Yeah, I’m already an advanced n00b at all XPath and etc_query things :)
Ready for a lesson? :) (…)
Great lesson! Thanks.
This is the theory, awaiting experimental confirmation. :)
With substring-before()
, it worked like a charm. But then, I wanted to take this one step further and use replace()
(which supports regexs), only to find out that on PHP, XPath is currently on v1.0, and replace()
belongs to XPath 2.0 specifications.
I was just starting to cry when then I looked at DOMXPath manual and found out about registerPHPFunctions.
Hope was immediately restored as I looked thru etc_query source code and found you implemented it for the functions
attribute :D
So, here is the etc_query snippet for the answer I was looking for
<txp:etc_query data='<txp:output_form form="main_nav" />' functions="preg_replace" replace='//a[@href=preg_replace("/\/<txp:article_url_title />$/i", "", "<txp:variable name="url_path" />")]/ancestor::li@@class={concat(@class," current")}' />
Offline
#115 2012-09-24 21:50:36
Re: etc_query: all things Textpattern
maniqui wrote:
Hope was immediately restored as I looked thru etc_query source code and found you implemented it for the
functions
attribute :D
I’d swear it was in TFM :) Great snippet, could it be seen in action?
Now a new update is ready, but before letting it out, I’d like etc_query
users advice on the syntax to adopt. The aim is to implement more DOM transformation functionality, namely:
- insert a new node before an existing one (
insertBefore
); - append a new child node to an existing one (
appendChild
); - move an existing node before another one (
insertBefore
); - move an existing node inside another one (
appendChild
); - replace a node by another node, new or existing.
The general scheme is xpath¤=[string | xpath]
, where ¤
is some symbol. Currently, the first point above uses xpath^=string
and the second one xpath+=string
, but this can yet be changed. The question is: what symbols seem natural for each operation? The usual candidates are ~
and $
, but I am unable to choose.
Offline
#116 2012-09-25 12:49:15
Re: etc_query: all things Textpattern
After hours of deliberation, our jury has unanimously adopted a new syntax, so version 1.1 is now released. Main news:
- replace attribute is enhanced with new dom transformation modes, see this example;
- delim attribute is dropped, the default
=
should suffice; - easier namespaced nodes retrieval.
Offline
#117 2012-09-25 15:16:59
Re: etc_query: all things Textpattern
The problem of namespaced feeds import (namely atom) mentioned here can now be solved in two ways, by registering a prefix for default namespace within markup:
<txp:etc_query url="http://gdata.youtube.com/feeds/api/users/boscartoon/playlists?v=2"
markup="xml:a"
query="a:feed/a:title"
...
or simply stripping out default xmlns declarations
<txp:etc_query url="http://gdata.youtube.com/feeds/api/users/boscartoon/playlists?v=2"
markup="xml:"
query="feed/title"
...
Offline
#118 2012-09-25 20:54:57
Re: etc_query: all things Textpattern
A new improvement on the etc_query snippet on this tip
<txp:etc_query data='<txp:output_form form="main_nav" />' functions="preg_replace" replace='//a[@href=preg_replace("@(/\d{4}/\d{2}/\d{2})?/<txp:article_url_title />$@i", "", "<txp:variable name="url_path" />") and @href!="/"]/ancestor::li@@class={concat(@class," current")}' />
This one will also match URLs schemes with /yyyy/mm/dd
dates on it.
Also, I’ve changed the regex delimites from /
to @
, to avoid having to escape /
.
Offline
#119 2012-09-25 20:58:06
Re: etc_query: all things Textpattern
BTW:
Gocom wrote:
Like this you mean?
code@code
. Works by combining both escaping and inline code syntax:
@ ==code@code== @
Leaves two visible spaces, but what can you do. Bracket tricks and nesting do not seem to work within inline code tags (for a reason most likely).
It seems that just one leading space would be enough, and will also avoid a trailing blank space being inside the inline code.
This:
@ ==code@code==@
Should output this: code@code
Offline
#120 2012-10-02 19:08:42
Re: etc_query: all things Textpattern
hello! how do i filter out a string from items? let’s say the word HELLO DOLLY appeared in each item and I don’t want it there.
Offline