Understanding dap

All you need to use dap in your document is a reference to dap runtime script: <script src="https://dap.js.org/0.5.js"></script>. You can start your dap experiments with an .html document like this:

<!DOCTYPE html>
<html>
 <head>
  <title>My dap playground</title>
  <script src="https://dap.js.org/0.5.js"></script>
 </head>
 <body>
  <h2>Welcome to dap</h2>
  <script>
  
   // My dap code here
   
   .RENDER() // this method renders the dap template
   
  </script>
 </body>
</html>

Save it on your Desktop (or anywhere else), then open in a web browser. Now you can simply copy-and-paste examples into this file, and refresh browser to see the examples working. Or, you can play with the code right here — all code samples are editable. Results of each code sample are shown thereunder.

Templates

Dap program is essentially a tree of templates for generating actual elements into DOM. The tree defines the logical structure of your application, while templates define generation and interaction rules for each element in that structure. Every template consists of an element signature and one or more rules for generated element.

'BUTTON.allcaps title="click me!"' // element signature specifying tag, css class and attribute
	.d("! `Curious") // d-rule, element generation rule that builds element's content
	.ui(":alert`Clicked!") // u-rule, event reaction rule

Signatures

Signatures designate generic properties for generated elements. In HTML, these generic properties are: tag name, css class, id and all other HTML attributes.

The simplest and the most common signature consists of a single lower-case word. Such signatures designate a default element, which is DIV, assigned a CSS class. For example: 'sidebar' will render into DOM as <DIV class="sidebar"> element. Multiple classes can be combined using a dot, in a CSS-like manner: 'message.critical' will render <DIV class="message critical">.

When a specific element is needed, its tag name is specified in upper case, e.g. 'SECTION.text.small' will render <SECTION class="text small">.

Element id can be specified with a # (also, in a CSS-like manner), e.g. 'IMG.cropped#Portrait' will render <IMG class="cropped" id="Portrait">.

Any other desired attributes can also be specified in the signature, just as in HTML. Examples of valid signatures are: 'A href=about.html', 'description contenteditable', 'OPTION disabled selected value="Please select"', 'FORM#pay method=post action="http://mysite.com/pay.php"', etc.

Rules

Rules are the most essential part of dap. Generation and interaction rules define the element's behavior on different phases of its life cycle:

Rules are assigned to a template using a "method" notation: d-rule is assigned by .d() method, a-rule by .a() method, etc.

Dap rule syntax

Dap rule syntax is simple, but very different from familiar C-like syntaxes. It has been developed to be easily parsable, concise and human-readable.

Rule steps

Dap rule is basically a sequence of steps, each step being an operator followed by arguments. Whitespaces separate arguments from the operator and from each other. Steps are separated by ; a semicolon folowed by whitespace.

'A'.d("! `Homepage; !! @target`_blank @href`http://dap.js.org/") // <A target="_blank" href="http://dap.js.org/">Homepage</A>

Here, the <A> element's generation rule consists of two steps:

  1. A ! (print) operator with a string literal Homepage as its only argument,
  2. A !! (set attribute) operator with two named arguments, for href and target attributes.

Identifiers (names for operators, variables and all other lexical elements) may consist of any combination of any characters except whitespace, brackets and reserved symbols .,;:$@=. All other characters are allowed. For example, ! and ?? are valid identifiers in dap.

Why use exclamation signs instead of good'ole print-like keywords? Well, dap "core" operators (as well as flatterners and convertors, discussed later) tend to be expressed by symbols, not verbal English words. First of all, it is shorter — just one or two characters. Secondly, it is well distinct from verbal identifiers for data or other, more specific, stuff. And, obviously, it is language-neutral: you can pronounce it however you like, and it will look equally well with lexems of any natural language you choose for identifiers in your dap program: English, Russian, Chinese, whatever.

Arguments are fed to the operator one by one, and operator consumes them sequentially. In the example above, the !! operator is first executed with @href`http://dap.js.org/ argument (named "href", valued "http://dap.js.org/"), and then with @target`_blank argument.

Operators may change the rule execution flow. For example, ? operator terminates execution of the rule, if none of its aguments is truthy; while * executes the rest of the rule multiple times.

Literals and constants

Literal values in dap rules are prefixed with a ` (backtick) which can be found on your keyboard somewhere around Esc key. Literal values are not very welcome in dap rules and are usually used for numbers or other short codes. They cannot contain spaces, because spaces are delimeters between tokens within a rule. Textual content, as well as constant values of any other kind should be defined in the .DICT section (the dictionary) and referred to as constants, good in the example below. Keeping all constants grouped in one place helps make the code cleaner and maintainance easier. Dap is rather encouraging to that coding style.

'B'.d("! good `literals_in_rules_are_not_good_for_long_texts") // simply a <B> element, which usually renders in bold font
.DICT({ // this is the dictionary
	good:"The right place for text and other constants is the dictionary, "
})

