From 56c2242e4ef06a5d8799dc50084447e3a3741efa Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Tue, 3 May 2022 12:55:10 +0100
Subject: [PATCH] Introduce genesis widget for dynamically creating widgets
See the "RedefineLet" test for a contrived example of usage
---
core/modules/widgets/genesis.js | 100 ++++++++++++++++++
.../tests/data/genesis-widget/DollarSigns.tid | 14 +++
.../genesis-widget/MultipleAttributes.tid | 14 +++
.../tests/data/genesis-widget/RedefineLet.tid | 33 ++++++
.../tests/data/genesis-widget/Simple.tid | 14 +++
.../tiddlers/widgets/GenesisWidget.tid | 34 ++++++
6 files changed, 209 insertions(+)
create mode 100644 core/modules/widgets/genesis.js
create mode 100644 editions/test/tiddlers/tests/data/genesis-widget/DollarSigns.tid
create mode 100644 editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid
create mode 100644 editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
create mode 100644 editions/test/tiddlers/tests/data/genesis-widget/Simple.tid
create mode 100644 editions/tw5.com/tiddlers/widgets/GenesisWidget.tid
diff --git a/core/modules/widgets/genesis.js b/core/modules/widgets/genesis.js
new file mode 100644
index 000000000..d6017453c
--- /dev/null
+++ b/core/modules/widgets/genesis.js
@@ -0,0 +1,100 @@
+/*\
+title: $:/core/modules/widgets/genesis.js
+type: application/javascript
+module-type: widget
+
+Genesis widget for dynamically creating widgets
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+var Widget = require("$:/core/modules/widgets/widget.js").widget;
+
+var GenesisWidget = function(parseTreeNode,options) {
+ this.initialise(parseTreeNode,options);
+};
+
+/*
+Inherit from the base widget class
+*/
+GenesisWidget.prototype = new Widget();
+
+/*
+Render this widget into the DOM
+*/
+GenesisWidget.prototype.render = function(parent,nextSibling) {
+ this.parentDomNode = parent;
+ this.computeAttributes();
+ this.execute();
+ this.renderChildren(parent,nextSibling);
+};
+
+/*
+Compute the internal state of the widget
+*/
+GenesisWidget.prototype.execute = function() {
+ var self = this;
+ // Collect attributes
+ this.genesisType = this.getAttribute("$type","element");
+ this.genesisTag = this.getAttribute("$tag","div");
+ this.genesisNames = this.getAttribute("$names","");
+ this.genesisValues = this.getAttribute("$values","");
+ // Construct parse tree
+ var parseTreeNodes = [{
+ type: this.genesisType,
+ tag: this.genesisTag,
+ attributes: {},
+ orderedAttributes: [],
+ children: this.parseTreeNode.children || []
+ }];
+ // Apply attributes in $names/$values
+ this.attributeNames = [];
+ this.attributeValues = [];
+ if(this.genesisNames && this.genesisValues) {
+ this.attributeNames = this.wiki.filterTiddlers(self.genesisNames,this);
+ this.attributeValues = this.wiki.filterTiddlers(self.genesisValues,this);
+ $tw.utils.each(this.attributeNames,function(varname,index) {
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],varname,self.attributeValues[index] || "");
+ });
+ }
+ // Apply explicit attributes
+ $tw.utils.each(this.attributes,function(value,name) {
+ if(name.charAt(0) === "$") {
+ if(name.charAt(1) === "$") {
+ // Double $$ is changed to a single $
+ name = name.substr(1);
+ } else {
+ // Single dollar is ignored
+ return;
+ }
+ }
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],name,value);
+ });
+ // Construct the child widgets
+ this.makeChildWidgets(parseTreeNodes);
+};
+
+/*
+Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
+*/
+GenesisWidget.prototype.refresh = function(changedTiddlers) {
+ var changedAttributes = this.computeAttributes(),
+ filterNames = this.getAttribute("$names",""),
+ filterValues = this.getAttribute("$values",""),
+ attributeNames = this.wiki.filterTiddlers(filterNames,this),
+ attributeValues = this.wiki.filterTiddlers(filterValues,this);
+ if($tw.utils.count(changedAttributes) > 0 || !$tw.utils.isArrayEqual(this.attributeNames,attributeNames) || !$tw.utils.isArrayEqual(this.attributeValues,attributeValues)) {
+ this.refreshSelf();
+ return true;
+ } else {
+ return this.refreshChildren(changedTiddlers);
+ }
+};
+
+exports.genesis = GenesisWidget;
+
+})();
diff --git a/editions/test/tiddlers/tests/data/genesis-widget/DollarSigns.tid b/editions/test/tiddlers/tests/data/genesis-widget/DollarSigns.tid
new file mode 100644
index 000000000..aba81a26f
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/genesis-widget/DollarSigns.tid
@@ -0,0 +1,14 @@
+title: Genesis/DollarSigns
+description: Usage of genesis widget with attributes starting with dollar signs
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$genesis $type="let" myvar="Kitten">(<$text text=<>/>)$genesis>
+<$genesis $type="let" $$myvar="Kitten">(<$text text=<<$myvar>>/>)$genesis>
+_
+title: ExpectedResult
+
+(Kitten)(Kitten)
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid b/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid
new file mode 100644
index 000000000..1edca2887
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid
@@ -0,0 +1,14 @@
+title: Genesis/MultipleAttributes
+description: Usage of genesis widget with multiple attributes
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$genesis $type="let" $names="myvar other" $values="Kitten Donkey" myvar="Shark">(<$text text=<>/>|<$text text=<>/>)$genesis>
+<$genesis $type="let" $names="$myvar $other" $values="Kitten Donkey" $$myvar="Shark">(<$text text=<<$myvar>>/>|<$text text=<<$other>>/>)$genesis>
+_
+title: ExpectedResult
+
+(Shark|Donkey)(Shark|Donkey)
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
new file mode 100644
index 000000000..88abc84c8
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
@@ -0,0 +1,33 @@
+title: Genesis/RedefineLet
+description: Using the genesis widget to override the let widget
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\function <$let>
+\whitespace trim
+<$setmultiplevariables $names="[enlist]" $values="[enlistaddprefix[--]addsuffix[--]]">
+<$slot $name="ts-body"/>
+$setmultiplevariables>
+\end
+<$let
+ one="Elephant"
+ $two="Kangaroo"
+ $$three="Giraffe"
+>
+(<$text text=<>/>)
+(<$text text=<<$two>>/>)
+(<$text text=<<$$three>>/>)
+$let>
+_
+title: Definition
+
+\whitespace trim
+_
+title: ExpectedResult
+
+(--Elephant--)
+(--Kangaroo--)
+(--Giraffe--)
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/genesis-widget/Simple.tid b/editions/test/tiddlers/tests/data/genesis-widget/Simple.tid
new file mode 100644
index 000000000..ff232dad6
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/genesis-widget/Simple.tid
@@ -0,0 +1,14 @@
+title: Genesis/Simple
+description: Simple usage of genesis widget
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$genesis $tag="div">Mouse$genesis>
+<$genesis $tag="div" class="tc-thing" label="Squeak">Mouse$genesis>
+_
+title: ExpectedResult
+
+Mouse
Mouse
\ No newline at end of file
diff --git a/editions/tw5.com/tiddlers/widgets/GenesisWidget.tid b/editions/tw5.com/tiddlers/widgets/GenesisWidget.tid
new file mode 100644
index 000000000..7adb8983b
--- /dev/null
+++ b/editions/tw5.com/tiddlers/widgets/GenesisWidget.tid
@@ -0,0 +1,34 @@
+caption: genesis
+created: 20220502144738010
+modified: 20220502144738010
+tags: Widgets
+title: GenesisWidget
+type: text/vnd.tiddlywiki
+
+! Introduction
+
+<<.from-version "5.2.3">> The genesis widget allows the dynamic construction of widgets, where the name and attributes of the widget can be dynamically determined, and do not need to be known in advance.
+
+! Content and Attributes
+
+The content of the `<$genesis>` widget is used as the content of the dynamically created widget.
+
+|!Attribute |!Description |
+|$type |The type of widget to create |
+|$tag |The HTML tag to be used for "element" widgets |
+|$names |An optional filter evaluating to the names of a list of variables to be applied to the widget |
+|$values |An optional filter evaluating to the values corresponding to the list of names specified in `$names` |
+|//{other attributes starting with $}// |Other attributes starting with a single dollar sign are reserved for future use |
+|//{attributes starting with $$}// |Attributes starting with two dollar signs are appplied as attributes to the output widget, but with the attribute name changed to use a single dollar sign |
+|//{attributes not starting with $}// |Any other attributes that do not start with a dollar are applied as attributes to the output widget |
+
+Note that attributes explicitly specified take precedence over attributes with the same name specified in the `$names` filter.
+
+! Examples
+
+<$macrocall $name='wikitext-example-without-html'
+src='<$set name="myTiddler" value="HelloThere">
+ <$set name="myVariable" tiddler=<> field={{$:/docs/anyField!!field}}>
+ <$text text=<>/>
+ $set>
+$set>'/>