arraysize: 0


Parse on demand

Many years ago I worked on a project for a MediaWiki-based website that attached a good deal of importance to the ability for the site to render content ‘on demand’, either reactively or with a minor delay. This refers to a mechanism by which pieces of additional content can be rendered and inserted on the fly, typically pending a particular interactive trigger, after the page has already loaded and importantly, without requiring the page to refresh. Whatever the bespoke solutions we came up with at the time, I feel that today there is much use for a generic solution that can be flexibly adopted to support a range of use cases, not untypically in conjunction with other extensions. Which is why I took the opportunity to write a new parser function.
Extension

Usage

The parser function #cr-parse-request uses the MediaWiki parse API to parse and serve wiki content. It is currently still part of the CODECSResources extension, but may a good candidate for being hived off into, or included in, a separate extension.

General

The syntax is relatively straightforward:

Example with comments
{{#cr-parse-request:<nowiki>{{Mytemplate
|page={{FULLPAGENAME}}
|offset={{{$offset|0}}}
}}</nowiki>
|trigger=either afterpageload (default), onscroll,  onclick, or onsubmit. See section below.
|targetaction=replace (default), append, or prepend. What to do with previous and newly generated content. 

|valsep=The separator for an array of values, such as values that come from form data. Defaults to a semi-colon.
|class=(Additional) HTML class. Optional.
|id=HTML id. Optional.

// Optional widgets
|paginationid=See below.
 |paginationclass=@todo
|loadmoreid=See below.
 |loadmoreclass=@todo

// Specific to onclick and onsubmit
|triggerid=The HTML id of the clickable element (onclick) or form (onsubmit) on the page.

// Specific to onsubmit
|updateurl=true (default) or false. Whether to update the URL's search parameters. Useful for bookmarking a particular query. When working with multiple forms on a page, you may want to switch it off.
}}
About the first parameter
  • The content to be parsed on demand is added to the first, nameless parameter.
  • Content must be placed between nowiki tags, with the exception of those parts that must be parsed or expanded before our parser function can act on them.
  • It supports the use of dynamic variables that look almost identical, apart from the dollar sign, to template variables, {{{$offset|0}}} in our example, as becomes clear when we get to it.
  • Line breaks are removed automatically to prevent MediaWiki from introducing unwanted paragraph markup.

Trigger options

Four options are available for use with "trigger":

  • afterpageload (default): parse directly after the page loads, typically as a way to speed up ‘first paint’. I prefer to reserve this for sections of the page that are not immediately visible anyway or at least, that are not too obtrusive. Jumpy, jittery effects are vexatious.
  • onscroll: parse as soon as the HTML element is scrolled into view.
  • onclick: allow the user to click an element to retrieve content. Could be used e.g. to fetch the answer to a quiz, or render a query with random results.
  • onsubmit: a more advanced option for working with forms.

afterpageload

{{#cr-parse-request:<nowiki>{{Foo|...}}</nowiki>
|trigger=afterpageload // can be omitted
}}
Example:
{{#cr-parse-request:<nowiki>{{#ask: [[Concept:All manuscripts]] [[Has repository::Id:Dublin, Trinity College]] [[Is published::Yes]] |order=random |limit=1 |searchlabel= }}</nowiki>
|trigger=afterpageload
}}
Result:

onscroll

Example:
{{#cr-parse-request:<nowiki>{{#ask: [[Concept:All texts]] [[Language::+]] |order=random |limit=1 |searchlabel= }}</nowiki>
|trigger=onscroll
}}
Result:

onclick

{{#cr-parse-request:<nowiki>...</nowiki>
|trigger=onscroll
|triggerid=click-to-parse
}}
{{#widget:Button|id=click-to-parse|text=Click me}}

Example:

Click to roll the dice:

onsubmit

This is where things get more interesting. See this page for an example.

Adding a form

This parser functions should be compatible with a variety of forms. So far I have tested with forms provided by the following extensions: Widgets (simple HTML form), Page Forms (embedded query form) and FlexForm.

Widgets

The extension comes with two navigational widgets, each with their own parser function, that can be used to update variables and refresh content:

  • pagination widget: clicking a tab replaces the content using a new offset.
  • load more widget: clicking the link or button appends content with a new offset at the bottom of the results.

pagination (#cr-pagination)

A pagination widget is used to navigate query results after splitting them into sequentially numbered 'pages'. Given how pagination works, it is to be used with targetaction=replace (default) in #cr-parse-request.

{{#cr-pagination:
|id=HTML id. Defaults to cr-pagination. This is currently required for #cr-parse-request.
|total=Total number of (query) results.
|limit=Maximum number of results to show per page.
|offset=Result offset, beg. with 0.
|max-pages=Maximum number of page navigation links to be shown in the widget. Can be omitted or set to "all" to render all possible page links.

// To be revised
|template=...
|offsetname=offset
|offsetparam=offset
}}

Features:

  • Contains three types of components:
    • previous/next page.
    • sequentially numbered links
    • first/last page
  • Pagination is omitted if there are fewer results than the given limit. There would be no point in having a single navigation link for the same results.
  • The page will automatically scroll up to the top of the results.
  • Etcetera. Just see for yourself.

At some point in the future, I intend to make some of these components configurable as opt-out features. For instance, the previous/next links may be all you require.

load more (#cr-load-more)

{{#cr-load-more:
|id=HTML id.
|total=Total number of results.
|limit-prev=Previous result limit used so the next offset can be calculated automatically. 
|limit-next=New result limit. Defaults to the value in "limit-prev".
|offset-prev=Previous offset. Can be set to {{{#offset|}}} to have it replaced dynamically.
|class=HTML class to style the button.
|text=Text to show, e.g. More results...
|trigger=either onclick (default) or onscroll
}}

This widget is currently designed with SMW and the outro template in mind. It supports two trigger modes by which more query results can be loaded:

  • onclick (default) - requires the user to click a button. With each new query result, a new button will be appended until there are no more results to show.
  • onscroll - new results are fetched when the user scrolls down the page and the widget comes into view, enabling what UI/UX designers often call ‘infinite scroll’. Personally, I am not particularly fond of this type of behaviour but since it is pervasive in interfaces today - think of social media feeds - I decided to add it in anyway. Of course, this feature makes sense only when you append results.
Example

Styling

Using the HTML class options, you are free to style the elements any way you like. A few classes are available for #cr-parse-request:

  • cr-parse-inline: simply intended to render new child elements that get appended with display:inline.
  • cr-fade-in: intended to load newly parsed content with a simple fade-in effect. See the example for "onclick" above.

Possible limitations

  • JavaScript code that comes with MediaWiki core and extensions is not always written to support deferred parsing. For instance, a JavaScript function may look for certain HTML elements to be present only when the page is loading.

Future

  • Rewrite in Vue? I wrote this in plain JavaScript, but these days MediaWiki ships with Vue v3 and the code could be greatly simplified using this JavaScript framework. There is an additional benefit to having Vue at my disposal, which is that it gives me access to MediaWiki's Codex design library.