Some TiddlyWiki CSS questions

I’m working on my Periodic Table Demo (as I usually start questions these days :slight_smile: .) The latest feature I added was a view of the table with hoverable section letting you display periods (essentially rows, but with some complexities at the bottom four rows), groups, (columns but skipping the bottom two rows), and element types (more complex shapes, visually segregated by colors.) You can see this behavior in the latest version.

I’m moving onto other things for the moment, but am also trying to learn a little more about how TW and CSS interact, so that when I try to extend this behavior, I’ll be able to understand it.

I have three questions. There is much more detail below, but the quick versions are:

  1. Are rendered <style> tags are somehow scoped to only apply to the tiddler they’re in? If so, how is that done?
  2. How big of a performance hit is dynamically generated CSS?
  3. Are there any examples of continually modifying a dynamic CSS tiddler by toggling many field values? Has it worked well?

Question 1

Are rendered <style> tags are somehow scoped to only apply to the tiddler they’re in? If so, how is that done?

I really want to know why the following actually works. My annotated table creates somewhat complex CSS rules that connect the hover of one node with the change in opacity in certain tiles. (Note this behavior is not yet available in Firefox!) There is nothing about the current tiddler in those rules, so I was expecting that if the tiddler were to show up twice (for instance if it were to be transcluded in another tiddler, and both were in the Story River), the hovering one of the controls in one of them would affect both. That’s not what I wanted to happen, so I’m pleasantly surprised by this little demo:

Chemistry — periodic table and related tools - Brave 2023-03-25 15-49-47 (3)

But I want to know why.

The element tiles look like this:

<div class="tile">
  <div class="element transition-metal period-4 group-11">
    <div class="number">29</div>
    <div class="mass">64</div>
    <div class="symbol">Cu</div>
    <div class="name">Copper</div>
  </div>
</div>

And one of the control blocks (bottom-left on the table) looks like this:

<div class="group-annotation">
  <a class="header"><b>Group</b></a>
  <a href="javascript:void(0)" class="focus focus-group-1">1</a>
  <a href="javascript:void(0)" class="focus focus-group-2">2</a>
  <!-- ... -->
  <a href="javascript:void(0)" class="focus focus-group-18">18</a>
</div>

<style>
/* lots of other bits elided */

table.periodic:has(.focus-group-1:hover) .element:not(.group-1) {opacity: .25}
table.periodic:has(.focus-group-2:hover) .element:not(.group-2) {opacity: .25}

/* ... */
table.periodic:has(.focus-group-18:hover) .element:not(.group-18) {opacity: .25}
</style>

Note there is nothing in there specific to the tiddler where this is happening. It feels like magic. I though I was going to have to either find a way to address this using qualify or simply ignore it. Instead it simply works the way I would like. What is this sorcery?


Question 2

How big of a performance hit is dynamically generated CSS?

The above is generated by

<$list filter=[range[1],[18]] variable="idx">
table.periodic:has(.focus-group-<<idx>>:hover) .element:not(.group-<<idx>>) {opacity: .25}
</$list>

There’s a similar list to generate the individual controls; and all this is duplicated three times for the three sets of controls. With seven periods, eighteen groups, and eleven types, that’s thirty-six rules that I’m recreating every time the user opens this tiddler, and the discussion for the next question may imply that I will have to double this to 72. Would a fixed list of these be more performant? Is it worth the additional bytes?

Note that even it it is, I won’t change this until the code feels fairly settled, as I won’t want to change that many separate rules every time I fiddle with the implementation. But I would like this to be as performant as possible.


Question 3

Are there any examples of continually modifying a dynamic CSS tiddler by toggling many field values? Has it worked well?

The next phase I’d like to implement for this section is to allow the user to click on one of these controls and make it a more permanent change, that is, for it to last beyond the hover. The hover would still rule, meaning that while you’re hovering the controls it would have exactly the behavior it has now, but when you are not hovering, the ones you’ve selected will determine which periods, groups, and types are focused. (Technically, that means that everything else is blurred.)

The implementation I was imagining was something like this:

title: $:/_/my/DynamicTableStylesheet
tags: $:/tags/Stylesheet
groups: 2 13
periods: 6
types: metals actinides

