TextWidget source?

I’m looking to create a widget. This would be about being an html container with a javascript code called in case of a double click. This code would not be a parameter (at least, in the beqinning)

Say something like that is intended:

<$click content="click me!"/>

would be resolved as

<div class="clickZone" onclick="myjscode()">click me !</div>

I thought that the source code of $TextWidget would be a good starting point. But I can’t find it.

Any help would be apreciated, including hinting an other starting point.

The linkcatcher widget allows you to “catch” the tm-navigate message. So you can create normal link-widget inside a linkcatcher-widget which does something completely different with the actions-parameter.

If you need more control you can use the event-catcher widget, where you have fine grained control over click and double-click events of any given DOM element

So there is no javascript code needed.

I shall use my widget in a lot of place. This would be a final user job to use it like that. So it’s got to be quick and simple job!

And I’d like to know how to do it. :slight_smile:

I have a problem in figuring out how widget render in the render method. I can’t see where and how they insert the html they need to insert into the DOM. As for the execute method, I have seen how to fetch inside for my widget parameters. I just can’t see how to use them (except for console.log stuff).

So I have a look at widgets sources again. And I found what I wanted with in TiddlyWiki5/core/modules/widgets/button.js at master · Jermolene/TiddlyWiki5 · GitHub

And I now have a preliminary working code (see below). But a question remains: where is the $ext widget source code???

(function(){

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

const Widget = require("$:/core/modules/widgets/widget.js").widget;

let InsertWidget = function(parseTreeNode, options) {
  this.initialise(parseTreeNode,options);
};

/*
Inherit from the base widget class
*/
InsertWidget.prototype = new Widget();

InsertWidget.prototype.execute = function() {
    this.codeInfo = this.getAttribute("codeInfo", "code");
    this.instructions = this.getAttribute("instructions", "what it means");
    this.example = this.getAttribute("example","sample");
    console.log("codeInfo=", this.codeInfo, "instructions=", this.instructions, "example=", this.example);
}

/*
Render this widget into the DOM
*/
InsertWidget.prototype.render = function(parent, nextSibling) {
  this.parentDomNode = parent;
  this.computeAttributes();
  this.execute();
  console.log("$insert.codeInfo=", this.codeInfo);

  const tag = "div";
  let node = this.document.createElement(tag);
  node.setAttribute("debug", this.codeInfo);
  node.setAttribute("onclick", `alert("clicked on ${this.codeInfo}")`);
  node.innerHTML = `looking for ${this.codeInfo} (${this.instructions})`;
  // Insert element
  parent.insertBefore(node, nextSibling);
  this.renderChildren(parent, nextSibling);
};

/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
InsertWidget.prototype.refresh = function(changedTiddlers) {
  return this.refreshChildren(changedTiddlers);
};

exports.insert = InsertWidget;

})();

Note: I have opted for onclick instead of addEventListener. Is that a crime? And should I bother with refresh ?

And also, is the widget built every so often by the render method? I would have thought of the creation of the DOM bits in a setup method instead of a render method.

I assume that $ext is a typo for text. Is this what you’re looking for?

https://github.com/Jermolene/TiddlyWiki5/blob/master/core/modules/widgets/text.js

1 Like

@Scott_Sauyet Yes, thank you! I used grep looking for TextWidget so I missed it!

This might be another good starting point: https://tiddlywiki.com/dev/#Javascript%20Widget%20Tutorial

There are a few sections of that tutorial related to refresh.

thank you @btheado . It helps.

I have since realised that I don’t know how to retrieve the values of tw variables in javarcript, especially in widget source code. I need to fetch the name of the current tiddler.

I also need to check if a string is the name of a tiddler.

Maybe I could call a tw procedure, but I still don’t know about that.

Every widget inherits it functions from the widget class. So every function defined in the base widget class can also be accessed in your widget.

see: var Widget = require("$:/core/modules/widgets/widget.js").widget; and

/*
Inherit from the base widget class
*/
YourWidgetName.prototype = new Widget();

which should be at the beginning of every widget in the core code.

See: TiddlyWiki5/core/modules/widgets/widget.js at master · pmario/TiddlyWiki5 · GitHub

  • this.getVariable(name, options) should give you what you need.
  • this.getVariable("currentTidder", {defaultValue: "test"}) should do what you want. The defaultValue is only needed, if the variable does not exist, which should not be the case for currentTiddler.

