This is an old revision of the document!


Functional Layout

Functional layout is a layout concept inspired by spreadsheets where the value of a cell can reference the value of another cell. For example “WidgetA.left” can be set to the expression “WidgetB.right + 10” which places WidgetA 10 pixels to the right of WidgetB.

Live demo is here: https://codepen.io/samhepworth/project/full/AONPBY/

The live demo is a “proof of concept” it is not intended for any real-world usage. The source code for the demo is optimized, but it can be optimized a lot more. However, more optimization will make the source code more difficult to read. Capabilities of the demo include:

  • Browser stack size is not a limitation. Recursive functions have been replaced with iterative functions where needed.
  • The code is fast and it is not difficult to make it even faster.
  • Only when a value change is it written to the DOM elements. This makes the code very fast.
  • All DOM updates happen in requestAnimationFrame callbacks.
  • There are some nice operators to select widgets in the expression language.
  • Expressions are compiled to javascript functions making expression evaluation very fast.
  • Expression references are looked up before expressions are evaluated. This enables expressions to be evaluated in a topologically sorted order.
  • The code is open source and distributed with a MIT license.

Limitations of the demo include:

  • Adding and removing widgets results in all references to be re-evaluated. This can be fixed, but it will make the source code more complicated.
  • Hidden widgets are not handled. One way to handle hidden widgets is to maintain a list of the child widgets that are not hidden, and basically ignore all the hidden widgets most of the time.
  • Properties are not inserted into the generated HTML when widgets are rendered. All properties are applied after creating the DOM elements. Rendering selected properties when the HTML is generated is a simple addition.
  • Values cannot have properties (composite values) - a value must be a property of a widget. It might be an advantage to rewrite the code so that widgets are a special kind of values, and that values can have properties.
  • Elimination of evaluation of “used” values. If a value is not used directly or indirectly to update a property on a DOM element, then this value does not need to be evaluated unless is it read. Not evaluating “used” values will provide a major performance boost.

Directed Acyclic Graph (DAG)

A directed acyclic graph (DAG) is a graph where there are no cycles and the edges between vertices can only go in one direction (otherwise there is a cycle). In this case a value is a vertice in the graph and a refernce to another value is an edge.

ExpressionReferences
a = b + ca reference b
a reference c
b = db reference d
c = dc reference d

Values are computed using depth-first recursive evaluation. This can lead to a deep call stack, so deep that it exceeds the browsers javascript stack depth maximum. To prevent this values are evaluated in a topologically sorted order when the call stack gets deep.

Delayed evaluation

When a value is set to a constant (simple value) or an expression a few things happen - as few a possible. When a value is set to an expression evaluation of the expression is delayed until the value is ready or the browser is ready to update the DOM. This saves time.

After evaluating an expression the new value is compared to the old value. Only if the new and old values differ is the DOM updated. This also saves time.

Widgets & Values

Values are stored as widget properties. Here is an example:

// Create widget "a" and make it a child og the DOM element with id "view". This is then a root widget.
var a = Widget("view", "a")
 
// Create value "ax" on widget "a"
Value(a, "ax");             
 
// Create value "ay" on widget "a". This value is written to the DOM element styles as fontWeight using the unit "px".
Value(a, "ay", "style:fontWeight:px"); 
 
// Create value "az" on widget "a"
Value(a, "az"); 
 
// Set "ax" to "10+20" that evaluates to "30"
a.ax = "10+20"; 
 
// Set "ay" to "ax+10" that evaluates to "40"
a.ay = "ax+10"; 
 
// Set "az" to the text "This is not an expression"
a.az = Simple("This is not an expression"); 
 
// Set "az" to the text "This is an expression"
a.az = '"This is not an expression"'; 

Expressions

A value can be a constant (a simple value) or an expression. Expressions may contain the following operators.

