widget

webclient grammar version yar.11.0

  1. Widgets
    1. Configuration
      1. Text, number, list and stategroup
      2. Binding
      3. Widgets
      4. Views and windows
    2. Expressions
      1. Context in expressions
    3. Let declarations
    4. Variables
    5. Navigation

Widgets


Widgets are used to initialize and compose controls and bind data to them using the client bindings. Widgets in turn are used by views which manage the binding to the application model.

A widget can be seen as two parts. First its configuration, and second its implementation. The widget configuration describes an interface for others that make use of widgets (i.e. views).

The implementation part of a widget determines what gets rendered using one or more controls. The implementation binds data from the client state or the widget configuration to the controls. Binding in this case means that the controls are updated whenever the source data changes. Selecting controls and binding data is done by using expressions.

'dependencies': dictionary { [ using ] }
'binding context': [ binds: ] stategroup (
	'none' { [ unbound ] }
	'select' {
		'binding': reference
		'switch client binding context': stategroup (
			'yes' { [ on (, ) ]
				'constrained on containing binding': stategroup (
					'yes' {
						'type path': component 'client binding type path'
						'instance binding': reference
					}
					'no' { [ unconstrained ]
						'instance binding': reference
					}
				)
			}
			'no' { }
		)
	}
)
'configuration': component 'widget configuration'
'define variable': stategroup (
	'yes' {
		'variable': component 'variable'
	}
	'no' { }
)
'expression': [ render: ] component 'expression'

Configuration


The configuration part of a widget consist of attributes of the types: widget, window, view, binding, number, text, list and stategroup.

Some attributes are optional if a default is set. For example text attributes can have a default set.

'language': text default: "English"

Text, number, list and stategroup

Attributes of this type are used to configure the behaviour and state of the widget. These values can be updated using expressions when invoked from an event.

Binding

A binding attribute references a binding type in the client bindings. It can be used to access data from the client state. When an attribute of this type is used in a view, it typically needs some additional model information like property name.

Widgets

A widget attribute references another widget definition. They can be used in expressions to bind to a markup attribute of a control or to invoke in an expression at the top level of the widget implementation.

Views and windows

Views are shown in a window. A window is a fixed place in the user interface where zero or more views can be rendered. A webclient project needs at least one window and one view to properly function.

For widgets, view attributes are used to open new content. E.g. opening a new view when the user clicks on a link or a row in a table.

'widget configuration' {
	'switch client binding context': stategroup (
		'yes' { [ on ]
			'constrained on containing binding': stategroup (
				'yes' { }
				'no' { [ unconstrained ]
					'instance binding': reference
				}
			)
			'type path': component 'client binding type path'
		}
		'no' { }
	)
	'type': stategroup (
		'widget' {
			'type': stategroup (
				'anonymous' { [ widget ] }
				'typed' {
					'type': [ widget: ] stategroup (
						'self' { [ self ] }
						'external' {
							'widget': reference
						}
					)
				}
			)
		}
		'window' { [ window ]
			'configuration': component 'widget configuration'
		}
		'view' { [ view ] }
		'number' { [ number ] dynamic-order
			'persistence': component 'configuration attribute persistence'
			'has default': stategroup (
				'yes' { [ default: ]
					'value': integer
				}
				'no' { }
			)
		}
		'text' { [ text ] dynamic-order
			'has default': stategroup (
				'yes' { [ default: ]
					'type': stategroup (
						'static' {
							'value': text
						}
						'phrase' {
							'phrase': reference
						}
						'unique id' { [ unique-id ] }
					)
				}
				'no' { }
			)
			'persistence': component 'configuration attribute persistence'
		}
		'list' { [ list ]
			'configuration': component 'widget configuration'
		}
		'state group' { [ stategroup ] dynamic-order
			'has default': stategroup (
				'yes' { [ default: ]
					'state': reference
				}
				'no' { }
			)
			'persistence': component 'configuration attribute persistence'
			'states': [ (, ) ] dictionary {
				'configuration': component 'widget configuration'
			}
		}
		'binding' { [ binding ]
			'constrained on containing binding': stategroup (
				'yes' {
					'type path': component 'client binding type path'
					'instance binding': reference
				}
				'no' { [ unconstrained ]
					'instance binding': reference
				}
			)
			'configuration': component 'widget configuration'
		}
		'node' { [ {, } ]
			'attributes': dictionary {
				'configuration': [ : ] component 'widget configuration'
			}
		}
	)
}
'client binding type path' {
	'has steps': stategroup (
		'no' { }
		'yes' {
			'type': stategroup (
				'state' {
					'state group': [ . ] reference
					'state': [ ? ] reference
				}
				'collection' {
					'collection': [ . ] reference
				}
				'binding' {
					'instance binding': [ / ] reference
				}
			)
			'tail': component 'client binding type path'
		}
	)
}
'configuration attribute persistence' {
	'persist': stategroup (
		'yes' { [ @persist ]
			'per session': stategroup (
				'yes' { [ session ] }
				'no' { }
			)
			'per entry': stategroup (
				'yes' { [ entry ] }
				'no' { }
			)
		}
		'no' { }
	)
}
// TODO update documentation

