HaXtatic Docs

hax.iterator

Outputs a sequence of values from the specified enumerator, applying the specified filtering/ordering/limits.

In a nutshell

  1. Declaration of an |X|hax.iterator:myTag: .. directive in a *.haxproj file,
    • further configuring: prefix, suffix, joinVia, content
  2. Usage anywhere: {X|myTag: .. |}

Hence, formatting and structure of the output is mostly defined in the X-tag's one-off declaration / configuration, while the per-tag invocation / parameters define what values to enumerate, which ones to show, in what order etc.

This topic is in a compact format for users already familiar with the essentials of declaring X-tags and invoking them. If found challenging, try the more-elaborate-and-introductory topics first to form a better grasp on the basics and overall mechanisms.

Stage:

Early or Page — this X-renderer determines dynamically (documented further below) whether or not it requires a page context for rendering, so depending on each hax.iterator-invoking X-tag's configuration and/or parameters:

  • some encountered during pre-templating (at start-up time) may be eagerly processed immediately in-place (for overall-reduced per-page processing loads),
  • and others will be skipped over, thus only process per-page, once present.

Declaration / Configuration

To demonstrate valid *.haxproj directives declaring hax.iterator X-tags:

|X|hax.iterator:myenum

|X|hax.iterator:myenumlist:
	prefix = "<ul class=\"my-blogs\"><li>",
	suffix = "</li></ul>",
	joinVia = "</li><li>",
	content =>
<a id="b_link_{:i:}" href="/{:v:}/index.html">	{B|title: {:v:} |}	</a>

To elaborate, for example the last one of the above, as all |X| directives declaring X-tags do:

  • begins with |X| followed by the X-renderer identifier (here hax.iterator)
  • followed by  : colon and the desired X-tag name to be used to invoke it (here myenumlist),
  • followed by  : colon and now hax.iterator-specific configuration:
  • a syntax-sensitive but optional properties block, comprised of all the following properties in this very order:
    1. prefix — (empty by default) the output to produce once immediately prior to enumeration
    2. suffix — (empty by default) the output to produce once immediately following enumeration
    3. joinVia — ( by default) the output to produce in-between all enumerated items
    4. content — the principal content output for each enumerated item
      • The placeholder  {:i:} outputs an iteration index: 0 for the first enumerated item and afterwards increments by 1 for each further item, regardless of its value or which ordering/filtering was active (ie. no gaps ever between successive such increments).
      • The placeholder  {:v:} outputs the current item in plain-text form as returned by the chosen enumerator.
      • The placeholder  {:n:} outputs the equivalent to  {:i:} + 1.
      • The placeholder  {:l:} outputs the total number ("length of list") of all items being currently enumerated.
      • If content is empty (the default), this is equivalent to it being  {:v:}.
      • If content does not contain any of these 2 placeholders, it will obviously be output repeatedly, identically, once per item.

Invocation / Parameters

For example, given the above example |X| declaration directive:
{X|myenumlist: BlokNames |} to output (displayed here with added line-wraps for readability)

<ul class="my-blogs">
<li><a id="b_link_
0" href="/basics/index.html"> Basics </a></li>
<li><a id="b_link_
1" href="/tags/index.html"> haXtags </a></li>
<li><a id="b_link_
2" href="/xtypes/index.html"> X-renderers </a></li>
</ul>

BlokNames is one of a handful of built-in enumerators — full list below. But for now, continuing this first exploration, to apply a sort order to the very same enumerator, prepend a so-called modifier: {X|myenumlist: But(Ordered Descending) BlokNames |}, this will instead output:

<ul class="my-blogs">
<li><a id="b_link_
0" href="/xtypes/index.html"> X-renderers </a></li>
<li><a id="b_link_
1" href="/tags/index.html"> haXtags </a></li>
<li><a id="b_link_
2" href="/basics/index.html"> Basics </a></li>
</ul>

Here, the But clause allows expressing the Ordered modifier, one of a handful of built-in such modifiers. Another is LimitTo for applying a limit {X|myenumlist: But(LimitTo 2) BlokNames |} to output:

<ul class="my-blogs">
<li><a id="b_link_
0" href="/basics/index.html"> Basics </a></li>
<li><a id="b_link_
1" href="/tags/index.html"> haXtags </a></li>
</ul>

Or applying that limit after sorting {X|myenumlist: But(LimitTo 2) (But(Ordered Descending) BlokNames )|} to output:

