-
Notifications
You must be signed in to change notification settings - Fork 12
Qplus syntax
Most of the expressive power behind CHT templates is provided by the dataflow architecture of jtlc and JXL. Q+ (Query Plus) is a simple textual representation of the abstract syntax trees that jtls works with, capable of encoding a restricted subset of all JXL programs. Q+ also slightly modifies the behavior of some of the JXL tags and adds certain new ones, as described below.
A Q+ expression is essentially a linearization of the abstract syntax tree, limited to no more than 2 children per node with the 2nd child (if present) required to be a leaf node. Thus each node may have two distinct arguments, arg p (predecessor) and arg o (own argument) which are mapped to the first and second argument of the corresponding JXL tag as follows:
- If arg o is present, it becomes the first argument and then, if arg p is present, it becomes the second one;
- Otherwise, arg p is the first argument (if present);
- In the leftmost stage of the pipeline, it is also possible to supply multiple arguments to the tag in place of arg o by enclosing them in square brackets as a comma-separated list.
The actual syntax of a Q+ query is a sequence of tagged stages separated with a vertical bar |
character:
FirstStage [
|
TagName [:
Argument ] … ]
where FirstStage follows a different rule from the subsequent stages, being either an Argument by itself or:
TagName
:
{[
Argument [,
… ]]
| Argument }
TagName determines the type of the AST node formed from each stage. The interpretation of Argument depends on the tag in question. Generally, the tags that don’t associate any special treatment with their first argument would interpret Argument as an expression, the way the expr:
tag does (new: there is an exception for lambda arguments). If however the argument is in a generator context — for example, when the entire query is the argument of a CHT <? foreach ?>
element — the interpretation changes to that of a query (i.e. equivalent to query:
Argument).
Spaces in Q+ queries are ignored, unless they happen to be in a string literal where they would matter. Note that arg o should not be explicitly quoted when it is treated by the tag as a string literal (e.g. in an argument of i18n:
). Any balanced quotes (single or double) within arg o are preserved: this is a way to embed Javascript string literals into expr:
expressions. To embed an unbalanced quote character, an unquoted vertical bar |
character or a backslash \
within arg o, escape it with a \
. Note also that vertical bar characters contained within balanced brackets of any kind allowed in Javascript expressions (round, square or curly) are not interpreted as delimiters in the sense of Q+ so they do not have to be escaped. Of course the rules regarding escape sequences within Javascript string literals still apply when those literals appear in Q+.
Q+ Query | JXL equivalent |
$.firstName |
expr("$.firstName") when in singleton mode, or query("$.firstName") when a generator |
expr:firstName |
expr("firstName") |
from:ids¦expr:$1[$].data¦defined |
defined( expr( "$1[$].data", from( expr( "ids" ) ) ) ) |
iota:[$.length,-1,0] |
iota( "$.length", -1, 0 ) |
Prefixing an argument that would otherwise be interpreted as an expression with an @
character, changes its interpretation from expr:
Argument to lambda:
Argument. This is a provision for passing lambdas into externally defined filters such as form validators. Substitutions inside lambdas are performed in a similar way to those in expressions, except that $
is interpreted as the actual parameter to be passed into the lambda function instead of current input of the pipeline stage where the lambda was defined.
Syntactically, every JXL tag can be accessed from Q+. In practice some of the tags cannot be used because Q+ lacks the means to supply them with proper arguments or place them in proper context. Q+ also defines tags of its own that are not accessible from JXL and yet more tags are added by the CHT (but only to Q+ queries embedded within CHT templates). Finally, Q+ provides an extension mechanism that lets the user inject tags of his own called custom filters).
Tag name | Notes on usage in Q+ |
acc |
its use pattern cannot be represented in Q+ |
arg |
unnecessary due to different interpretation of $1 through $9 (see expr below) |
bind |
cannot be used since there’s no way to embed a function into Q+. Use filters or global functions instead |
current |
use $ instead |
defined |
can be used |
each |
very limited in capability but can be used, for example, to nest queries — query: OuterQuery ¦each: InnerQuery |
extend |
can be used in slots of <?scope?> element |
expr |
interprets $1 through $9 as referring to arguments of the compiled template (equivalent of arg() in JXL) unless they were supplied to the first stage using the alternative syntax with square brackets |
from |
can be used |
group |
cannot be used in Q+. Use the CHT <?group?> element instead |
iota |
makes sense only as the first stage where it requires the alternative syntax with square brackets in all cases but the simplest |
keys |
can be used |
lambda |
can be used as the first stage, in most cases @ -prefixed expressions are the preferred alternative |
last |
can be used |
many and one
|
technically can be used, but the application is hard to imagine |
query |
can be used; caveat about $1 through $9 applies here as well |
quote |
useless |
replace |
can be used |
scope |
cannot be used in Q+. Use the CHT <?scope?> element instead |
setkey |
can be used |
Convert their first and only argument (usually arg p) to a string value in lower or upper case respectively.
An equivalent of the JXL array sink: evaluates its arg o in an iterative context and returns the resulting array.
Available only in queries contained within CHT templates
<div class="myClass" {{attributes:$.extraAttributes}}>
Formats its argument (assumed to be a dictionary) as a list of HTML attributes by enumerating its properties; property names should be valid attribute names from the HTML standpoint, the values are converted to strings and properly escaped. If the value is undefined, null
or false
, the attribute is not rendered (use this to selectively mask attributes from nested scopes). If the value is equal to true
, only the name of the attribute is rendered (use this for such attributes as checked
).
This tag does not need to be followed by |raw
if it is the last (or only) stage of a Q+ pipeline.
Note that attributes:
collects properties from the Javascript object passed in as the argument as well as from its prototype chain!
An equivalent of the JXL dictionary sink in combination with many()
: from:$|setkey:newKey|dict
translates into:
{ _: many(
setkey(
expr( "$.newKey" ),
from( current() )
)
) }
Allows inserting a sequence of statements with side effects into a Q+ query; does not alter the current input or return any value when used alone:
do:{ console.log( "We are here..." ); }
Note that the above example will not inject any text into the template. It is possible to inject multiple statements as part of the own argument of do:
and declare local variables within it (the fragment is not injected into the evaluator directly but is enclosed in a nested function). When do:
appears in a longer sequence of Q+ tags, it acts as a passthrough for the value of $
.
All substitutions provided for the expr:
tag are also performed within the body of do:
.
Available only in queries contained within CHT templates
Apply the appropriate HTML escaping to their first and only argument (usually arg p). Note that CHT parser inserts these tags automatically when the expression appears in the context of HTML text content or HTML attribute value respectively, so their explicit use by the user is rare. An example where it is indeed appropriate is when the resulting content has to be further modified by the user-supplied code:
{{$|escapeText|expr:$.replace('\n','<br>')|raw}}
will insert HTML content generated from current input while preserving the line breaks.
Available only in queries contained within CHT templates
Treats its first argument (typically arg o) as a string literal and maps it against the translation dictionary at compile time; use to insert localized strings into HTML arguments:
<img src="myimage.png" alt="{{i18n:My image}}">
The i18n:
can also substitute its arg p into the resulting string with dojo.replace() syntax:
<a href="/users/{{$.userName}}.html" title="{{$.userName|i18n:Go to {0}\'s home page}}">
Available only in queries contained within CHT templates
Prevents CHT from automatically inserting escapeText:
or escapeAttribute:
; should be the last stage of the pipeline to take effect.
Available only in queries contained within CHT templates
Inserts a Javascript expression that will evaluate to the value of the argument at the time toParsedDom() executes. This is the mechanism used to connect codebehind objects to the data objects during template processing:
dojo.declare( 'myWidget', null, {
constructor: function( bag, elt ) {
dojo.mixin( this, bag );
dojo.connect( elt, 'onclick', this, this.onclick );
},
data: null,
onclick: function() {
// Do something with this.data
}
} );
<? template useMyWidget ?>
<a dojoType='myWidget' data='{{ref:$}}'>Use myWidget!</a>
<? /template ?>
The ref:
tag can also be used to inject references into scripts embedded into the template. Note, however, that once toParsedDom()
has completed, the variables referred to by ref:
expressions go out of scope. Therefore the only legitimate use for ref:
injection into scripts is in startup scripts executed by dojo.parser.parse() after the codebehind object has been instantiated:
<? template AuthorSelect ?>
<select dojoType='dijit.form.ComboBox'
onchange='this.newBookPane.onAuthorChanged( this, this.indexInAuthorsList )'>
<script type="dojo/method">
this.newBookPane={{ref:$1}};
this.indexInAuthorsList = {{$1.authors.length}};
</script>
<option selected></option>
<? foreach "from:$" ?>
<option>{{firstName}} {{lastName}}</option>
<? /foreach ?>
</select>
<? /template ?>
In the example above the startup script is used to inject additional properties into the widget for subsequent use by the inline event handler. References could not have been injected into the handler itself because by the time it executes the ref:
expressions would be out of scope.
The first argument (in most cases, arg o) has to be a slot-yielding expression, which is to say either Target .
Name or Target [
Expr ]
Note that the expression must syntactically conform to one of these two patterns (parentheses around it are allowed), not just evaluate to something that is effectively a property within some Javascript dictionary. The slot:
tag rewrites this expression as
bindSlot("
Name",
Target)
or
bindSlot(
Expr,
Target)
respectively. The bindSlot()
function may be passed into the constructor of Q+ (and, by extension, CHT) object as one of configuration options; the default implementation creates a signature-overloaded function that returns current value of the property when called with no arguments and sets and returns the new property value when called with one argument:
function bindSlot( propname, object ) {
return function(v) {
if( arguments.length < 1 ) return object[propname];
else return object[propname] = v;
}
}
Available only within queries passed as arguments into the CHT element <? when ?>
<? when "a.get()|wait|expr:$.user" ?>
{{name}}
<? /when ?>
Used to explicitly specify the point in the pipeline where a promise has to be resolved to a value. In the example above, a.get()
is expected to return a promise resolving to an object that contains a property named user
which is to serve as current input for the body of <? when ?>
. It is possible to chain multiple promise resolutions in one <? when ?>
by using wait:
multiple times.
If the argument of <? when ?>
does not contain an explicit wait:
, one is added at the end implicitly, resulting in the default behavior described in the reference.
Whenever a tag name encountered in a Q+ expression does not match one of the built-in tags or filters, it is looked up in the filters
dictionary supplied to the dojox.jtlc.qplus constructor. Two kinds of custom filters are supported: functions and expressions:
If the supplied custom filter is a function, it translates into an instance of bind
with arg p (if present) as the first argument. Note that this behavior is the opposite of the built-in tags which may be confusing, but conforms to the JXL convention of evaluating the first argument of a function as a generator in iterative contexts. For example,
<? foreach "from:verbs|translateTo:'French'" ?>
<? if $# ?>, <? /if ?>
<code>$</code>
<? /foreach ?>
will generate a list of French translations from the array $.verbs
, provided that translateTo(verb,language)
does indeed work! Note that 'French'
is quoted: otherwise it would have been evaluated as an expression $.French
.
If the supplied custom filter is a string, it translates into an instance of expr
with the filter text as its first argument, arg p (if present) as the second and arg o as the third if it is present together with arg p, or as the second otherwise. Overall, the result is very similar to using a function filter except the expression is injected into the body of the compiled template inline.
The filter expression may refer to the arguments of the compiled template via $1
through $9
unless it has been invoked with both arg p and arg o, in which case arg o becomes $1
and $2
through $9
still refer to template’s aruments.
If the match of a tag name against filters
dictionary fails, Q+ attempts to find a global function with this name (using dojo.getObject()) as a last resort. It will also do that for qualified tag identifiers (i.e. two or more names separated with a dot) which are never matched against built-in tags or custom filters. If successful, the tag will work as a function filter, that is, it’ll bind the global function to arg p and/or arg o.
A twist on the rule above is available for a simpler access to methods of global object classes such as Date
:
from:timestamps|Date.toLocaleTimeString
is an effective equivalent of a longer and less readable
from:timestamps|expr:new Date($).toLocaleTimeString()
This construct can also be used with user-defined classes and methods but note that the resulting expression always instantiates a new object using the corresponding class constructor without checking whether its argument may already have the right type!