OperatorDescription
“text”A string. Enter a quote as \“ and escape as \\.
“This is a text”
“This is a \”text\” with qoutes and \\escape“.
number.fractionA number with an optional fraction.
10
10.20
nameWidget or value with a given name. If a value is not found on the current widget, it is checked if there exists a global value with the given name.
left : Value named left.
a.left : Value left on child widget a
/Root widget.
/left : Value left of widget root
/a.left : Value left of child widget a of widget root.
^Parent widget.
^left : Value left of parent widget.
^a.left : Value left of sibling widget a.
^*.width : Width of this and all sibling widgets.
<Previous widget.
<left : Left value on previous widget.
^<left : Left value on previous widget of parent widget.
>Next widget.
>left : Left value on next widget.
^>left : Left value on next widget of parent widget.
#Named widget or value.
#a : Any value named a.
#a.left : Left value of any widget named a.
#a.<left : Left value on widget previous to any widget name a
#^Named widget or value on any parent.
#^a : A value named a on any parent.
#^a.left : Value left on widget a on any parent.
##Widget with id.
##w101.left : Left value of widget with id w101.
##w101^.left : Left value of parent of widget with id W101.
*All siblings.
*left : Left value of all children widgets.
^*.left : Left value of this and all sibling widgets.
.Widget property.
a.left : Left value of child widget a.
^a.left left value of sibling widget a.
firstFirst widget.
first.left : Left value of first child.
^first.left : Left value of first sibling.
lastLast widget.
last.left : Left value of last child.
^last.left : Left value of last sibling.
+ -Sign
-10 * +2
%Percentage
10 * %2 : Equals 10 * (2/100)
+ -Addition and subtraction.
left+width : Left value of this widget plus width value of this widget.
* /Multiplication and division.
left*width/2 : Left value of this widget multiplied by width value of this widget divided by 2.| |( )|(2+2)*(1+3)
> < == = <= >= !=Compare values.
left < a : True if value left is less than value a
&& & || |Logic
a > 10 & a < 20: True if (a > 10) and (a < 20)
function(arguments)Function call.
max(2, 3, 4): Call function max with 3 arguments.
a ? b : cIf a then b else c.
a < b ? “a is less than b” : “b is less than or equal to a”

Default Global Values

The following global values are predefined:

GlobalDescription
undefinedA ready only value which equals undefined.
max(…)Return max of arguments. Arguments can be arrays.
sum(…)Return sum of arguments. Arguments can be arrays.
nan(a)Return 0 if isNaN(a) else return a.
nan(a, b)Return b if isNan(a) else return a.
nan(a, b, c)Return c if isNan(a) else return b.

Default Widget Values

A widget has a number of predefined values:

Widget ValuesDescription
indexIndex of widget.
nan(<index, -1) + 1
countCount of children.
nan(last.index + 1, 0)
totalCountTotoal count of children.
count + sum(*.totalCount)
xThis value is changed by moving the widget left/right.
0
yThis value is changed by moving the widget up/down.
0
wThis value is changed by sizing the widget left/right.
0
hThis value is changed by sizing the widget up/down.
0
widthWidth of widget.
w
heightHeight of widget.
h
leftLeft position of widget.
x
right-width
topTop position of widget.
y
bottom-height
rightRight position of widget.
left+width
x
bottomBottom position of widget.
top+height
y
clientLeftGlobal left position of widget.
nan(^clientLeft, ^clientLeft + ^border + ^padding, 0)+left
clienTopGlobal top position of widget.
nan(^clientTop, ^clientTop + ^border + ^padding, 0) + top
clientRightGlobal right position of widget.
max(*.clientRight, clientLeft + width)
clientBottomGlobal bottom position of widget.
max(*.clientBottom, clientTop + height)
overflowWidget.style.overflow
undefined
borderWidget border. Left, top, right and bottom border is same value.
undefined
borderColorBorder color.
undefined
borderStyleBorder style.
solid
paddingWidget padding. Left, top, right and bottom padding is same value.
0
marginWidget margin.
0
innerWidthWidth excluding padding and border
width - border*2 - padding*2
innerHeightHeight excluding padding and border
height - border*2 - padding*2
outerWidthWidth including margin
width + margin*2
outerHeightHeight including margin
height + margin*2
colorstyle.color
undefined
backgroundColorstyle.backgroundColor
undefined
htmlWidget html. HTML for widget children is not part of this.
Widget FunctionsDescription
bounds(left, top, width, height)Set left, top, width and height. If an argument is undefined the value is not set.
xywh(x, y, w, h)Set x, y, w, h. If an argument is undefined the value is not set.