1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-09-11 23:09:42 +00:00
TiddlyWiki5/core/modules/new_widgets/list.js
Jeremy Ruston 9e3618bdcf Rename the 'title' attributes of various widgets to 'tiddler'
The change is to avoid confusion with the HTML 'title' attribute. The
name 'tiddler' better emphasises the purpose of the attribute, too.
2013-10-30 13:36:44 +00:00

261 lines
7.3 KiB
JavaScript
Executable File

/*\
title: $:/core/modules/new_widgets/list.js
type: application/javascript
module-type: new_widget
List and list item widgets
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/new_widgets/widget.js").widget;
/*
The list widget creates list element sub-widgets that reach back into the list widget for their configuration
*/
var ListWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
ListWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
ListWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
ListWidget.prototype.execute = function() {
// Get our attributes
this.template = this.getAttribute("template");
this.editTemplate = this.getAttribute("editTemplate");
this.preserveCurrentTiddler = this.getAttribute("preserveCurrentTiddler","no") === "yes";
// Compose the list elements
this.list = this.getTiddlerList();
var members = [],
self = this;
// Check for an empty list
if(this.list.length === 0) {
members = this.getEmptyMessage();
} else {
$tw.utils.each(this.list,function(title,index) {
members.push(self.makeItemTemplate(title));
});
}
// Construct the child widgets
this.makeChildWidgets(members);
};
ListWidget.prototype.getTiddlerList = function() {
var defaultFilter = "[!is[system]sort[title]]";
return this.wiki.filterTiddlers(this.getAttribute("filter",defaultFilter),this.getVariable("currentTiddler"));
};
ListWidget.prototype.getEmptyMessage = function() {
var emptyMessage = this.getAttribute("emptyMessage",""),
parser = this.wiki.new_parseText("text/vnd.tiddlywiki",emptyMessage,{parseAsInline: true});
if(parser) {
return parser.tree;
} else {
return [];
}
};
/*
Compose the template for a list item
*/
ListWidget.prototype.makeItemTemplate = function(title) {
// Check if the tiddler is a draft
var tiddler = this.wiki.getTiddler(title),
isDraft = tiddler && tiddler.hasField("draft.of"),
template = this.template,
templateTree;
if(isDraft && this.editTemplate) {
template = this.editTemplate;
}
// Compose the transclusion of the template
if(this.hasAttribute("hackTemplate")) {
templateTree = [{type: "transclude", attributes: {tiddler: {type: "string", value: title}}}];
} else {
if(template) {
templateTree = [{type: "transclude", attributes: {tiddler: {type: "string", value: template}}}];
} else {
if(this.parseTreeNode.children && this.parseTreeNode.children.length > 0) {
templateTree = this.parseTreeNode.children;
} else {
// Default template is a link to the title
templateTree = [{type: "element", tag: this.parseTreeNode.isBlock ? "div" : "span", children: [{type: "link", attributes: {to: {type: "string", value: title}}, children: [
{type: "text", text: title}
]}]}];
}
}
if(!this.hasAttribute("hackCurrentTiddler")) {
templateTree = [{type: "tiddler", attributes: {tiddler: {type: "string", value: title}}, children: templateTree}]
}
}
// Return the list item
return {type: "listitem", itemTitle: title, children: templateTree};
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ListWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
// Completely refresh if any of our attributes have changed
if(changedAttributes.filter || changedAttributes.preserveCurrentTiddler) {
this.refreshSelf();
return true;
} else {
// Handle any changes to the list
return this.handleListChanges(changedTiddlers);
}
};
/*
Process any changes to the list
*/
ListWidget.prototype.handleListChanges = function(changedTiddlers) {
// Get the new list
var prevList = this.list;
this.list = this.getTiddlerList();
// Check for an empty list
if(this.list.length === 0) {
// Check if it was empty before
if(prevList.length === 0) {
// If so, just refresh the empty message
return this.refreshChildren(changedTiddlers);
} else {
// Replace the previous content with the empty message
var nextSibling = this.findNextSiblingDomNode();
this.removeChildDomNodes();
this.makeChildWidgets(this.getEmptyMessage());
this.renderChildren(this.parentDomNode,nextSibling);
return true;
}
} else {
// If the list was empty then we need to remove the empty message
if(prevList.length === 0) {
this.removeChildDomNodes();
this.children = [];
}
// Cycle through the list, inserting and removing list items as needed
var hasRefreshed = false;
for(var 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;
}
}
// Remove any left over items
for(t=this.children.length-1; t>=this.list.length; t--) {
this.removeListItem(t);
hasRefreshed = true;
}
return hasRefreshed;
}
};
/*
Find the list item with a given title, starting from a specified position
*/
ListWidget.prototype.findListItem = function(startIndex,title) {
while(startIndex < this.children.length) {
if(this.children[startIndex].parseTreeNode.itemTitle === title) {
return startIndex;
}
startIndex++;
}
return undefined;
};
/*
Insert a new list item at the specified index
*/
ListWidget.prototype.insertListItem = function(index,title) {
var newItem = this.makeChildWidget(this.makeItemTemplate(title));
newItem.parentDomNode = this.parentDomNode; // Hack to enable findNextSiblingDomNode() to work
this.children.splice(index,0,newItem);
var nextSibling = newItem.findNextSiblingDomNode();
newItem.render(this.parentDomNode,nextSibling);
return true;
};
/*
Remvoe the specified list item
*/
ListWidget.prototype.removeListItem = function(index) {
// Remove the DOM nodes owned by this item
this.children[index].removeChildDomNodes();
// Remove the child widget
this.children.splice(index,1);
};
exports.list = ListWidget;
var ListItemWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
ListItemWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
ListItemWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
ListItemWidget.prototype.execute = function() {
// Set the current list item title
this.setVariable("listItem",this.parseTreeNode.itemTitle);
// Construct the child widgets
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ListItemWidget.prototype.refresh = function(changedTiddlers) {
return this.refreshChildren(changedTiddlers);
};
exports.listitem = ListItemWidget;
})();