/*\ title: $:/core/modules/widgets/list/list.js type: application/javascript module-type: widget The list widget \*/ (function(){ /*jslint node: true, browser: true */ /*global $tw: false */ "use strict"; var ListWidget = function(renderer) { // Save state this.renderer = renderer; // Initialise the listviews if they've not been done already if(!this.listViews) { ListWidget.prototype.listViews = {}; $tw.modules.applyMethods("listview",this.listViews); } // Generate widget elements this.generate(); }; /* These types are shorthands for particular filters */ var typeMappings = { all: "[!is[system]sort[title]]", recent: "[!is[system]sort[modified]]", missing: "[is[missing]sort[title]]", orphans: "[is[orphan]sort[title]]", system: "[is[system]sort[title]]" }; ListWidget.prototype.generate = function() { // Get our attributes this.itemClass = this.renderer.getAttribute("itemClass"); this.template = this.renderer.getAttribute("template"); this.editTemplate = this.renderer.getAttribute("editTemplate"); this.emptyMessage = this.renderer.getAttribute("emptyMessage"); this["class"] = this.renderer.getAttribute("class"); // Set up the classes var classes = ["tw-list-frame"]; if(this["class"]) { $tw.utils.pushTop(classes,this["class"]); } // Get the list of tiddlers object this.getTiddlerList(); // Create the list var listMembers = []; if(this.list.length === 0) { // Check for an empty list listMembers = [this.getEmptyMessage()]; } else { // Create the list for(var t=0; t 0) { // Use our content as the template templateTree = this.renderer.parseTreeNode.children; } else { // Use default content templateTree = [{ type: "element", tag: "$view", attributes: { field: {type: "string", value: "title"}, format: {type: "string", value: "link"} } }]; } } // Create the transclude widget return { type: "element", tag: "$transclude", isBlock: this.renderer.parseTreeNode.isBlock, attributes: { target: {type: "string", value: title}, template: {type: "string", value: template} }, children: templateTree }; }; /* Remove a list element from the list, along with the attendant DOM nodes */ ListWidget.prototype.removeListElement = function(index) { // Get the list element var listElement = this.children[index]; // Invoke the listview to animate the removal if(this.listview && this.listview.remove) { if(!this.listview.remove(index)) { // Only delete the DOM element if the listview.remove() returned false listElement.domNode.parentNode.removeChild(listElement.domNode); } } else { // Always remove the DOM node if we didn't invoke the listview listElement.domNode.parentNode.removeChild(listElement.domNode); } // Then delete the actual renderer node this.children.splice(index,1); }; /* Return the index of the list element that corresponds to a particular title startIndex: index to start search (use zero to search from the top) title: tiddler title to seach for */ ListWidget.prototype.findListElementByTitle = function(startIndex,title) { while(startIndex < this.children.length) { if(this.children[startIndex].widget.children[0].attributes.target === title) { return startIndex; } startIndex++; } return undefined; }; ListWidget.prototype.postRenderInDom = function() { this.listview = this.chooseListView(); this.history = []; }; /* Select the appropriate list viewer */ ListWidget.prototype.chooseListView = function() { // Instantiate the list view var listviewName = this.renderer.getAttribute("listview"); var ListView = this.listViews[listviewName]; return ListView ? new ListView(this) : null; }; ListWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { // Reexecute the widget if any of our attributes have changed if(changedAttributes.itemClass || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.type || changedAttributes.filter || changedAttributes.template || changedAttributes.history || changedAttributes.listview) { // Regenerate and rerender the widget and replace the existing DOM node this.generate(); var oldDomNode = this.renderer.domNode, newDomNode = this.renderer.renderInDom(); oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode); } else { // Handle any changes to the list, and refresh any nodes we're reusing this.handleListChanges(changedTiddlers); // Update the history list var history = this.renderer.getAttribute("history"); if(history && changedTiddlers[history]) { this.handleHistoryChanges(); } } }; ListWidget.prototype.handleListChanges = function(changedTiddlers) { var t, prevListLength = this.list.length, self = this; // Get the list of tiddlers, having saved the previous length this.getTiddlerList(); // Check if the list is empty if(this.list.length === 0) { // Check if it was empty before if(prevListLength === 0) { // If so, just refresh the empty message $tw.utils.each(this.children,function(node) { if(node.refreshInDom) { node.refreshInDom(changedTiddlers); } }); return; } else { // If the list wasn't empty before, empty it for(t=prevListLength-1; t>=0; t--) { this.removeListElement(t); } // Insert the empty message this.children = this.renderer.renderTree.createRenderers(this.renderer,[this.getEmptyMessage()]); $tw.utils.each(this.children,function(node) { if(node.renderInDom) { self.renderer.domNode.appendChild(node.renderInDom()); } }); return; } } else { // If it is not empty now, but was empty previously, then remove the empty message if(prevListLength === 0) { this.removeListElement(0); } } // Step through the list and adjust our child list elements appropriately for(t=0; t=t; n--) { this.removeListElement(n); } // Refresh the node we're reusing this.children[t].refreshInDom(changedTiddlers); } } // Remove any left over elements for(t=this.children.length-1; t>=this.list.length; t--) { this.removeListElement(t); } }; /* Handle any changes to the history list */ ListWidget.prototype.handleHistoryChanges = function() { // Get the history data var historyAtt = this.renderer.getAttribute("history"), newHistory = this.renderer.renderTree.wiki.getTiddlerData(historyAtt,[]); // 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.listview && this.listview.navigateTo) { this.listview.navigateTo(newHistory[entry]); } entry++; } // Update the history this.history = newHistory; }; exports.list = ListWidget; })();