Go to main content

Textpattern CMS support forum

You are not logged in. Register | Login | Help

#1 2025-03-18 16:17:50

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,851
Website GitHub

How do I use if_different only above a certain number of matches?

I have a calendar of events that can have a very varying number of entries depending on whether filtered by organiser (=article category) or showing all organisations, as well as the time of year (lots of events in summer, fewer in winter).

I have used if_different to insert month headings between ul-lists of articles (using this clever method worked out by bloke and etc). That works great for long lists but looks really busy for shorter lists when some organisers have perhaps only a few events in a year. There’s then a month heading per event. In such cases – let’s say with fewer than 8 articles –, I’d rather do without the subheadings.

The only way I can think of doing this is to make a query to get the article id#s, count them, then output with or without subheadings.

To complicate matters, the original query is made using smd_query with the populate="article" because it joins results from two different sets of articles and filters against dates stored in custom_fields). Once I have a comma-separated list of article-id#, I can feed them back into article_custom to output them in the desired format.

Two questions:

  • Is there a cleverer way to do this, e.g. to cache the original query results and then re-output them differently without a renewed query? AFAIK using populate="article" in smd_query adds them to $thisarticle.
  • Do I not need to worry about performance because Textpattern or mysql does some kind of caching in the background when making a repeat query?

TXP Builders – finely-crafted code, design and txp

Offline

#2 2025-03-18 16:30:04

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,851
Website GitHub

Re: How do I use if_different only above a certain number of matches?

Following on from this idea of “querying the results of a previously executed query”, I’d also like to make a category filter based on the list of category1 values in the event list. Currently I duplicate the query but with a different output form.


TXP Builders – finely-crafted code, design and txp

Offline

#3 2025-03-18 22:46:22

etc
Developer
Registered: 2010-11-11
Posts: 5,342
Website GitHub

Re: How do I use if_different only above a certain number of matches?

Your markup (ul-lists) is rich enough to allow etc_query count list items and remove ‘short’ lists headings. But when do you need to remove them: if the total number of articles is less than 8, or the months list is shorter than 8, or..?

Offline

#4 2025-03-18 23:18:51

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,851
Website GitHub

Re: How do I use if_different only above a certain number of matches?

I was thinking the total number of returned articles. I can get that by incrementing a counter in the listitem form.

Thanks for the idea with etc_query. You’re suggesting I save the output of the first query to a txp variable, and then, if my counter is less than my threshold value, that I use etc_query to collect all the relevant event-item html entries, skipping the headings. I guess then, the txp variable serves as the data attribute, and that I would need an appropriate XPath filter to retrieve just the relevant markup.

Currently my markup with headings is:

<h4 class="event-month" id="march-2025">
    March 2025
</h4>
<ul class="event-list plainlist">
    <li>
         <ul class="event-item prose flow">
            <!-- the event details -->
        </ul>
    </li>
    <li>
         <ul class="event-item prose flow">
            <!-- the event details -->
        </ul>
    </li>
    ...
</ul>
<h4 class="event-month" id="april-2025">
    April 2025
</h4>
<ul class="event-list plainlist"> …

The desired markup without headings would mean retrieving each ul.event-item and then rewrapping them with wraptag="ul" class="event-list plainlist" break="li", e.g.:

<ul class="event-list plainlist">
    <li>
         <ul class="event-item prose flow"> …
         <ul class="event-item prose flow"> …
         <ul class="event-item prose flow"> …
         …
    </li>
</ul>

TXP Builders – finely-crafted code, design and txp

Offline

#5 2025-03-19 08:37:21

etc
Developer
Registered: 2010-11-11
Posts: 5,342
Website GitHub

Re: How do I use if_different only above a certain number of matches?

jakob wrote #339318:

Thanks for the idea with etc_query. You’re suggesting I save the output of the first query to a txp variable, and then, if my counter is less than my threshold value, that I use etc_query to collect all the relevant event-item html entries, skipping the headings. I guess then, the txp variable serves as the data attribute, and that I would need an appropriate XPath filter to retrieve just the relevant markup.

