-
Notifications
You must be signed in to change notification settings - Fork 12
adstream.navigator API
The application map serves to parse URL hashes and match them to executable sequences of data access requests, template renderings and calls to user-defined functions. The map is global and should be constructed at initialization time: i.e. while the Javascript modules are loaded and initialized. Each module may add its own patterns and sequences to the map, the only requirement is that the patterns do not conflict with each other, i.e. no two patterns should match the same hash. Once the controller object has been initialized and started, the map can be modified no further.
The modules adding entries to the map should include the following dependency line:
dojo.require('adstream.navigator');
Note that adstream.navigator
itself does not directly depend on other libraries except Dojo core; modules using adstream.data
and template facilities should specify these dependencies explicitly.
adstream.navigator
.on( hash_matching_pattern )
/* Chained calls establishing the sequence */
.on( another_pattern )
/* ... */
Used to add a hash matching pattern to the application map; API calls that establish the executable sequence for this pattern are chained. You can also chain the patterns themselves: calling .on(pattern)
at the end of previous chain has the same effect as adstream.navigator.on(pattern)
.
adstream.navigator
expects the application to structure its URL hashes in a specific way, which should be sufficiently generic for most purposes yet convenient for the facilities provided by the library. In its most general form, the hash may consist of:
-
Path: a sequence of words separated by forward slash (
/
) characters. Words may contain any characters except forward slash (/
), colon (:
), question mark (?
) or ampersand (&
); it is also unwise to use the hash mark (#
) as part of the hash. Generally, the path should neither begin nor end with a forward slash. - Optional action: a colon (
:
) followed by a word. - Optional parameters: a question mark (
?
) followed by a list of parameters formatted in the same way as the URL query string, i.e. using ampersand (&
) as the delimiter and equality sign (=
) to separate parameter names from values.
Of these three parts, the parameters cannot be matched by a pattern: they are always parsed into a key-value dictionary. The patterns are strings that will be matched against the combination of path and action. Patterns may contain the following placeholders to specify variable parts of the hash that have to be extracted and passed to the rest of the chain:
- Key field placeholder: an asterisk (
*
) used in place of one of the path components. It will match any word; usually the key fields are IDs of items inadstream.data
containers. A list of words matched against all of the key fields in a pattern in the order in which they appear is called a key. It is convenient to prefix key fields with fixed path components: they will serve as names for these field values in a separately prepared dictionary. - Trailing placeholder: a pair of asterisks (
**
) used as the last path component; will match any path suffix, with or without forward slashes, including an empty string. Trailing placeholder does appear in the key as its last component. - List of acceptable actions: a colon (
:
) followed by one or more words separated with a comma (,
) and, optionally, terminated with a question mark (?
). The pattern will match only hashes containing those actions; if question mark is present at the end, it will also match hashes without an action. - Action placeholder: a colon followed by an asterisk (
:*
). The pattern will match hashes with any actions, or without an action specified.
Many of the API methods used in chains can accept string arguments with placeholders for key field values. All of these arguments are processed with the fillKeyFields()
method of the hash event object, described below. There are two types of placeholders in this substitution:
- An asterisk (
*
) is substituted with the next key field, in the order of appearance in the string. - A colon (
:
) followed by an identifier (a letter followed by a sequence of letters, digits or underscore_
characters) is substituted with the key field thus named. Note that dash (-
) is excluded from the list of accepted characters and can be used as a separator. By-name substitutions do not affect the matching between*
placeholders and key fields.
Note that template names in render()
have a substitution rule of their own and do not recognize the placeholders described above.
After being matched against a specific pattern in the map, the hash is parsed into an object that is passed as an argument into all of user-defined functions and templates executed from the chain. The object provides the following properties and methods:
Contains the action extracted from the hash, if any.
An array of string values matched against the placeholders in the order of their appearance in the pattern.
A dictionary where key fields are listed “by name”: path components immediately preceding the placeholders in the pattern are used as keys in this dictonary.
Parsed dictionary of query string parameters extracted from the hash (not from the URL iself: note that the latter may also contain a query string of its own).
var formatted = event.fillKey( format_string );
Substitutes key field values for placeholders in the string argument and returns the result. This method is used by other API methods indirectly to form adstream.data
paths, DOM element IDs etc. but may also be used directly by the application code for any purpose.
event.fillMetadata( container.view() );
or
event.fillMetadata( container.filter() );
Finds parameters matching the properties of view or filter metadata in an adstream.data
container and populates the appropriate slots. This method is used by get()
and getw()
methods indirectly but can be helpful to the user-supplied callbacks passed into these methods in lieu of the string arguments.
Although all map-building API calls may be chained one after the other, there is an implied nesting that determines how individual actions are sequenced. Essentially, the API calls at the same level are executed in parallel and their results are applicable to the immediately chained calls of the lower level; call at the same or higher level ends the sub-tree and begins a new one:
-
on()
(only one is matched at a time) -
get()
orgetw()
, optionally terminated by anotify()
where()
-
call()
,render()
orload()
(althoughget()
andgetw()
do not pass arguments intoload()
)
It is convenient to use structured tabulation to reflect the relationship between chained calls, e.g.:
adstream.navigator
.on('invoices/*')
.getw('invoices/*')
.render('invoices.View')
.on('invoices/*:edit')
.getw('invoices/*')
.where(isEditable)
.render('invoices.Form')
.where(isNotEditable)
.render('invoices.View')
.call(warnNonEditable)
;
.getw( optional_root, path_1 ... path_N )
or
.getw( optional_root, { name_1: path_1 ... name_N: path_N } )
Methods get()
and getw()
retrieve model objects from adstream.data
store and prepare parameters for user-defined functions or templates that are executed with call()
and render()
, respectively. The signatures of get()
and getw()
are identical, the difference is that get()
serves as a passthrough for the value returned by the get()
method of adstream.data
— which may be a promise — while getw()
resolves all promises before passing control to dependent callbacks.
First argument, optional_root
, should be an object returned by the adstream.data.connect() function. If the argument is omitted, the global adstream.navigator.config.schemaRoot
is used in its stead. The two alternative ways to specify paths result in two different types of arguments passed to callbacks: either an argument list with one adstream.data
object per argument (array of objects in case of render()
) or a single dictionary where adstream.data
objects are named properties.
In either case, individual path arguments could be either strings with substitution patterns or function references for computing the path indirectly. These functions receive a reference to the hash event object both as this
and as the first and only argument and are expected to perform the get()
operation on a suitable adstream.data
node and return its result.
.call( callback_function )
Installs a function that will be called upon hash pattern match and passed the adstream.data
objects prepared by the get()
or getw()
calls that precede the call()
invocation in the chain.
The callback_function
is also passed the hash event object: both as this
and as the last argument. The rest of the arguments are determined by the get()
or getw()
invocation that call()
was chained to; if it was chained directly to on()
, the function will receive string values from the hash that were matched to *
and/or **
placeholders in the pattern as its first N arguments.
The value returned by callback_function
is interpreted as follows:
- if it is a promise object, it will affect
notify()
action (if any) and the/adstream/navigator/changed
topic notification; - if it is an instance of
Error
(or ifcallback_function
throws an exception), the call has (synchronously) failed, also affectingnotify()
and/adstream/navigator/changed
; - otherwise the call is considered successful and the value disregarded.
.render( template_name, element_id, placement )
Loads template_name
using the CHT loader, evaluates it with parameters provided by the get()
or getw()
invocation that render()
call is chained to and renders the result using the render() method of the template instance according to the specified element_id
and placement
.
The template_name
argument is a string that may contain one substitution pattern of a specific form: :
OptionalPrefix *
. If present, this placeholder will be substituted with a combination of the OptionalPrefix with the action part of the hash; if the matched hash contained no action, the placeholder will be substituted with an empty string. Thus, .render('my.invoice:_*')
will render template my.invoice_edit
if the matched hash contained :edit
as the action and my.invoice
if no action was present.
Both element_id
and placement
are optional, but element_id
may not be omitted while placement
is specified as both arguments are strings and cannot be distinguished by their values. The acceptable values and defaults for the placement
are the same as for the last argument of dojo.place() (i.e. if placement
is omitted, the results of template expansion will replace the content of element_id
). The element_id
argument may contain substitution placeholders. If omitted, the library will search a global array of strings adstream.navigator.config.defaultViews
and the first string to correspond to an existing DOM element ID will be used as the target. Failing that, the library will use DOM element associated with the navigator controller object (typically document body) as the target.
The template evaluator is invoked with hash event object as its this
(i.e. the top-level scope) and its second argument (i.e. $1
). The first argument is either the same as the first argument of the callback function in call()
or an array containing the first N arguments that such a callback function would receive if N is greater than 1. This means that using get()
or getw()
with multiple path arguments would result in the corresponding objects passed into the template as an array whereas using a single path argument or a path dictionary passes the results into the template as is.
The result of render()
execution is the same as the value returned by the render() method of the template instance and it impacts any notify()
actions and the /adstream/navigator/changed
topic notification accordingly.
.load( url, element_id, placement )
Retrieves HTML content (via HTTP GET method and the XmlHTTPRequest
API) at the specified url
and then inserts it into DOM according to the specified element_id
and placement
.
The url
argument is either a string with substitution placeholders or a reference to a function returning such a string (the function will receive the hash event object as both this
and the only argument when called). Arguments element_id
and placement
are treated identically to those in the render()
method.
The execution result of a load()
is a promise value that resolves when the HTML fragment is successfully inserted; it impacts any notify()
actions and the /adstream/navigator/changed
topic notification accordingly.
.where( callback_function )
Installs a callback_function
that will be evaluated when the hash pattern has been matched and arguments are available and conditionally executes or bypasses any invocations of call()
, render()
or load()
chained to it. The callback_function
receives the same this
and arguments as the callback executed by call()
and must evaluate synchronously (i.e. should not return a promise). If it returns a “truthy” value, its dependent chain (i.e. everything up to the next where()
, notify()
, get()
, getw()
or on()
, whichever comes first) is executed; otherwise the dependent chain is skipped.
.notify( callback, errback )
Executes callback
after the dependent chain has successfully completed, or the optional errback
in case of any failures in the chain. The execution is functionally equivalent to:
dojo.when( result_of_dependent_chain, callback, errback )
and the result returned by callback
or errback
respectively becomes the result of the entire chain, which means that errback
, if provided, has a chance to handle a failure without propagating it to the navigator controller.
The callback
does not receive a useful argument; errback
receives the first Error
instance thrown by the dependent chain.
The dependent chain of notify()
consists of API method invocations preceding it (rather than following, as is the case for other methods), all the way to a get()
or getw()
inclusively, or to on()
. A .notify()
always ends the dependent chain of get()
or getw()
, so any methods coming after notify()
are effectively chained to its parent on()
.
The navigator controller is a singleton object (actually a dijit, i.e. a subclass of dijit._Widget) that should be associated with the top-level DOM element relevant to the application. In most cases this is the document body.
Creation of an adstream.navigator.Controller
instance may be programmatic:
(new adstream.navigator.Controller( {}, dojo.body() )).startup();
or declarative, using the Dojo parser and data-dojo-type
attribute. Note however that the controller interprets current hash as part of its startup()
method therefore the entire application map has to have been established by that time, i.e. all modules contributing to this map should have been loaded. Thus if the controller is associated with body element declaratively and auto-parsing is on, the main module must synchronously load (using dojo.require()
) all the other modules.
The controller subscribes to /dojo/hashchange
topic notification and operates automatically. It provides the following topic notifications of its own:
Published when the new hash has been matched and parsed, right before executing the chain. The only argument passed into the callback is the hash event object.
Published when chain has successfully completed execution, i.e. all promises returned by the chain elements have been resolved. If any of the promises fails, and is not handled by a notify()
, this notification is not published. The only argument passed into the callback is the hash event object.
Published when errors encountered during the execution of a chain caused it to fail. The only argument passed into the callback is the Error
instance that has initially triggered the failure.
If the controller encounters a hash that cannot be matched by any of the patterns in the map, it automatically redirects the browser to the configured default: adstream.navigator.config.defaultHash
. If this global is not set, the controller will ignore such a hash.
If a hash matches one of the patterns but cannot be parsed or executed upon — this typically indicates an error in the application map, such as on()
without a dependent chain — the controller will not publish /adstream/navigator/error
, or, indeed, do anything other than printing an error message in the console.
The global dictionary adstream.navigator.config
is used by the navigator components to obtain various default settings that can be customized by the user. At present, the following settings are recognized:
-
defaultHash
: a URL hash that the browser is forwarded to when controller encounters a hash that cannot be matched to any of the patterns in the map; -
defaultViews
: an array of element IDs to be tried byrender()
andload()
that do not specify the target element explicitly; for backward compatibility reasons it currently defaults to['app-view','app-main']
if not set by the user; -
schemaRoot
: a reference to the object returned by the adstream.data.connect() function that will be used as a reference point forget()
andgetw()
that do not specify the root object explicity.