<ul class="my-blogs">
<li><a id="b_link_
0" href="/xtypes/index.html"> X-renderers </a></li>
<li><a id="b_link_
1" href="/tags/index.html"> haXtags </a></li>
</ul>

By now, this is starting to become unwieldy: multiple such nested Buts can be somewhat verbose and keeping track of correct parenthesis placement easily error-prone, plus their right-to-left flow (ie. above: first-sort-then-limit logic written as first the limit part, then the sort part) possibly counterintuitive for non-programmers. The (functionally equivalent But alternative, the) With clause avoids such nesting-via-parens and flips that right-to-left flow over. So the same output as above can be achieved with a much simpler notation: {X|myenumlist: With BlokNames [Ordered Descending , LimitTo 2] |} — ie. "with the enumerated values, but ordered this way: give us the first 2":

<ul class="my-blogs">
<li><a id="b_link_
0" href="/xtypes/index.html"> X-renderers </a></li>
<li><a id="b_link_
1" href="/tags/index.html"> haXtags </a></li>
</ul>

Enumerators

  • Range startnum endnum — a range of numbers,
    • eg. {X|myenum: Range 2 7 |} gives: 2, 3, 4, 5, 6, 7
  • Values [..] — a given list of text values,
    • eg. {X|myenum: Values ["Hudak", "Wadler", "Peyton-Jones", "Bird", "Okasaki"] |} gives: Hudak, Wadler, Peyton-Jones, Bird, Okasaki
  • BlokNames — names of all Bloks defined in the project,
    • eg. {X|myenum: BlokNames |} gives: basics, tags, xtypes
  • FeedNames blokstoo? — names of all "feeds" known in the project, including or excluding the above BlokNames
    • eg. {X|myenum: FeedNames True |} gives: xdesc, basics, tags, xtypes
    • eg. {X|myenum: FeedNames False |} gives: xdesc
  • FeedPosts and FeedValues — a bit more involved:

Feed enumerators

In addition to the simpler enumerators outlined above, FeedValues and FeedPosts enumerate items derived on the fly from the project's Blok pages (if any) and/or the project's "feed-posts" (if any). These 2 enumerators have quite a few commonalities:

  • A filter query and another 2nd parameter, the full forms being:
    • FeedPosts <filter> [] and
    • FeedValues <filter> "<fieldname>"
  • The first (the filter) part is either simply All (no filtering, anything goes), or the functionally exactly equivalent Some{ feeds=[], cats=[], dates=AnyDate } (also no filtering), or the same syntax-sensitive form but with some or all of these 3 properties set:
    • feeds — a list of "feed" names and/or Blok names to include when enumerating items (empty  [] means "all of them")
    • cats — the list of categories to include (only for "feed"posts, not Bloks; empty  [] means "all of them")
    • dates — either AnyDate to indicate "from any date" or Between "from" "up-until" with both text values of course encoding dates in the well-known, machine-friendly format "YYYY-MM-DD"
  • The second part gives:
    • for FeedPosts the list of custom field names to include (in addition to the standard ones) in the syntactic de-facto vars that will be output (explained further below)
    • for FeedValues the name of the "post" field (custom or standard) whose value should be output (at most once per iteration)

In the current version, a known issue: in a project with Blok pages, using in Blok pages either All or any Some that effectively selects any Bloks as feeds is "not currently supported" (ie. expect incorrect, inconsistent or missing results). All other pages are OK for such uses, however, as are all auto-generated Blok 'index' pages (if any). To be rectified in a future release — still, the functionality as-is is valuable enough for the already-working-today use cases.

FeedValues

In this site, {X|myenum: FeedValues All "cat" |} produces some peculiar outputs: demoSimplest, demoCfgArgs, hax.miniTag, hax.htmlImage, hax.htmlLink, hax.htmlLinks, hax.htmlAnchors, hax.xmlEscape, hax.dtFormat, hax.unMarkup, hax.noOp, hax.snippet, hax.iterator, hax.feedView, —that final comma baffles, for one— here's how and why:

  • All uses as input feeds essentially what the described-earlier FeedNames True enumerator outputs: xdesc, basics, tags, xtypes — in this site there are 3 Bloks defined and 1 "feed" (named xdesc, in the bottom ~80-90 lines of default.haxproj).
  • The latter's numerous individual "posts" in xdesc all have (perhaps somewhat-unusually-so) each a unique category set (all beginning with hax.* but that's simply due to the content semantics of this site: documenting HaXtatic) — Blok pages however don't have such categories and hence the final (seemingly superfluous) comma in the output above: from also outputting that empty value once.
  • The final implication then is that FeedValues in particular doesn't output duplicate values (only 1 faux-cat was output from a few dozen Blok "posts") — making FeedValues the appropriate choice for grouping for example actual posts together in individual (to be dynamically assembled from such unique-value outputs) sub-ordinate hax.iterators.

