diff --git a/core/modules/new_widgets/list.js b/core/modules/new_widgets/list.js index 522c1f104..ad654033f 100755 --- a/core/modules/new_widgets/list.js +++ b/core/modules/new_widgets/list.js @@ -19,6 +19,12 @@ The list widget creates list element sub-widgets that reach back into the list w */ 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); }; @@ -45,6 +51,8 @@ ListWidget.prototype.execute = function() { 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 = [], @@ -59,6 +67,11 @@ ListWidget.prototype.execute = function() { } // Construct the child widgets this.makeChildWidgets(members); + // Clear the last history + this.history = []; + // Construct the storyview + var StoryView = this.storyViews[this.storyViewName]; + this.storyview = StoryView ? new StoryView(this) : null; }; ListWidget.prototype.getTiddlerList = function() { @@ -111,15 +124,42 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of 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) { + if(changedAttributes.filter || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) { this.refreshSelf(); return true; } else { // Handle any changes to the list - return this.handleListChanges(changedTiddlers); + 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 */ @@ -192,20 +232,30 @@ ListWidget.prototype.findListItem = function(startIndex,title) { 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); + // 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; }; /* -Remvoe the specified list item +Remove the specified list item */ ListWidget.prototype.removeListItem = function(index) { - // Remove the DOM nodes owned by this item - this.children[index].removeChildDomNodes(); + 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); }; diff --git a/core/modules/startup.js b/core/modules/startup.js index 58afd6349..f28ad312c 100755 --- a/core/modules/startup.js +++ b/core/modules/startup.js @@ -78,7 +78,9 @@ exports.startup = function() { }); // Install the scroller $tw.pageScroller = new $tw.utils.PageScroller(); - $tw.rootWidget.addEventListener("tw-scroll",$tw.pageScroller); + $tw.rootWidget.addEventListener("tw-scroll",function(event) { + $tw.pageScroller.handleEvent(event); + }); // Install the save action handler $tw.wiki.initSavers(); $tw.rootWidget.addEventListener("tw-save-wiki",function(event) { diff --git a/core/modules/storyviews/classic.js b/core/modules/storyviews/classic.js new file mode 100644 index 000000000..9728ce4a1 --- /dev/null +++ b/core/modules/storyviews/classic.js @@ -0,0 +1,94 @@ +/*\ +title: $:/core/modules/storyviews/classic.js +type: application/javascript +module-type: storyview + +Views the story as a linear sequence + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var ClassicStoryView = function(listWidget) { + this.listWidget = listWidget; +} + +ClassicStoryView.prototype.navigateTo = function(historyInfo) { + var listElementIndex = this.listWidget.findListItem(0,historyInfo.title); + if(listElementIndex === undefined) { + return; + } + var listItemWidget = this.listWidget.children[listElementIndex], + targetElement = listItemWidget.findFirstDomNode(); + // Scroll the node into view + this.listWidget.dispatchEvent({type: "tw-scroll", target: targetElement}); +}; + +ClassicStoryView.prototype.insert = function(widget) { + var targetElement = widget.findFirstDomNode(), + duration = $tw.utils.getAnimationDuration(); + // Get the current height of the tiddler + var computedStyle = window.getComputedStyle(targetElement), + currMarginBottom = parseInt(computedStyle.marginBottom,10), + currMarginTop = parseInt(computedStyle.marginTop,10), + currHeight = targetElement.offsetHeight + currMarginTop; + // Reset the margin once the transition is over + setTimeout(function() { + $tw.utils.setStyle(targetElement,[ + {transition: "none"}, + {marginBottom: ""} + ]); + },duration); + // Set up the initial position of the element + $tw.utils.setStyle(targetElement,[ + {transition: "none"}, + {marginBottom: (-currHeight) + "px"}, + {opacity: "0.0"} + ]); + $tw.utils.forceLayout(targetElement); + // Transition to the final position + $tw.utils.setStyle(targetElement,[ + {transition: "opacity " + duration + "ms ease-in-out, " + + "margin-bottom " + duration + "ms ease-in-out"}, + {marginBottom: currMarginBottom + "px"}, + {opacity: "1.0"} + ]); +}; + +ClassicStoryView.prototype.remove = function(widget) { + var targetElement = widget.findFirstDomNode(), + duration = $tw.utils.getAnimationDuration(); + // Get the current height of the tiddler + var currWidth = targetElement.offsetWidth, + computedStyle = window.getComputedStyle(targetElement), + currMarginBottom = parseInt(computedStyle.marginBottom,10), + currMarginTop = parseInt(computedStyle.marginTop,10), + currHeight = targetElement.offsetHeight + currMarginTop; + // Remove the dom nodes of the widget at the end of the transition + setTimeout(function() { + widget.removeChildDomNodes(); + },duration); + // Animate the closure + $tw.utils.setStyle(targetElement,[ + {transition: "none"}, + {transform: "translateX(0px)"}, + {marginBottom: currMarginBottom + "px"}, + {opacity: "1.0"} + ]); + $tw.utils.forceLayout(targetElement); + $tw.utils.setStyle(targetElement,[ + {transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms ease-in-out, " + + "opacity " + duration + "ms ease-in-out, " + + "margin-bottom " + duration + "ms ease-in-out"}, + {transform: "translateX(-" + currWidth + "px)"}, + {marginBottom: (-currHeight) + "px"}, + {opacity: "0.0"} + ]); +}; + +exports.classic = ClassicStoryView; + +})(); \ No newline at end of file diff --git a/core/ui/PageTemplate.tid b/core/ui/PageTemplate.tid index 56d795e4c..96f9c8f0a 100644 --- a/core/ui/PageTemplate.tid +++ b/core/ui/PageTemplate.tid @@ -26,7 +26,7 @@ title: $:/core/ui/PageTemplate
-<$list filter="[list[$:/StoryList]]" history="$:/HistoryList" template="$:/core/ui/ViewTemplate" editTemplate="$:/core/ui/EditTemplate" listview={{$:/view}} /> +<$list filter="[list[$:/StoryList]]" history="$:/HistoryList" template="$:/core/ui/ViewTemplate" editTemplate="$:/core/ui/EditTemplate" storyview={{$:/view}} />