Exclamation sign ! in the beginning of the rule is a "print" operator, it prints its arguments into the element. Arguments (or tokens) in dap rules are separated by whitespace.

Execution of dap program

Contexts

Every generated element is bound to two independent local contexts: status context and data context. Element's dap rules are executed within these two contexts. Constants defined in a dictionary are visible globally within a dap program.

Status context and status variables

Status variables provide the main reactivity mechanism in dap. Status variables are defined in d-rules and can be modified in u-rules in response to some events. When a status variable is modified, all elements that depend on it, are automatically updated. Defined in element's status context, a variable is visible to that element and to all its descendants. In other words, an element's status context covers variables defined in its own d-rule and in d-rules of its ancestors. In dap rules, status variables are prefixed with $.

'cheese'.d("$value=" // d-rule defines a status variable named $value
	,'BUTTON'.d("! cheese").ui("$value=`cheeeeese!") // u-rules update the variable `
	,'INPUT placeholder="or type in here"'.d("!! $value").ui("$value=#:value")
	,'message'.d("! $value") // d-rule displays the variable
)
.DICT({
	cheese: "Say cheese!"
})

In this example, the $value variable is defined for the DIV class='cheese' element and is thus accessible to all its descendants. The BUTTON and INPUT elements can modify $value according to their ui-rules, executed on their default ui events (click for BUTTON, change for INPUT): BUTTON sets it to "cheeeeese!", INPUT sets it to the the user input. On the other hand, the INPUT and the DIV class='message' elements refer to $value in their d-rules, that is: they are generated using this variable's value, they depend on it, and thus they will re-build every time $value is updated.

Here, you might have noticed that BUTTON and DIV.message templates use ! operator to output their content (cheese constant and $value status variable, respectively), while INPUT template uses !! operator. The difference between the two operators is that the latter, !!, sets element's own properties instead of generating child elements. Here, it sets the INPUT element's value attribute.

Data entries

Every generated element is bound to some data context, usualy it is a data row in some dataset. Element's data context is independent from its status context and can be shared among several relevant elements, that belong to the same data row. Fields of a data row, or data entries, can be read and modified in both u- and d-rules. Data entries are not tracked for changes and do not cause updates to other elements. In rules, data entries are prefixed with a . (dot).

'colored-fruits'.d("$color= $fruit=" // $color and $fruit variables are common to all descendants hereof
	,'UL.labelled'
		.a("!! (hint.fruit $fruit.name)concat@label") // this UL's label attribute displays selected $fruit
		.d("* color" // for each data row in color dataset (which is defined in the .DICT section)
			,'LI'	.d("! (.color $fruit.name)sentence") // print current .color datum combined with common $fruit status
				.ui("$color=.color") // on user event, update $color status with current .color
		)
	,'UL.labelled'
		.a("!! (hint.color $color)concat@label") // this list's label shows selected color
		.d("*@ fruit" // for each data row in fruit dataset, preserving field names
			,'LI'	.d("!! ($color .name)sentence .desc@title") // add text and set title attribute
				.ui("$fruit=$") // on user event, $fruit is assigned the entire current datarow
		)
	,'BUTTON'.d("! hint.reset.color").ui("$color=") // this is how a "nothing" looks like in dap
	,'BUTTON'.d("! hint.reset.fruit").ui("$fruit=")
	,'message'.d("? ($color $fruit)!; ! (hint.choice $color $fruit.name $fruit.desc:brackets)sentence:uppercase,glitzy")
)
.DICT({
	color	:["red","orange","yellow","green"], // datasets can be simple arrays of strings...
	fruit	:[ // ...or can be arrays of anything else
			{name:"apple", desc:"juicy fruit, usually round, contains iron"},
			{name:"pear", desc:"sweet fruit in shape of a... well... pear"},
			{name:"banana", desc:"long, bow-shaped fruit with soft thick skin"}
		],
	
	hint	:{ // constants in the dictionary can be grouped...
		color	:"Color: ", 
		fruit	:"Fruit: ",
		reset	:{ // ...to arbitrary depth
			color: "Reset color",
			fruit: "Reset fruit"
		},
		choice	:"Your choice is"
	}
})
.FUNC({
	flatten	:{ // combine several values into one single value
		sentence: values=>values.reverse().join(" ") // flatteners see arguments in reverse order
	},
	convert	:{ // convert one single value into another single value
		uppercase: str=>str.toUpperCase(), // capitalize the string...
		brackets : str=>"("+str+")", // ...add brackets...
		glitzy: str=>"*** "+str+" ***" // ...and surround it with stars! wow!
	}
})

