/*\ title: $:/core/modules/widgets/list.js type: application/javascript module-type: widget List and list item widgets \*/ (function(){ /*jslint node: true, browser: true */ /*global $tw: false */ "use strict"; var Widget = require("$:/core/modules/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) { // Initialise the storyviews if they've not been done already if(!this.storyViews) { ListWidget.prototype.storyViews = {}; $tw.modules.applyMethods("storyview",this.storyViews); } // Main initialisation inherited from widget.js 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); // Construct the storyview var StoryView = this.storyViews[this.storyViewName]; this.storyview = StoryView ? new StoryView(this) : null; }; /* 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.variableName = this.getAttribute("variable","currentTiddler"); this.storyViewName = this.getAttribute("storyview"); this.historyTitle = this.getAttribute("history"); // 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); // Clear the last history this.history = []; }; 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(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} ]}]}]; } } // Return the list item return {type: "listitem", itemTitle: title, variableName: this.variableName, 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.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) { this.refreshSelf(); return true; } else { // Handle any changes to the list var hasChanged = this.handleListChanges(changedTiddlers); // Handle any changes to the history stack if(this.historyTitle && changedTiddlers[this.historyTitle]) { this.handleHistoryChanges(); } return hasChanged; } }; /* Handle any changes to the history list */ ListWidget.prototype.handleHistoryChanges = function() { // Get the history data var newHistory = this.wiki.getTiddlerData(this.historyTitle,[]); // Ignore any entries of the history that match the previous history var entry = 0; while(entry < newHistory.length && entry < this.history.length && newHistory[entry].title === this.history[entry].title) { entry++; } // Navigate forwards to each of the new tiddlers while(entry < newHistory.length) { if(this.storyview && this.storyview.navigateTo) { this.storyview.navigateTo(newHistory[entry]); } entry++; } // Update the history this.history = newHistory; }; /* 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 for(t=this.children.length-1; t>=0; t--) { this.removeListItem(t); } var nextSibling = this.findNextSiblingDomNode(); 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=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) { // Create, insert and render the new child widgets var widget = this.makeChildWidget(this.makeItemTemplate(title)); widget.parentDomNode = this.parentDomNode; // Hack to enable findNextSiblingDomNode() to work this.children.splice(index,0,widget); var nextSibling = widget.findNextSiblingDomNode(); widget.render(this.parentDomNode,nextSibling); // Animate the insertion if required if(this.storyview && this.storyview.insert) { this.storyview.insert(widget); } return true; }; /* Remove the specified list item */ ListWidget.prototype.removeListItem = function(index) { var widget = this.children[index]; // Animate the removal if required if(this.storyview && this.storyview.remove) { this.storyview.remove(widget); } else { widget.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(this.parseTreeNode.variableName,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; })();