Expressions


Expressions are used to yield a value. At the top level of the widget implementation it is used to select a control. The most simple variant of an expression is.

control 'empty' ( )

The above snippet initializes a new control called ‘empty’ and renders nothing.

Expressions are also used to bind data to control properties.

'text' = $ .'text' value

Context in expressions

Widget expressions use a so called context to get their data from or to execute instructions.

At the start of the widget, the context is set to the binding context of the node and the root widget configuration. The binding context refers to a binding in the client bindings.

This is best explained using the following example.

binds: 'node' {
	'text': binding 'text' {
		'empty label': text
	}
} as $

render: switch $ .'text'[ .'is set'] (
	|'no' as $ => control 'text' (
		'text' = $ .'empty label' value
	)
	|'yes' as $'text value' => control 'text' (
		'text' = $'text value'[ .'text']
	)
)

The widget is configured to start at the node binding. It has one binding attribute called text. That attribute has one sub attribute empty label.

The widget expression starts with a switch to check if the text property is set or not. First the text attribute is selected using the double colon keyword (::). This does two things. First it set the widget configuration context to the node containing the empty label attribute. Second it switches the client binding context from node to text since it is a binding attribute.

Then the is set property is selected. The is set property is a member of the text binding from the client bindings. The two cases of the switch are handled. In the no case the context is assigned to the anonymous variable $. This does not change the configuration context, but does change the client binding context to 'text'.'is set'?'no'. The yes case defines a names scope $'text value'. It is used to get the text value from.

To summarize. There are two scopes:

Pro Tip! Use variable names when widgets get more complex and mix different scopes.