If you have etc_query already installed, that’s probably the less resistance way. But it’s a large plugin (though it might replace smd_query), so installing it just for this task looks like an overkill.

I was thinking the total number of returned articles. I can get that by incrementing a counter in the listitem form.

You seem to retrieve article ids with smd_query and then passing the list to article_custom? Then you can count the articles before calling article_custom, and then use the appropriate form. For example, remove anything but commas from the list and calculate the remaining length with, say, evaluate.

Offline

#6 2025-03-19 08:57:34

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,851
Website GitHub

Re: How do I use if_different only above a certain number of matches?

I’ve tried both ways for now.

Variant 1 with multiple, successive queries is as follows and uses smd_query to get a comma-separated list of the relevant article id numbers:

<txp:variable name="event_item_ids">
    <txp:smd_query query='…' wraptag="" break="," populate="article">
        <txp:article_id /><txp:variable name="event_count" add />
    </txp:smd_query>
</txp:variable>

Using the incremental counter, I can then test for:

<txp:if_variable name="event_count" not>
    <!-- message about no events -->
<txp:else />
    <txp:evaluate query='<txp:variable name="event_count" /> > 8'>
        <!-- list of events with month headings -->
        <txp:article_custom form="event_item_by_month" id='<txp:variable name="event_item_ids" />' limit="0" />
    <txp:else />
        <!-- regular list of events -->
        <txp:article_custom form="event_item" id='<txp:variable name="event_item_ids" />' limit="0" />
    </txp:evaluate>
</txp:if_variable>

In practice, that doesn’t ‘seem’ particularly slow.

———

Variant 2 using etc_query is as follows: I first do the smd_query as before but with the full html output using the event_item_by_month form and store all of that in a variable called events_by_month. I also add <txp:variable name="event_count" add /> inside that form.

<txp:variable name="events_by_month">
    <txp:smd_query query='…' wraptag="" break="" populate="article" form="event_item_by_month" />
</txp:variable>

Then I do the same event_count check for no output / the number of matches as above but outputting either the full html stored in the variable, or the stripped down html obtained using etc_query as follows:

<txp:if_variable name="event_count" not>
    <!-- message about no events -->
<txp:else />
    <txp:evaluate query='<txp:variable name="event_count" /> > 8'>
        <!-- list of events with month headings -->
        <txp:variable name="events_by_month" />
    <txp:else />
        <!-- regular list of events -->
        <txp:etc_query data='<txp:variable name="events_by_month" />' markup="html" query="//ul[contains(@class, 'event-item')]" wraptag="ul" break="li"  class="event-list plainlist" />
    </txp:evaluate>
</txp:if_variable>

There’s an even more complex expression for an item that contains a class in this XPath cheatsheet (last item: Class check). FWIW, it also works with query="//ul//li//ul".

———

However, …

… I realise I have a thinking mistake when it comes to making the category filter in the sidebar. That should show all the possible categories with active events, but on pages where I am filtering by category, the original markup to filter from contains only the active category. So, I need to rethink that…

Possibilities:

  1. Variant with successive queries: maybe output all events regardless of category with smd_query, then filter by category in the article_custom instead, or …
  2. Variant with etc_query: output all markup with smd_query, the filter the resulting html by category using etc_query, which means finding a more complex XPath query.
  3. Do two smd_queries, one for the category, one for all.

Given that 1. and 2. return an event count for all items, even when filtered by category, I’d need another way of testing for the number of matches. So I guess version 3 will be most likely. *sigh*. I suppose that’s not all that different to the typical configuration of using context-sensitive txp:article for the main column output and txp:article_custom for sidebar filters.


TXP Builders – finely-crafted code, design and txp

Offline

#7 2025-03-19 12:00:31

etc
Developer
Registered: 2010-11-11
Posts: 5,342
Website GitHub

Re: How do I use if_different only above a certain number of matches?

