How to get transclusion depth?

(Discourse won’t let me post without at least 20 characters, and apparently quotes don’t count, so I’m writing this junk sentence.)

I think there would be value pursuing as simple an example as you can of what you are trying to do. Ideally a package we can drop on tiddlywiki.com and experiment with. Looking at tiddlywiki at this level of sophistication gets complex perhaps as a result of its flexibility. I expect it is quite easy to get the same results by more than one path.

  • The standard way to fill a slot is using the fill widget?
  • I think they were designed for a much simpler use case, but I applauded your objectives. Initially only the ts-raw was in effect valid.
  • Traditionally the approach is to transclude the content of a macro, procedure or tiddler. The slot is the first time we gained access to the content of the calling widget to support custom widgets and is still incomplete.

@pmario @TW_Tones Here is an oversimplified example of why I want to be able to use slots at arbitrary depths without having to worry about the $depth attribute. I don’t yet know how to format the json so that it can be dragged onto a TiddlyWiki to be imported, but this should at least be able to be downloaded and the contents copied and pasted into the $:/Import tiddler. Thanks @Scott_Sauyet

Oversimplified version (1).json (1.6 KB)

I have created an <$error.context> custom widget that does nothing but display its ts-raw slot. I have also created a <$safe.button> widget that is a wrapper for the <$button> widget, except that it wikifies the slot of the error context widget into a widget tree and detects the presence of any <$error> widgets within the <$error.context> widget, and disables the button if there are any errors present.

As a demonstration of how this could be used, I have created a couple of input fields, and some logic to produce <$error> widgets if there are any incorrect inputs. This acts as “validations” that also result in the button being disabled.

The limitation I want to overcome is that the <$safe.button> widget needs to know the relative depth it’s being used inside the <$error.context> widget in order to get the correct slot and check for the <$error> widgets. I have simply hardcoded it as 2, but because of this, I have to use the <$safe.button> as a direct child of the <$error.context> and can’t make tiddlers containing the buttons etc. (or else would have to use $fillignore everywhere along the chain to the <$safe.button> widget).

Even if I don’t make the tool public, I myself don’t want to have to deal with the $depth attribute. That’s why I wanted to be able to simply compute it.

I originally tried doing the wikification of the slot in the <$error.context> widget itself, thus creating a variable that the <$safe.button> widget can access. The issue with that is that any time the widget tree for the slot changes (such as if an error message appears or disappears), any contained <$edit-text> widget will lose focus.

I am currently looking at making the <$safe.button> widget send a custom message that contains enough information to simulate what would happen if all the same attributes were given to a <$button> widget, and adding a <$messagecatcher> to the <$error.context> widget that catches the custom message. When it catches the actions, it would wikify the ts-raw slot (into the widget tree) in order to check for <$error> widgets, and if there aren’t any errors, it would run the actions that simulate what would’ve happened with a normal <$button> widget (using the information in the custom message to do so).

I’m also considering avoiding slots altogether and using some kind of template in place of a custom <$error.context> widget. That doesn’t seem as nice to use, though.

In the tiddler’s More Actions dropdown is an “export tiddler” option. You can choose “JSON file” from there:

image

For more than one, you can write a filter that includes exactly the tiddlers you want to export and put it in the Advanced Search > Filter box, and there will be an export button that will allow you to choose JSON.

(Note that when you do this, although the pre-import screen puts them in alphabetic order, the one that says “The following tiddlers were imported:” restores the order from the filter. This lets you put the most important ones [e.g. “My Overview”] first in the list…)

1 Like

Thanks, @Scott_Sauyet

Fixed (also fixed the bug caused by changing temp tiddler titles in one place but not another)

Should probably also change to display: table-cell but that’s not too important here.

It will likely require a real expert to answer for certain, but my guess is that this is not available in wikitext. You might look to the rendering code or, as mentioned earlier, to the qualify widget to see if this could be added to the core. You could fiddle with updating it for your needs with an eye towards both solving it for your own use and creating code that could be contributed back to the core.

Maybe one day! (I would imagine that it could be kept track of in the widget tree)

2 Likes

I will look at your example tomorrow. best of luck

A js macro would be needed:

/*\
title: depth.js
type: application/javascript
module-type: macro


\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

/*
Information about this macro
*/

exports.name = "depth";

exports.params = [];

/*
Run the macro
*/
exports.run = function() {
	var name ="transclusion",
	depth=0,
	node = this;
	while(node && node.parentWidget) {
		if($tw.utils.hop(node.parentWidget.variables,name)) {
			depth++;
		}
		node = node.parentWidget;
	}
	return depth;
};

})();
2 Likes