So much for All, now to demonstrate Some more examples:

  • {X|myenum: FeedValues Some{ feeds=[],cats=[],dates=AnyDate } "dt:year"|} — as mentioned, such a Some is identical to All, but here instead of a post's cat, its date's year → 1234, 2016
  • {X|myenum: FeedValues Some{ feeds=["xdesc"],cats=[],dates=AnyDate } "dt:year"|}1234
  • {X|myenum: FeedValues Some{ feeds=["xdesc"],cats=[],dates=AnyDate } "cat" |} — as we saw, xdesc has 11 "posts" but each has a uniquely distinct cat: → demoSimplest, demoCfgArgs, hax.miniTag, hax.htmlImage, hax.htmlLink, hax.htmlLinks, hax.htmlAnchors, hax.xmlEscape, hax.dtFormat, hax.unMarkup, hax.noOp, hax.snippet, hax.iterator, hax.feedView
  • {X|myenum: FeedValues Some{ feeds=["xdesc"],cats=[],dates=AnyDate } "link"|} — however, most of them share the same (empty, hence the below extra comma) link: → http://github.com/metaleap/haxtatic/blob/master/src/X/DemoSimplest.hs, http://github.com/metaleap/haxtatic/blob/master/src/X/DemoCfgArgs.hs,
  • {X|myenum: FeedValues Some{ feeds=["xdesc"],cats=[],dates=Between "1234-11-" "1234-12-31" } "dt"|} — filtering by date range: → 1234-12-15, 1234-11-15
  • {X|myenum: FeedValues Some{ feeds=["xdesc"],cats=[],dates=Between "1234-11-" "1234-12-31"} "cfgmore"|} — but both these results from that date-range filter share the same cfgmore (custom more field) value: → (no other settings)

FeedPosts

So the above outlines exhaustively how FeedValues operates, hoes does FeedPosts differ?

  • Whereas FeedValues enumerated unique single-field values for all "posts" selected,
  • with FeedPosts the entire (if passing the filter) post (all its field values) is output.
  • All selected "posts" from selected "feeds" are enumerated pre-sorted "newest/latest first" according to the post date field, unless an Ordered modifier overrides this order.

For this, clearly FeedPosts shouldn't just dictate some particular output formatting or other for such data records! Instead, all that it outputs for each item is a syntactic notation of vars to then be fed directly into an outer hax.snippet. So just a moderate grasp of snippets and how to populate their vars when invoking them via X-tags, plus the above joinVia/content directive properties (plus possibly the below WrapEachIn modifier) clears the path to infinitely versatile micro-content rendering.

To give an impression of this in practice, here's just the output produced when selecting: {X|myenum: FeedPosts Some{ feeds=["xdesc"], cats=["hax.snippet"], dates=AnyDate} ["cfgmore"] |} — ie. selecting from this site's xdesc "feed" just the "post" describing hax.snippet with not-only all standard fields (more below) but-also the custom more field cfgmore:

("cfgmore","<code>vars</code>, <code>content</code>"),("feed","xdesc"),("dt","1234-06-05"),("dt:year","1234"),("cat","hax.snippet"),("title",""),("link",""),("content","Renders the named \"snippet\" (aka. \"controls\" / \"components\" / \"sub-templates\") substituting the specified\r\n\tnamed-parameter values.")

A simple list of name-value-pair tuples — just without the enclosing  [] square brackets that are not to be forgotten when ensuring the wrapping of these per-item outputs inside what will typically (effectively have to) end up amounting to both {X|mysnippet: vars=[ and ], content=> |}.

"That sounds more complicated than necessary, wouldn't it be nicer if one could specify just-the-name of any snippet and FeedPosts then invoked it directly, instead of outputting syntax to then wrap other syntax around?" Certainly true, but this would lose the flexibility to include further additional vars (or even content=>, or future parameters) with the snippet's X-tag. Maybe such a shortcut will appear in a future release.

