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 $type="let" $$myvar="Kitten">(<$text text=<<$myvar>>/>) +_ +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 $type="let" $names="$myvar $other" $values="Kitten Donkey" $$myvar="Shark">(<$text text=<<$myvar>>/>|<$text text=<<$other>>/>) +_ +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"/> + +\end +<$let + one="Elephant" + $two="Kangaroo" + $$three="Giraffe" +> +(<$text text=<>/>) +(<$text text=<<$two>>/>) +(<$text text=<<$$three>>/>) + +_ +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 $tag="div" class="tc-thing" label="Squeak">Mouse +_ +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=<>/> + +'/>