kopia lustrzana https://github.com/miklobit/TiddlyWiki5
Extend list widget with "index" attribute (#5611)
* Extend list widget with "index" attribute * Fix refreshing bug * Clarify performance notenew-json-store-area
rodzic
a725da2b39
commit
85ba7ac041
|
@ -61,6 +61,7 @@ ListWidget.prototype.execute = function() {
|
||||||
this.template = this.getAttribute("template");
|
this.template = this.getAttribute("template");
|
||||||
this.editTemplate = this.getAttribute("editTemplate");
|
this.editTemplate = this.getAttribute("editTemplate");
|
||||||
this.variableName = this.getAttribute("variable","currentTiddler");
|
this.variableName = this.getAttribute("variable","currentTiddler");
|
||||||
|
this.indexName = this.getAttribute("index");
|
||||||
this.storyViewName = this.getAttribute("storyview");
|
this.storyViewName = this.getAttribute("storyview");
|
||||||
this.historyTitle = this.getAttribute("history");
|
this.historyTitle = this.getAttribute("history");
|
||||||
// Compose the list elements
|
// Compose the list elements
|
||||||
|
@ -72,7 +73,7 @@ ListWidget.prototype.execute = function() {
|
||||||
members = this.getEmptyMessage();
|
members = this.getEmptyMessage();
|
||||||
} else {
|
} else {
|
||||||
$tw.utils.each(this.list,function(title,index) {
|
$tw.utils.each(this.list,function(title,index) {
|
||||||
members.push(self.makeItemTemplate(title));
|
members.push(self.makeItemTemplate(title,index));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Construct the child widgets
|
// Construct the child widgets
|
||||||
|
@ -105,7 +106,7 @@ ListWidget.prototype.getEmptyMessage = function() {
|
||||||
/*
|
/*
|
||||||
Compose the template for a list item
|
Compose the template for a list item
|
||||||
*/
|
*/
|
||||||
ListWidget.prototype.makeItemTemplate = function(title) {
|
ListWidget.prototype.makeItemTemplate = function(title,index) {
|
||||||
// Check if the tiddler is a draft
|
// Check if the tiddler is a draft
|
||||||
var tiddler = this.wiki.getTiddler(title),
|
var tiddler = this.wiki.getTiddler(title),
|
||||||
isDraft = tiddler && tiddler.hasField("draft.of"),
|
isDraft = tiddler && tiddler.hasField("draft.of"),
|
||||||
|
@ -128,7 +129,14 @@ ListWidget.prototype.makeItemTemplate = function(title) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Return the list item
|
// Return the list item
|
||||||
return {type: "listitem", itemTitle: title, variableName: this.variableName, children: templateTree};
|
var parseTreeNode = {type: "listitem", itemTitle: title, variableName: this.variableName, children: templateTree};
|
||||||
|
if(this.indexName) {
|
||||||
|
parseTreeNode.index = index.toString();
|
||||||
|
parseTreeNode.indexName = this.indexName;
|
||||||
|
parseTreeNode.isFirst = index === 0;
|
||||||
|
parseTreeNode.isLast = index === this.list.length - 1;
|
||||||
|
}
|
||||||
|
return parseTreeNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -142,7 +150,7 @@ ListWidget.prototype.refresh = function(changedTiddlers) {
|
||||||
this.storyview.refreshStart(changedTiddlers,changedAttributes);
|
this.storyview.refreshStart(changedTiddlers,changedAttributes);
|
||||||
}
|
}
|
||||||
// Completely refresh if any of our attributes have changed
|
// Completely refresh if any of our attributes have changed
|
||||||
if(changedAttributes.filter || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) {
|
if(changedAttributes.filter || changedAttributes.variable || changedAttributes.index || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) {
|
||||||
this.refreshSelf();
|
this.refreshSelf();
|
||||||
result = true;
|
result = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -211,23 +219,41 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
|
||||||
this.removeChildDomNodes();
|
this.removeChildDomNodes();
|
||||||
this.children = [];
|
this.children = [];
|
||||||
}
|
}
|
||||||
// Cycle through the list, inserting and removing list items as needed
|
// If we are providing an index variable then we must refresh the items, otherwise we can rearrange them
|
||||||
var hasRefreshed = false;
|
var hasRefreshed = false,t;
|
||||||
for(var t=0; t<this.list.length; t++) {
|
if(this.indexName) {
|
||||||
var index = this.findListItem(t,this.list[t]);
|
// Cycle through the list and remove and re-insert the first item that has changed, and all the remaining items
|
||||||
if(index === undefined) {
|
for(t=0; t<this.list.length; t++) {
|
||||||
// The list item must be inserted
|
if(hasRefreshed || !this.children[t] || this.children[t].parseTreeNode.itemTitle !== this.list[t]) {
|
||||||
this.insertListItem(t,this.list[t]);
|
if(this.children[t]) {
|
||||||
hasRefreshed = true;
|
this.removeListItem(t);
|
||||||
} else {
|
}
|
||||||
// There are intervening list items that must be removed
|
this.insertListItem(t,this.list[t]);
|
||||||
for(var n=index-1; n>=t; n--) {
|
|
||||||
this.removeListItem(n);
|
|
||||||
hasRefreshed = true;
|
hasRefreshed = true;
|
||||||
|
} else {
|
||||||
|
// Refresh the item we're reusing
|
||||||
|
var refreshed = this.children[t].refresh(changedTiddlers);
|
||||||
|
hasRefreshed = hasRefreshed || refreshed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Cycle through the list, inserting and removing list items as needed
|
||||||
|
for(t=0; t<this.list.length; t++) {
|
||||||
|
var index = this.findListItem(t,this.list[t]);
|
||||||
|
if(index === undefined) {
|
||||||
|
// The list item must be inserted
|
||||||
|
this.insertListItem(t,this.list[t]);
|
||||||
|
hasRefreshed = true;
|
||||||
|
} else {
|
||||||
|
// There are intervening list items that must be removed
|
||||||
|
for(var n=index-1; n>=t; n--) {
|
||||||
|
this.removeListItem(n);
|
||||||
|
hasRefreshed = true;
|
||||||
|
}
|
||||||
|
// Refresh the item we're reusing
|
||||||
|
var refreshed = this.children[t].refresh(changedTiddlers);
|
||||||
|
hasRefreshed = hasRefreshed || refreshed;
|
||||||
}
|
}
|
||||||
// Refresh the item we're reusing
|
|
||||||
var refreshed = this.children[t].refresh(changedTiddlers);
|
|
||||||
hasRefreshed = hasRefreshed || refreshed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove any left over items
|
// Remove any left over items
|
||||||
|
@ -257,7 +283,7 @@ Insert a new list item at the specified index
|
||||||
*/
|
*/
|
||||||
ListWidget.prototype.insertListItem = function(index,title) {
|
ListWidget.prototype.insertListItem = function(index,title) {
|
||||||
// Create, insert and render the new child widgets
|
// Create, insert and render the new child widgets
|
||||||
var widget = this.makeChildWidget(this.makeItemTemplate(title));
|
var widget = this.makeChildWidget(this.makeItemTemplate(title,index));
|
||||||
widget.parentDomNode = this.parentDomNode; // Hack to enable findNextSiblingDomNode() to work
|
widget.parentDomNode = this.parentDomNode; // Hack to enable findNextSiblingDomNode() to work
|
||||||
this.children.splice(index,0,widget);
|
this.children.splice(index,0,widget);
|
||||||
var nextSibling = widget.findNextSiblingDomNode();
|
var nextSibling = widget.findNextSiblingDomNode();
|
||||||
|
@ -311,6 +337,11 @@ Compute the internal state of the widget
|
||||||
ListItemWidget.prototype.execute = function() {
|
ListItemWidget.prototype.execute = function() {
|
||||||
// Set the current list item title
|
// Set the current list item title
|
||||||
this.setVariable(this.parseTreeNode.variableName,this.parseTreeNode.itemTitle);
|
this.setVariable(this.parseTreeNode.variableName,this.parseTreeNode.itemTitle);
|
||||||
|
if(this.parseTreeNode.indexName) {
|
||||||
|
this.setVariable(this.parseTreeNode.indexName,this.parseTreeNode.index);
|
||||||
|
this.setVariable(this.parseTreeNode.indexName + "-first",this.parseTreeNode.isFirst ? "yes" : "no");
|
||||||
|
this.setVariable(this.parseTreeNode.indexName + "-last",this.parseTreeNode.isLast ? "yes" : "no");
|
||||||
|
}
|
||||||
// Construct the child widgets
|
// Construct the child widgets
|
||||||
this.makeChildWidgets();
|
this.makeChildWidgets();
|
||||||
};
|
};
|
||||||
|
|
|
@ -350,6 +350,123 @@ describe("Widget module", function() {
|
||||||
expect(wrapper.children[0].children[4].sequenceNumber).toBe(5);
|
expect(wrapper.children[0].children[4].sequenceNumber).toBe(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("should deal with the list widget using an index variable", function() {
|
||||||
|
var wiki = new $tw.Wiki();
|
||||||
|
// Add some tiddlers
|
||||||
|
wiki.addTiddlers([
|
||||||
|
{title: "TiddlerOne", text: "Jolly Old World"},
|
||||||
|
{title: "TiddlerTwo", text: "Worldly Old Jelly"},
|
||||||
|
{title: "TiddlerThree", text: "Golly Gosh"},
|
||||||
|
{title: "TiddlerFour", text: "Lemon Squash"}
|
||||||
|
]);
|
||||||
|
// Construct the widget node
|
||||||
|
var text = "<$list index='index'><$view field='text'/><$text text=<<index>>/><$text text=<<index-first>>/><$text text=<<index-last>>/></$list>";
|
||||||
|
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
|
||||||
|
// Render the widget node to the DOM
|
||||||
|
var wrapper = renderWidgetNode(widgetNode);
|
||||||
|
// Test the rendering
|
||||||
|
expect(wrapper.innerHTML).toBe("<p>Lemon Squash0yesnoJolly Old World1nonoGolly Gosh2nonoWorldly Old Jelly3noyes</p>");
|
||||||
|
// Test the sequence numbers in the DOM
|
||||||
|
expect(wrapper.sequenceNumber).toBe(0);
|
||||||
|
expect(wrapper.children[0].sequenceNumber).toBe(1);
|
||||||
|
expect(wrapper.children[0].children[0].sequenceNumber).toBe(2);
|
||||||
|
expect(wrapper.children[0].children[1].sequenceNumber).toBe(3);
|
||||||
|
expect(wrapper.children[0].children[2].sequenceNumber).toBe(4);
|
||||||
|
expect(wrapper.children[0].children[3].sequenceNumber).toBe(5);
|
||||||
|
expect(wrapper.children[0].children[4].sequenceNumber).toBe(6);
|
||||||
|
expect(wrapper.children[0].children[5].sequenceNumber).toBe(7);
|
||||||
|
expect(wrapper.children[0].children[6].sequenceNumber).toBe(8);
|
||||||
|
expect(wrapper.children[0].children[7].sequenceNumber).toBe(9);
|
||||||
|
expect(wrapper.children[0].children[8].sequenceNumber).toBe(10);
|
||||||
|
expect(wrapper.children[0].children[9].sequenceNumber).toBe(11);
|
||||||
|
expect(wrapper.children[0].children[10].sequenceNumber).toBe(12);
|
||||||
|
expect(wrapper.children[0].children[11].sequenceNumber).toBe(13);
|
||||||
|
expect(wrapper.children[0].children[12].sequenceNumber).toBe(14);
|
||||||
|
expect(wrapper.children[0].children[13].sequenceNumber).toBe(15);
|
||||||
|
expect(wrapper.children[0].children[14].sequenceNumber).toBe(16);
|
||||||
|
expect(wrapper.children[0].children[15].sequenceNumber).toBe(17);
|
||||||
|
// Add another tiddler
|
||||||
|
wiki.addTiddler({title: "TiddlerFive", text: "Jalapeno Peppers"});
|
||||||
|
// Refresh
|
||||||
|
refreshWidgetNode(widgetNode,wrapper,["TiddlerFive"]);
|
||||||
|
// Test the refreshing
|
||||||
|
expect(wrapper.innerHTML).toBe("<p>Jalapeno Peppers0yesnoLemon Squash1nonoJolly Old World2nonoGolly Gosh3nonoWorldly Old Jelly4noyes</p>");
|
||||||
|
// Test the sequence numbers in the DOM
|
||||||
|
expect(wrapper.sequenceNumber).toBe(0);
|
||||||
|
expect(wrapper.children[0].sequenceNumber).toBe(1);
|
||||||
|
expect(wrapper.children[0].children[0].sequenceNumber).toBe(18);
|
||||||
|
expect(wrapper.children[0].children[1].sequenceNumber).toBe(19);
|
||||||
|
expect(wrapper.children[0].children[2].sequenceNumber).toBe(20);
|
||||||
|
expect(wrapper.children[0].children[3].sequenceNumber).toBe(21);
|
||||||
|
expect(wrapper.children[0].children[4].sequenceNumber).toBe(22);
|
||||||
|
expect(wrapper.children[0].children[5].sequenceNumber).toBe(23);
|
||||||
|
expect(wrapper.children[0].children[6].sequenceNumber).toBe(24);
|
||||||
|
expect(wrapper.children[0].children[7].sequenceNumber).toBe(25);
|
||||||
|
expect(wrapper.children[0].children[8].sequenceNumber).toBe(26);
|
||||||
|
expect(wrapper.children[0].children[9].sequenceNumber).toBe(27);
|
||||||
|
expect(wrapper.children[0].children[10].sequenceNumber).toBe(28);
|
||||||
|
expect(wrapper.children[0].children[11].sequenceNumber).toBe(29);
|
||||||
|
expect(wrapper.children[0].children[12].sequenceNumber).toBe(30);
|
||||||
|
expect(wrapper.children[0].children[13].sequenceNumber).toBe(31);
|
||||||
|
expect(wrapper.children[0].children[14].sequenceNumber).toBe(32);
|
||||||
|
expect(wrapper.children[0].children[15].sequenceNumber).toBe(33);
|
||||||
|
expect(wrapper.children[0].children[16].sequenceNumber).toBe(34);
|
||||||
|
expect(wrapper.children[0].children[17].sequenceNumber).toBe(35);
|
||||||
|
expect(wrapper.children[0].children[18].sequenceNumber).toBe(36);
|
||||||
|
expect(wrapper.children[0].children[19].sequenceNumber).toBe(37);
|
||||||
|
// Remove a tiddler
|
||||||
|
wiki.deleteTiddler("TiddlerThree");
|
||||||
|
// Refresh
|
||||||
|
refreshWidgetNode(widgetNode,wrapper,["TiddlerThree"]);
|
||||||
|
// Test the refreshing
|
||||||
|
expect(wrapper.innerHTML).toBe("<p>Jalapeno Peppers0yesnoLemon Squash1nonoJolly Old World2nonoWorldly Old Jelly3noyes</p>");
|
||||||
|
// Test the sequence numbers in the DOM
|
||||||
|
expect(wrapper.sequenceNumber).toBe(0);
|
||||||
|
expect(wrapper.children[0].sequenceNumber).toBe(1);
|
||||||
|
expect(wrapper.children[0].children[0].sequenceNumber).toBe(18);
|
||||||
|
expect(wrapper.children[0].children[1].sequenceNumber).toBe(19);
|
||||||
|
expect(wrapper.children[0].children[2].sequenceNumber).toBe(20);
|
||||||
|
expect(wrapper.children[0].children[3].sequenceNumber).toBe(21);
|
||||||
|
expect(wrapper.children[0].children[4].sequenceNumber).toBe(22);
|
||||||
|
expect(wrapper.children[0].children[5].sequenceNumber).toBe(23);
|
||||||
|
expect(wrapper.children[0].children[6].sequenceNumber).toBe(24);
|
||||||
|
expect(wrapper.children[0].children[7].sequenceNumber).toBe(25);
|
||||||
|
expect(wrapper.children[0].children[8].sequenceNumber).toBe(26);
|
||||||
|
expect(wrapper.children[0].children[9].sequenceNumber).toBe(27);
|
||||||
|
expect(wrapper.children[0].children[10].sequenceNumber).toBe(28);
|
||||||
|
expect(wrapper.children[0].children[11].sequenceNumber).toBe(29);
|
||||||
|
expect(wrapper.children[0].children[12].sequenceNumber).toBe(38);
|
||||||
|
expect(wrapper.children[0].children[13].sequenceNumber).toBe(39);
|
||||||
|
expect(wrapper.children[0].children[14].sequenceNumber).toBe(40);
|
||||||
|
expect(wrapper.children[0].children[15].sequenceNumber).toBe(41);
|
||||||
|
// Add it back a tiddler
|
||||||
|
wiki.addTiddler({title: "TiddlerThree", text: "Something"});
|
||||||
|
// Refresh
|
||||||
|
refreshWidgetNode(widgetNode,wrapper,["TiddlerThree"]);
|
||||||
|
// Test the refreshing
|
||||||
|
expect(wrapper.innerHTML).toBe("<p>Jalapeno Peppers0yesnoLemon Squash1nonoJolly Old World2nonoSomething3nonoWorldly Old Jelly4noyes</p>");
|
||||||
|
// Test the sequence numbers in the DOM
|
||||||
|
expect(wrapper.sequenceNumber).toBe(0);
|
||||||
|
expect(wrapper.children[0].sequenceNumber).toBe(1);
|
||||||
|
expect(wrapper.children[0].children[0].sequenceNumber).toBe(18);
|
||||||
|
expect(wrapper.children[0].children[1].sequenceNumber).toBe(19);
|
||||||
|
expect(wrapper.children[0].children[2].sequenceNumber).toBe(20);
|
||||||
|
expect(wrapper.children[0].children[3].sequenceNumber).toBe(21);
|
||||||
|
expect(wrapper.children[0].children[4].sequenceNumber).toBe(22);
|
||||||
|
expect(wrapper.children[0].children[5].sequenceNumber).toBe(23);
|
||||||
|
expect(wrapper.children[0].children[6].sequenceNumber).toBe(24);
|
||||||
|
expect(wrapper.children[0].children[7].sequenceNumber).toBe(25);
|
||||||
|
expect(wrapper.children[0].children[8].sequenceNumber).toBe(26);
|
||||||
|
expect(wrapper.children[0].children[9].sequenceNumber).toBe(27);
|
||||||
|
expect(wrapper.children[0].children[10].sequenceNumber).toBe(28);
|
||||||
|
expect(wrapper.children[0].children[11].sequenceNumber).toBe(29);
|
||||||
|
expect(wrapper.children[0].children[12].sequenceNumber).toBe(42);
|
||||||
|
expect(wrapper.children[0].children[13].sequenceNumber).toBe(43);
|
||||||
|
expect(wrapper.children[0].children[14].sequenceNumber).toBe(44);
|
||||||
|
expect(wrapper.children[0].children[15].sequenceNumber).toBe(45);
|
||||||
|
});
|
||||||
|
|
||||||
it("should deal with the list widget followed by other widgets", function() {
|
it("should deal with the list widget followed by other widgets", function() {
|
||||||
var wiki = new $tw.Wiki();
|
var wiki = new $tw.Wiki();
|
||||||
// Add some tiddlers
|
// Add some tiddlers
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
caption: list
|
caption: list
|
||||||
created: 20131024141900000
|
created: 20131024141900000
|
||||||
modified: 20190608162410684
|
modified: 20210416175333981
|
||||||
tags: Widgets Lists
|
tags: Widgets Lists
|
||||||
title: ListWidget
|
title: ListWidget
|
||||||
type: text/vnd.tiddlywiki
|
type: text/vnd.tiddlywiki
|
||||||
|
@ -82,10 +82,43 @@ The action of the list widget depends on the results of the filter combined with
|
||||||
|template |The title of a template tiddler for transcluding each tiddler in the list. When no template is specified, the body of the ListWidget serves as the item template. With no body, a simple link to the tiddler is returned. |
|
|template |The title of a template tiddler for transcluding each tiddler in the list. When no template is specified, the body of the ListWidget serves as the item template. With no body, a simple link to the tiddler is returned. |
|
||||||
|editTemplate |An alternative template to use for [[DraftTiddlers|DraftMechanism]] in edit mode |
|
|editTemplate |An alternative template to use for [[DraftTiddlers|DraftMechanism]] in edit mode |
|
||||||
|variable |The name for a [[variable|Variables]] in which the title of each listed tiddler is stored. Defaults to ''currentTiddler'' |
|
|variable |The name for a [[variable|Variables]] in which the title of each listed tiddler is stored. Defaults to ''currentTiddler'' |
|
||||||
|
|index |<<.from-version "5.1.24">> Optional name for a [[variable|Variables]] in which the numeric index of each listed tiddler is stored (see below) |
|
||||||
|emptyMessage |Message to be displayed when the list is empty |
|
|emptyMessage |Message to be displayed when the list is empty |
|
||||||
|storyview |Optional name of module responsible for animating/processing the list |
|
|storyview |Optional name of module responsible for animating/processing the list |
|
||||||
|history |The title of the tiddler containing the navigation history |
|
|history |The title of the tiddler containing the navigation history |
|
||||||
|
|
||||||
|
!! `index` attribute
|
||||||
|
|
||||||
|
The optional `index` attribute specifies the name of a variable to hold the numeric index of the current item in the list.
|
||||||
|
|
||||||
|
Two additional variables are also set to indicate the first and last items in the list:
|
||||||
|
|
||||||
|
* `<index-variable-name>-first` is set to `yes` for the first entry in the list, `no` for the others
|
||||||
|
* `<index-variable-name>-last` is set to `yes` for the last entry in the list, `no` for the others
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
<$list filter="[tag[About]sort[title]]" index="index">
|
||||||
|
<div>
|
||||||
|
<<index>>: ''<$text text=<<currentTiddler>>/>'' (is first: <<index-first>>, is last: <<index-last>>)
|
||||||
|
</div>
|
||||||
|
</$list>
|
||||||
|
```
|
||||||
|
|
||||||
|
Displays as:
|
||||||
|
|
||||||
|
<<<
|
||||||
|
<$list filter="[tag[About]sort[title]]" index="index">
|
||||||
|
<div>
|
||||||
|
<<index>>: ''<$text text=<<currentTiddler>>/>'' (is first: <<index-first>>, is last: <<index-last>>)
|
||||||
|
</div>
|
||||||
|
</$list>
|
||||||
|
<<<
|
||||||
|
|
||||||
|
Note that using the `index` attribute degrades the performance of the list widget because it prevents the optimisation of refreshes by moving list items around instead of rerendering them.
|
||||||
|
|
||||||
!! Edit mode
|
!! Edit mode
|
||||||
|
|
||||||
The `<$list>` widget can optionally render draft tiddlers through a different template to handle editing, see DraftMechanism.
|
The `<$list>` widget can optionally render draft tiddlers through a different template to handle editing, see DraftMechanism.
|
||||||
|
|
Ładowanie…
Reference in New Issue