Step 5 - Adding controls for individual Notes
Changes
Download
We can download this and drag the resulting file to our test wiki:
SuppNotes_Step5.json (4.3 KB)
We can simply accept the overlaying of the earlier code. But there is a new tiddler included, and that one is a JS module, so we will need to save and reload our sample wiki to see these changes.
Screenshots
Commits
| 1a 7eba2 | Add dummy buttons for delete and edit |
| 8d8247b | Make delete button work |
| 445a0ea | Move the buttons up a level for better formatting |
Explanation
JSON Content
Once again, there is nothing surprising in the JSON. We do keep the Dummy Text note we added last time to "HelloThere", but other than that, nothing has changed.
Custom JavaScript jsondelete operator
There is a new tiddler this time, a custom operator to delete a node in a JSON string. A different version of this operator is scheduled to be included in 5.4.0, but for now we’re using a custom version, one which should act much the same but which is simpler to include here.
Note well: We do not need to understand how this works under the hood to follow along here. Those who are not interested in the JavaScript nuts and bolts can feel free to skip this session. We won’t judge. And we’ll catch up to you in the View Template section.
Code
exports["jsondelete"] = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
var data = $tw.utils.parseJSONSafe(title,title);
if(data) {
const res = deepDelete(operator.operands)(data);
results.push(JSON.stringify(res));
}
});
return results;
};
const deepDelete = ([first, ...rest] = []) => (obj,
p = Number(first),
a = p >= 0 ? p : Array.isArray(obj) && Math.max(0, obj.length + p),
) =>
first == undefined
? obj
: Array.isArray(obj)
? rest.length == 0
? [...obj.slice(0, a), ...obj.slice(a + 1)]
: [...obj.slice(0, a), deepDelete(rest)(obj[a]), ...obj.slice(a + 1)]
: rest.length == 0
? Object.fromEntries (Object.entries(obj).filter(([k, v]) => k !== first))
: Object.fromEntries(Object.entries(obj).map(
([k, v]) => (k == first) ? [k, deepDelete(rest)(v)] : [k, v]
))
Analysis
This has the public exported function jsondelete, which is a Tiddlywiki wrapper around the function deepDelete. This function is written in a very different style than most TW code, using nested conditional operations and expressions instead of statements. The basic idea is that we accept an array of indices and return a function which takes an object, traverses that object along the path of node names supplied, and when that path is exhausted, remove the current element.
This is a recursive function. The base case is when the path is empty, and we return the object intact. Then we fork on whether we have an array or something else. In either case, we fork on whether there is any remaining path beyond the current node.
- If we’re in an array and have no remaining path, we return an array with all the elements before and all the elements after the current index, but not the element at the index.
- If we’re in an array and the path goes deeper, we return all the elements before the current index, make a recursive call back to this function with the remaining path and the element at this index, include the results and then include the elements after that index.
- If we’re in an object and have no remaining path, we decompose our object into a list of key-value pairs, filter out those with keys matching our current index, then reconsistuting the remaining back into an object.
- If we’re in an object and the path goes deeper, we decompose our object into a list of key-value pairs, converting those with keys matching our current index by recursively call our function with the remaining path and the value, leaving the others intact, then reconsistuting the results back into an object.
(This breakdown makes it clear that we’re missing the case where the element is neither an array nor an object. While we won’t fix it now, that should be taken up soon. (TODO))
View Template
To those who skipped the deep-dive into the JS, welcome back to the tour!
delete-note procedure
We start with a new procedure which calls our new operator:
\procedure delete-note(index)
<$action-setfield
$tiddler="$:/supp-info/notes/content"
$value={{{ [{$:/supp-info/notes/content}jsondelete<currentTiddler>,<index>format:json[2]] }}}
/>
</$let>
\end delete-note
We call the jsondelete operator on our JSON content using the current tiddler and the index supplied, format the result in a more readable format (format:json[2]), and then override that JSON content with this new value.
Updated note handling
Here we add two new buttons next to the note, one to call the delete operation we’ve added, and one to trigger edit mode.
<div class="note-row">
<$button actions=`<<delete-note $(index)$>>` ><span class="icon">{{$:/core/images/delete-button}}</span></$button>
<$button actions=`<<>>` ><span class="icon">{{$:/core/images/edit-button}}</span></$button>
<div class="note">
<$wikify name="note" text={{{ [{$:/supp-info/notes/content}jsonget<currentTiddler>,<index>] }}} output="html"><<note>></$wikify>
</div>
</div>
We hook a real activity to the delete button, but for this iteration leave the edit one as a dummy. Most of this is simple, but we should pay attention to how our delete button operation is configured. There are different ways to do this. Older code usually nested action widgets inside the $button contents. That still works, but most modern code uses the actions string attribute as above, allowing us to delay the calling of the widget until the button is pressed. Only then is the string interpreted. But, we want to pass our index parameter along so that it always included. For this we use Substituted Attribute Values:
<$button actions=`<<delete-note $(index)$>>` >
These allow us to include a variable’s value (index) directly in a string. A similar form allows us to use the output of a filter expression instead.
In our iteration on the second note for our tiddler (remember, that means index 1), the above would be equivalent to
<$button actions="<<delete-note 1>>" >
And, when the button is pressed, our procedure will be run.
Again, for those of us who haven’t been following along, let’s not forget that when we add this code to our running wiki, we will need to save and refresh to see everything work. JavaScript tiddlers need that boost from startup.

