'expression' {
	'type': stategroup (
		'node switch' { [ switch ]
			'context': component 'conditional context selection'
			'cases': [ (, ) ] group { dynamic-order
				'node': group { [ | node ]
					'variable': component 'variable assignment'
					'expression': [ => ] component 'one or more expression'
				}
				'none': [ | none => ] component 'one or more expression'
			}
		}
		'state switch' { [ switch ]
			'context': component 'context selection'
			'type': stategroup (
				'configuration' {
					'states': [ (, ) ] dictionary { [ | ]
						'variable assignment': component 'variable assignment'
						'next': [ => ] component 'one or more expression'
					}
				}
				'binding' {
					'property': [ [ ., ] ] reference
					'states': [ (, ) ] dictionary { [ | ]
						'variable assignment': component 'variable assignment'
						'next': [ => ] component 'one or more expression'
					}
				}
			)
		}
		'value switch' { [ switch ]
			'value': component 'scalar'
			'compare to': [ compare (, ) ] component 'scalar'
			'last case': reference = last
			'cases': [ (, ) ] dictionary (
				static 'equals' [ == ]
				static 'not equal' [ != ]
				static 'greater than' [ > ]
				static 'greater than or equals' [ >= ]
				static 'less than' [ < ]
				static 'less than or equals' [ <= ]
			) { [ | ]
				'has predecessor': stategroup = node-switch predecessor (
					| node = 'yes' { 'case' = predecessor }
					| none = 'no'
				)
				'expression': [ => ] component 'one or more expression'
			}
		}
		'set switch' { [ switch ]
			'context': component 'context selection'
			'collection': [ [ ., ] ] reference
			'cases': [ (, ) ] group { dynamic-order
				'none': [ | none => ] component 'one or more expression'
				'match node': stategroup (
					'no' { }
					'yes' { [ | node ]
						'variable assignment': component 'variable assignment'
						'next': [ => ] component 'one or more expression'
					}
				)
				'nodes': group { [ | nodes ]
					'variable assignment': component 'variable assignment'
					'next': [ => ] component 'one or more expression'
				}
			}
		}
		'walk' { [ walk ]
			'context': component 'context selection'
			'type': stategroup (
				'configuration' { [ * ] }
				'widget binding' {
					'collection property': [ [ ., * ] ] reference
					'sort': stategroup (
						'yes' { [ sort by ]
							'by': [ (, ) ] component 'expression'
						}
						'no' { }
					)
				}
			)
			'variable': component 'variable'
			'entry expression': [ => ] component 'one or more expression'
		}
		'target' {
			'value': component 'expression target'
		}
	)
}
'expression target' {
	'type': stategroup (
		'control' {
			'control binding': component 'control binding'
		}
		'create entry' {
			'for': stategroup (
				'state' {
					'state': reference
				}
				'node' { }
			)
			'target': stategroup (
				'node' {
					'node': component 'node binding'
				}
				'widget configuration' {
					'configuration': [ : ] component 'expression target'
				}
			)
		}
		'widget configuration' {
			'context': stategroup (
				'instance' {
					'context': component 'context selection'
				}
				'type' { [ % ]
					'select': stategroup (
						'current' { }
						'attribute' {
							'attribute': [ . ] reference
						}
					)
				}
			)
			'assign': stategroup (
				'binding' { [ as binding ]
					'configuration': component 'expression target'
				}
				'widget' { [ as widget ] }
				'plain' { [ ; ] }
			)
		}
		'none' { [ none ] }
		'instruction' {
			'instruction': component 'instruction selection'
		}
		'sort collection' {
			'context': component 'context selection'
			'direction': stategroup (
				'ascending' { [ ascending ] }
				'descending' { [ descending ] }
			)
		}
		'scalar' {
			'value': component 'scalar'
		}
		'let declaration' {
			'path': component 'context ancestor path'
			'declaration': [ $ ] reference
		}
	)
}
'expression list' {
	'optional context definition': component 'optional context definition'
	'expression': component 'expression'
	'has next': stategroup (
		'yes' {
			'next': [ , ] component 'expression list'
		}
		'no' { }
	)
}
'one or more expression' {
	'multiplicity': stategroup (
		'singular' {
			'expression': component 'expression'
		}
		'plural' {
			'expression': [ [, ] ] component 'expression list'
		}
	)
}
'node binding' { [ (, ) ]
	'attributes': dictionary {
		'expression': [ = ] component 'one or more expression'
	}
}
'control binding' {
	'binding type': stategroup (
		'static' {
			'library': stategroup (
				'additional' {
					'control': [ control ] reference external
				}
				'default' {
					'control': [ control ] reference
				}
			)
			'node binding': component 'node binding'
		}
		'window' { [ window ]
			'context': component 'context selection'
			'variable': [ => ] component 'variable'
			'control binding': [ => ] component 'control binding'
		}
		'widget' {
			'source': stategroup (
				'configuration' {
					'context': component 'context selection'
				}
				'inline' {
					'context': component 'optional context selection'
					'type': [ widget ] stategroup (
						'self' { [ self ] }
						'external' {
							'widget': reference
						}
					)
					'configuration': component 'widget configuration binding'
				}
			)
		}
		'client binding widget' {
			'context': [ binding widget ] component 'context selection'
		}
	)
}
'widget configuration binding' {
	'expression': component 'one or more expression'
}
'scalar' {
	'binding type': stategroup (
		'static text' {
			'text': text
		}
		'phrase' {
			'phrase': reference
		}
		'configuration' {
			'context': component 'context selection'
			'type': stategroup (
				'text' { [ value ] }
				'number' { [ # value ] }
			)
		}
		'event' {
			'type': stategroup (
				'text' { [ argument ] }
				'number' { [ # argument ] }
			)
		}
		'binding' {
			'context': component 'context selection'
			'type': stategroup (
				'key' { [ .key ] }
				'property' { [ [, ] ]
					'type': stategroup (
						'text' { [ . ] }
						'reference' { [ > ] }
						'number' { [ # ] }
					)
					'property': reference
				}
			)
		}
		'static number' {
			'value': integer
		}
		'unary expression' {
			'type': stategroup (
				'absolute value' { [ abs ] }
				'sign inversion' { [ - ] }
			)
			'value': [ (, ) ] component 'scalar'
		}
		'list expression' {
			'operation': stategroup (
				'sum' { [ sum ] }
				'minimum' { [ min ] }
				'maximum' { [ max ] }
				'product' { [ product ] }
			)
			'value': [ (, ) ] component 'one or more expression'
		}
		'current time' { [ current time ]
			'throttle': stategroup (
				'yes' {
					'interval': [ interval: ] integer
				}
				'no' { }
			)
		}
		'list index' {
			'context': [, index ] component 'context selection'
		}
	)
	'transform': stategroup (
		'no' { }
		'yes' { [ transform ]
			'transformer': reference
		}
	)
	'format': stategroup (
		'no' { }
		'yes' { [ format ]
			'formatter': reference
		}
	)
}
'instruction selection' {
	'action': stategroup (
		'execute instruction' {
			'context': component 'context selection'
			'instruction': [ >> ] reference
			'has arguments': stategroup (
				'no' { }
				'yes' {
					'first argument': [ (, ) ] component 'instruction argument'
				}
			)
		}
		'update configuration' { [ set ]
			'context': component 'context selection'
			'instruction': [ = ] component 'widget configuration binding'
		}
	)
}
'instruction argument' {
	'type': stategroup (
		'number' { [ number ]
			'value': component 'expression'
		}
		'text' { [ text ]
			'value': component 'expression'
		}
		'file' { [ file ] }
		'view' { [ view ]
			'context': component 'context selection'
		}
	)
	'next argument': stategroup (
		'no' { }
		'exists' { [ , ]
			'argument': component 'instruction argument'
		}
	)
}

Let declarations

'let declarations' {
	'let declarations': dictionary { [ let $ ]
		'target': [ : ] stategroup (
			'control' { }
			'number' { [ number, = ] }
			'text' { [ text, = ] }
			'control property' {
				'library': stategroup (
					'additional' {
						'control': [ control ] reference external
					}
					'default' {
						'control': [ control ] reference
					}
				)
				'type path': [, = ] component 'control type path'
			}
			'widget property' {
				'type': [ widget ] stategroup (
					'self' { [ self ] }
					'external' {
						'widget': reference
					}
				)
				'type path': [, = ] component 'widget type path'
			}
		)
		'expression': component 'one or more expression'
	}
}
'control type path' {
	'attribute': [ . ] reference
	'has step': stategroup (
		'no' { }
		'yes' {
			'type': stategroup (
				'collection' {
					'path': [ * ] component 'control type path'
				}
				'state' {
					'state': [ ? ] reference
					'path': component 'control type path'
				}
			)
		}
	)
}
'widget type path' {
	'attribute': [ . ] reference
	'has step': stategroup (
		'no' { }
		'yes' {
			'type': stategroup (
				'collection' {
					'path': [ * ] component 'widget type path'
				}
				'state' {
					'state': [ ? ] reference
					'path': component 'widget type path'
				}
				'binding' {
					'path': component 'widget type path'
				}
			)
		}
	)
}

Variables

'variable' {
	'context': stategroup (
		'current' {
			'path': component 'context selection path'
		}
		'other context' {
			'context': component 'context selection'
		}
	)
	'identifier': [ as $ ] stategroup (
		'anonymous' { }
		'named' {
			'named values': dictionary {
				'has successor': stategroup = node-switch successor (
					| node = 'yes' { }
					| none = 'no'
				)
			}
			'name': reference = first
		}
	)
	'let declarations': component 'let declarations'
	'more variables': stategroup (
		'yes' { [ , ]
			'variable': component 'variable'
		}
		'no' { }
	)
}
'optional context definition' {
	'define context': stategroup (
		'yes' {
			'variable': [ define context, => ] component 'variable'
		}
		'no' { }
	)
}
'variable assignment' {
	'assign': stategroup (
		'yes' {
			'variable': component 'variable'
		}
		'no' { }
	)
}
'context selection' {
	'change context to': stategroup (
		'engine state' { [ engine ]
			'engine state binding': reference
		}
		'other context' {
			'ancestor path': component 'context ancestor path'
		}
	)
	'path': component 'context selection path'
}
'context ancestor path' {
	'has step': stategroup (
		'no' {
			'context': stategroup (
				'anonymous' { [ $ ] }
				'named' {
					'variable name': [ $ ] reference
				}
			)
		}
		'yes' { [ ^ ]
			'tail': component 'context ancestor path'
		}
		'root' { [ root ] }
	)
}
'context selection path' {
	'has steps': stategroup (
		'no' { }
		'yes' {
			'step': stategroup (
				'attribute' {
					'attribute': [ . ] reference
				}
				'client binding' {
					'binding': [ / ] reference
				}
			)
			'tail': component 'context selection path'
		}
	)
}
'optional context selection' {
	'change context': stategroup (
		'no' { }
		'yes' {
			'context': component 'context selection'
		}
	)
}
'conditional context selection' {
	'change context to': stategroup (
		'engine state' { [ engine ]
			'engine state binding': reference
		}
		'other context' {
			'ancestor path': component 'context ancestor path'
		}
	)
	'path': component 'conditional context selection path'
}
'conditional context selection path' {
	'has steps': stategroup (
		'no' { }
		'yes' {
			'type': stategroup (
				'attribute' {
					'attribute': [ . ] reference
				}
				'list entry' {
					'index': [ [, ] ] component 'scalar'
				}
				'widget configuration state' {
					'state': [ ? ] reference
				}
				'client binding' {
					'binding': [ [, ] ] component 'client binding context selection'
				}
			)
			'tail': component 'conditional context selection path'
		}
	)
}
'client binding context selection' {
	'type': stategroup (
		'binding' {
			'binding': [ / ] reference
		}
		'collection entry' {
			'collection': [ . ] reference
			'key': [ [, ] ] component 'scalar'
		}
		'state' {
			'state group': [ . ] reference
			'state': [ ? ] reference
		}
	)
	'has next': stategroup (
		'yes' {
			'next': component 'client binding context selection'
		}
		'no' { }
	)
}