This page is part of a static HTML representation of the TiddlyWiki at https://tiddlywiki.com/dev/

Child widgets tutorial

 22nd February 2019 at 11:11pm

Introduction

Until now the examples have covered only simple, leaf widgets, but widgets can be arranged into a hierarchy. Compared to a leaf-only widget, widget classes which support having children must contain code to instantiate the children.

Not all widgets need to support having children. Widgets whose only purpose is to integrate third party javascript libraries, for example may not need it.

wiki text ⇨ parse tree ⇨ widget tree ⇨ DOM tree

  1. wiki text - Users write content in tiddlers using wiki text.
  2. parse tree - A parse tree is a JSON data structure describing the wiki text. Wiki text can be converted into a parse tree using this.wiki.parseText.
  3. widget tree - Most items in the parse tree correspond to one or more items in the widget tree. The this.makeChildWidgets method is used to convert the parse tree into a widget tree.
  4. DOM tree - A DOM tree is a standard javascript datastructure used by browsers to display a web page. The this.renderChildren method is used to instantiate the widget class and then render each widget in the widget tree. If a given widget will be visible in the browser, then one or more DOM nodes will be created.

Examples

Simple trees without nesting

Widgets in wiki text have a syntax similar to html (i.e. <$list/>). But all wiki markup is seen by the tiddlywiki code as widgets.

Even a simple string with no special syntax will be treated as a widget. In this case a widget of type text:

wiki texthtmlrenders as
hello hello hello

parse treewidget tree


[
    {
        "type": "text",
        "text": "hello",
        "start": 0,
        "end": 5
    }
]



{
    "type": "widget",
    "children": [
        {
            "type": "text",
            "text": "hello"
        }
    ]
}

The above bare string of text is mostly just a synonym for this widget syntax:

wiki texthtmlrenders as
<$text text=hello/> hello hello

parse treewidget tree


[
    {
        "type": "text",
        "start": 0,
        "attributes": {
            "text": {
                "start": 6,
                "name": "text",
                "type": "string",
                "value": "hello",
                "end": 17
            }
        },
        "orderedAttributes": [
            {
                "start": 6,
                "name": "text",
                "type": "string",
                "value": "hello",
                "end": 17
            }
        ],
        "tag": "$text",
        "isSelfClosing": true,
        "end": 19,
        "isBlock": false
    }
]



{
    "type": "widget",
    "children": [
        {
            "type": "text",
            "attributes": {
                "text": "hello"
            }
        }
    ]
}

Some of the details of the parseTree and widgetTree are different in the two examples, but the general structure is the same and the rendered output is the same.

In these two simple examples, there is no nesting and so no child widgets are created.

Trees with one level of nesting

This next example shows the structure for bold wiki syntax. It is still simple, but does introduce one level of nesting: elementtext

The wiki syntax for bold converts to the standard html element strong. Any standard html element is represented in tiddlywiki as a widget of type element:

wiki texthtmlrenders as
''bold'' <strong>bold</strong> bold

parse treewidget tree


[
    {
        "type": "element",
        "tag": "strong",
        "children": [
            {
                "type": "text",
                "text": "bold",
                "start": 2,
                "end": 6
            }
        ]
    }
]



{
    "type": "widget",
    "children": [
        {
            "type": "element",
            "tag": "strong",
            "children": [
                {
                    "type": "text",
                    "text": "bold"
                }
            ]
        }
    ]
}

Another example showing one level of nesting (linktext):

wiki texthtmlrenders as
<$link to=atiddler>link</$link> <a class="tc-tiddlylink tc-tiddlylink-missing" href="atiddler.html">link</a> link

parse treewidget tree


[
    {
        "type": "link",
        "start": 0,
        "attributes": {
            "to": {
                "start": 6,
                "name": "to",
                "type": "string",
                "value": "atiddler",
                "end": 18
            }
        },
        "orderedAttributes": [
            {
                "start": 6,
                "name": "to",
                "type": "string",
                "value": "atiddler",
                "end": 18
            }
        ],
        "tag": "$link",
        "end": 19,
        "isBlock": false,
        "children": [
            {
                "type": "text",
                "text": "link",
                "start": 19,
                "end": 23
            }
        ]
    }
]



{
    "type": "widget",
    "children": [
        {
            "type": "link",
            "attributes": {
                "to": "atiddler"
            },
            "children": [
                {
                    "type": "text",
                    "text": "link"
                }
            ]
        }
    ]
}

Widgets with no DOM contribution

Not all widgets contribute items to the DOM. The purpose of some widgets is to affect the display of descendant widgets by changing some state. The $set widget for example sets a variable which will be accessible to the descendant widgets:

wiki texthtmlrenders as
<$set name=myvar value=hi/>

parse treewidget tree