So this.getVariable("currentTidder") should do the trick.

Code of .getVariable

Thank you @pmario, this should do a lot.

There is still something I need to know, which is getting fields value (including the text field) from any given tiddlers in my javascript code for the widget. I cannot see that in the widget.js file. And I am not able to get that from the whole source.

to retrieve a tiddler object by title:
var tiddler = this.wiki.getTiddler("tiddler title here");

to retrieve a specific field from a tiddler object:
var text = tiddler.fields.text;

to retrieve titles matching a filter:
var titles = this.wiki.filterTiddlers(filter);

Thank you @saqimtiaz . This is located in boot.js.

Now, I need to wikify things I got from tiddlers in my javascript. I could copy bits of wikify.js but this is not the way to go.

It really depends on what you are trying to achieve. For anything but the simplest use cases, I usually prefer transcluding a template tiddler, where the template tiddler transcludes whatever I want to display. You can transclude a tiddler by providing the relevant parse tree, or by creating a transclude widget directly and rendering it within your widget. I can think of very few situations where a widget would need to wikify something in JavaScript.

The key to good development practice in TiddlyWiki is usually to think in terms of basic primitives such as widgets and filter operators where they each do one thing well, and can be combined to achieve the overall desired functionality. So your widget should ideally be doing the least possible to cover any gaps not already possible in wikitext and falling back to wikitext for the remainder. This of course makes assumptions about the nature of the widget you are writing but it is difficult to offer better guidance in the absence of further details about the use case.

The widget-class has an .initialise(parseTreeNode,options) function, which does the basic initialisation.

The line 39 contains this.wiki = options.wiki … So every widget has direct access to the tw internal wiki-store

So as Saq wrote this.wiki.getTiddler("title") should return a wiki object.

Be aware this object is read only. Changing values there has no effect to the internal store.

The TW store is immutable. It can only be changed by using this.wiki.addTidder(tiddler-object), which expects a NEW tiddler object.

A bare bone new tiddler can be created with:

var myTiddler = new $tw.Tiddler({title:"my-new-tiddler", text:"some text", field-a:"field value"})
this.wiki.addTiddler(myTiddler);

So if “my-new-tiddler” exists, it will be overwritten and it will also loose all it’s data.

So the right workflow to modify an existing tiddler is:

  • Read the existing tiddler
  • Copy the existing data into a new tiddler
  • Change the fields you want to change
  • Update the “modified” date field
  • Add the new tiddler to the wiki store.

In code inside a widget may looks like this:

	var TITLE = "asdf"
	var updateFields = {title: TITLE}; // This field must exist to create a valid tiddler
	updateFields.test = "test";        // set test-field
	updateFields["test-with-hyphens"] = "test with hypens";

	var myNewTiddler;
	var myTiddler = this.wiki.getTiddler(TITLE);

	if(this.wiki.tiddlerExists(TITLE)) {
		// If there was a tiddler with that title: Do something with eg: myTiddler.fields.title

		// To modify an existing tiddler just use it as the __first parameter__.
		// Every "fields" object that comes later will overwrite the fields it contains
		myNewTiddler = new $tw.Tiddler(/*see:*/myTiddler,updateFields,this.wiki.getModificationFields())
	} else {
		// Create that tiddler - See: myTiddler is missing below
		myNewTiddler = new $tw.Tiddler(this.wiki.getCreationFields(),updateFields,this.wiki.getModificationFields())
	}
	this.wiki.addTiddler(myNewTiddler);

So the only way to write a tiddler is to create a completely new one and overwrite the existing one. (code is tested - and verbose for easy debugging)

To add the “created” field we have a function: this.wiki.getCreationFields()
To modify the “modified” field we use: this.wiki.getModificationFields()

Hope that helps
-mario

@saqimtiaz I am using a tool to make it easy to fill a text with holes, which is a common feature of administrative tasks. I want it to both explain what is asked and help in making sure everything is here and also being convenient to use.