@buggyj I believe that’s the idea. However, it doesn’t seem to increase the depth level with macro calls, custom widgets, and the like. I’m not sure why, since viewing the widget tree for all those things shows they are all transclusions in the end. (All those things affect the needed $depth attribute for slots.)

Still, this may have provided me with a few clues about how to dig into TW JS code enough to figure out how to get the solution.

I must admit, I am finding if difficult to understand your example, perhaps I should “bow out”, But I can’t help but think this is over complicated, and there is a standard TiddlyWiki script solution, however you have not really outlined what you want done, only the way you are doing “it”, and telling us what is no good with it.

  • I imagine the reason I see it this way, is something about your real need is lost in the example?

I have a hint to you that may help, see the end, at a conceptual level.

It seems to me all you are after is a button to be disabled until two conditions are met?

  • For some reason you have gone off trying to retrieve this conditional information via slot widgets and redefining the button, or are you?
  • This may be exacerbated by your use of the if statements, because the result is only displayed, and can’t be passed to the button. It is also split into two separate if statements.
    • So this has led you down the path of trying to use the depth (in some ways really height), to retrieve the above code, wikify it “again” to find if the button should be disabled.

Could this have some relevance here ActionWidget Execution Modes you are using the genesis widget to create a button, who’s key quality is the onclick action?

  • More so now I don’t think so.

@bluepenguindeveloper , with all respect, Your code is most inventive, curious and possibly ingenious, but seems to me, to be the result of a conceptual error in your design and coding. You have “gone down the wrong rabbit hole”.

  • Although, as always, I may be wrong.

Hint

As soon as you invoke a widget, that is something with a $sign you are saying “display this”, Not compute this for later use, just display it.

  • However nested inside a widget you can go deeper and deeper in nested widgets to work out what to display, but from the outer widget down, it will only be displayed.
    • Keep in mind you can display interactive components like a button, and they can do things, but they are still just displayed.
  • It seems in many ways this is somewhat true for the %if statements. Nothing that happens after the condition can ever come outside of that statement, only displayed.

Conclusion

You seem not to be aware of the above, which is understandable, and you are trying to fight back, by trying to access the result of wikification above, to change something lower down.

  • I believe this is totally unnecessary and there are other code patterns to achieve the outcome.

The question is would you value me putting time into reverse engineering your code and design a better solution?

  • Could you help reduce the time I spend, by giving a “code agnostic” requirement statement?

How I would do it?

  • I would not have an error.context
  • I would use functions and variables and multiple conditions
  • I would only use widgets, and %if statements, other than the list widget, at the lowest level possible, and to display something.

People that don’t know this, including me in the past, always think wikifying it is the answer. But that just compounds the problem. There are very few ways this (wikification) is appropriate, if any. Wikify is also discouraged for performance reasons.

@TW_Tones I’m not gonna go so far as to ask you (or anyone else) to design a solution for me.

I was actually looking to make a suite of widgets for building UIs, many of which wrap the existing widgets (edit-text, etc.) but also can be passed conditions (filters) which display validation messages (I used $error for the example, but I intend them to look and behave differently). And I wanted the validations to have actual force, without changing the button every time I change one of the conditions.

Maybe the best TW way to do this is to add pragmas (\define or \procedures) for all the filters and reference all the same filters in the button to disable it. But I was trying to avoid having to add everything to both the button and whatever field is using them… too easy to forget to add or remove it from one thing and then have a mismatch.

Thus, I was looking for a way for the button to detect the presence of the error messages without having to change the code for the button every time I add or remove code for an error message elsewhere. (I looked to see if there was some way, even if only in action widgets, that I detect DOM nodes with a CSS selector or XPATH selector, but I couldn’t find a way other than wikify.)

I thought about having the custom input widgets record the valid/invalid values into state tiddlers which the custom button widget could lookup, but the problem is that the same fields could change due to things outside the custom input widget. So I thought, the custom input widgets would just need to listen for changes to the fields, and update the validation states on any changes to those fields regardless of the origin/cause. But I found a topic you created where it was answered that there was no such mechanism.

Maybe I am just trying to do something that TW isn’t designed for. One thing it doesn’t seem to be great for is modularization (in this case, the button being modular in the sense that it doesn’t have to know the conditions that cause validations to appear, only whether there are any).

Alternatively, maybe I could look into whether a custom solution with JS could detect DOM nodes - although, outside of action strings, this seems problematic. (Or maybe a custom JS plugin could allow for field listeners, although that seems like a potentially more complicated solution than wikification, and potentially similarly performance-intensive.)

1 Like

I believe the word you’re looking for is “convoluted” :wink: (Please be assured that I take no offense.)

maybe this:

/*\
title: depth.js
type: application/javascript
module-type: macro


\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

/*
Information about this macro
*/