<!-- here there be magic -->

Actions connected to clicking the various controls would use listops to add or remove values from the groups, periods, and types fields. The magic section I pretty well know how to write, creating a rule that says all tiles are faded (assuming there is at least one value in any one of the controls), then adding more specific rules that say the elements with class group-2, group-13, period-6, .metals, or actinides are not faded. I’ll have to play games with specificity to make this work seamlessly with the hovering, but I’m sure I can work that out.

If it made more sense, I could skip the listops and simply have toggle-able fields for each of my 36 properties. Does either one make more sense to you, for performance reasons, for cleanliness, or for anything else?

I’ve not seen anything like this in the wiki’s I’ve examined. Has anyone done something like this? Did it work? Are there significant pitfalls? Again, are there substantial performance concerns?

Or do you have suggestions for other techniques that sound simpler?

Also, as a user, would you expect those choices to vanish when you close the annotated table tiddler? Or would they hang around until the next time you opened it? I can’t see a reason these would last beyond the current session, but if you have arguments for that, I’d be interested to hear them, too

A small side note:
Is it possible to have version number somewhere in Periodic Table Tiddler or site title/subtitle?
I mean somewhere in front page.

Unfortunately no! If you use block of styles like <style>...</style> in one tiddler when it is viewed in storyriver all other tiddlers in storyriver have access to that block! So if you have <style>h2{color:red}</style> in one tiddler and it is open in story river, h2 for all tiddlers open in story river is red.

Question 1 answer addon

I will just add here, to @Mohammad’s responce if the content of the style tags defines a named class, and this is only applied to elements in the current tiddler, then of course it only impacts the current tiddler.

  • That is using the style tags is like adding it to a stylesheet tiddler with the $:/tags/Stylesheet tag, it becomes global,
    • and the last will win,including the inline style tags, when the CSS in the style tags is visible.
  • CSS by definition uses a sophisticated set of selectors CSS Selectors Reference and it is these that determin where it applies.
<style>
.weirdclassname {
css here
}
</style>

<span class=weirdclassname> class applied here</span> and anwhere visible you assign weirdclassname.
  • So you use selectors to ensure specificity
    • or don’t

The result is the world of “CSS is your oyster”, and tfor it to behave any other way would limit its capacity.

  • Remember also, one or more classes in a tiddlers class field will be applied to the current tiddler.

My general answer here is you can’t ask CSS to do too much, it is often very fast, after all HTML and CSS are designed for this.

In your list widget example, it is generated when the list / tiddler is viewed and thats the end of it, the performance issue is typicaly in the render process.

  • Yes if you follow the implications of the following notes.

See my answer to question 2, the issue is as I understand it, does a change force a rerender occur? This will be the perfomace issue, not changing CSS values.

  • So if your html, css selectors and table data, are renderd on screen and you make changes to a seperate stylesheet, I think no (tiddlyWiki) rerender is needed, the CSS is simply “reapplied” using very fast browser handling of CSS.
  • I stand to be corrected but think this is correct.

It is helpful to think of performance in terms of impact on render, refresh and reaction to user input.

Dynamically generated CSS is recalculated as part of the TW refresh process, so potentially on every key stroke. While this can seem like it would be prohibitive from a refresh performance point of view, in reality the calculations that take place are negligible compared to the rest of the refresh cycle.

When the output CSS of a stylesheet tiddler changes, the entire style element (consisting of the output of all CSS tiddlers) is replaced and this causes the browser to have to recalculate the CSSOM and can trigger a repaint or reflow. If this is triggered on every keystroke the performance hit can be noticeable as it can make user input (typing) lag. If it is triggered by user actions such as clicking a button, it is rarely noticeable.

The alternative in TW is to have dynamically assigned class names that change in response to state tiddlers changing, for example:

<div class={{{ [<mystatetiddler>get[text]match<this-element>then[active]] }}}>
</div>

If you only have a few such elements, using filtered attributes to assign classes in this way is usually more performant, especially considering that filters are precompiled and cached for repeated execution, as this avoids the browser needing to recalculate the entire CSSOM. However, with many elements the balance tilts in favour of dynamic CSS, despite the browser needing to recalculate the CSSOM and associated reflow.