So far, I did it with macro. But I lack a feature (being able to go directly to the plug when a hole is met and there is data to fill it (which I call the plug). Then I remembered that it was said that macro were not easy on the system, while widget were. So I thought it could be a good idea to have a widget instead of a macro and started seeing for that (and to achieve my latest need).

Sample of a text, in the way it is coded by the provider of the text with holes:

The application will use PHP version <<insert phpVersion>>.

or if using widget:

The application will use PHP version <$insert item=phpVersion/>.

This is a very simple case. The macro/widget also accept a help text and an example text. That text is written by non technical people. using a macro or a widget is all I can ask for them. Besides, there are many holes. All programming should be done in the macro or in the widget.

I think I should be more specific if I want you to be able to help me. Again, thank you to all of you for your help!

So, here is the end of my widget render method:

const tag = "div";
  let node = this.document.createElement(tag);
  node.setAttribute("debug", this.codeInfo);
  node.setAttribute("onclick", `alert("clicked on ${this.codeInfo}")`);
  node.innerHTML = this.output;
  // Insert element
  parent.insertBefore(node, nextSibling);
  this.renderChildren(parent, nextSibling);

This works OK as long as this.output is pure html. But if it contains wiki text such a widget, it does not of course. So, let say that output value is

no content for <$link to="$:/user/data/pcdgen/projects/bdmsda/exemple">example</$link>

How should I proceed to achieve what I want to output on the tiddler where my $insert widget is (that is, display that error message)?

https://tiddlywiki.com/dev/#Child%20widgets%20tutorial

wiki text ⇨ parse tree ⇨ widget tree ⇨ DOM tree

  1. wiki text - Users write content in tiddlers using wiki text.
  2. parse tree - A parse tree is a JSON data structure describing the wiki text. Wiki text can be converted into a parse tree using this.wiki.parseText.
  3. widget tree - Most items in the parse tree correspond to one or more items in the widget tree. The this.makeChildWidgets method is used to convert the parse tree into a widget tree.
  4. DOM tree - A DOM tree is a standard javascript datastructure used by browsers to display a web page. The this.renderChildren method is used to instantiate the widget class and then render each widget in the widget tree. If a given widget will be visible in the browser, then one or more DOM nodes will be created.

Your this.output contains wiki text so you need to pass it to this.wiki.parseText. The output parseTree should be passed to this.makeChildWidgets. The makeChildWidgets call should come before the renderChildren call.

3 Likes

@btheado I’m afraid I need more information. I read that I need to replace /node.innerHTML = this.output; with an other code. You gave me some hints, but I am unable to get a functioning code.

Here what I do first for this new code (I read some widget sources to get the ideas):

const parser = this.wiki.parseText(this.output);

But then? Here is what I tried to put after that first line, and which all fail with a “TypeError: undefined has no properties” js exception.

  • this.makeChildWidgets(parser.tree)
  • const parsed = {type: "widget", children: parser.tree}; this.makeChildWidgets(parsed);

After that, I would just do

this.makeChildWidgets(this.wiki.parseText(this.output));
  // Insert element
  parent.insertBefore(node, nextSibling);
  this.renderChildren(parent, nextSibling);

I have also tried to modify my own code. I used $wikify 2 or 3 deep. It works… but there is a problem. My this.output code is built by wikifying twice or trice a bit of wikitext whichh contains something like:

<span class="hole" title="(inline) application">BDMSDA <$button class=editPlug actions="""<$action-sendmessage $message=tm-edit-tiddler $param="$:/user/data/pcdgen/projects/bdmsda/plugs/application"/>""" aria-label="modifier la donnée « application »">&#10144; application</$button></span>

The first $wikify is OK with that. The second one too. But the third one completely removes the actions=... stuff. Thus, my button does nothing. Has someone encountered such a problem before? Should I create a new topic for this very problem?

IMO this should be a GitHub Discussion: Jermolene/TiddlyWiki5 · Discussions · GitHub

IMO It’s way to technical for this forum.

I’ve finally been successive on using @btheado hints. Here is the way to use it in my widget class.

InsertWidget.prototype.render = function(parent, nextSibling) {
  this.parentDomNode = parent;
  this.computeAttributes();
  this.execute();
  const parseMode = { parseAsInline: true, configTrimWhiteSpace: true };
  const parser = this.wiki.parseText("text/vnd.tiddlywiki", this.output, parseMode);
  this.makeChildWidgets(parser.tree);
  this.renderChildren(parent, nextSibling);
};

Now, there can be use of widget in the this.output property. They will be properly handled.