TiddlyWiki / TiddlyWiki5

A self-contained JavaScript wiki for the browser, Node.js, AWS Lambda etc.
https://tiddlywiki.com/
Other
8.09k stars 1.19k forks source link

Thinking about action widgets #1518

Open Drakor opened 9 years ago

Drakor commented 9 years ago

Hi,

I feel bad for bringing this up and I apologize to everyone, who feels annoyed by me writing about this. I have thought about it for a long while and I am interested what others think about the matter.

Intro

Action widgets have been introduced as a means to allow multiple actions to happen on some DOM event (most prominently a button click). For this, it was chosen to use a mechanism that is part of the widget tree and therefore part of the DOM and the presentation layer of TiddlyWiki. This allowed for easy hook-ups between the places where events happened to a bit of javascript code, which would execute an action.

For developers this is a sufficient concept, since it allows them to finally get the DOM events they need and have their application logic sitting in a comfortable place away from the harsh world of the DOM and all that presentation related stuff and do so in a way that integrates well into the TiddlyWiki architecture. Also for normal people with no programming language, this allowed them to choose what action(s) they want to have happen.

However, when programming a more complex system, people can choose the amount of stuff they want to put in an action widget. I can program a single action-widget that does what I want and put it in the WikiText, or I can try to program a meaningful amount of action widgets that each are responsible for a distinct and clear task within the overall action. The latter approach also allows for easy composability and customizability by either you or the end user, who doesn't know how to program, since it is easy to just rearrange a few action widgets and maybe add a new one. In fact, one could even see action-widgets as a way to SCRIPT TiddlyWiki without JS and I will just refer to Jed's /TriggerAction/ daemon, which is exactly that, using action-widgets as a means of scripting.

Therefore it is only fitting, that the invokeAction mechanism used, supported a good degree of composability and nesting and also allowed people to control the flow of action invocations, since an invokeAction Call only triggered the next level in the tree. Here example of how action widgets WERE used:

Example 1

Example 1 Fairly standard

Example 2

Example 2 Sometimes necessary when one widget is a container. Still fairly standard.

Example 3

Example 3 This is an example that makes use of the ability to direct the control-flow and uses action-widgets more like a scripting language. (Note: This does not work in vanilla per se, but only after a few VERY trivial changes)

Problem

It is easy to see that by changing the invokeAction method to its current state (see #1396), neither example 2, nor example 3 have any chance of working (that is, if you do not want to play around with different event objects and changing the action's source code, or overwriting the invokeActionCall method on the widget), so people are restricted to example 1 although stuff in deeper levels of the tree is triggered as well, which only ever was to overcome a HTML node or at most another widget, that modifies the environment, like the <$set> widget.

To tackle this problem, there are several thing you can do:

Here is the current invokeActions method (for reference):

/*
Invoke any action widgets that are descendants of this widget.
*/
Widget.prototype.invokeActions = function(event) {
return this.invokeActionCall(this, event);
};
/*
Recursively search through descendants, invoking all actions encountered.
*/
Widget.prototype.invokeActionCall = function(here, event) {
var handled = false;
for(var t=0; t<here.children.length; t++) {
    var child = here.children[t];
    if(child.invokeAction && child.invokeAction(this,event)) {
        handled = true;
    }
    if(this.invokeActionCall(child, event)) {
        handled = true;
    }
}
return handled;
};

Here would be one, that supports example 2 and 3 (maybe with some modifications, like a circumstantial action-end widget)

/*
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;
};

/*
Forward unhandled action calls
*/
Widget.prototype.invokeAction = function(triggeringWidget,event) {
    return this.invokeActions(event); //maybe the triggering widget should be passed along in this case too ?
};

Here would be another one, that works similiar to the first one, but can stop, when the child handles the action call (which I think makes sense):

/*
Invoke any action widgets that are descendants of this widget.
*/
Widget.prototype.invokeActions = function(event) {
return this.invokeActionCall(this, event);
};
/*
Recursively search through descendants, invoking all actions encountered.
*/
Widget.prototype.invokeActionCall = function(here, event) {
var handled = false;
for(var t=0; t<here.children.length; t++) {
    var child = here.children[t];
    var result = false;
    if(child.invokeAction) {
        result = child.invokeAction(this,event);
    }
    // If the child didn't handle the call itself, propagate to children
    handled = handled | (result || this.invokeActionCall(child, event));
}
return handled;
};

(Note: some of the things above are pseudo code to illustrate the point, also these are just examples, both of which would require additional things to be implemented, at least another action widget, which specifically directs the control flow)

The main difference to the very first (a.k.a. current) implementation is, that a widget is able to stop or prohibit propagation through to its children. This is similiar to the options a DOM node has to prevent propagation (or bubbling) of DOM events.

However, I think the real question is: Does TiddlyWiki want to support this kind of scripting shown in example 2 and 3 ? Should actions be composable in this way ?

If the answers are "no", think of the TriggerAction daemon Jed cam up with. He doesn't really need action-widgets, does he ? Configurable start up tasks, that subscribe to the daemon would do the job, right ? Why try to use action-widgets to script stuff, when they aren't suited for that.

I hope you see the problem I am thinking about and it might be that I overthink it, but I need to hear the opinions of others on this matter to finally stop thinking about this. Thank you for reading,

/Andreas

Jermolene commented 9 years ago

Hi @Drakor good stuff, thank you. To answer your question, yes, I was indeed thinking that action widgets could grow to enable them to become the primary scripting interface to TiddlyWiki - the idea being that we'd develop some shorthand wikitext syntax for common idioms.

The problem you bring up is why I originally only had action invocations working on the immediate children of a trigger widget.

Thanks to your examples, I realise that the recent change has broken the example of a button inside a dropzone; the action widgets inside the button would now be triggered by the dropzone widget.

Perhaps a reasonable fix is for only a few hand-picked widgets to propagate actions: maybe <$list>, <$reveal> and html elements?

Drakor commented 9 years ago

Yes, if you don't want to add a widget called <action-end>or similar that just does not propagate to its children (so you would encapsulate the button inside such an <action-end>), then picking which widgets do and which ones do not propagate is probably the way to go.

It might be worth adding another base class for that .. so having Widget and WidgetPropagate (not necessarily those names) and then change the prototype of other widgets to the appropriate one. I guess that saves a bit of repetition.

Also, for now at least the dropzone widget doesn't throw any action and neither does the keyboard widget and other "area" widgets, but for some of those I guess an action functionality will be added eventually (I think the keyboard widget is a good example for that).

/Andreas

tobibeer commented 9 years ago

The main difference to the very first (a.k.a. current) implementation is, that a widget is able to stop or prohibit propagation through to its children. This is similiar to the options a DOM node has to prevent propagation (or bubbling) of DOM events.

Propagation needs to be preventable both ways, down or up the widget tree / dom. An action-invoking widget must be able to specify if the event is to bubble up the dom widget tree or not just as it needs to be able to specify whether the event is pushed down to child widgets (even nested in perhaps superfluous html elements).

pmario commented 9 years ago

Related Issue and test case: https://groups.google.com/d/msg/tiddlywiki/IkiM_VoFFu8/w72bW4ECzXEJ see attachment.

pmario commented 7 years ago

@Jermolene ... label: "discussion"