You may also find of interest this discussion which covers some of this in more detail:

Another technique that I have found to be marginally more performant that dynamic stylesheets in limited testing is using wikitext to dynamically assign CSS variables on DOM nodes and having static CSS that responds to those variables.

The core does this for things like the sidebar based on the value of a tiddler’s text field, and I do something similar in Streams for highlighting the selected item or during drag and drop. There should be no significant difference between using separate tiddlers or many fields of a single tiddler.

However, I highly recommend to use a separate state or temp tiddler to save the state information such as groups, periods and types rather than saving them in the stylesheet tiddler. If you ever package this as a plugin, modifying the stylesheet tiddler would create a real tiddler overriding the shadow tiddler in the plugin. Then if users upgrade the plugin they would not pick benefit from the newer version of the stylesheet tiddler due to the override.

2 Likes

Great answer with the technical knowledge to back it up from @saqimtiaz

Even more on Question 1

I decided to try ChatGPT to help me illustrate the use of style tags and appropriate selectors, the full conversation, answered and editing for use in tiddlers is here Using selectors to keep style tags only impacting in specific tiddler_table.json (6.4 KB)

Wow !

However to cut a long story short consider this in a tiddler;

<style>
    /* Define CSS classes for the specific table */
    #my-table {
      border-collapse: collapse;
      width: 100%;
      max-width: 800px;
      margin: 0 auto;
      font-family: Arial, sans-serif;
    }
    #my-table th {
      background-color: #4CAF50;
      color: white;
      padding: 8px;
      text-align: left;
    }
    #my-table td {
      border: 1px solid #ddd;
      padding: 8px;
      text-align: left;
    }
    #my-table tr:nth-child(even) {
      background-color: #f2f2f2;
    }
    #my-table tr:hover {
      background-color: #ddd;
    }
  </style>

<table id="my-table">
  <thead>
    <tr>
      <th>First Name</th>
      <th>Last Name</th>
      <th>Age</th>
      <th>Country</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>John</td>
      <td>Doe</td>
      <td>30</td>
      <td>USA</td>
    </tr>
    <tr>
      <td>Jane</td>
      <td>Doe</td>
      <td>28</td>
      <td>Canada</td>
    </tr>
    <tr>
      <td>Bob</td>
      <td>Smith</td>
      <td>45</td>
      <td>Australia</td>
    </tr>
    <tr>
      <td>Alice</td>
      <td>Johnson</td>
      <td>33</td>
      <td>UK</td>
    </tr>
  </tbody>
</table>
  • notice in the above the CSS applies to the below table and no other (unless it uses table id=my-table) so other tables will not be impacted.
  • Of course, place this in a stylesheet tiddler and apply @saqimtiaz advice an hey presto.

The rule responsible for dimming the groups depends on there being an element with a hover state inside the table.

table.periodic:has(.focus-group-1:hover) .element:not(.group-1) {opacity: .25}

1 Like

The ChatGPT table included hover as well. Cool

Sure. It’s usually in the URL when I post it, but I’ll see if I can find a quick way to auto-update it on the page. I usually also post a “latest” URL, and I won’t guarantee that its accurate there, as I might update this one numerous times between versions. But the versions could have it.

1 Like

That’s what I thought, and that’s why I was really surprised by the behavior.

Of course it does. I wrote it that way. D’oh!! Somehow when I was imagining this, I wasn’t figuring on how :has works and assumed I’d have an issue. Then I wrote it, got it working well, and finally went to address the issue I believed all along I had, never noticing that the implementation fixed it.

I saw all the earlier responses when I woke up this morning, and I didn’t recheck when I logged in, so I created a demo of the “magic” and was about to post it, when I saw that there’s a new answer. Thank you very much; this was driving me crazy!

Thank you for your detailed answer. It sounds as though I’m traveling in the right direction. It’s very nice to get confirmation before I’m too heavily committed.

This community is fantastic!

Oh yes, I forgot to mention that. I was definitely expecting this to be in a $:/temp tiddler. My only question is whether I should remove that tiddler when the table tiddler was closed or persist it when they reopen the table. And that’s much more a usability question than a technical TW one. (Still any suggestions would be appreciated.)

