mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-23 18:17:20 +00:00
Introduce genesis widget (#6961)
* Initial Commit * Fix version number * Fix docs date
This commit is contained in:
parent
dd66fcc759
commit
4e9267ea58
@ -12,12 +12,26 @@ Parse tree utility functions.
|
|||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add attribute to parse tree node
|
||||||
|
Can be invoked as (node,name,value) or (node,attr)
|
||||||
|
*/
|
||||||
exports.addAttributeToParseTreeNode = function(node,name,value) {
|
exports.addAttributeToParseTreeNode = function(node,name,value) {
|
||||||
var attribute = {name: name, type: "string", value: value};
|
var attribute = typeof name === "object" ? name : {name: name, type: "string", value: value};
|
||||||
|
name = attribute.name;
|
||||||
node.attributes = node.attributes || {};
|
node.attributes = node.attributes || {};
|
||||||
|
node.orderedAttributes = node.orderedAttributes || [];
|
||||||
node.attributes[name] = attribute;
|
node.attributes[name] = attribute;
|
||||||
if(node.orderedAttributes) {
|
var foundIndex = -1;
|
||||||
|
$tw.utils.each(node.orderedAttributes,function(attr,index) {
|
||||||
|
if(attr.name === name) {
|
||||||
|
foundIndex = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(foundIndex === -1) {
|
||||||
node.orderedAttributes.push(attribute);
|
node.orderedAttributes.push(attribute);
|
||||||
|
} else {
|
||||||
|
node.orderedAttributes[foundIndex] = attribute;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
108
core/modules/widgets/genesis.js
Normal file
108
core/modules/widgets/genesis.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*\
|
||||||
|
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({filterFn: function(name) {
|
||||||
|
// Only compute our own attributes which start with a single dollar
|
||||||
|
return name.charAt(0) === "$" && name.charAt(1) !== "$";
|
||||||
|
}});
|
||||||
|
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.genesisRemappable = this.getAttribute("$remappable","yes") === "yes";
|
||||||
|
this.genesisNames = this.getAttribute("$names","");
|
||||||
|
this.genesisValues = this.getAttribute("$values","");
|
||||||
|
// Construct parse tree
|
||||||
|
var isElementWidget = this.genesisType.charAt(0) !== "$",
|
||||||
|
nodeType = isElementWidget ? "element" : this.genesisType.substr(1),
|
||||||
|
nodeTag = isElementWidget ? this.genesisType : undefined;
|
||||||
|
var parseTreeNodes = [{
|
||||||
|
type: nodeType,
|
||||||
|
tag: nodeTag,
|
||||||
|
attributes: {},
|
||||||
|
orderedAttributes: [],
|
||||||
|
children: this.parseTreeNode.children || [],
|
||||||
|
isNotRemappable: !this.genesisRemappable
|
||||||
|
}];
|
||||||
|
// Apply explicit attributes
|
||||||
|
$tw.utils.each($tw.utils.getOrderedAttributesFromParseTreeNode(this.parseTreeNode),function(attribute) {
|
||||||
|
var name = attribute.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],$tw.utils.extend({},attribute,{name: name}));
|
||||||
|
});
|
||||||
|
// 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] || "");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
})();
|
@ -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=<<myvar>>/>)</$genesis>
|
||||||
|
<$genesis $type="$let" $$myvar="Kitten">(<$text text=<<$myvar>>/>)</$genesis>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>(Kitten)(Kitten)</p>
|
@ -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=<<myvar>>/>|<$text text=<<other>>/>)</$genesis>
|
||||||
|
<$genesis $type="$let" $names="$myvar $other" $values="Kitten Donkey" $$myvar="Shark">(<$text text=<<$myvar>>/>|<$text text=<<$other>>/>)</$genesis>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p>(Kitten|Donkey)(Kitten|Donkey)</p>
|
14
editions/test/tiddlers/tests/data/genesis-widget/Simple.tid
Normal file
14
editions/test/tiddlers/tests/data/genesis-widget/Simple.tid
Normal file
@ -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 $type="div">Mouse</$genesis>
|
||||||
|
<$genesis $type="div" class="tc-thing" label="Squeak">Mouse</$genesis>
|
||||||
|
+
|
||||||
|
title: ExpectedResult
|
||||||
|
|
||||||
|
<p><div>Mouse</div><div class="tc-thing" label="Squeak">Mouse</div></p>
|
29
editions/tw5.com/tiddlers/widgets/GenesisWidget.tid
Normal file
29
editions/tw5.com/tiddlers/widgets/GenesisWidget.tid
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
caption: genesis
|
||||||
|
created: 20220924140702430
|
||||||
|
modified: 20220924140702430
|
||||||
|
tags: Widgets
|
||||||
|
title: GenesisWidget
|
||||||
|
type: text/vnd.tiddlywiki
|
||||||
|
|
||||||
|
! Introduction
|
||||||
|
|
||||||
|
<<.from-version "5.2.4">> The <<.wlink GenesisWidget>> widget allows the dynamic construction of another widget, where the name and attributes of the new widget can be dynamically determined, without needing to be known in advance.
|
||||||
|
|
||||||
|
! Content and Attributes
|
||||||
|
|
||||||
|
The content of the <<.wlink GenesisWidget>> widget is used as the content of the dynamically created widget.
|
||||||
|
|
||||||
|
|!Attribute |!Description |
|
||||||
|
|$type |The type of widget or element to create (an initial `$` indicates a widget, otherwise an HTML element will be created) |
|
||||||
|
|$names |An optional filter evaluating to the names of a list of attributes 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='<$genesis $type="div" class="tc-thing" label="Squeak">Mouse</$genesis>'/>
|
93
plugins/tiddlywiki/jasmine/run-wiki-based-tests.js
Normal file
93
plugins/tiddlywiki/jasmine/run-wiki-based-tests.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*\
|
||||||
|
title: $:/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js
|
||||||
|
type: application/javascript
|
||||||
|
tags: [[$:/tags/test-spec]]
|
||||||
|
|
||||||
|
Tests the wiki based tests
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var TEST_WIKI_TIDDLER_FILTER = "[type[text/vnd.tiddlywiki-multiple]tag[$:/tags/wiki-test-spec]]";
|
||||||
|
|
||||||
|
var widget = require("$:/core/modules/widgets/widget.js");
|
||||||
|
|
||||||
|
describe("Wiki-based tests", function() {
|
||||||
|
|
||||||
|
// Step through the test tiddlers
|
||||||
|
var tests = $tw.wiki.filterTiddlers(TEST_WIKI_TIDDLER_FILTER);
|
||||||
|
$tw.utils.each(tests,function(title) {
|
||||||
|
var tiddler = $tw.wiki.getTiddler(title);
|
||||||
|
it(tiddler.fields.title + ": " + tiddler.fields.description, function() {
|
||||||
|
// Add our tiddlers
|
||||||
|
var wiki = new $tw.Wiki();
|
||||||
|
wiki.addTiddlers(readMultipleTiddlersTiddler(title));
|
||||||
|
// Complain if we don't have the ouput and expected results
|
||||||
|
if(!wiki.tiddlerExists("Output")) {
|
||||||
|
throw "Missing 'Output' tiddler";
|
||||||
|
}
|
||||||
|
if(!wiki.tiddlerExists("ExpectedResult")) {
|
||||||
|
throw "Missing 'ExpectedResult' tiddler";
|
||||||
|
}
|
||||||
|
// Construct the widget node
|
||||||
|
var text = "{{Output}}\n\n";
|
||||||
|
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||||
|
// Render the widget node to the DOM
|
||||||
|
var wrapper = renderWidgetNode(widgetNode);
|
||||||
|
// Clear changes queue
|
||||||
|
wiki.clearTiddlerEventQueue();
|
||||||
|
// Run the actions if provided
|
||||||
|
if(wiki.tiddlerExists("Actions")) {
|
||||||
|
widgetNode.invokeActionString(wiki.getTiddlerText("Actions"));
|
||||||
|
refreshWidgetNode(widgetNode,wrapper);
|
||||||
|
}
|
||||||
|
// Test the rendering
|
||||||
|
expect(wrapper.innerHTML).toBe(wiki.getTiddlerText("ExpectedResult"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function readMultipleTiddlersTiddler(title) {
|
||||||
|
var rawTiddlers = $tw.wiki.getTiddlerText(title).split("\n+\n");
|
||||||
|
var tiddlers = [];
|
||||||
|
$tw.utils.each(rawTiddlers,function(rawTiddler) {
|
||||||
|
var fields = Object.create(null),
|
||||||
|
split = rawTiddler.split(/\r?\n\r?\n/mg);
|
||||||
|
if(split.length >= 1) {
|
||||||
|
fields = $tw.utils.parseFields(split[0],fields);
|
||||||
|
}
|
||||||
|
if(split.length >= 2) {
|
||||||
|
fields.text = split.slice(1).join("\n\n");
|
||||||
|
}
|
||||||
|
tiddlers.push(fields);
|
||||||
|
});
|
||||||
|
return tiddlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWidgetNode(parser,wiki) {
|
||||||
|
return wiki.makeWidget(parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseText(text,wiki,options) {
|
||||||
|
return wiki.parseText("text/vnd.tiddlywiki",text,options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderWidgetNode(widgetNode) {
|
||||||
|
$tw.fakeDocument.setSequenceNumber(0);
|
||||||
|
var wrapper = $tw.fakeDocument.createElement("div");
|
||||||
|
widgetNode.render(wrapper,null);
|
||||||
|
// console.log(require("util").inspect(wrapper,{depth: 8}));
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshWidgetNode(widgetNode,wrapper) {
|
||||||
|
widgetNode.refresh(widgetNode.wiki.changedTiddlers,wrapper);
|
||||||
|
// console.log(require("util").inspect(wrapper,{depth: 8}));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
Loading…
Reference in New Issue
Block a user