"Post" field names

In addition to the specified haxproj custom more fields, the following standard fields are always returned (even if empty) as de-facto vars by FeedPosts, and also all understood by FeedValues:

  • feed — the post's "feed" name or Blok name
  • dt — post date in the standard format YYYY-MM-DD
  • dt:year — post date's year only (eg. for grouping with FeedValues)
  • cat — for Blok "posts": empty — otherwise: as set for the post
  • title — for Blok "posts": detected from <h1> like {P|title|} — otherwise: as set for the post
  • link — for Blok "posts": relative URI path to the page — otherwise: as set for the post
  • content — for Blok "posts": content of the first <p> if any — otherwise: as set for the post

Modifiers

So enumerators are built-in routines that know how to iterate certain ranges, collections, lists, etc. Can their result sets be "tweaked"/mangled/sliced/diced/etc prior to the final output? This is achieved by adding to the 1 enumerator possible per hax.iterator X-tag any number of modifiers.

As explained above, any such modifiers can be expressed either one-per-But (which in turn can be nested), or stated as an ordered sequence of multiple such modifiers per With clause.

(Technically, one could also specify a complete But clause (in parens) as the enumerator in a With clause, or one could also specify a complete With clause (in parens) as the enumerator in a But clause, but practically there's no good reason one should want to.)

Syntax: using With (enumerator) [modifier 1st , modifier 2nd , .. , modifier last], always wrap the enumerator in parentheses and all modifiers together in 1 set of square brackets. Using But (only modifier) (enumerator-or-another-But), always wrap the 1 modifier in parentheses and then subsequently the enumerator too.

LimitTo <number>

Limits the number of items:

  • {X|myenum: But (LimitTo 2) (FeedNames True) |} produces xdesc, basics instead of xdesc, basics, tags, xtypes

Skip <number>

Skips a number of items:

  • {X|myenum: With (Range 12 3) [Skip 4] |} produces 8, 7, 6, 5, 4, 3 instead of 12, 11, 10, 9, 8, 7, 6, 5, 4, 3

WrapEachIn (<prefix> , <suffix>)

Encloses every item within a given prefix and suffix:

  • {X|myenum: But ( WrapEachIn ("/" , "/index.html") ) (BlokNames) |} produces /basics/index.html, /tags/index.html, /xtypes/index.html instead of basics, tags, xtypes

Ordered <sortorder>

Re-orders the items either Ascending or Descending or Shuffled:

  • {X|myenum: With (FeedNames True) [Ordered Descending] |} produces xtypes, xdesc, tags, basics instead of xdesc, basics, tags, xtypes
  • {X|myenum: But (Ordered Ascending) (Values ["zeta","phi","gamma","beta","alpha"]) |} produces alpha, beta, gamma, phi, zeta instead of zeta, phi, gamma, beta, alpha
  • {X|myenum: With (Range 1 23) [Ordered (Shuffle False) , LimitTo 6, WrapEachIn ("#",". ")] |} produces #23. , #22. , #21. , #20. , #18. , #17. instead of 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23
    • Only either (Shuffle True) or (Shuffle False) are valid. When used in a central project file such as a *.haxproj, a template or a snippet (rather than directly inside a content source file), Shuffle True shuffles differently for each output file while Shuffle False shuffles identically for all output files during this processing run (but still varying with each processing run).
  • {X|myenum: With (Values ["foo","bar"]) [Ordered None] |} "produces" foo, bar "instead of" foo, bar — ie. None does not touch the items at all: useless perhaps, except for example when quickly and temporarily wanting to disable reordering somewhere inside some But nesting.

As noted earlier, the FeedPosts enumerator already orders items returned (not alphabetically but by post date), and always does so: defaulting to descending absent any Ordered placed to the contrary. So Ordered Descending, Ordered None or no Ordered modifier whatsoever all result in identical outputs, while Ordered Ascending shows "oldest first" and of course a Shuffle just randomizes the sort order as described above.

Render stage

An X-tag of type hax.iterator defaults to Early but is delayed to Page stage whenever:

  • it contains an Ordered (Shuffle True) modifier (see above)
  • the project has Blok pages and the X-tag uses a FeedValues or FeedPosts enumerator (see above) selecting either All or Some that will effectively include any such Bloks via its specified feeds property.