I’d vote for:

  • Variant 2: only one db query + fast dom parsing of a small fragment. You might even replace etc_query with some regex trim/replace.
  • Possibility 3: retrieves only necessary data, especially if you use DISTINCT for categories.

Offline

#8 2025-03-19 16:16:25

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,851
Website GitHub

Re: How do I use if_different only above a certain number of matches?

Thank you. Well, I tried making a category filter for the pre-retrieved complete HTML output using etc_query and this XPath query:

//ul[contains(@class, 'event-item')][li[@class='event-item__ort' and text()='<txp:category />']]

That works quite well. However, I couldn’t work out a way to simply retrieve just the individual category values from the html output using XPath. I tried with:

//li[@class='event-item__ort']/text()

to get all the <li class="event-item__ort">…</li> parts of the source, but the text is the category titles. But still, it seems I need to ‘resort’ to PHP to deduplicate the results and construct a filter table, this time using category titles. At this point, I feel like I’m jumping through hoops to replicate txp category filtering functionality…

… so, in the end I went for #3 using the following extra query:

<txp:smd_query query='SELECT DISTINCT Category1
                        FROM textpattern WHERE Status IN (4)
                            AND section = "kalender"
                            AND custom_24 >= CURDATE()
                        ORDER BY Category1 ASC'
                    break=",">{Category1}</txp:smd_query>

using your DISTINCT tip (thank you!) which gives me a comma-separated list of category names of articles still in the calendar (custom_24 is the end_date). I can then use that for the categories attribute of txp:category_list to create a list of category filter links.

I learned something again today :-)


TXP Builders – finely-crafted code, design and txp

Offline

#9 2025-03-19 16:38:26

giz
Member
From: New Zealand
Registered: 2004-07-26
Posts: 342
Website GitHub Twitter

Re: How do I use if_different only above a certain number of matches?

Two css selectors :has() and :nth-child() can be combined to hide elements within a list according to the list’s size.

Something along the lines of:

.event-month {
  display: none;
}

div:has(> .event-list:nth-child(6)) {
  .event-month {
    display: block;
  }
}

Offline

#10 2025-03-19 21:18:58

etc
Developer
Registered: 2010-11-11
Posts: 5,342
Website GitHub

Re: How do I use if_different only above a certain number of matches?

jakob wrote #339330:

it seems I need to ‘resort’ to PHP to deduplicate the results

Not necessary, valueless replace removes duplicates (or is it a 4.9 thing?):

<txp:variable name="test" value="a,b,ab,b,a" breakby replace break="," output />
<!-- outputs 'a,b,ab' -->

But dom-parsing large chunks might be unnecessarily expensive.

giz wrote #339331:

Two css selectors :has() and :nth-child() can be combined to hide elements within a list according to the list’s size.

CSS powers fascinate me, but can it also alter the html markup?

Offline

#11 2025-03-19 21:29:47

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,851
Website GitHub

Re: How do I use if_different only above a certain number of matches?

giz wrote #339331:

Two css selectors :has() and :nth-child() can be combined to hide elements within a list according to the list’s size.

Clever idea. Now why hadn’t I thought of that?! Obviously overthinking things. Thanks!

etc wrote #339332:

Not necessary, valueless replace removes duplicates (or is it a 4.9 thing?):

<txp:variable name="test" value="a,b,ab,b,a" breakby replace break="," output />...

I’m on the most recent beta, so that’s okay, but I didn’t know about the valueless replace attribute. Another thing learned!

CSS powers fascinate me, but can it also alter the html markup?

No, it just hides the titles from view on all lists except those with more than 6 children.


TXP Builders – finely-crafted code, design and txp

Offline

#12 2025-03-20 16:21:28

giz
Member
From: New Zealand
Registered: 2004-07-26
Posts: 342
Website GitHub Twitter

Re: How do I use if_different only above a certain number of matches?

etc wrote #339332:

CSS powers fascinate me, but can it also alter the html markup?

It can in a limited way – but for the most part, sadly no.

Offline

Board footer

Powered by FluxBB