Advanced Templates Module (PF1)
A thread to discuss Svelte and the Advanced Templates module.
23 Replies
Current code is in this branch: https://github.com/dmrickey/ckl-advanced-templates-pf1/tree/ui_update/src/module/view
p.s. I'm on lunch while at Jury duty and I don't know when we'll be called back in, so if I stop responding that's why 😅
Yeah.. I'll take a look at that branch now.
What is the
action
prop? Does this data change or is it static? This could also be moved to a context quite likely. It also is reasonable to move the context setting to the component constructor here https://github.com/dmrickey/ckl-advanced-templates-pf1/blob/ui_update/src/module/view/show-template-settings.js
The Svelte component constructor accepts props, but also a context
Map where you can set the context data in the constructor and don't have to do that inside the component itself. See the constructor options
What is the current state of the code branch? Is it working and you want to refine it? Or is it a current work in progress. I haven't installed the module to try running it and there are several aspect that can potentially be improved. Just from looking at the code in template-settings.svelte
it is not clear how the reactive block executes except for when the first time the component is instantiated. I gather this block runs immediately and subsequently isn't triggered:
I'd need to install this module to examine it more, but let me know the status of this branch.
In all of the settings components and the shared component I see that you directly bind / modify the updates object. This shouldn't trigger reactivity or the reactive block quoted above.
Is there a reason you don't wrap the settings components in a form and have an on:submit
handler to then process all of the form data?
For example:
Doing it this way also doesn't require each of the TemplateApplication
components to accept a slot for the shared settings. In this case you set the name
field of each input to the desired key properties for the resulting data like:
Inside one of the template components...
It basically looks like your current approach is overcomplicated / not using Svelte for its strengths.What is the action prop?This is an Item-like object defined by the Pathfinder system. The action itself could change, but the specific data I'm reading off it basically won't change unless someone is doing that manually. Outside of a macro (or some other mod that visualizes Item data), the only way to edit the data I'm specifically using in the svelte component is directly in the component (and few system UI inputs that I'm hiding and providing different inputs for).
What is the current state of the code branch?Just to recap - previously the mod opened all of my extra settings in a svelte modal and those modifications were saved when the user hit "ok" on the modal. The goal of this branch is to instead inject my settings directly in the system's Item's sheet and save as modifications are made (or more probably, debounced so they're saved shortly after modifications are made). Answer - so far I've refactored it so that my inputs are now instead within the Item's sheet and I'm happy with the layout. I'm still working on the best way to use a
store
and save the modifications as they're made.
I gather this block runs immediately and subsequently isn't triggered:That was me testing to see if that was viable, I was guessing it wasn't, but it was just a quick test (and it wasn't a viable option). It did actually run some times, but not in some cases. I thought I had already removed that. looking back now I see that I forgot to push that commit 😅
Is there a reason you don't wrap the settings components in a form and have an on:submit handler to then process all of the form data?Yes there's a reason, that was the method when it was its own modal, but now that I'm injecting it into the Item's sheet directly, I want to adhere to how users already expect the item sheet to behave (which is that it saves directly as changes are made). Now that I'm injecting it into the system's sheet, it's already contained within a form that I'm not "in charge of"
Yeah.. I guess that is true that you can't have a form inside a form. Hmm...
Lastly, I'm using a slot because this (see image) is how I want the layout. The dark red outline is the shared template. The blue is the circle template--basically there are checkboxes that I want to keep grouped together. So any checkboxes that are just yes/no are all grouped together at the bottom whether they're from shared or from the specific template shapes/types
Re: injecting input elements. It's quite possible that if you format the input elements correctly w/
name
and targeting the correct flag attribute that Foundry in processing the form will update the underlying document without you having to do anything. IE when it's closed or normally saved by Foundry.
Some of the components have name
defined in various input elements, but not all. Does this cause a conflict w/ the default Foundry handling of a FormApplication?I honestly have no idea what the default way Foundry handles that is (I've never had to write a document sheet from scratch before). Their API isn't exactly the easiest to read if you don't already know what you're looking for. Are you saying that it's probable I can just fill the data path into the name attribute and it may save without me manually intervening?
What app are you injecting into? It's likely a FormApplication for sure.
ItemActionSheet
(pf1-specific sheet)
I know all the various inputs all already have name
filled in
(though I don't know how it handles radio buttons because PF1 doesn't have radio buttons on any of its sheets)
Oh yeah, I didn't see the second part, I believe it is a FormApplication
Yes, I just double checked, it is a FormApplicationThe
name
in a standard FormApplication / DocumentSheet, etc. should follow the object access path IE x.y.z
. Having not looked at PF1 the default DocumentSheet from Foundry has _updateObject
as this:
You might want to put a debug console log statement there in DocumentSheet or any further overridden _updateObject
in the PF1 ItemActionSheet
to verify that no additional data from the injection is being picked / potentially serialized. My assumption that anything w/ a name
field inside of a form in FormApplication / DocumentSheet may be picked up.
The PF1 ItemActionSheet has a _updateObject
implementation. It's possible that any of your injected input elements with name
defined will be picked up in the normal form submission.Yeah I saw that. I've updated everything to have an appropriate name, but something is triggering itself over and it's just saving continuously now..
Well.. I would strip things down to a very simple injection with one additional input element and try and divide and conquer from there to find what may cause the loop. It doesn't look like you have the
$destroy
handling in any render / close hooks for ItemActionSheet
for instance.I realized what was happening with the data being saved weird. I was calling
jq.hide()
for the inputs that I was replacing, but since they were still in the dom (just hidden) foundry was saving the values using the same name
attribute (mine plus the now-hidden system's inputs) as an array under that property. calling jq.remove()
for those system elements resolved it.
I am calling $destroy, it's just in a hook outside of that "svelte folder". I haven't looked into memory management yet, but I can say that the svelte component is being re-added every time the render hook is called (which is whenever anything for the Item is being saved) and is only being destroyed once at the end. But I'll do some debugging later and see what the memory snapshots say
I've basically got these two methods that are called for the render
and close
hooks
Good good.. You'll soon be the foremost expert around here on embedding Svelte components in stock Foundry FormApplications. 😄
Re: and is only being destroyed once at the endYou can add an
onDestroy
callback just to log that the $destroy
takes affect when desired. In template-settings.svelte
add:
That should provide enough proof without having to get into comparing memory snapshots and such. Of course doing analysis of retained memory is the truly full view of what is going on and a good skill to have in general regardless of programming language.
---
In general I'd say it's best to try and not use JQuery wherever possible in ones code by converting or feeding your additional methods with the underlying element from Foundry. Eventually JQuery might dissapear from Foundry, but the modern ES / DOM support makes JQuery essentially irrelevant. IE there is a remove() method on all DOM elements.In general I'd say it's best to try and not use JQuery wherever possible in ones code by converting or feeding your additional methods with the underlying element from Foundry. Eventually JQuery might dissapear from Foundry, but the modern ES / DOM support makes JQuery essentially irrelevant. IE there is a remove() method on all DOM elements.Yeah I totally agree. Most of the time I usually avoid jquery and do straight dom manipluation. I threw that in quickly but thanks for the reminder to go back in and update it.
Good good.. You'll soon be the foremost expert around here on embedding Svelte components in stock Foundry FormApplications. 😄Yay 😅 lol. This was good practice for my other mod where I'm doing something similar. I'm not sure if the result is similar enough to what I was originally aiming for though. I guess I'll find out ¯\_(ツ)_/¯
Yeah.. It would be nice to separate the injected input elements and handle them in a more reactive manner. It likely is possible by omitting the
name
attribute for injected input elements as they shouldn't get picked up in the Foundry form processing. This does create a lot of additional complexity in coming up with a way to handle things reactively. In the near future with Svelte 5 this can be a bit more viable, but implementing something for Svelte 4 will be a bit more messy and probably isn't worth the hassle. IE just accepting that any changes to your data will go through the normal Foundry form processing.I was able to confirm that I could still save my data separate from the sheet handling the form data before I added/fixed all the
name
attributes. And in this case simply using the name
attribute worked--I'm not sure it will in my other case thoughMaintainability and coming back to this code the next time is the main consideration. It's definitely an interesting use case.
Yeah using the
name
attribute relies on the system's implementation of _updatedObject
for this FormApplication. It probably won't change in a way that would break this, but there's no guarantee (obviously)
At the very least, I'm happy that I was able to get the template injected into the native view seemlessly without having to resort to hbsI'm a bit hesitant to suggest this, but there is a reasonable way to handle this w/ TRL and a 3rd party helper.
One thing that you can do now with TRL is create a custom object store using
propertyStore
which is an additional 3rd party store helper. This file exports createUpdatesStore
which you pass the Foundry doc to initialize the store in the top level Svelte component.
createUpdatesStore.js
:
For a clean implementation you might want to move all of the custom store creation above into prepareData
and have prepareData
return the updates
store.
You can then do the following in template-settings.svelte
:
Then in your child Svelte components:
The above is all pseudo-code I just typed out, so there could be problems, but it is pretty close to how it should be; just haven't tested it in this particular scenario. This is a way forward to create a custom object store that should be more manageable / less maintenance burden than going a more bespoke route. Of course you'd have to know about all of these techniques that goes beyond the standard Svelte store helpers. This is where TRL provides additional flexibility.
The bindings in the input elements are actually stores and any changes to the child property stores will receive an update to the main updates
store which is subscribed to in template-settings.svelte
.
subscribeIgnoreFirst
does what it says. The callback is not triggered on registration, but only subsequent updates.
With Svelte 5 you will be able to create a custom stateful object using $state
, but the result will be very similar to the above route that is currently possible.
You can read more about svelte-writable-derived
as it is a 3rd party library added to TRL: https://github.com/PixievoltNo1/svelte-writable-derived. It is exported in TRL from '#runtime/svelte/store/writable-derived'.
Let me know if you are going to try any of the above as I can help you refine things as necessary... I changed the above pseudo-code to be a bit more accurate. I still haven't run / tested this code, but it should be pretty close to the pattern to follow now.Thanks, I'll definitely take a look at this as I like the idea of being more in control of saving it rather than relying on the system's
FormApplication._updateObject
implementation.
In your last example, would <input type=text bind:value={$updates.data.measureTemplate.customColor} />
not work? It specifically needs to be a separate top level variable?It should work in Svelte 5 when creating a
$state
-ful data structure. The Svelte 4 compiler when you do $update.data.store
Applies the store to update
and not the child property access. I've always thought something like this $(update.data.store)
could have been supported, but I never got around to prototyping it and bringing this up. The Svelte 3 & 4 compiler has some limitations. A lot of that goes away with Svelte 5 as things can be designed in a more explicit manner. This comes at the expense of just "magic" happening. IE $:
reactive statements are going away. The old way of working with Svelte will still be supported for Svelte 5, but likely deprecated by Svelte 7.
You can likely find a bit of info on Svelte 5 now. They are calling the new compiler directives "runes".