exports.name = "depth";

exports.params = [];

/*
Run the macro
*/
exports.run = function() {
	var name ="transclusion",
	depth=0,
	node = this;
	while(node && node.parentWidget) {
		if(node.parentWidget.parseTreeNode.type == "transclude") {
			depth++;
		}
		node = node.parentWidget;
	}
	return depth;
};

})();

I will test this out tomorrow. I don’t remember if the parse tree, or only the widget tree, shows things like <<someMacro>> as transclusions.

(I also notice that you have "transclusion" for the variable "transclude" in the condition. That may be correct if “transclusion” was for the widget tree and “transclude” for the parse tree. The variable could even be commented out in this version from what I can tell.)

I think you did choose the wrong approach:

For input validation I would use functions eg: f.inputIsValid(tiddler, test). For your example I would use:

  • The function gets a tiddler title, which contains the input text
  • It gets a second parameter test that is used with the regexp.

This filter can become as complex as it needs to be. The result will be “yes” or “no” if the input is valid.
In our case if the string contains the <test> string, it is not valid

\function f.inputIsValid(tiddler,test) [<tiddler>get[text]regexp<test>then[no]else[yes]]

If 2 or more inputs can disable a button I would create a second function named: f.disableButton()

  • f.disableButton() calls 2 f.inputIsValid with different parameters as described above
  • If one of the functions returns no the :intersection will propagate the info and :then[[yes]] will be retuned.
  • If all functions return yes the :else[[no]] path will be active.
\function f.disableButton()
  [f.inputIsValid[test-e],[e]] [f.inputIsValid[test-o],[o]] 
  :intersection[[no]] :then[[yes]]
    :else[[no]]
\end

The whole code will look as follows. I think thats simple to read and to understand. Using functions, the validation can be as complex as you want them to be. The different elements should still be straight forward.

\function f.inputIsValid(tiddler,test) [<tiddler>get[text]regexp<test>then[no]else[yes]]

\function f.disableButton()
[f.inputIsValid[test-e],[e]] [f.inputIsValid[test-o],[o]]
:intersection[[no]]
  :then[[yes]]
  :else[[no]]
\end 

no e: <$edit-text tag="input" tiddler="test-e" placeholder="no e allowed"/>
no o: <$edit-text tag="input" tiddler="test-o" placeholder="no o allowed"/>

input-e is valid: <<f.inputIsValid tiddler:"test-e" test:"e" >><br>
input-o is valid: <<f.inputIsValid tiddler:"test-o" test:"o">>

disable button: <<f.disableButton>>

<$button disabled=<<f.disableButton>>>click</$button>

I think \functions and \procedures are our friends here. I think there is no need for custom widgets, that overwrite core widgets.


For a more complex UI that heavily reuses code and UI elements you can have a look at the new rewritten TableOfContents macros. It makes heavy use of functions, procedures that are internally executed as transclusions. But none of them needs slots and depths information.

There is a pending PR and this is the link to the TOC macros code: TiddlyWiki — a non-linear personal web notebook

2 Likes

My argument exactly.

Personally I would separate out the edit and validation process to a module, a tiddler, procedure or function for example get valid test without string xyz. You would then call this once with o and another time with e - or even just provide an filter, function or function name to an edit and validate Procedure.

Not at all, it may be “convoluted” relative to a solution I can imagine, as I said you have gone down a rabbit hole, with false assumptions, but in that world of assumptions, found an interesting path by which to (almost) address your needs. In this case I would call your false assumptions “constraints” which often once applied generate new ideas.

  • Inventive, curious and possibly ingenious and totally unnecessary :nerd_face: which I never object to, because you are pushing the edges of possibility outwards.

I am not sure how to reset your current perspective, especially if you can’t get an idea from @pmario’s suggestion.

It seems to me that 1) this is more idiomatic TW, and 2) the fundamental limitation that I was trying to get around (and technically can, with wikification) is that if I want to use the same condition in multiple places, I have to change multiple places in the code.

If we have the function f.disableButton, then we will not need to update the button widget every time we add a new field with a validation, but we do need to add it to the f.disableButton function. Even if we choose to use \procedure to define the regular expressions (so that if the regexp needs to change we can only change it in one place), we would still have to 1) add the procedure, 2) add it to the f.disableButton function, and 3) add the function call to display it under the input field.

It seems I cannot merely add a new field and add a function call to that field and have the button be disabled just from that; I will necessarily have to also add the function call somewhere else (either in the button or in another function).

The updated code seems to work as expected/needed. Thanks!

I don’t know why sometimes I can mark a solution and sometimes I can’t, but this is the solution to the original question (use case etc. aside).