From 0dcf54c3b59ed04645928f0ec4ced647e5a0da7f Mon Sep 17 00:00:00 2001 From: Jermolene Date: Wed, 8 Oct 2014 17:45:26 +0100 Subject: [PATCH] Add support for action widgets This is part of the groundwork for fixing #336 --- core/language/en-GB/Misc.multids | 1 + core/modules/widgets/action-navigate.js | 79 ++++++++++++++++++ core/modules/widgets/action-sendmessage.js | 82 +++++++++++++++++++ core/modules/widgets/button.js | 9 +- core/modules/widgets/navigator.js | 16 +++- core/modules/widgets/widget.js | 14 ++++ .../tw5.com/tiddlers/ActionNavigateWidget.tid | 36 ++++++++ .../tiddlers/ActionSendMessageWidget.tid | 31 +++++++ editions/tw5.com/tiddlers/ActionWidgets.tid | 23 ++++++ .../WidgetMessage_ tm-new-tiddler.tid | 8 +- .../tw5.com/tiddlers/widgets/ButtonWidget.tid | 21 +++-- 11 files changed, 303 insertions(+), 17 deletions(-) create mode 100644 core/modules/widgets/action-navigate.js create mode 100644 core/modules/widgets/action-sendmessage.js create mode 100644 editions/tw5.com/tiddlers/ActionNavigateWidget.tid create mode 100644 editions/tw5.com/tiddlers/ActionSendMessageWidget.tid create mode 100644 editions/tw5.com/tiddlers/ActionWidgets.tid diff --git a/core/language/en-GB/Misc.multids b/core/language/en-GB/Misc.multids index 991fe28f9..8d91730cb 100644 --- a/core/language/en-GB/Misc.multids +++ b/core/language/en-GB/Misc.multids @@ -8,6 +8,7 @@ ConfirmCancelTiddler: Do you wish to discard changes to the tiddler "<$text text ConfirmDeleteTiddler: Do you wish to delete the tiddler "<$text text=<>/>"? ConfirmOverwriteTiddler: Do you wish to overwrite the tiddler "<$text text=<<title>>/>"? ConfirmEditShadowTiddler: You are about to edit a ShadowTiddler. Any changes will override the default system making future upgrades non-trivial. Are you sure you want to edit "<$text text=<<title>>/>"? +DefaultNewTiddlerTitle: New Tiddler DropMessage: Drop here (or click escape to cancel) Encryption/ConfirmClearPassword: Do you wish to clear the password? This will remove the encryption applied when saving this wiki Encryption/PromptSetPassword: Set a new password for this TiddlyWiki diff --git a/core/modules/widgets/action-navigate.js b/core/modules/widgets/action-navigate.js new file mode 100644 index 000000000..b6688df08 --- /dev/null +++ b/core/modules/widgets/action-navigate.js @@ -0,0 +1,79 @@ +/*\ +title: $:/core/modules/widgets/action-navigate.js +type: application/javascript +module-type: widget + +Action widget to navigate to a tiddler + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget; + +var NavigateWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +NavigateWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +NavigateWidget.prototype.render = function(parent,nextSibling) { + this.computeAttributes(); + this.execute(); +}; + +/* +Compute the internal state of the widget +*/ +NavigateWidget.prototype.execute = function() { + this.actionTo = this.getAttribute("$to"); + this.actionScroll = this.getAttribute("$scroll"); +}; + +/* +Refresh the widget by ensuring our attributes are up to date +*/ +NavigateWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes["$to"] || changedAttributes["$scroll"]) { + this.refreshSelf(); + return true; + } + return this.refreshChildren(changedTiddlers); +}; + +/* +Invoke the action associated with this widget +*/ +NavigateWidget.prototype.invokeAction = function(triggeringWidget,event) { + var bounds = triggeringWidget && triggeringWidget.getBoundingClientRect && triggeringWidget.getBoundingClientRect(), + suppressNavigation = event.metaKey || event.ctrlKey || (event.button === 1); + if(this.actionScroll === "yes") { + suppressNavigation = false; + } else if(this.actionScroll === "no") { + suppressNavigation = true; + } + this.dispatchEvent({ + type: "tm-navigate", + navigateTo: this.actionTo === undefined ? this.getVariable("currentTiddler") : this.actionTo, + navigateFromTitle: this.getVariable("storyTiddler"), + navigateFromNode: triggeringWidget, + navigateFromClientRect: bounds && { top: bounds.top, left: bounds.left, width: bounds.width, right: bounds.right, bottom: bounds.bottom, height: bounds.height + }, + navigateSuppressNavigation: suppressNavigation + }); + return true; // Action was invoked +}; + +exports["action-navigate"] = NavigateWidget; + +})(); diff --git a/core/modules/widgets/action-sendmessage.js b/core/modules/widgets/action-sendmessage.js new file mode 100644 index 000000000..fa788240a --- /dev/null +++ b/core/modules/widgets/action-sendmessage.js @@ -0,0 +1,82 @@ +/*\ +title: $:/core/modules/widgets/action-sendmessage.js +type: application/javascript +module-type: widget + +Action widget to send a message + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget; + +var SendMessageWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +SendMessageWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +SendMessageWidget.prototype.render = function(parent,nextSibling) { + this.computeAttributes(); + this.execute(); +}; + +/* +Compute the internal state of the widget +*/ +SendMessageWidget.prototype.execute = function() { + this.actionMessage = this.getAttribute("$message"); + this.actionParam = this.getAttribute("$param"); +}; + +/* +Refresh the widget by ensuring our attributes are up to date +*/ +SendMessageWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes["$message"] || changedAttributes["$param"]) { + this.refreshSelf(); + return true; + } + return this.refreshChildren(changedTiddlers); +}; + +/* +Invoke the action associated with this widget +*/ +SendMessageWidget.prototype.invokeAction = function(triggeringWidget,event) { + // Get the parameter + var param = this.actionParam; + // If the parameter is missing then we'll assemble the attributes as a hashmap + if(!param) { + param = Object.create(null); + var count = 0; + $tw.utils.each(this.attributes,function(attribute,name) { + if(name.charAt(0) !== "$") { + param[name] = attribute; + count++; + } + }); + // Revert to an empty parameter if no values were found + if(!count) { + param = undefined; + } + } + // Dispatch the message + this.dispatchEvent({type: this.actionMessage, param: param, tiddlerTitle: this.getVariable("currentTiddler")}); + return true; // Action was invoked +}; + +exports["action-sendmessage"] = SendMessageWidget; + +})(); diff --git a/core/modules/widgets/button.js b/core/modules/widgets/button.js index 21268d7a5..142c13ada 100644 --- a/core/modules/widgets/button.js +++ b/core/modules/widgets/button.js @@ -59,6 +59,9 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { // Add a click event handler domNode.addEventListener("click",function (event) { var handled = false; + if(self.invokeActions(event)) { + handled = true; + } if(self.to) { self.navigateTo(event); handled = true; @@ -87,6 +90,10 @@ ButtonWidget.prototype.render = function(parent,nextSibling) { this.domNodes.push(domNode); }; +ButtonWidget.prototype.getBoundingClientRect = function() { + return this.domNodes[0].getBoundingClientRect(); +} + ButtonWidget.prototype.isSelected = function() { var tiddler = this.wiki.getTiddler(this.set); return tiddler ? tiddler.fields.text === this.setTo : this.defaultSetValue === this.setTo; @@ -99,7 +106,7 @@ ButtonWidget.prototype.isPoppedUp = function() { }; ButtonWidget.prototype.navigateTo = function(event) { - var bounds = this.domNodes[0].getBoundingClientRect(); + var bounds = this.getBoundingClientRect(); this.dispatchEvent({ type: "tm-navigate", navigateTo: this.to, diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js index 2241180e4..4c554a70e 100755 --- a/core/modules/widgets/navigator.js +++ b/core/modules/widgets/navigator.js @@ -374,11 +374,19 @@ NavigatorWidget.prototype.handleCancelTiddlerEvent = function(event) { // Create a new draft tiddler NavigatorWidget.prototype.handleNewTiddlerEvent = function(event) { // Get the story details - var storyList = this.getStoryList(); - // Get the template tiddler if there is one - var templateTiddler = this.wiki.getTiddler(event.param); + var storyList = this.getStoryList(), + templateTiddler,originalTitle; + // Get the template + if(typeof event.param === "object") { + templateTiddler = event.param; + originalTitle = templateTiddler.title; + } else { + templateTiddler = this.wiki.getTiddler(event.param); + originalTitle = templateTiddler && templateTiddler.fields.title; + } + originalTitle = originalTitle || $tw.language.getString("DefaultNewTiddlerTitle"); // Title the new tiddler - var title = this.wiki.generateNewTitle((templateTiddler && templateTiddler.fields.title) || "New Tiddler"); + var title = this.wiki.generateNewTitle(originalTitle); // Create the draft tiddler var draftTitle = this.generateDraftTitle(title), draftTiddler = new $tw.Tiddler({ diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index df434973c..a4942a86f 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -475,6 +475,20 @@ Widget.prototype.removeChildDomNodes = function() { } }; +/* +Invoke any action widgets that are immediate children of this widget +*/ +Widget.prototype.invokeActions = function(event) { + var handled = false; + for(var t=0; t<this.children.length; t++) { + var child = this.children[t]; + if(child.invokeAction && child.invokeAction(this,event)) { + handled = true; + } + } + return handled; +}; + exports.widget = Widget; })(); diff --git a/editions/tw5.com/tiddlers/ActionNavigateWidget.tid b/editions/tw5.com/tiddlers/ActionNavigateWidget.tid new file mode 100644 index 000000000..a27809412 --- /dev/null +++ b/editions/tw5.com/tiddlers/ActionNavigateWidget.tid @@ -0,0 +1,36 @@ +caption: action-navigate +created: 20141008163514491 +modified: 20141008164303144 +tags: Widgets ActionWidgets +title: ActionNavigateWidget +type: text/vnd.tiddlywiki + +! Introduction + +The ''action-navigate'' widget is an [[action widget|ActionWidgets]] that sends a [[tm-navigate|WidgetMessage: tm-navigate]] message back up the widget tree. ActionWidgets are used within triggering widgets such as the ButtonWidget. + +! Content and Attributes + +The ''action-navigate'' widget is invisible. Any content within it is ignored. + +|!Attribute |!Description | +|$to |The title of the target tiddler for the navigation (if not provided defaults to the [[WidgetVariable: currentTiddler]] | +|$scroll |Optional parameter determining whether the navigation will also cause a scroll to the target tiddler (see below) | + +!! Scroll handling + +The optional `$scroll` attribute can be set to "yes" to force scrolling to occur to bring the target tiddler into view. If set to "no" then scrolling does not occur. If the `$scroll` attribute is omitted then scrolling occurs unless either: + +* the control key is pressed +* the action was initiated with the middle mouse button (if available) + +! Examples + +Here is an example of button that navigates to two different tiddlers at once: + +<$macrocall $name='wikitext-example-without-html' +src='<$button> +<$action-navigate $to="ButtonWidget" $scroll="no"/> +<$action-navigate $to="ActionWidgets"/> +Click me! +</$button>'/> diff --git a/editions/tw5.com/tiddlers/ActionSendMessageWidget.tid b/editions/tw5.com/tiddlers/ActionSendMessageWidget.tid new file mode 100644 index 000000000..6b621df67 --- /dev/null +++ b/editions/tw5.com/tiddlers/ActionSendMessageWidget.tid @@ -0,0 +1,31 @@ +caption: action-sendmessage +created: 20141008134309742 +modified: 20141008162952455 +tags: Widgets ActionWidgets +title: ActionSendMessageWidget +type: text/vnd.tiddlywiki + +! Introduction + +The ''action-sendmessage'' widget is an [[action widget|ActionWidgets]] that sends a [[message|WidgetMessages]] back up the widget tree. ActionWidgets are used within triggering widgets such as the ButtonWidget. + +! Content and Attributes + +The ''action-sendmessage'' widget is invisible. Any content within it is ignored. + +|!Attribute |!Description | +|$message |The message to send (eg, [[WidgetMessage: tm-new-tiddler]]) | +|$param |Optional parameter string whose meaning is dependent on the message being sent | +|//{any attributes not starting with $}// |Multiple parameters that are attached to the message if the `$param$` attribute is not provided | + +! Examples + +Here is an example of button that displays both a notification and a wizard, and creates a new tiddler with tags and text: + +<$macrocall $name='wikitext-example-without-html' +src='<$button> +<$action-sendmessage $message="tm-modal" $param="SampleWizard"/> +<$action-sendmessage $message="tm-notify" $param="SampleNotification"/> +<$action-sendmessage $message="tm-new-tiddler" title="This is newly created tiddler" tags="OneTag [[Another Tag]]" text=<<now "Today is DDth, MMM YYYY">>/> +Click me! +</$button>'/> diff --git a/editions/tw5.com/tiddlers/ActionWidgets.tid b/editions/tw5.com/tiddlers/ActionWidgets.tid new file mode 100644 index 000000000..f1f9de2ca --- /dev/null +++ b/editions/tw5.com/tiddlers/ActionWidgets.tid @@ -0,0 +1,23 @@ +created: 20141008134425548 +modified: 20141008144957192 +tags: Widgets +title: ActionWidgets +type: text/vnd.tiddlywiki + +Action widgets are a special type of widget that perform an action such as sending a message, navigating to a tiddler, or changing the value of a tiddler. They are used in association with other widgets that trigger those actions (for example, the ButtonWidget). + +Action widgets are invisible. They must be the immediate children of their parent triggering widget. The actions are performed in sequence. For example, here is a button that triggers two actions of sending different messages: + +``` +<$button> +<$action-sendmessage $message="tm-home"/> +<$action-sendmessage $message="tm-full-screen"/> +Click me! +</$button> +``` + +Take care not to accidentally introduce an extra line break after the opening tag of the button widget. Doing so will trigger the WikiText parser to wrap the action widgets in a paragraph element. This means that the action widgets will not be triggered as they are no longer immediate children of the triggering widget. + +The following action widgets are provided: + +<<list-links "[tag[ActionWidgets]]">> diff --git a/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-new-tiddler.tid b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-new-tiddler.tid index 6cad0c4ae..9085c9a8a 100644 --- a/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-new-tiddler.tid +++ b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-new-tiddler.tid @@ -1,17 +1,17 @@ +caption: tm-new-tiddler created: 20140226194405353 -modified: 20140724194729158 +modified: 20141008142952355 tags: Messages navigator-message title: WidgetMessage: tm-new-tiddler type: text/vnd.tiddlywiki -caption: tm-new-tiddler The new tiddler message creates a new draft tiddler and adds it to the current story. It requires the following properties on the `event` object: |!Name |!Description | -|param |Optional title of a tiddler to use as a template for the new tiddler | +|param |Either the title of a tiddler to use as a template for the new tiddler or a hashmap of tiddler fields | |navigateFromTitle |Title of the tiddler from which the navigation to the new tiddler was initiated | -The new tiddler message is usually generated with the LinkWidget or the ButtonWidget and is handled by the NavigatorWidget. +The new tiddler message is usually generated with the LinkWidget, ButtonWidget or ActionSendMessageWidget and is handled by the NavigatorWidget. ! Example diff --git a/editions/tw5.com/tiddlers/widgets/ButtonWidget.tid b/editions/tw5.com/tiddlers/widgets/ButtonWidget.tid index cd96a91cf..f3f52412a 100644 --- a/editions/tw5.com/tiddlers/widgets/ButtonWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/ButtonWidget.tid @@ -1,17 +1,22 @@ -title: ButtonWidget -created: 201310241419 -modified: 201406170837 -tags: Widgets caption: button +created: 20131024141900000 +modified: 20141008145311298 +tags: Widgets +title: ButtonWidget +type: text/vnd.tiddlywiki ! Introduction The button widget displays an HTML `<button>` element that can perform a combination of optional actions when clicked: -* Navigate to a specified tiddler -* Dispatch a user defined [[widget message|Messages]] -* Trigger a user defined [[popup|PopupMechanism]] -* Assign new text to a specified tiddler +* Executing any ActionWidgets that are immediate children of the button widget +* Execute any integrated actions: +** Navigate to a specified tiddler +** Dispatch a user defined [[widget message|Messages]] +** Trigger a user defined [[popup|PopupMechanism]] +** Assign new text to a specified tiddler + +The integrated actions are provided as a shortcut for invoking common actions. The same functionality is available via ActionWidgets, with the exception of the support for highlighting selected popups. ! Content and Attributes