mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-12-28 02:50:27 +00:00
Add $let widget (#6148)
* $let widget added and tested * Documentation for $let, doc improvements for $vars * let properly avoids refreshing when possible * $let Changes as recommended by others * Removed superfluous super method call Also improved $let test
This commit is contained in:
parent
c099bf9893
commit
2bfe522b72
96
core/modules/widgets/let.js
Normal file
96
core/modules/widgets/let.js
Normal file
@ -0,0 +1,96 @@
|
||||
/*\
|
||||
title: $:/core/modules/widgets/let.js
|
||||
type: application/javascript
|
||||
module-type: widget
|
||||
|
||||
This widget allows defining multiple variables at once, while allowing
|
||||
the later variables to depend upon the earlier ones.
|
||||
|
||||
```
|
||||
\define helloworld() Hello world!
|
||||
<$let currentTiddler="target" value={{!!value}} currentTiddler="different">
|
||||
{{!!value}} will be different from <<value>>
|
||||
</$let>
|
||||
```
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
var LetWidget = function(parseTreeNode,options) {
|
||||
// Initialise
|
||||
this.initialise(parseTreeNode,options);
|
||||
};
|
||||
|
||||
/*
|
||||
Inherit from the base widget class
|
||||
*/
|
||||
LetWidget.prototype = new Widget();
|
||||
|
||||
/*
|
||||
Render this widget into the DOM
|
||||
*/
|
||||
LetWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.parentDomNode = parent;
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
this.renderChildren(parent,nextSibling);
|
||||
};
|
||||
|
||||
LetWidget.prototype.computeAttributes = function() {
|
||||
// Before computing attributes, we must make clear that none of the
|
||||
// existing attributes are staged for lookup, even on a refresh
|
||||
var changedAttributes = {},
|
||||
self = this;
|
||||
this.currentValueFor = Object.create(null);
|
||||
$tw.utils.each(this.parseTreeNode.orderedAttributes,function(attribute,index) {
|
||||
var value = self.computeAttribute(attribute),
|
||||
name = attribute.name;
|
||||
if(name.charAt(0) !== "$") {
|
||||
// Now that it's prepped, we're allowed to look this variable up
|
||||
// when defining later variables
|
||||
self.currentValueFor[name] = value;
|
||||
}
|
||||
});
|
||||
// Run through again, setting variables and looking for differences
|
||||
$tw.utils.each(this.currentValueFor,function(value,name) {
|
||||
if (self.attributes[name] !== value) {
|
||||
self.attributes[name] = value;
|
||||
self.setVariable(name,value);
|
||||
changedAttributes[name] = true;
|
||||
}
|
||||
});
|
||||
return changedAttributes;
|
||||
};
|
||||
|
||||
LetWidget.prototype.getVariableInfo = function(name,options) {
|
||||
// Special handling: If this variable exists in this very $let, we can
|
||||
// use it, but only if it's been staged.
|
||||
if ($tw.utils.hop(this.currentValueFor,name)) {
|
||||
return {
|
||||
text: this.currentValueFor[name]
|
||||
};
|
||||
}
|
||||
return Widget.prototype.getVariableInfo.call(this,name,options);
|
||||
};
|
||||
|
||||
/*
|
||||
Refresh the widget by ensuring our attributes are up to date
|
||||
*/
|
||||
LetWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if($tw.utils.count(changedAttributes) > 0) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
};
|
||||
|
||||
exports["let"] = LetWidget;
|
||||
|
||||
})();
|
@ -29,14 +29,12 @@ var VarsWidget = function(parseTreeNode,options) {
|
||||
/*
|
||||
Inherit from the base widget class
|
||||
*/
|
||||
VarsWidget.prototype = Object.create(Widget.prototype);
|
||||
VarsWidget.prototype = new Widget();
|
||||
|
||||
/*
|
||||
Render this widget into the DOM
|
||||
*/
|
||||
VarsWidget.prototype.render = function(parent,nextSibling) {
|
||||
// Call the constructor
|
||||
Widget.call(this);
|
||||
this.parentDomNode = parent;
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
@ -63,7 +61,7 @@ Refresh the widget by ensuring our attributes are up to date
|
||||
*/
|
||||
VarsWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(Object.keys(changedAttributes).length) {
|
||||
if($tw.utils.count(changedAttributes) > 0) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
|
@ -263,19 +263,9 @@ Compute the current values of the attributes of the widget. Returns a hashmap of
|
||||
*/
|
||||
Widget.prototype.computeAttributes = function() {
|
||||
var changedAttributes = {},
|
||||
self = this,
|
||||
value;
|
||||
self = this;
|
||||
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
|
||||
if(attribute.type === "filtered") {
|
||||
value = self.wiki.filterTiddlers(attribute.filter,self)[0] || "";
|
||||
} else if(attribute.type === "indirect") {
|
||||
value = self.wiki.getTextReference(attribute.textReference,"",self.getVariable("currentTiddler"));
|
||||
} else if(attribute.type === "macro") {
|
||||
value = self.getVariable(attribute.value.name,{params: attribute.value.params});
|
||||
} else { // String attribute
|
||||
value = attribute.value;
|
||||
}
|
||||
// Check whether the attribute has changed
|
||||
var value = self.computeAttribute(attribute);
|
||||
if(self.attributes[name] !== value) {
|
||||
self.attributes[name] = value;
|
||||
changedAttributes[name] = true;
|
||||
@ -284,6 +274,20 @@ Widget.prototype.computeAttributes = function() {
|
||||
return changedAttributes;
|
||||
};
|
||||
|
||||
Widget.prototype.computeAttribute = function(attribute) {
|
||||
var value;
|
||||
if(attribute.type === "filtered") {
|
||||
value = this.wiki.filterTiddlers(attribute.filter,this)[0] || "";
|
||||
} else if(attribute.type === "indirect") {
|
||||
value = this.wiki.getTextReference(attribute.textReference,"",this.getVariable("currentTiddler"));
|
||||
} else if(attribute.type === "macro") {
|
||||
value = this.getVariable(attribute.value.name,{params: attribute.value.params});
|
||||
} else { // String attribute
|
||||
value = attribute.value;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/*
|
||||
Check for the presence of an attribute
|
||||
*/
|
||||
|
@ -247,6 +247,40 @@ describe("Widget module", function() {
|
||||
expect(wrapper.children[0].children[2].sequenceNumber).toBe(4);
|
||||
});
|
||||
|
||||
it("should deal with the let widget", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
wiki.addTiddlers([
|
||||
{title: "TiddlerOne", text: "lookup"},
|
||||
{title: "TiddlerTwo", lookup: "value", newlookup: "value", wrong: "wrong"},
|
||||
{title: "TiddlerThree", text: "wrong", value: "Happy Result", wrong: "ALL WRONG!!"}
|
||||
]);
|
||||
var text="\\define macro() TiddlerThree\n"+
|
||||
"\\define currentTiddler() TiddlerOne\n"+
|
||||
"<$let "+
|
||||
"field={{!!text}} "+
|
||||
"currentTiddler='TiddlerTwo' "+
|
||||
"field={{{ [all[current]get<field>] }}} "+
|
||||
"currentTiddler=<<macro>>>"+
|
||||
"<$transclude field=<<field>>/></$let>";
|
||||
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||
var wrapper = renderWidgetNode(widgetNode);
|
||||
expect(wrapper.innerHTML).toBe("<p>Happy Result</p>");
|
||||
|
||||
// This is important. $Let needs to be aware enough not to let its
|
||||
// own variables interfere with its ability to recognize no change.
|
||||
// Doesn't matter that nothing has changed, we just need to make sure
|
||||
// it recognizes that that its outward facing variables are unchanged
|
||||
// EVEN IF some intermediate variables did change, there's no need to
|
||||
// refresh.
|
||||
wiki.addTiddler({title: "TiddlerOne", text: "newlookup"});
|
||||
expect(widgetNode.refresh({})).toBe(false);
|
||||
|
||||
// But if we make a change that might result in different outfacing
|
||||
// variables, then it should refresh
|
||||
wiki.addTiddler({title: "TiddlerOne", text: "badlookup"});
|
||||
expect(widgetNode.refresh({})).toBe(true);
|
||||
});
|
||||
|
||||
it("should deal with attributes specified as macro invocations", function() {
|
||||
var wiki = new $tw.Wiki();
|
||||
// Construct the widget node
|
||||
|
57
editions/tw5.com/tiddlers/widgets/LetWidget.tid
Normal file
57
editions/tw5.com/tiddlers/widgets/LetWidget.tid
Normal file
@ -0,0 +1,57 @@
|
||||
title: LetWidget
|
||||
created: 20211028115900000
|
||||
tags: Widgets
|
||||
caption: let
|
||||
|
||||
! Introduction
|
||||
|
||||
<<.from-version "5.2.1">> The <<.wid let>> widget allows multiple variables to be set in one operation. In some situations it can result in simpler code than using the more flexible <<.wlink SetWidget>> widget. It differs from the <<.wlink VarsWidget>> widget in that variables you're defining may depend on earlier variables defined within the same <<.wid let>>.
|
||||
|
||||
! Content and Attributes
|
||||
|
||||
The content of the <<.wid let>> widget is the scope for the value assigned to the variable.
|
||||
|
||||
|!Attribute |!Description |
|
||||
|//{attributes not starting with $}// |Each attribute name specifies a variable name. The attribute value is assigned to the variable |
|
||||
|
||||
Attributes are evaluated in the order they are written. Attributes with the same name are allowed. Each time a duplicate attribute is encountered, it will replace the existing value set by the earlier duplicate.
|
||||
|
||||
! Examples
|
||||
|
||||
Consider a case where you need to set multiple variables, where some depend on the evaluation of others.
|
||||
|
||||
Using the <<.wid let>> widget, this situation may be handled in the following way:
|
||||
|
||||
```
|
||||
\define helloworld() Hello world!
|
||||
|
||||
<$let target="MyTiddler" currentTiddler={{{ [<target>prefix[$:/settings/for/]] }}} settings={{!!text}} currentTiddler=<<target>> >
|
||||
The settings for <<currentTiddler>> are: <<settings>>
|
||||
</$let>
|
||||
```
|
||||
|
||||
In contrast, here is the same example using the <<.wid set>> widget:
|
||||
|
||||
```
|
||||
<$set name="target" value="MyTiddler" >
|
||||
<$set name="currentTiddler" value={{{ [<target>prefix[$:/settings/for/]] }}} >
|
||||
<$set name="settings" value={{!!text}} >
|
||||
<$set name="currentTiddler" value=<<target>> >
|
||||
The settings for <<currentTiddler>> are: <<settings>>
|
||||
</$set>
|
||||
</$set>
|
||||
</$set>
|
||||
</$set>
|
||||
```
|
||||
|
||||
! Remarks
|
||||
|
||||
This widget differs from <<.wid vars>> in the following way:
|
||||
|
||||
* Each variable's definition will be immediately available to all proceeding variables in the same let widget. This differs from vars, in which definitions which depend on some variable will always look to the widget's outer scope for a value.
|
||||
|
||||
This widget differs from <<.wid set>> in the following ways:
|
||||
|
||||
* A fallback (also known as "emptyValue") cannot be specified
|
||||
* Filters cannot be used to produce a conditional variable assignment
|
||||
* Variable names must be literal strings
|
@ -6,20 +6,22 @@ caption: vars
|
||||
|
||||
! Introduction
|
||||
|
||||
The ''vars'' widget allows multiple variables to be set in one operation. In some situations it can result in simpler code than using the more flexible SetWidget.
|
||||
The <<.wid vars>> widget allows multiple variables to be set in one operation. In some situations it can result in simpler code than using the more flexible <<.wlink SetWidget>> widget. It differs from the <<.wlink LetWidget>> in that variables cannot interfere with the evaluation of other variables within the same <<.wid vars>>.
|
||||
|
||||
! Content and Attributes
|
||||
|
||||
The content of the `<$vars>` widget is the scope for the value assigned to the variable.
|
||||
The content of the <<.wid vars>> widget is the scope for the value assigned to the variable.
|
||||
|
||||
|!Attribute |!Description |
|
||||
|//{attributes not starting with $}// |Each attribute name specifies a variable name. The attribute value is assigned to the variable |
|
||||
|
||||
Attributes will not interfere with the evaluation of other attributes. So if one attribute sets <<.attr currentTiddler>>, and another attribute uses <<.attr currentTiddler>> in its evaluation, it will use the value of <<.attr currentTiddler>> that exists outside the widget's scope.
|
||||
|
||||
! Examples
|
||||
|
||||
Consider a case where you need to set multiple variables.
|
||||
|
||||
Using the `<$vars>` widget, this situation may be handled in the following way:
|
||||
Using the <<.wid vars>> widget, this situation may be handled in the following way:
|
||||
|
||||
```
|
||||
\define helloworld() Hello world!
|
||||
@ -29,7 +31,7 @@ Using the `<$vars>` widget, this situation may be handled in the following way:
|
||||
</$vars>
|
||||
```
|
||||
|
||||
In contrast, here is the same example using the `<$set>` widget:
|
||||
In contrast, here is the same example using the <<.wid set>> widget:
|
||||
|
||||
```
|
||||
<$set name="greeting" value="Hi" >
|
||||
@ -43,7 +45,7 @@ In contrast, here is the same example using the `<$set>` widget:
|
||||
|
||||
! Remarks
|
||||
|
||||
It should be noted that this widget differs from the set widget in the following ways:
|
||||
It should be noted that this widget differs from the <<.wid set>> widget in the following ways:
|
||||
|
||||
* A fallback (also known as "emptyValue") cannot be specified
|
||||
* Filters cannot be used to produce a conditional variable assignement
|
||||
|
Loading…
Reference in New Issue
Block a user