[
    {
        "type": "set",
        "start": 0,
        "attributes": {
            "name": {
                "start": 5,
                "name": "name",
                "type": "string",
                "value": "myvar",
                "end": 16
            },
            "value": {
                "start": 16,
                "name": "value",
                "type": "string",
                "value": "hi",
                "end": 25
            }
        },
        "orderedAttributes": [
            {
                "start": 5,
                "name": "name",
                "type": "string",
                "value": "myvar",
                "end": 16
            },
            {
                "start": 16,
                "name": "value",
                "type": "string",
                "value": "hi",
                "end": 25
            }
        ],
        "tag": "$set",
        "isSelfClosing": true,
        "end": 27,
        "isBlock": false
    }
]



{
    "type": "widget",
    "children": [
        {
            "type": "set",
            "attributes": {
                "name": "myvar",
                "value": "hi"
            }
        }
    ]
}

Nothing is rendered and there is no html, but the parse tree and widget tree contain information about the variable.

Dynamic widget children using this.wiki.parseText

In all the examples so far, there has been a close mapping between the nesting structure of the parse tree and the widget tree. If the item is in the parse tree, then it is in the widget tree. If the parse tree item has children, then the widget tree has the same number of children.

However, there are several examples of widgets in which more levels of nesting are created dynamically during the widget rendering process

The $macrocall widget is one example:

wiki texthtmlrenders as
<$macrocall $name=now/> 16:46, 19th January 2023 16:46, 19th January 2023

parse treewidget tree


[
    {
        "type": "macrocall",
        "start": 0,
        "attributes": {
            "$name": {
                "start": 11,
                "name": "$name",
                "type": "string",
                "value": "now",
                "end": 21
            }
        },
        "orderedAttributes": [
            {
                "start": 11,
                "name": "$name",
                "type": "string",
                "value": "now",
                "end": 21
            }
        ],
        "tag": "$macrocall",
        "isSelfClosing": true,
        "end": 23,
        "isBlock": false
    }
]



{
    "type": "widget",
    "children": [
        {
            "type": "macrocall",
            "attributes": {
                "$name": "now"
            },
            "children": [
                {
                    "type": "vars",
                    "children": [
                        {
                            "type": "text",
                            "text": "16:46, 19th January 2023"
                        }
                    ]
                }
            ]
        }
    ]
}

The parse tree has just a single type=$macrocall element with no children. The widget tree has that same type=$macrocall element, but it also has a child widget which wasn't in the parse tree.

In all cases, the $macrocall widget calls the given macro and then calls this.wiki.parseText on the output. This results in a new parse tree which is used to make the child widgets.

In this particular case, the now macro is called. It returns a simple string of text, so when it is parsed the result causes the creation of a single child text widget containing the current date and time.

Widgets which do not support nesting

Just as some widgets can cause widget trees to have more nesting than the parse tree, some widgets can cause widget trees to have less nesting.

This happens when there is no use for the widget to have any children. The $text widget, for example, has no use for displaying any descendants.

This behavior is accomplished by not calling the makeChildWidgets method. Without that method call, the child widgets are not created from the child parse tree items. For example:

wiki texthtmlrenders as
<$text text="hi">ignored child text</$text> hi hi

parse treewidget tree


[
    {
        "type": "text",
        "start": 0,
        "attributes": {
            "text": {
                "start": 6,
                "name": "text",
                "type": "string",
                "value": "hi",
                "end": 16
            }
        },
        "orderedAttributes": [
            {
                "start": 6,
                "name": "text",
                "type": "string",
                "value": "hi",
                "end": 16
            }
        ],
        "tag": "$text",
        "end": 17,
        "isBlock": false,
        "children": [
            {
                "type": "text",
                "text": "ignored child text",
                "start": 17,
                "end": 35
            }
        ]
    }
]



{
    "type": "widget",
    "children": [
        {
            "type": "text",
            "attributes": {
                "text": "hi"
            }
        }
    ]
}

Since the $text widget does not have a call to makeChildWidgets, 'ignored child text' above is present in the parse tree, but not in the widget tree.

Reference

methodwhen to callwhat it does
makeChildWidgetsdirectly or indirectly from render methodconverts child parse tree items into widget tree items
renderChildrendirectly or indirectly from render method, after the makeChildWidgets callcalls the render method of the child widget
refreshChildrendirectly or indirectly from refresh method, only needed if refreshSelf is not calledcalls the refresh method of of the child widgets so they can check if refresh is needed
this.wiki.parseTextpass the output parse tree to makeChildWidgetsconverts the given text to a parse tree...only needed for dynamically constructed parsetree
example callpurposewidgets using this approach
this.makeChildWidgets()construct child widgets from the static parse tree$link, $button, etc.
this.makeChildWidgets(parseTreeNodes)construct child widgets from a dynamic parse tree$list, $transclude, $macrocall, etc
this.renderChildren(parent, nextSibling)when the widget adds nothing to the dom, just pass the render arguments through to the children$set, $importvariables, $tiddler, $wikify, etc.
this.renderChildren(domNode, null)passes the dom node generated by this widget into the children$link, $button, etc