Editable fields table.json (9.6 KB) (code reproduced below)
\procedure edit-field(field, label, display:"<$transclude $field=<<field>> />", copy:"no")
\function useLabel() [<label>!match[]] ~[<field>]
\function baseState() [[$:/state/edit/]] [<field>] +[join[]]
\function fieldValue() [<currentTiddler>get<field>]
\function tempValue() [<temp>get<field>]
\procedure edit-toggle() <$action-listops $tiddler=<<state>> $field="text" $subfilter="+[toggle[edit]]" />
\procedure done()
<$action-deletefield $tiddler=<<state>> $field="text" />
<$action-deletefield $tiddler=<<temp>> $field=<<field>> />
\end done
\procedure save()
<$action-setfield $tiddler=<<currentTiddler>> $field=<<field>> $value=<<tempValue>> />
<<done>>
\end save
\procedure cancel()
<$action-deletefield $tiddler=<<temp>> $field=<<field>> />
<<done>>
\end cancel
\procedure delete()
<$action-confirm $message=`Do you wish to delete the '$(field)$' field?`>
<$action-deletefield $tiddler=<<currentTiddler>> $field=<<field>> />
<<done>>
</$action-confirm>
\end delete
<$let temp=`$:/temp/volatile/edits/$(currentTiddler)$`>
<$qualify title=<<baseState>> name="state">
<tr>
<th>
<$button actions=<<edit-toggle>> class="tc-btn-invisible field-label">
''<<useLabel>>''
</$button>
</th>
<td class="content">
<% if [<state>!text[edit]] %>
<$button actions=<<edit-toggle>> class="tc-btn-invisible">
<<display>>
</$button>
<% else %>
<% if [<fieldValue>!match[]] %>
<pre title="Current value"><<display>></pre>
<% endif %>
<$keyboard key="enter" actions=<<save>>>
<$keyboard key="shift+backspace" actions=<<cancel>>>
<$keyboard key="delete" actions=<<delete>>>
<$edit-text tiddler=<<temp>> field=<<field>>
default=<<fieldValue>>
placeholder=<<field>>
class="tc-max-width"/>
</$keyboard>
</$keyboard>
</$keyboard>
<% endif %>
</td>
<td class="buttons">
<% if [<state>text[edit]] %>
<$button
actions=<<save>>
class="tc-btn-invisible"
tooltip="Save changes - [enter]">
{{$:/core/images/done-button}}
</$button>
<$button
actions=<<cancel>>
class="tc-btn-invisible"
tooltip="Cancel changes - [shift+backspace]">
{{$:/core/images/cancel-button}}
</$button>
<$button
actions=<<delete>>
class="tc-btn-invisible"
tooltip="Remove this field - [delete]">
{{$:/core/images/delete-button}}
</$button>
<% endif %>
<% if [<fieldValue>] [<tempValue>] +[!match[]then<copy>match[yes]] %>
<$button
message="tm-copy-to-clipboard"
param={{{ [<tempValue>!match[]] ~[<fieldValue>] }}}
class="tc-btn-invisible copy-button"
tooltip="Copy field value to clipboard">
{{$:/core/images/copy-clipboard}}
</$button>
<% endif %>
</td>
</tr>
</$qualify>
</$let>
\end edit-field
\procedure enlist-field(field)
\function last() [enlist<items>count[]compare:number:gt[2]then[, ]] [[ and ]] +[join[]]
\whitespace trim
<$let items={{{ [<currentTiddler>each:list-item<field>sort[]] +[format:titlelist[]join[ ]] }}}>
<$list filter="[enlist<items>butlast[]]" join=", " />
<$list filter="[enlist<items>butlast[]limit[1]]"><<last>></$list>
<$list filter="[enlist<items>last[]]" />
</$let>
\end
<style>
.view-field-edits :is(th, td) {
padding: 0.15em 0.5em;
}
.view-field-edits th {
min-width: max-content;
width: 20%;
white-space: nowrap;
text-align: right;
}
.view-field-edits th button.field-label {
text-transform: capitalize;
width: 100%;
text-align: right;
}
.view-field-edits td.content {
width: 80%;
padding: 0.15em 0.15em;
}
.view-field-edits td.content pre {
margin: 0.15em 0;
}
.view-field-edits td.buttons {
min-width: max-content;
white-space: nowrap;
border-left: 0px;
padding: 0;
}
.view-field-edits td.buttons button { display: inline-block; margin: 0.15em; line-height: 1.5em;}
.capitalize { text-transform: capitalize; }
</style>
<table class="view-field-edits tc-max-width">
<!-- Title Row -->
<<edit-field caption "Title" copy:"yes">>
<!-- Author Row -->
<<edit-field author display:"<<enlist-field author>>">>
<!-- Year Row -->
<<edit-field year "Publication year">>
<!-- Medium Row -->
<<edit-field medium>>
<!-- URL Row -->
<<edit-field url "URL" display:"""<a href={{!!url}} class="tc-tiddlylink-external">{{!!url}}</a>""">>
</table>
I recycled some of your ViewTemplate table + the <<enlist-field>>
procedure I’d written up previously for testing purposes, but all you really need here are the edit-field
procedure (and the styles to make it look a little nicer).
Current keyboard settings:
- [enter] = save changes
- [shift+backspace] = cancel changes
- [delete] = delete field
I was originally going to use esc
for the cancel key, but it’s already in use as the default ‘exit draft mode?’ key, so I switched to something that shouldn’t conflict if you happen to be editing fields in the draft preview window.
If I were releasing this as a plugin, I’d probably include some config settings to let you remap the keybindings more easily… but honestly, I think I’ve tinkered with this about as much as I care to at the moment. Do feel to remix it however you like, though!
A few more design notes:
I was initially using the <<edit-toggle>>
macro to handle both editing and saving; it can do both since it toggles the text of the <<state>>
tiddler between “edit” and “”, depending on the current value. But since I’m storing the edits in a temporary tiddler here, there are actually at least two possibilities:
- save changes and leave edit mode
- ignore changes and leave edit mode (and, potentially, delete the temporary value)
I decided to retain the original behavior of clicking on the label cell to enter/leave edit mode, but not to make any decisions about unsaved changes if you exit this way.
- Any changes you made in the temporary tiddler will be retained in that tiddler, but will not be saved back to the “real” field until you explicitly choose to do so by hitting “enter” or hitting the save button.
- Similarly, temporary edits won’t be cleared until you hit “shift+backspace” or the cancel button.
This means we’re up to 3 possible actions — <<edit-toggle>>
, <<save>>
, and <<cancel>>
— where both <<save>>
and <<cancel>>
also use <<done>>
to take you out of edit mode and clean up the draft. I took Tony’s suggestion and added a <<delete>>
action as well, which also cleans up after itself.
Please note: I’m currently using temporary tiddlers (prefixed with $:/temp/volatile
) to store the field “drafts”. This has two major benefits:
- Tiddlers prefixed with
$:/temp/volatile
are automatically subject to refresh throttling, which should cut down on potential lag while typing.
-
$:/temp
tiddlers don’t persist between wiki sessions, so they won’t clutter your wiki.
However, this also means that any unsaved field drafts won’t be retained across sessions! If you don’t like this behavior, you can replace the $:/temp/volatile
prefix with $:/state
.
Have fun!