How comes that a label attribute's value is rendered above the UL.labelled elements? Well, this is a simple CSS-trick (see the rules for .labelled class in styles.css), that legally allows to make dap code even more concise and well-structured by reducing the count of required template elements.

Here, all elements inside the 'colored-fruits' share the same status context with $color and $fruit status variables defined in it. However, each LI element in the two lists has its own data context, containing one data entry, either .color or .fruit

Another interesting point of this example is the .FUNC section (functionality) and the d-rule of 'message' element. Meet: flatteners and convertors...

Flatteners and convertors

For data transformations, dap provides mechanism of flatteners and covertors. These are ordinary Javascript functions — but with imposed uniform contracts:

Convertors are value=>value functions, that is, they take a single value and return a single value. This contract allows to chain several convertors to be sequentially applied to a value, like value:converter1,converter2,converterN which is roughly equiuvalent to converterN(converter2(converter1(value))) in Javascript notation. In the example above, the conversion chain :uppercase,glitzy first converts the input to upper case, then wraps it into the glitzy asterisks, as specified in the .FUNC section.

Flatteners are ([values],[names])=>value functions, or [values]=>value if names are not used. These functions allow to "flatten" arbitrary number or arguments into a single value. In the example above, the ()sentence flattener builds up a sentence from several wordings, by simply stitching them with a space character between. This sentence (a single string value) is then fed to the convertors.

Another flattener featuring in 'message' d-rule is dap's standard ()! flattener, that tests if ALL of its arguments are truthy. Its counterpart, ()? flattener, that you will see in the example below, tests if ANY of its arguments is truthy.

Named arguments

Having argument names provided along with values gives some extra possibilities to the flatteners. For example, parameter strings (parameterized URLs being typical example) can be built easily.

'stocks'.d("$code= $warehouse="
	,'INPUT placeholder="Item code"'.ui("$code=#:value")
	,'INPUT placeholder="Warehouse"'.ui("$warehouse=#:value")
	,'message'.d("? ($code $warehouse)?; ! hint (base@ $code $warehouse)uri")
)
.DICT({
	hint: "Constructed URI: ",
	base: "https://mygoods.com/getstocks.php?"
})

So, what new can we see here? A # special entry that evaluates to "this element" at run time, a standard :value converter that "converts" an element to its "value" (which is slightly different for various types of elements), and standard ()uri flattener that "flattens" its arguments into a single urlencoded query string using their values and names.

Names of arguments are normally taken from their identifiers. For $foo variable, or .foo data entry, or foo constant, the name will be "foo". If argument is dot-routed, the name will be the tail part of its route: for $customer.details.address the name will be simply "address". The argument's default name can be altered with the @ aliaser. For example, $customer.details.address@ship-to will be named "ship-to".

Arguments can have empty names. By default, literals and flattened results have empty names, as they're not referred to by any indetifier. But they can be named explicitly, too: @city`Moscow, or (.user.nickname .user.email .user.phone-number)?@login.

On the other hand, arguments named by default can be made "nameless", applying an empty aliaser to it. In the example above, $code and $warehous arguments of ()uri flattener are named, and ()uri treats them as URI parameters, that have to appear in the resulting URI string formatted as "&name=value", while base@ argument is nameless, so ()uri does not treat it as URI parameter, but inserts its value as it is.

Working with external data

Querying for external data is very simple with a standard :query converter that "converts" an URL into result of a HTTP request to that URL:

'heroes'.d("$id= $name="
	,'UL'.d("* (heroes@)uri:query@" // query data from the URL, parse it and populate a dataset from it  
		,'LI.hero'
			.d("!! .name@ .id@title")
			.ui("$id=.id $name=.name")
	)
	,'message'.d("? $id; ! (message $id $name)format")
)
.DICT({
	heroes:"/samples/heroes.json",
	message:"Hero id: {id}\n Hero name: {name}"
})

Here, we get our data from a static file heroes.json which contains some super-hero data in a JSON array. With the :query convertor, this file is asynchronously requested, parsed (based on its content-type header) and fed to * operator, that populates <LI class="hero"> elements for each row in that rowset.

If you click a hero's name in the list, status variables $id and $name are updated with the hero's respective data, and the summary message updates to reflect these changes.

The heroes list is taken from the Tour of Heroes example inspired by the Angular tutorial.

Making parameterized queries to external sources

Now that we can work with datasets, construct parameterized URIs, and query external data, let's combine it all together, and build a car picker for a car parts shop.

Querying logic in the example below is straight-forward: first, user selects production year and maker from predefined lists (years array is generated immediately in the DICT, and makes are taken from an external JSON file); then, for these year and make, a model list is queried from an external data source. Then, for selected model, a list of engines and corresponding car-codes is queried. Here, we use https://dapmx.org/stuff/rockauto.php endpoint, which is essentially an adapter to www.rockauto.com's database, accepting HTTP GET queries and returning results in JSON format.