Thank you. I was mostly worried about the “dynamic” aspect of this. I didn’t know if when a tiddler containing a stylesheet is closed, whether the CSS rules that it generated are removed from memory, or if they simply accrete and clog up the works. That is probably not a problem for typical usage, but I keep hearing people talk about leaving TiddlyWiki instances running for months and I didn’t know the implications.

I’m afraid I might run into those. I see some occasional slowdowns when generating the annotated table, never terrible, but a little worrying. I don’t actually understand shat they’re about, but wondered if somehow this regeneration of the CSS might be related. They seem to correlate, very roughly, to the time I’ve had the wiki open without a refresh. Because I’m mid-development, that isn’t often a very long period, but I do have a slight concern. Eventually, I believe the CSS will be in its own tiddler which won’t be changed much, so the problem may take care of itself. We’ll see.

Thank you for your insights!

I’ve yet to even try ChatGPT (or Bard.) That dialogue is both impressive and terrifying!

It the tiddler has the tag $:/tags/Stylesheet, then the css becomes “global” regardless of being present in the river or elsewhere.
It the tiddler does NOT have the tag $:/tags/Stylesheet, then the tiddler must be rendered somewhere (e.g. river, sidebar, wherever) for the css to be applied.

That’s me. No implications whatsoever. In fact, I begin to doubt the browser’s mastery of memory management ever before I doubt TW’s capabilities.

Sidenote:

Try to avoid using HTML element ids (#my-id) in css if you can. If you need to target a specific tiddler, preface your selector with [data-tiddler-title=my-title].

1 Like

I would be inclined to let the state persist when the table is reopened within the same wiki session (without reloading the wiki). Partly this is because I dislike tying implementation details too strongly to the default TiddlyWiki UI. Imagine that you use a hook to reset/delete the temp tiddler holding the state when the tiddler holding the periodic table is closed from the story. Well, what if the periodic table was opened in a new window, or a modal, or in a custom layout without a story? All of a sudden the intended behaviour falls apart.

If you use a $:/temp tiddler, the state will automatically reset when the wiki is reloaded as temp tiddlers are never saved. At times I have a config setting that determines whether to persist the state across wiki sessions. Under the covers the config setting just switches between using a $:/temp prefix for the tiddler holding the state, vs a different prefix such as $:/config/periodictable/state.

Note that where consistency in user experience is important, I avoid using the $:/state prefix quite often due the fact that these tiddlers are by default persisted in single file wikis but not in node.js wikis.

As an aside, I can imagine a custom layout option for the periodic table being handy in some situations. The lowest hanging fruit is giving the periodic table tiddler the tag $:/tags/Layout and the fields name and description. If you use any global macros, you will need to import those as well.

Thank you for the reply. I do understand that, and expect to eventually take advantage of it, once changes to this area are minimized. I was just worried that if something came up to prevent me from doing so (and that wouldn’t surprise me) I might have problems not so much with total memory but with too many CSS rules, even if they are merely multiple copies of the same rule.

Does this work for you?

That’s what you should get if you visit the main page, https://crosseye.github.io/TW5-Chemistry/ or the version-specific page https://crosseye.github.io/TW5-Chemistry/0.7.8/. If I’ve pushed changes but not yet a version, it’s at https://crosseye.github.io/TW5-Chemistry/latest/, and that will have, say, 0.7.8+ with the + indicating the change.

Obviously, I could update the site title or subtitle SideBarSegments instead. But when I try that, I don’t like the extra spacing it introduces between title and subtitle. I’m sure that could be fixed, but is this a reasonable alternative?

1 Like

Which also applies to this:

I had started to add the following paragraph but backspaced over it – @saqimtiaz’s point covers exactly the same ground I was intending to cover…

If you’re adding to the TW UI, you will benefit from prefacing your selector with a selector “partial” that selects the container (notionally, .my-container):

.my-container [data-tiddler-title=my-title] { ... }

For example, the following applies style only when your tiddler is present in the Sidebar:

.tc-sidebar-scrollable [data-tiddler-title=my-title] {
  ...
}

Or, perhaps, only in the More tab of the Sidebar:

.tc-sidebar-tabs-more [data-tiddler-title=my-title] {
  ...
}