'carshop'.d("$carname= $make= $year= $model= $engine="
	,'car-picker'.d(""
		,'SPAN'.d(""
			,'SELECT.year' // years are taken immediately from DICT
				.d("Hint(label.year@label); * year; Option(.year@value)")
				.ui("$year=#:value")
			,'SELECT.make' // makes are taken from a static file
				.d("Hint(label.make@label); * (makes@)uri:query@make; Option(.make@value)")
				.ui("$make=#:value")
		).u("$model= $engine=") // reset $model and $engine, when make or year were changed 
		,'SELECT.model' // once both $make and $year are set, related models are queried
			.d("? ($make $year)!; Hint(label.model@label); * (api@ $make $year)uri:query@model; Option(.model@value)")
			.ui("$model=#:value $engine=")
		,'SELECT.engine' //once $model is set, carcodes are queried parametrically for given $make, $year and $model
			.d("? $model; Hint(label.engine@label); * (api@ $make $year $model)uri:query; Option(.engine@label (`, .engine .carcode)join@value)")
			.ui("$engine=#:value")
	).u("$carname=( $engine ( $year $make $model $engine )spaced )!") // $carname will be empty until $engine is set
	,'H3'.d("! ($carname label.hint)?") // print $carname if set, otherwise a hint
	,'A target=_blank'.d("? $carname; !! label.redir@ (`https://www.rockauto.com/en/catalog/ (`, $make $year $model $engine)join)uri:space2plus@href")
)
.DICT({
	api	: "https://dapmx-org.1gb.ru/stuff/rockauto.php?", // dynamic data is taken from Rockauto.com
	year	: Array.from({length:20},(v,i)=>2019-i), // back to 20 years from year 2019
	makes	: "/samples/carmakes.json", // car makes are collected in a static file
	label	:{
		hint	: "Select a car, please",
		please	: "Please select",
		make	: "Car make",
		year	: "Production year",
		model	: "Model name",
		engine	: "Engine",
		redir	: "Search parts for this car at Rockauto.com"
	},
	// below are a couple of reusable parameterized templates
	Hint	: 'OPTION selected disabled'.d("! .label"), // a placeholder hack for a SELECT
	Option	: 'OPTION'.d("!! .value (.label .value)?") // set .value as attribute and display .label or .value 
})
.FUNC({
	convert:{
		space2plus: url=>url.replace(/ /g,'+').replace(/%20/g,'+')
	}
})

The new things in the example above are the reusable templates Hint(.label) and Option(.label .value) which are used in the several SELECT elements to display initial hints and available options.

See a complete car parts shop demo here. The whole thing is roughly 200 lines of code, 3.5 kiB gzipped.

Parameterized reusable templates

Parameterized reusable templates to dap are essentially what functions are to Javascript. It is a means of code clarification by extracting repeated pieces of code into reusable entities, optionally with parameters. In dap, templates can be defined in the .DICT section, as any other data. Templates can be either invoked using bracket notation, or inlined — "printed" as any other constant from the dictionary.

If invoked with brackets, template parameters become its data context, descendant to the caller's data context. If inlined, template is executed in the caller's data context.

A multi-level list of recursive structure is a good use case for reusable templates. Here is an example table of contents (for RFC7540, HTTP/2 specs), built from an external file.

import untab from "/stuff/untab.dap.js";

'contents-sample'.d("List( URL:query,decode@items )") // root invokes List() for the top-level items as a parameter
.DICT({
	URL	:"/samples/rfc7540contents.txt",
	
	List	:'UL.contents'.d("* .items@; ! Item"), // List inlines Item for each row in a dataset
	Item	:'LI'.d("! Sref .heading Pref (.items List)!"), // Item inlines hyperlink templates and List for sub-items, if present
	Sref	:'A.section target=rfc'	.d("!! .section@ ( href `section- .section )concat@href"),
	Pref	:'A.page target=rfc'	.d("!! .page@ ( href `page- .page )concat@href"),
	
	href	:"https://tools.ietf.org/html/rfc7540#"
})
.FUNC(untab)

Here, the URL provides textual data in a custom indented table format, which is not JSON. Thus, the :query converter does not parse it automatically, but only performs a HTTP request and returns the response text "as is". To have it converted to a dataset, we use a decode converter from an external dap file, untab.dap.js, that converts indented text to a dataset object. Of course, we could simply have used an equivalent JSON file instead, and wouldn't need an extra decoder then... But the example above takes us to namespaces.

Namespaces

Namespaces allow to reuse dictionaries and functionalities from other dap files. Somewhat similar to namespaces in Java or C#, or modules in Javascript, but simpler. To gain access to the dictionary and functionality of external dap file, you simply reference it in the .USES section.