#Version 12 Deep Dive - Application V2

1 messages · Page 1 of 1 (latest)

eager hill
#

Now that V12 Prototype 2 is available our first usable prototype of ApplicationV2 is available!

I'm creating this thread to consolidate some resources for those of you looking into the new framework and to answer any questions you have while exploring it.

API Docs (WIP)

Real-World Examples

The following Setup applications have been converted to ApplicationV2, all of which can be inspected within the public/scripts/setup.js file:

EULA
JoinGameForm
SetupAuthenticationForm
SetupMenu
SetupSidebar
SetupWarnings
UserManagement

I think the JoinGameForm and UserManagement are probably the best real-world examples to look at because they strike a balance between actually having some meaningful UX while not being overwhelmingly arcane.

Timelines

We anticipate keeping Application (v1) around for a long time, at least through V15. Whether Application v1 ever gets fully deprecated will depend on a lot of things but it's possible it will never go away entirely. We'll see what the adoption rate looks like over the coming years.

#

Example Usage

Here's a sort of basic example usage that illustrates how you would define an ApplicationV2 class using some of its available options. This example assumes the referenced template files exist.

const {HandlebarsApplicationMixin, ApplicationV2} = foundry.applications.api;

class MyApplicationV2 extends HandlebarsApplicationMixin(ApplicationV2) {

  /** @inheritDoc */
  static DEFAULT_OPTIONS = foundry.utils.mergeObject(super.DEFAULT_OPTIONS, {
    id: "my-application-{id}",
    window: {
      title: "SOME.Localized.Window.Title",
      icon: "fa-solid fa-triangle-exclamation"
    },
    position: {
      width: 680
    },
    actions: {
      buttonAction1: MyApplicationV2.#onClickButton1,
      buttonAction2: MyApplicationV2.#onClickButton2
    }
  }, {inplace: false});

  /** @override */
  static PARTS = {
    sidebar: {
      id: "sidebar",
      template: "mymodule/templates/my-app-sidebar.hbs",
      scrollable: [".sidebar-scroll-panel"]
    },
    main: {
      id: "main",
      template: "mymodule/templates/my-app-main.hbs",
      forms: {
        ".primary-form": MyApplicationV2.#onSubmitPrimaryForm
      }
    }
  }

  static #onClickButton1(event, target) {
    console.log("Clicked button 1!");
  }

  static #onClickButton2(event, target) {
    console.log("Clicked button 2!");
  }

  static #onSubmitPrimaryForm(event, form, formData) {
    console.log("Submitted primary form!");
  }
}
ancient crescent
#

I'm just here to see how the VUE/REACT implementations go

indigo niche
#

/ Svelte with TyphonJS 😛

quick sky
#

Party Sheet (in pf2e) faked parts sub-rendering so it'll be neat to eventually convert to the real thing

#

Though I wonder if detecting what part should re-render is automatic with the handlebars version or needs to be done manually.

iron hamlet
#

@quick sky how

#

concatenating separately compiled templates?

lapis field
vernal sand
#

Yeah, I'll definitely be playing around with this for the Vue sheet on 13th Age. I've also got a variant of Boilerplate based on that Vue sheet, so this would be a good opportunity to catch it up with the main Boilerplate repo.

quick sky
# iron hamlet <@104263996810469376> how

Overriding render to inject sub-renders into the main one, and then render properties to only replace some of them explicitly. This thread isn't for that so that's the best I can give you.

#

Vue/React is the real exciting stuff anyways

devout narwhal
#

/ Svelte with TyphonJS?

lapis field
iron hamlet
#

one thing that I don't quite get is how it will get hooked up

#

so far I've only dealt with SPAs completely written in Vue/React/Angular

sonic orbit
indigo niche
#

It already has a bunch of its own functions including Applications, so I wonder if this will allow for some optimizations or new features

#

But up to @tulip bronze to look into it 😆

eager hill
steel dome
#

assuming this

    actions: {
      buttonAction1: MyApplicationV2.#onClickButton1,
      buttonAction2: MyApplicationV2.#onClickButton2
    }

is replacing the v1 activateListeners? How does it know what to attach the listeners to

iron hamlet
#

so in those callbacks, would completely reinstantiate svelte & co from the ground up?

devout narwhal
#

it doesn't really belong here, but that api site is looking nice

indigo niche
iron hamlet
#

I mean Svelte's big feature is not re-rendering everything completely after a single, small change

indigo niche
#

Ye. The issue is the destruction of reactive elements that no longer exist (like a closed application window)

gaunt condor
#

Previously Basic Sight handled darkvision + unrestricted light perception. Now it handles just darkvision.

Is basic vision going to renamed in that case?

#

Although isn't there also a separate mode called Darkvison? If that's the case, what is the difference between Basic Vision and Darkvison?

devout narwhal
gaunt condor
#

Doh, thanks

iron hamlet
#

what do we know about forms so far? arrays have been really difficult in that regard

#

are those handled in a similar fashion as in v1? Is the v2 change basically a more open/performant render method?

eager hill
#

We find that "click thing, something happens" is by far the most common usage pattern for event listeners in apps, so we wanted to make that especially easy

steel dome
#

So it attaches all the provided callbacks to all data-action elements?

#

or matches by the property key?

eager hill
#

So in this example your HTML markup would look something like:

<button type="button" data-action="buttonAction1">Click Me for thing 1!</button>
<button type="button" data-action="buttonAction2">Click Me for thing 2!</button>
eager hill
ancient crescent
#

And that's a general click listener, so we can still use a tags and whatever other else we've got going?

eager hill
#

As for any other arbitrary type of event listener, the HandlebarsApplication uses the following method which is intended to be extended by subclasses:

    /**
     * Attach event listeners to rendered template parts.
     * @param {string} partId                       The id of the part being rendered
     * @param {HTMLElement} htmlElement             The rendered HTML element for the part
     * @param {ApplicationRenderOptions} options    Rendering options passed to the render method
     * @protected
     */
    _attachPartListeners(partId, htmlElement, options) {}
#

I think the JoinGameForm and UserManagement are probably the best real-world examples to look at because they strike a balance between actually having some meaningful UX while not being overwhelmingly arcane.

#

My own experience so far with migrating applications over is that usually there is a small net reduction in LOC and a net increase in clarity of behaviors.

#

I hope others end up experiencing the same, but we'll see.

steel dome
#

deleting 3 lines of code every core update is my favorite part

ancient crescent
#

Less LOC === better code, this is known

outer hound
#

this sorta begs the question of what is a part

humble sorrel
#

I'll certainly be interested to see what the implementation difference ends up looking like compared to my own sub-rendering system used in Caeora's Token Hoard 😃

outer hound
eager hill
# outer hound this sorta begs the question of _what is a part_

FAQ: What are "Parts"?

Good question, we allow you to split up your application into multiple templated parts rather than forcing the entire application to be a monolith. The most significant advantage of this is that you can re-render only a part of your application if other parts don't need to change. It also helps to provide some natural structure for organizing your HTML, code, listeners, and styles.

You can (and often will) define applications that only have a single part, but there are great use cases for multiple parts. For example, consider a character sheet which has a sidebar and a bunch of tabs like an "inventory" tab or something. If an embedded item in the Actor changes you could selectively re-render only the inventory tab via app.render({parts: ["inventory"]}) rather than re-rendering the entire thing. Whether this is appropriate or useful depends on your use case of course.

The JoinGameForm is a nice example which uses 4 different parts, and contextually renders them according to whether the user has the "minimal" login theme applied or not.

ancient crescent
#

(pin that in this thread?)

hazy ferry
#

I do like me some modularity.

ancient crescent
#

Practically speaking: With the HandlebarsApplicationMixin notation, is the expectation that we'll get Node packages that provide systems & modules with SveltApplicationMixin, ReactApplicationMixin, VueApplicationMixin, etc?

#

since the underlying interaction & provisioning of those libraries should be reusable?

outer hound
#

I suspect they'll be library modules, rather than node modules

wide lake
outer hound
#

Actually that makes me wonder will there be a successor for FormApplication, like FormApplicationV2?

#

and will app.render() finally be a promise 😄

eager hill
wide lake
outer hound
#

okay so functionally there is no difference between a Form and a "regular" Application, which is neat. Are there any differences in terms of implementing those? Currently custom formApps need to implement a function unless they want to grab the formdata directly from the element

outer hound
outer hound
eager hill
outer hound
#

oh woops, must've missed that, my bad

#

well this does mean we don't need to make the entire app a form anymore

eager hill
#

Correct, an app can have any number of forms in it!

outer hound
#

no more wierd handling of button elements, yaaaaaay

ancient crescent
#

type=button is still useful

outer hound
#

oh defiently, but there was some extra handling we had to do for special cases (the whole "press enter to save change but that also triggers the nearest button in the DOM")

wide lake
#

So right now in V1 I’m overriding activateListeners as my trigger for when to tell React to re-render a document sheet. Is the intent with V2 that I’d just hook into the update* hooks to get that info?

eager hill
#

But yes, you could - for example - define _onRender() and _onFirstRender() as a good moment to register events or other post-render handling.

#

But if you're taking over the rendering workflow you probably want to do it in _renderHTML()

wide lake
#

I’ve never been more jazzed for a Foundry feature that, if everything works right, will have zero visible impact on my system.

outer hound
#

Oh right, another question, is it still possible to override how the template is determined? in ApplicationV1 you'd need to override the template getter to do so. Will that change with ApplicationV2?

#

for purposes like dynamically grabbing a template for a specific item type

  override get template(): string {
    return `systems/swade/templates/item/${this.type}.hbs`;
  }
#

I'm asking specifically because the majority of the configuration is still handled via static properties, so I can't really tell what template I might need since I don't know what item type I will be rendering at that time

undone turtle
#

From looking at the mixin, guessing you'd override _configureRenderOptions since that's where it copies the static PARTS into an options variable
edit: nevermind, that seems to just capture the ID/names of the parts,

humble sorrel
undone turtle
#

hmm, maybe not

outer hound
#

I wonder if it wouldn't be better to make the config properties part of the Application instance instead. As a static property it can't make any informed decisions based on the type of document it needs to render, and a DocumentSheet might need to render n different types of documents

#

items, cards, actors, combatants, etc they're all getting type and system properties after all

#

that way you could also use instance methods for the applications actions, which give you a lot more freedom since you can access local properties, like the document (or whatever other data) the application is displaying

#

like the actor it's displaying, or the item. You probably get my drift 😄

#

I might also be entirely off-base with these suggestions

outer hound
eager hill
outer hound
#

I am more than willing to listen 😄

ancient crescent
#

Right now many large systems reuse a single sheet class for all of their item types — dnd5e and SWADE being the two I use. What's the theory for those?

outer hound
#

well you can currently register different sheets for different types, so having a WeaponSheet, a ArmorSheet and a ShieldShield would certainly make sense

steel dome
#

and then just recycle some PARTS?

outer hound
#

I am not looking forward to that refactor. Inheritance here we come wooo 😄 (I say this not with malice)

outer hound
ancient crescent
#

I guess that's not too different from what's happening with data models

outer hound
#

However that doesn't really address my concerns about the static nature of the application actions

steel dome
#

i suppose you dont need to inherit, you can jsut mergeObject or use a spread operator. Technically.

outer hound
steel dome
#

right, yeah

eager hill
outer hound
#

ah like an ambient this kinda like jQuery does it?

steel dome
#

he said the bad word

solar stone
#

I imagine the this.constructor trick

eager hill
#

handler.call(this, …args)

outer hound
#

somebody pick up the phone because I called it 🤣

eager hill
#

They don’t have to be static class methods. I just do that for code organization. The handler can be any function

eager hill
steel dome
#

does appv2 have less jq

outer hound
#

sure, but assigning instance functions will be wonky, because this isn't this if that makes sense.

#

hopefully!

eager hill
outer hound
#

fancy

steel dome
#

😩 👌

drifting mirage
#

whats wrong with jquery?

outer hound
eager hill
# drifting mirage whats wrong with jquery?

Opinions vary, but my opinion is that JavaScript has evolved (read: improved) enough that JQuery simply doesn’t add that much value on top of native functions, so it becomes a more opinionated and less obvious decision to use it.

outer hound
#

there's still a lot of utility you can do with jquery that requires more code in JS/CSS but in general that is very treu, vanilla JS has evolved a lot

ancient crescent
#

I will say app v2 overall feels like I'm going to need the more experienced devs to build things out before I worry about transitioning

solar stone
#

That's always how I do things 😆

drifting mirage
#

personally i much prefer jquery for the things i do, makes it much easier to shuffle around the DOM

ancient crescent
#

Even with just HandlebarsMixin I want to see what a document sheet setup looks like

eager hill
outer hound
#

From what I've seen so far it seems like it's an easier barrier of entry but more work if you really wanna get into the weeds

#

overall still a net positive tho!

eager hill
outer hound
#

I shall wait with bated breath 😄 (or is it baited)

ancient crescent
#

Are there plans for v12 to support what we currently use DocumentSheet for? Or is Appv2 this version going to just be for the various utility apps people have?

steel dome
steel dome
#

Say if I just want to get a quick value via input

eager hill
ancient crescent
#

OK, so Prototype 2 is the framework, then in API v1 or v2 we can expect some more helper classes?

#

Does the github explain what kinds of helper classes we can expect by Stable? (DialogV2, DocumentSheetV2, etc.?)

wide lake
# drifting mirage whats wrong with jquery?

When I first got into Foundry dev, I saw that it was hb + jq, and my first thought was, “man, that’s how we used to do frontend dev back in 2010”. The fact that was (even in AppV1) open enough to allow to cram a more modern toolkit in there was what drew me in.

eager hill
drifting mirage
#

handlebars is amazing

wide lake
outer hound
#

It's certainly a refreshing change of pace from my reactive-pattern dayjob lol

eager hill
#

I’ve got to afk for a bit, but I’ll be back later and we can code up or convert a few example apps

eager hill
outer hound
#

thanks again for your hard work, you and the rest of staff

ancient crescent
#

Even with the move to v2, having HandlebarsMixin and some helper subclasses would be the best path forward for new devs - it's much easier for people to sink their teeth into given how many people use Foundry as their first serious independent coding project

#

The clearly defined templates are way easier to debug & comprehend

outer hound
#

shudders in pre-17 Angular syntax

#

(totally off-topic tho)

ancient crescent
#

oh hey that's another library I'd be curious to see people implement as either a foundry library or node package

wide lake
ancient crescent
#

(I wonder if there's a good way to do something like have a dual-library/node package, where for dev & intellisense purposes you have the node package and then for actual use in Foundry you have the library mod)

drifting mirage
ancient crescent
#

it might be best for some more combined efforts in that direction

drifting mirage
#

thats what the core foundry types in that repo are from aswell

#

the package types i made myself

tulip bronze
#

First I want to congratulate Atropos on moving the platform forward according to his vision. From a platform perspective re: UI API it's a generally tough job to create a generic enough API to service many different purposes. I'm sure more refinement will be coming as folks start exploring new possibilities.

I've only had time to take a cursory look at the ApplicationV2 class.

I will finally follow up with suggestions on refinements for positional / browser related inefficiencies that I still see in App v2 from visual code inspection. App v2 apps should still be "herky jerky" in window dragging / positional aspects just like App v1 in the current state. This is an area where I spent a lot of time, profiling, and building an efficient window position system ~2 years ago w/ my framework and it does indeed work differently yet is still compatible w/ App v1; hard to say right now how things would fit w/ App v2.

Re: @indigo niche question in regard to potential new capabilities / features possible w/ App v2

It's just going to take time on my end to work through this shift. There doesn't appear to be anything in particular that App v2 brings to the table in regard to new feature capabilities in respect to App v1 and what currently exists for TRL (my framework). The only medium term concern is the longevity of App v1 on the platform (should be years) and any drift encountered from modules that extend apps through adhoc injection (looking at you PopOut!) where HTML structure / CSS classes may vary. In other words it should be business as usual for those using my framework and adjustments will be made along the way as any drift shows up.

I know the majority of 3rd party package devs haven't taken the time to evaluate what I've released and that is what it is. The scope of TRL is significantly larger than just plopping in a Svelte component in the app window content area. The features available now in TRL (and for 2+ years) go well beyond the feature set of App v1 / App v2 (still got to wait for v12 stable and get things running for a full analysis!) and that is OK. I think having a diversity of approaches and options as a developer is great. To that end I also recognize the travails of building on a platform one doesn't control; there is always an associated risk to that.

As much as I'm passionate about the TTRPG space and think Foundry is the best and will remain the best 2D aligned VTT my priority especially over the next year is releasing my framework to the larger web dev / Svelte community and building products around that. I've been using Foundry as a way to incubate this effort while working w/ early developers who share my passion for excellent interactive experiences in the TTRPG space. I intend to keep compatibility w/ Foundry and indeed that produces its own set of architecture challenges well beyond getting things aligned w/ App v2 in the grander scope.

drifting mirage
indigo niche
tulip bronze
# drifting mirage whats your framework? havent really heard of it before

Item Piles is a fantastic example as well; Wasp always rocking it hard!

whats your framework? havent really heard of it before

This is a nuanced situation, but the honest answer is that I haven't felt welcome to discuss it openly on this server. I know this statement potentially can open a can of worms and that isn't my intention here especially in the frame of all the hard work put in on App v2. "Competition" vs collaboration can be a difficult proposition at times in life. The framework is OSS, so there isn't monetary angles involved, but still a difficult to navigate situation nonetheless. So, more or less it's been a word of mouth spread. I run a separate server for supporting folks in a more direct manner. I even made a launch trailer when I knew I was really onto something, but never posted it publicly. And if you watch it don't take the "dig" on JQuery / Handlebars too seriously in the beginning; I just wanted to try my hand at Blender v3 when it dropped and do some rotoscoping; my opinion is that this tech served Foundry well and works great up to a point.

The other half too is just being a single engineer. It's tough focusing 99.9% on engineering and switching hats to promotion. I've come to treat this phase as working in stealth and the big reveal and awareness drive will be the public launch to the wider web dev community. Working on that this year and oh boy between Svelte 5 & future Foundry support there is whole lot to do before getting there on the engineering side let alone promotion. If there is any future success though it all came about here first.

I have decades of professional experience / passionate about dev-tools development and also in depth experience putting in serious time w/ App v1 via all of the work done in '21 on Forien's Quest Log. That's about as complex of a module one can make w/ the core tech (sanely) and pushed things pretty far. In evaluating a path forward from there "TRL" / TyphonJS Runtime Library was born Oct '21 and rather than keep it to myself I decided to pursue it full time and share it with others.

eager hill
lapis field
# drifting mirage whats wrong with jquery?

Another answer is that it's full of legacy support. While the jQuery 4 beta drops some old browsers, while making other breaking changes along the way, it's only trimming back to IE11 (earmarked for being axed in jQuery 5 or something), jQuery 3.x supports as far back as IE9 of all things. Won't deny that it still does make some things easier still in this day and age, but being a box full of black magic is not really a good thing. jQ 4 does allow you to break up the monolith that is jQ to take the pieces you need, but at that point I'd just move to modern tools and packages for the functionality you need.

peak olive
#

Hm, how to get the setup.js file onto my phone for reading...

eager hill
#

Okay, here it is - a basic but functional DialogV2. Room for improvement but it works!

import ApplicationV2 from "./application-v2.mjs";
import {mergeObject} from "../../../common/utils/helpers.mjs";

/**
 * @typedef {import("../_types.mjs").ApplicationConfiguration} ApplicationConfiguration
 */

/**
 * @typedef {Object} DialogV2Button
 * @property {string} action
 * @property {string} label
 * @property {string} [icon]
 * @property {ApplicationClickAction} callback
 */

/**
 * @typedef {Object} DialogV2Configuration
 * @property {DialogV2Button[]} buttons
 * @property {string} content
 * @property {function(any): Promise<void>} submit
 */

/**
 * @extends {ApplicationV2<ApplicationConfiguration & DialogV2Configuration>}
 */
export default class DialogV2 extends ApplicationV2 {

  /** @inheritDoc */
  static DEFAULT_OPTIONS = mergeObject(super.DEFAULT_OPTIONS, {
    id: "dialog-{id}",
    classes: ["dialog"],
    tag: "aside",
    window: {
      frame: true,
      positioned: true,
      minimizable: false
    }
  });

  /** @inheritDoc */
  _initializeApplicationOptions(options) {
    options = super._initializeApplicationOptions(options);
    if ( !options.buttons.length ) throw new Error("You must define at least one entry in options.buttons");
    for ( const button of options.buttons ) {
      options.actions[button.action] = async (event, target) => {
        const result = await button.callback?.(event, target);
        await options.submit?.(result);
        await this.close();
      }
    }
    return options;
  }

  /** @override */
  async _renderHTML(context, options) {
    const form = document.createElement("form");
    form.className = "dialog-form standard-form";
    form.autocomplete = "off";
    const buttons = this.options.buttons.map(b => {
      let buttonContent = b.label;
      if ( b.icon ) buttonContent = `<i class="${b.icon}"></i>${buttonContent}`;
      return `<button type="button" data-action="${b.action}">${buttonContent}</button>`;
    });
    form.innerHTML = `<div class="dialog-content">${this.options.content}</div>
      <footer class="form-footer">${buttons.join("")}</footer>`;
    return form;
  }

  /** @override */
  _replaceHTML(result, content, options) {
    content.replaceChildren(result);
  }

  /**
   * Wrap the Dialog application in an enclosing promise which returns the result upon user input.
   * @param {Partial<ApplicationConfiguration & DialogV2Configuration>} options
   * @returns {Promise<any>}
   */
  static async prompt(options) {
    return new Promise(resolve => {
      const submit = async result => resolve(result);
      const dialog = new DialogV2({...options, submit});
      dialog.render(true);
    });
  }
}
#

Example usage:

result = await foundry.applications.api.DialogV2.prompt({
  window: {
    title: "My Dialog Prompt",
    icon: "fa-solid fa-question",
  },
  content: `
    <div class="form-group">
      <label>Pick a Number</label>
      <input name="pick" type="number" min="1" max="10" step="1"> 
      <p class="hint">Pick a number between 1 and 10!</p>
    </div>
  `,
  buttons: [
      {action: "submit", label: "Submit", icon: "fa-solid fa-check", callback: (event, button) => {
          return button.closest("form").pick.valueAsNumber;
      }}
  ]
});
#

Now of course, this is more of a low-level application that you wouldn't have to implement something like this every time. This DialogV2 would just be the basis for doing a lot of things. Not sure if that's helpful or not, I hope so @steel dome

#

When the dialog is submitted the awaited result is returned, in this case 5

#

Some notes on this example:

  • I didn't use any particular rendering backend for this, just a pure ApplicationV2 subclass as an example of rendering an application using a simple approach like string literals or creating HTML nodes via the JS API.
  • This is a good example of how to have an app subclass with custom constructor options which extend the base ApplicationConfiguration type and are configured in _initializeApplicationOptions. We take whatever dialog buttons are passed in to the constructor and wrap them to "submit" the form.
  • An example of customizing which top-level element tag is used for the application, in this case the whole app is an <aside>
  • In this example the app is prevented from being minimizable

This is a pretty crude initial take on what a DialogV2 might look like, but it could be improved and probably will be. Expect to see this or something like it in the next release 😉

#

Here's another example usage of this same DialogV2 class to do a simple yes/no query that resolves as a boolean

doThing = await foundry.applications.api.DialogV2.prompt({
  window: {
    title: "My Dialog Prompt",
    icon: "fa-solid fa-question",
  },
  content: "<p>Do you want to do the thing?</p>",
  buttons: [
      {action: "yes", label: "Yes", icon: "fa-solid fa-check", callback: (event, button) => true},
      {action: "no", label: "No", icon: "fa-solid fa-times", callback: (event, button) => false}
  ]
});
#

not useless for 30m work!

ancient crescent
#

So, comparing to current Dialog.prompt - you've added some additional configuration, which is a stylistic difference here. The functional difference is title => window.title, which a more "pure" re-implementation of Dialog.prompt could easily resolve

eager hill
#

I guess FWIW I didn't look at the existing Dialog class at all, I was just trying to make an example of how to do something in V2, but yeah I would want to compare to how the V1 works and have parity where possible.

ancient crescent
#

yeah the current Dialog.prompt makes a lot of hard defaults, e.g. one button that always uses a checkmark and you can only change the label

tender horizon
#

it (and confirm) also only pass along specific keys to wait, which I found out just yesterday, that it was stripping the extra keys I support in data in my Dialog subclass cripes

#

if that could be avoided in the v2 implementation (ie, catch ...rest and pass along), that'd be chefkiss

outer hound
#

will it be possible to have dynamic titles for appv2?

steel dome
#

and the icon, and whatever else goes in DEFAULT_OPTIONS.window 🤔

outer hound
#

yeah swade has a lot of window titles that are "Do X for Y"

#

"Pick a card for <insert name here>"

#

having a getter one could override for those sorta things is incredibly convenient in the current AppV1

#

I'm not saying AppV2 needs to support the exact same patterns/properties, but having a migration guide for that sorta stuff would be useful

drifting mirage
#

Having support for buttons in the titlebar would also be really nice, with core options to show and hide text and buttons, the bar tends to get so full that the close button isnt visible with alot of modules

eager hill
#

looks like this

strong stream
#

Do they have to be grouped? Most of the time it's more convenient to have them directly accessible, it's only an issue when there are a ton of modules adding stuff to the header

outer hound
#

yeah out of sheer curiosity: Are they auto-grouped? Can you make multiple groups?

eager hill
#

To clarify it’s a drop-down menu, like a context menu, not a group. It’s to ensure there is only one button in the header frame to prevent horizontal space contention

#

Currently there is not support for groups within that menu, but it’s a reasonable suggestion to consider!

strong stream
#

Sure. But for most people there aren't horizontal space issues, it's only an issue for people running a bunch of modules

Losing direct access to controls no matter what feels like the rest of us are being punished for the indiscretions of heavy module users

eager hill
#

It’s sort of a “why we can’t have nice things” situation. Because the potential is there for any app to have an arbitrarily large number of header buttons all apps have to deal with a header drop-down now. I realize it might not seem like a win, but this WAS a requested feature by many users

strong stream
#

IMO it would be better to figure out if it's needed for a window being rendered and then make the decision, rather than losing functionality for everyone because of the rare users that have module issues like that

humble sorrel
#

im sure if someone absolutely had to have a button on the header, they could inject it directly

#

ive been avoiding putting buttons in the header lately because of the potential overflow

#

despite how easy it is to do so 😩

strong stream
humble sorrel
#

again, i think if a particular dev absolutely must have a direct header button, you can do so, you just have to purposefully break the standard pattern

strong stream
humble sorrel
#

core button....like..."close"?

steel dome
#

Without having looked at the v12 implementation; in v11 we use a hook, a css class, a label, and an onclick callback. A functional approach could be an additional property, ungrouped (or some other name) which would place it outside of a contextmenu if explicitly true.

strong stream
#

Every single control other than the Close is buried deeper.

Including stuff like changing the sheet or accessing token settings

steel dome
#

So if someone is adamant and convinced their button mustn't ever be in a contextmenu, they just add one more property.

#

but contextmenu being the default

humble sorrel
#

despite any potential losses, which i guess are more valuable to others, not having to concern myself with header button space when i want a generic sheet action is a huge W in my opinion

strong stream
#

It's a pretty big loss of functionality IMO, burying everything a click deeper on the off chance you have too many modules adding buttons there. I would rather maintain access to controls if at all possible

eager hill
austere shadow
#

Given the way AppV2 works, I'd imagine it wouldn't be too difficult to give it a shot yourself mxzf, allowing buttons in the title bar that only collapse if it becomes too big SorWhat

If it doesn't become a core feature/someone else ain't first I'll definitely be looking into it myself, as lots of players will never find something if it's not right in their face.

strong stream
humble sorrel
#

the current state of direct header buttons is just simply not maintainable

#

this is a great direction to move towards

eager hill
small trench
#

The prototype token is already confusing for new users. Hiding it one level deeper is probably not going to help. But still the extra menu is good thinking. But another Option wouöd be to just hide the text of the buttons and only show the icon. This way a lot more space is available

austere shadow
strong stream
austere shadow
steel dome
#

thats already doable by just not having a label property

small trench
#

But all modules have it in place

steel dome
#

making the placement of the button user configurable as i suggested above seems like a solution that could satisfy everyone 🤷‍♂️

drifting mirage
drifting mirage
small trench
#

The label or the new menu have the advantage that they probably work decent on tablets and touch because no tooltip required

drifting mirage
# small trench The label or the new menu have the advantage that they probably work decent on t...

the main reason they work better on mobile is because of the extremly limited width, there is no space for more than 2 header buttons (including close) on mobile. But, for desktop and tablets, it depends on preference and available width, so having both options (drawer only, and the current version, but with it collapsing into a drawer if they would take up too much of the width of the bar) would IMO be the ideal solution, and is something I'm going to put on my issue tracker for my mobile sheet module

devout narwhal
drifting mirage
#

the collapsing can be achieved using only CSS depending on the HTML implementation

ancient crescent
#

Maybe I'm just missing this, but where is the App V2 stuff in the client side code?

strong stream
#

It looks like that file has replaced commons.js for the various deeper inheritence stuff

ancient crescent
#

Ah. Request for the next v12 version - please include stuff that's getting moved to ESM into the existing common/client structure that's provided for devs

eager hill
#

I'll include the un-bundled code

ancient crescent
wet solar
#

I'm not sure if it's a thing with many systems, but some that I've used have some kind of lock/unlock mechanisms for sheets. Having something like that built in by default could help streamline/standardize such a feature 🙏

outer hound
#

lock/unlock what exactly?

ancient crescent
#

I think like the new dnd5e play/edit mode

wet solar
# outer hound lock/unlock what exactly?

Some sheets are not always editable. They can be locked or be put in an enhanced edit mode that adds more UI while editing the sheet.

Like in my Fate system, the sheet has to be put in an edit mode for some additional Ui to be rendered.

Some older screenshots which show the difference:

outer hound
#

ah yeah like that. That's entirely on the system level

ancient crescent
#

Yeah Patrick is bringing up if there will be some native handling in DocumentSheetV2

wet solar
#

Sure. But we're getting a v2 and some (or many?) systems do this, so a standardized feature could be an idea for that

ancient crescent
#

I think it's a reasonable ask

strong stream
undone turtle
#

I wonder if the same could be useful if the system has different sheets (or HBS templates) for users of different permissions (i.e. owner vs. observer vs. limited)

outer hound
#

that can already by done technically by overriding the template getter, at least in the current Application framework

ancient crescent
#

Yeah it's hard to comprehend how exactly this fits together until I see more examples, e.g. how easily can the parts be swapped out

wet solar
#

I've built a system where multiple character sheets (all as their own application with only their own parts rerendering) were rendered in a single application window (group view). I wonder if that would be a lot easier with this new system

ancient crescent
#

Certainly seems like it with the usage of parts for partial re-renders

wide lake
#

The docs for the ApplicationV2 getters imply that state returns an object, but it (obviously) returns a number. I don't know if that's a tsdoc thing, like it's supposed to read as an enum?

humble sorrel
#

i believe that is denoting an enum and its primitive values

#

i.e. "returns one of the following named values"

wide lake
humble sorrel
#

im unfamiliar with that specific syntax myself

#

but now want to know more haha

ancient crescent
#

TS doesn't really support enums

#

or at least it's kinda weird

wide lake
#

Ehhh, something's up[, because RENDER_STATES has the exact same signature

strong stream
#

(While state will be a specific one returned)

wide lake
wide lake
jovial hatch
#

Would have to check the source, but there might be either a dedicated type declaration or a transform of the whole object to a union missing.

strong stream
#

Inspecting the code itself makes it pretty clear what's going on exactly

    /**
     * The sequence of rendering states that describe the Application life-cycle.
     * @enum {number}
     */
    static RENDER_STATES = Object.freeze({
      ERROR: -3,
      CLOSING: -2,
      CLOSED: -1,
      NONE: 0,
      RENDERING: 1,
      RENDERED: 2
    });

    /**
     * The current render state of the Application.
     * @type {ApplicationV2.RENDER_STATES}
     */
    get state() {
      return this.#state;
    }
wide lake
#

I had half an hour to play with this and I've just been copying types in to an ambient declaration 😆 I'll actrually try and use it tomorrow.

jovial hatch
#

The return could be something like (typeof ApplicationV2.RENDER_STATES)[keyof typeof ApplicationV2.RENDER_STATES], which should resolve to the available numbers.

strong stream
outer hound
austere shadow
austere shadow
#

Question for staff: Registering an AppV2 application using Items.registerSheet causes the options constructor to be called with the instance of your Item.

Therefore the ApplicationV2.options gets "polluted" with all the properties of the item unless overridden by AppV2 like id, and the actual {editable} options property gets eaten up.

In AppV1 the DocumentSheet has a constructor of (object, options) instead of purely options.

Is the intention that a DocumentSheetV2 should be implemented, and if so will we be seeing a core implementation or is it intended to be left up to individual systems/modules to create a "DocumentSheetV2"?

wide lake
eager hill
austere shadow
#

I'll probably try my hand at an implementation of a DocumentSheetV2 mixin, if anything for just toying around with how AppV2 works x)

Edit: Discovered manipulating constructors in mixins is an anti-pattern so I'm settling on a subclass of AppV2

austere shadow
#

Got distracted but if anyone wants to play around with an absolutely basic "DocumentSheetV2" enjoy, very basic implementation without any submission/form logic, might take a crack at that once I've gotten more comfortable with handling multiple parts and see if I can setup a rudimentary "FormAppV2" that takes advantage of registering multiple forms.

/**
 * Extends the basic ApplicationV2 class to incorporate shared functionality for Document applications
 * This is a rudemantary implementation without any form logic, following the same pattern as the original DocumentSheet
 */
export class DocumentSheetV2 extends foundry.applications.api.ApplicationV2 {
    /**
     * @param {ClientDocument} object A Document instance which should be managed by this application.
     * @inheritdoc
     */
    constructor(object, options) {
        super(options);
        this.object = object;
    }

    /**
     * @override
     * @inheritdoc
     */
    static DEFAULT_OPTIONS = {
        ...foundry.applications.api.ApplicationV2.DEFAULT_OPTIONS,
        classes: ["document-sheet"],
    };

    /**
     * @override
     * @inheritdoc
     */
    get title() {
        const reference = 'name' in this.document ? ` ${this.document.name}` : "";
        return `${game.i18n.localize(this.document.constructor.metadata.label)}${reference}`;
    }

    /**
     * A semantic convenience reference to the Document instance which is the target object for this form.
     * @type {ClientDocument}
     */
    get document() {
        return this.object;
    }

    /**
     * Adds the same data as the original DocumentSheet, @see DocumentSheet[getData]
     * @returns {Promise<Record<string, unknown>}
     */
    async _prepareContext() {
        return {
            document: this.document,
            data: this.document.toObject(false),
            limited: this.document.limited,
            options: this.options,
            owner: this.document.isOwner,
            title: this.title,
        }
    }
}

I personally used this to setup an item sheet like this:
class ItemSheet extends foundry.applications.api.HandlebarsApplicationMixin(DocumentSheetV2)

#

Question: In HandlebarsApplicationMixin _renderHTML currently the renderTemplate function is called with the context as options, would it be possible to add the part object / partId of the part being rendered to the context?

I can imagine the usecase of re-using the same HBS template for multiple parts, but perhaps wanting to run different HBS logic based on which version you're in, this way we'd be able to implement such a logic.

wide lake
#

One minor observation on ApllicationV2: how about renaming _renderHTML and _replaceHTML with _renderDOM amd _replaceDOM?
HTML is a string of text, and there is no HTML anywhere in these methods. I mean, you might be rendering a text template to create HTML along the way, but you might not.

outer hound
#

maybe _renderElement and _replaceElement since we're not rendering/replacing the entire DOM

#

unless DOM can also mean "a part of document"

wide lake
karmic current
#

From my own comment there: Having a way to register a form to the Token Config and then have it be rendered in a "Modules" tab which has its own navbar like Vision/Light tabs do would be great to avoid this issue altogether.

outer hound
#

that might be something that could/should be done for all core document sheets.

#

adding a tab is a pretty common pattern

ancient crescent
#

It's also a major complaint of the current Application class, that additional tabs are a huge pain

wide lake
#

My applicationV2s are transparent. This is probably because I have forgotten to ___________________ ?

#

Oh, there's a foundry2.css that's not getting loaded 🤔

wide lake
#

Okay, better, but still missing a certain je ne sais quoi

#

also loading foundry2.css has weirded my UI

ancient crescent
#

probably because it's not really meant to be shared yet

#

(this is the downside of prototypes is they're really rough for actual development)

wide lake
outer hound
#

foundry is working on a CSS overhaul so you mnight be looking at a WIP

wide lake
#

So, I put React into an ApplicationV2!. Not too difficult (but then it wasn't too bad in V1 either), and I'm looking forward to P3 to see where the API goes.

The API is obviously very focused on HTML, which is understandable because the main audience is still going to be people writing Handlebars or similar. But as a React dev, it still felt a bit like I was working around the system rather than with it. For example, _renderHTML does nothing for me, because I'm going to be handing the content element to React and letting it do my rendering.

As a developer in my day job, I really keen on understanding and expressing the intent of an API, so I'd be keen to know how Atro and the Foundry staff envisaged this API being used with FE toolkits like React/Vue/Svelte etc.

eager hill
#

I have some thoughts but need to see a bit more what you’re approach is before commenting

eager hill
#

I need to define some more v1 theme styling shims

wide lake
# eager hill Awesome. Can you share a minimal “hello world” example of what the code looks li...

For sure. So the React component is just this:

export const DummyAppV2Component: React.FC<DummyAppV2ComponentProps> = ({
  children,
}) => {
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <div>{children}</div>
      <div
        css={{
          border: "1px solid #7007",
          padding: "0.5em",
          textAlign: "center",
          background: "#fff1",
          fontSize: "2em",
          margin: "0.5em",
        }}
      >
        {count}
      </div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};
#

And the Application V2 looks like:

class DummyAppV2 extends foundry.applications.api.ApplicationV2 {
  static DEFAULT_OPTIONS: RecursivePartial<
    Omit<foundry.applications.types.ApplicationConfiguration, "uniqueId">
  > = {
    ...foundry.applications.api.ApplicationV2.DEFAULT_OPTIONS,
    classes: ["document-sheet"],
    position: {
      height: 100,
      width: 200,
    },

    window: {
      ...foundry.applications.api.ApplicationV2.DEFAULT_OPTIONS.window,
      title: "DummyAppV2",
      frame: true,
    },
  };

  reactRoot: Root | undefined;

  override async _renderHTML() {
    const div = document.createElement("div");
    return div;
  }

  override _replaceHTML(result: any, content: HTMLElement, options: any) {
    systemLogger.log("DummyAppV2._replaceHTML", result, content, options);
    if (!this.reactRoot) {
      this.reactRoot = createRoot(content);
    }
    this.reactRoot.render(
      <DummyAppV2Component>
        <div css={{ fontSize: "2em" }}>Hello from React</div>
      </DummyAppV2Component>,
    );
    // content.replaceChildren(result);
  }
}
#

So I'm kinda doing nothing in _renderHTML to match the types I got from the docs

#

And then using _replaceHTML as my cue to actual kick off the react root.

wide lake
eager hill
#

Helpful thanks I have a few thoughts and suggestions

#

But first a newbie question because I don’t know react.

#

How does the html rendered by your react component get into the AppV2 content div?

#

That is unclear to me from the code you shared

wide lake
#

What that says is "Hey, React, here's a div, and here's a component. Have at it!"

#

Oh sorry, also relevant:

#
      this.reactRoot = createRoot(content);
#

That's where I'm telling React to take ownership of that content element (the one passed in as the second argument to _replaceHTML)

eager hill
#

Aah okay that’s what i meant

wide lake
#

Yeah sorry, I'm so steeped in this stuff that I forget it's not obvious!

eager hill
#

Okay I have thoughts, but I’ll have to share in a bit. Making breakfast for the little ones right now

wide lake
#

Nice, I'm doing lunch.

vernal sand
#

@wide lake it’s nifty to see that example, nice work! That’s pretty similar to what injecting Vue looks like for me in ApplicationV1, so I feel validated, lol. I’m interested to see Atro’s thoughts, but I imagine there’s just a fundamental difference of how integrated reactive renderers are. For instance, I don’t really see myself taking advantage of things like parts or tabs in Vue since those would need to be handled within the Vue application itself.

wide lake
#

Re parts and tabs: yeah, exactly. Parts is a mechanism to not have to render the whole application when only a part has changed. When I read that I was thinking “Yeah, that’s React main superpower!”

vernal sand
#

That may actually be a significant difference from my setup, as I’ve got my Vue applications set to render once, but on subsequent application render() calls, I just update the props passed into the Vue app rather than running another Vue render/mount.

wide lake
vernal sand
#

I’m hoping to be able to play around with it sometime this weekend or maybe next week. I’m actually in the process of building a new feature in ApplicationV1 that’s not tied to document sheets and would be a good candidate to try porting over.

eager hill
#

@wide lake okay, back. So here's what I would imagine based on your example would be the most natural way to integrate given the current API. (removing the typescript for easier readability):

class ReactAppV2 extends foundry.applications.api.ApplicationV2 {
  static DEFAULT_OPTIONS = {...}

  reactRoot;

  async _renderFrame(options) {
    const element = super._renderFrame(options);
    const target = this.hasFrame ? element.querySelector(".window-content") : element;
    this.reactRoot = createRoot(target);
    return element;
  }

  async _renderHTML(context, options) {
    this.reactRoot.render(...); // Probably depends on context
  }
}

_renderFrame only occurs once and is the most natural point (given the current API) to bind the content div to your react component.

_renderHTML is the semantically appropriate place to render updates to the HTML of the app. You're possibly a little bit concerned about the expected return value here, but I think it's a documentation issue. You only need to return something in _renderHTML if you are using that returned value in _replaceHTML.

#

Two documentation changes I'll make based on your example:

  1. The return type of _renderHTML should be Promise<any>
  2. _replaceHTML is no longer @abstract but rather only @protected
#

Furthermore, my advice would be to implement React functionality as a mixin rather than as a base class. The goal will be to be able to augment a base class with a different rendering backend. For example, you might want to have MyActorSheet1 extends HandlebarsMixin(ActorSheet) and MyActorSheet2 extends ReactMixin(ActorSheet) and MyActorSheet3 extends VueMixin(ActorSheet) which all retain shared interfaces and behaviors of the base ActorSheet which is agnostic to how it gets rendered.

#

So your use case might look like:

class MySpecificReactApp extends ReactRenderingMixin(ApplicationV2) {...}
#

The base classes we provide are likely to be:

ApplicationV2
  DialogV2
  DocumentSheetV2
    ActorSheetV2
    ItemSheetV2
    ...

Each of which would be agnostic to how they are rendered and each of which could be extended with a specialized rendering backend.

#

We want it to be easy for someone to do MySystemActorSheet extends VueRenderingBackend(ActorSheet) and get all the base ActorSheet functionality without having to override or fight against handlebars rendering behaviors.

vernal sand
#

Oh, awesome, yeah that looks like a much cleaner setup for reactive renderers and would let me toss a bunch of safety code I currently have in my ApplicationV1 equivalent. Very interested in trying that out soon 👀

wide lake
wide lake
inner echo
#

The thought of converting all of my Applications to v2 is very daunting. I'm not sure I'll ever get round to it unless I'm forced to.

ancient crescent
#

AppV1 is not being deprecated in v12, so I expect people will write conversion guides over the next year

austere shadow
#

So far it doesn't seem that bad to convert tbh.

inner echo
#

I'm aware of that, but there'd be a lot of work regardless of having handy guides around.

#

I think that depends a lot on the complexity of your existing applications. 🙂

ancient crescent
#

hopefully the upcoming DocumentSheetV2 makes conversion easy

austere shadow
#

^

#

I feel like once we have the DocumentSheetV2 and the like it won't take too much time and effort.

#

For what I've played around so far with, I'm pretty optimistic for how long it'll take, even with complex applications.

inner echo
#

I have, for example, a custom application that's 3k lines of code, While I can see some advantages to being able to render parts of it separately, I'm not yet sold on the cost/benefit ratio to undertake the work involved in converting.

strong stream
inner echo
#

There's a bit of a sense of burnout after four years of development and three sets of major model changes.

vernal sand
#

I'm super excited for ApplicationV2, but I would definitely want a looooong deprecation period or even an shim that sticks around indefinitely. You can likely go for a minimal setup where you ignore the new fancy stuff in favor of a direct port, but dropping the old Application class in v13 or whenever has the potential to kill systems/modules the same way that the document refactors did.

inner echo
#

Well, all the info so far suggests a long deprecation period, I'm just saying the way I feel right now I probably won't do the work unless I have to or there's compelling benefits to justify the work.

strong stream
#

Yeah, I haven't poked at it deeply myself, but as someone who's perfectly content with prefers using Handlebars for template rendering I haven't really seen any compelling reason to use ApplicationV2 stuff myself

ancient crescent
inner echo
#

The big benefits I've seen so far are ease of creating new applications and ability to render sub-parts of applications.

vernal sand
#

Decoupling rendering from the application itself is huge for support of React/Vue/Svelte, but that's a niche use case. Sub parts are the most obvious benefit for general use cases IMO, but I think to really take advantage of them you would need to get more in the weeds of ApplicationV2 and possibly markup/logic changes. With that said, I imagine you could just stick with a single part and include jQuery in your activateListeners() and avoid most of the pain points with a transition.

outer hound
#

I don't wanna be a doomer but let's not forget the CSS changes coming 😄

outer hound
#

On a different note: are there any plans/lists for which core applications will switch over to AppV2 with v12? I'm asking specifically because swade extends the combattracker 😄

austere shadow
outer hound
#

fair enough, thanks

eager hill
outer hound
#

fair enough!
Am I correct in assuming you'll give out advance notice for the applications that are getting migrated to AppV2?

eager hill
#

we wouldn't make this change directly in Stable - but I suspect that's not what you mean.

outer hound
#

No, that's exactly what I meant 😄
Something like them being in a Prototype or API version

eager hill
#

Anything that gets converted to ApplicationV2 in v12 will happen before the User Testing phase

#

so in the next release or 2

outer hound
#

understandable. I'm looking forward to all the fancy new rendering backends!

#

I'll be impressed if somebody manages to integrate angular 🤣

austere shadow
#

Part of me wants to integrate blazor wasm just for the fucks of it.

eager hill
#

DocumentSheetV2 is (minimally) operational!

eager hill
#

We have a working ApplicationV2 example in core for V12 that provides an example for others to observe and follow 🙂

devout narwhal
#

😮

#

Renamed GM user!

steel dome
#

Atro's favorite color is purple confirmed

strong stream
devout narwhal
austere shadow
outer hound
#

it's all so... black. I presume that's a WiP of the new styles?

devout narwhal
#

black and not fully opaque

eager hill
#

I was focused on getting it mechanically working rather than visually styling it. There are some things that need improvement for sure.

#

ApplicationV2 instances - by default - opt into new “theme v2” styles but that can be changed

#

Those styles are not really refined yet

eager hill
#

Eager to receive some continued AppV2 feedback from folks who have a chance to try it out!

austere shadow
#

If I wasn't in session I'd be all over it, but tomorrow I'll be all over it ^^;

vernal sand
#

I'm probably going to give Vue a whirl on it over the weekend. The code samples from a few weeks back looked super promising catjam

wet solar
vernal sand
# wet solar I've started working with Vue a few weeks ago. Whats the currently available sol...

13th Age is using it, and Ironsworn is using it in a similar approach (but with TypeScript, IIRC). There’s a version of Boilerplate that uses Vue and describes the setup, but it’s for Foundry v9. The sheet application didn’t change much though since then, so the overall structure is still the same. https://gitlab.com/asacolips-projects/foundry-mods/vue3boilerplate

ancient crescent
#

Atro, could you elaborate on the choice to use widget in the UserConfig handlebars as opposed to a more traditional if/else block?

eager hill
#

in this case it could have been handled via just more HTML markup

ancient crescent
#

Cool, so widget is a way to use formField but replace the default markup with something more custom?

#

Feedback on formField: I would appreciate the label input pair to get for/id attributes when I'm building on DocumentSheet, probably by using the document.uuid to protect uniqueness?

#

Alternatively, use label as a wrapper for the whole element

#

It would be nice to be able to click on that label element and that count as clicking on the input

orchid hinge
#

So, crazy question. Is there or will there be a "tutorial"/reference step by step doc to explain a lot of ApplicationV2. Or does any community member have the heart to do so? It is easier for me to follow than scouring an API doc. I know it'd be a large undertaking but... I always feel I am building things not how it is intended or using things in an inefficient or unintended way but don't have a dev intended "how to" doc to know if I am or not.

ancient crescent
orchid hinge
#

Yeah I meant getting started on it, or if there will be in the future

ancient crescent
#

I think the goal is by the end of Dev phase to have all of those ready

#

At which point I know I'll work with asa to update Boilerplate

orchid hinge
#

What is Boilerplate?

#

I know what a boilerplate is, I am assuming there is a project called "Boilerplate"?

#

To give a good starter?

#

Wait is there already a "How To" documentation somewhere and I have been fumbling these past few years only referencing an API doc?

ancient crescent
#

Yes and yes

orchid hinge
#

🙃

ancient crescent
austere shadow
#

I'll say I find it amusing that DocumentSheetV2 uses the AppV1 DocumentSheetConfig. Totally understand why, but amusing nonetheless. 😛

#

I will say it does give a great example how you can use both concurrently! Which is what I've been doing during the prototype builds (use AppV1 Actor/Item sheets, and AppV2 for any bespoke applications).

#

Feedback:

I wonder if DocumentSheetV2#onSubmit should forward event/form to DocumentSheetV2#_prepareSubmitData. I can imagine a use case where you may wish to do manipulation on the formData object based on some property of the SubmitEvent (perhaps you have different 'submit' buttons?)

Instead of then having to overwrite the onSubmit handler with your own, since it's private and can't be overridden through inheritance, just so you can forward event/form to _prepareSubmitData, which is protected and therefore can be overridden through inheritance.

So maybe change the syntax of _prepareSubmitData to _prepareSubmitData(formData, options) called from #onSubmit as const submitData = this._prepareSubmitData(formData, {event, form});

austere shadow
#

User Configuration Form Suggestion:

When you have a character selected and you open the Form, it shows the Actor's img, but if you don't have a character selected when you open the form / change your character, the image doesn't update.

The img on the left here should probably update whenever you change the select, especially so when an img is set and you change it to a different character, and it doesn't update until you hit Save and re-open the app.

austere shadow
#

Which makes me wonder if in-general we should have a 'widget'-esque handlebar helper?

Something akin to:
{{widget callback option1=value1 option2=value2}}

widget(callback, options) {
  const output = callback(options);
  const html = 'outerHTML' in output ? output.outerHTML : output;
  return new Handlebars.SafeString(html);
}
#

ui.activeWindow

Atm there is foundry.applications.api.ApplicationV2.#frontApp but considering this is a private variable, we have no way of checking what the "front app" is as far as I can tell? Would be useful to combine with foundry.applications.instances

austere shadow
eager hill
eager hill
eager hill
austere shadow
#

Cheers! Only thing I haven't had the chance to play around with yet are v2 tabs, was planning on having a look at that next but then first decided to refactor my existing v2 apps to the core DocumentV2 instead of my own "mvp" edition I clobbered together a few weeks back :p

jovial hatch
#

Speaking of tabs, what's the intended way to designate a tab to be the active one on initial render?

eager hill
# jovial hatch Speaking of tabs, what's the intended way to designate a tab to be the active on...

Here’s how I did it while playing around with the system. I’m not sure if there is a better way:

https://github.com/foundryvtt/crucible/blob/app-v2-redesign/module/applications/sheets/armor.mjs

GitHub

Crucible is an innovative and modern tabletop role-playing game system built exclusively for Foundry Virtual Tabletop. - foundryvtt/crucible

#

Pass the state of tabs in as part of _renderContext and then use that tabs object to render each template part with the correct initial state

ancient crescent
#

So it doesn't get missed

formField labels

It would be good if clicking on the label clicked into the field

  1. Add for/id attributes, probably by using the document.uuid to protect uniqueness?
  2. Alternatively, restructure so label wraps both the text and the input
eager hill
#

Really wish the HTML standard would evolve so that labels “for” attribute would target a named input element within the same containing form

#

The current design of needing for to target a global id is insane to me

ancient crescent
#

Yeah it's not ideal for sure

eager hill
#

I can’t think of any good reason that change wasn’t made like 10 years ago

ancient crescent
#

For SWADE in our last major version I just spent a few hours adding all the for/id using UUIDs and it is a genuine improvement in form usability, especially for check boxes

eager hill
#

Yeah checkboxes for sure

ancient crescent
#

${document.uuid}-${field.name} or something to that effect

eager hill
#

That is a place where we currently label wrap

karmic frigate
#

Seems like it might be better to use {appId}.{field.name}, since the App’s ID is guaranteed to be unique at any given time while the document might have more than one document sheet open, leading to duplicate IDs

austere shadow
#

or at least; I pressume that is what causes this behavior (I'm struggling to locate the logic that re-renders the same sheet, instead of creating a dupe)

#

Ah, ApplicationV2#_insertElement.

#
const existing = document.getElementById(element.id);
if ( existing ) existing.replaceWith(element);
austere shadow
austere shadow
# eager hill Here’s how I did it while playing around with the system. I’m not sure if there ...

I have a feeling we're missing a crucial piece of the puzzle to use this fully as inspiration :p

https://github.com/foundryvtt/crucible/blob/f6a1c6d8c6a279ef2ecf3e16de7bf3d07e67752c/module/applications/sheets/base-item.mjs#L8

GitHub

Crucible is an innovative and modern tabletop role-playing game system built exclusively for Foundry Virtual Tabletop. - foundryvtt/crucible

#

(ItemSheetV2)

eager hill
karmic frigate
ancient crescent
#

That's a current point of friction and I know a ticket related to that got put into v12

eager hill
austere shadow
#

I was literally just checking "how easy is it to convert an item sheet from AppV1 to AppV2, and the only bottleneck I have right now is DragDrop, since-as Atropos just pointed out-there is none.

eager hill
#

probably V12 Dev 2 release will have something on that front.

ancient crescent
#

Drag and drop seems like a very important piece of what ItemSheet and ActorSheet would uniquely offer

eager hill
#

Crucible is a good test case for me to make some system improvements while also testing out and refining the AppV2 infrastructure

#

it will certainly need drag and drop

steel dome
#

What is the "this was a lot more work than I anticipated" count at?

austere shadow
#

Minus updating my custom styles to be properly compatible to StyleV2, took me ~10 min to convert a 250 LoC AppV1 ItemSheet -> DocumentSheetV2

Ofc. Without Drag & Drop.

eager hill
austere shadow
#

Only thing I can't quite get working is the initial tab showing properly.

#

Cause as far as I can tell there isn't anything in AppV2 that does initial tab showing.

eager hill
ancient crescent
#

Alongside the relative Uuid improvements I'd love if Actor 2 natively handled AEs shown via allApplicableEffects

eager hill
austere shadow
#

Yeah, so for me, this works fine:

tabGroups = {
    sheet: null
}

defaultTabs = {
    sheet: "overview"
}

_onRender(context, options) {
    for(const [group, tab] of Object.entries(this.defaultTabs)) {
        this.changeTab(tab, group);
    }
}
eager hill
#

it's technically better to have the initial state set immediately as part of the render

#

rather than painting and then re-painting

austere shadow
#

Question

Are there plans to implement something akin to _getHeaderButtons.

Previously we could very easily create header buttons by doing something like:

_getHeaderButtons() {
    const buttons = super._getHeaderButtons();

    buttons.unshift({
        label: "Send to Chat",
        icon: "fas fa-comment",
        onclick: () => this.object.sendToChat?.();
    });

    return buttons;
}

To achieve the same effect, following the approach of the DocumentSheetV2 source code, I added the following code to _renderFrame:

async _renderFrame(options) {
    const frame = super._renderFrame(options);
    
    const sendToChat = `<button type="button" class="header-control fa-solid fa-comment" data-action="sendToChat" data-tooltip="Send to Chat" aria-label="Send to Chat"></button>`
    this.window.close.insertAdjacentHTML("beforebegin", sendToChat);
    
    return frame;
}

As well as adding to DEFAULT_OPTIONS: actions.sendToChat: () => this.document.sendToChat?.()

Seems like something that ApplicationV2 could support natively like it did in AppV1?

eager hill
#

it's a minor thing, but I would be looking for a more "technically pure" solution I think

austere shadow
#

How did AppV1 handle this?

eager hill
# austere shadow ### Question Are there plans to implement something akin to `_getHeaderButtons`....

There is an API for this as part of the app config, see DEFAULT_OPTIONS.window.controls, you provide an array of ApplicationHeaderControlsEntry which is:

/**
 * @typedef {Object} ApplicationHeaderControlsEntry
 * @property {string} icon                      A font-awesome icon class which denotes the control button
 * @property {string} label                     The text label for the control button
 * @property {string} action                    The action name triggered by clicking the control button
 * @property {boolean} visible                  Is the control button visible for the current client?
 */
eager hill
austere shadow
#

Ahh.

strong stream
#

I thought Application just handled tabs by doing display:none on the inactive divs

austere shadow
#

They still do, but to mark the initial tab as active would be a repaint

#

seems to be in _activateCoreListeners -> for each tab call Tabs#bind (AppV1)

eager hill
#

part of the reason I was reluctant to do this in AppV2 is it's fairly renderer-specific. What is a global solution that works for handlebars might not be the right thing to do for Vue or React or something.

#

it feels like the decision of "how do I set initial state of my rendered HTML" is a renderer-specific problem that requires a renderer-specific solution

ancient crescent
#

Would be good to work through as people figure out Vue and React

austere shadow
# eager hill it feels like the decision of "how do I set initial state of my rendered HTML" i...

While I do agree, at the very least it could be included in the HandlebarsApplicationMixin.

What could be done is within ApplicationV2#render between await this._renderHTML and this._replaceHTML add a if ( isFirstRender ) this._setInitialTab

At which point for the HandlebarsApplicationMixin the initial tab could be set based on some configuration, and the people building the Vue/React/Other mixins can similarly do the same.

eager hill
austere shadow
#

Yep, you're right on the first-render part. But yeah I think it'd be a sustantial improvement.

austere shadow
# ancient crescent Drag and drop seems like a very important piece of what ItemSheet and ActorSheet...

Honestly, I feel like all Applications should support Drag & Drop, just like how AppV1 also did it for non-document apps.

I have more than enough applications that are purely DragDrop based / DataModel or multi-Document based where I use the Drag & Drop engine quite significantly.

I hope we may see an implementation in V12 Dev 2? It's quite crucial for almost any application I've created, very few I have don't have some kind of DragDrop functionality.

eager hill
#

I’ve been hankering to redesign the roll table ui. I might take that on as a more ambitions AppV2 that would require a drag+drop solution

#

No promises

austere shadow
#

Looking forward to it either way! Roll tables can use a face-lift hah.

vernal sand
ancient crescent
vernal sand
#

I'll take another pass at not reimplementing tabs though when I work on my ApplicationV2 port of some of my Vue stuff. As long as I'm triggering a Vue props update, I should be able to get it to work with the built in listeners for tabs.

austere shadow
#

Question

Currently ApplicationV2#onSubmitForm is a private method, I wonder if this is quite necessary?

In an AppV1 application I used _onSubmit to manipulate a tagify input (as an empty input field on tagify is set to "" instead of [] which throws an error in JSON.parse during FormDataExtended casting.) before it was sent to _updateObject.

Right now however, #onSubmitForm is the first thing called and the first thing it does is:
const formData = new FormDataExtended(this.#element);

So to implement the intended behavior I need, I have to overwrite _attachFrameListeners to not apply the #onSubmitForm, and then make my own version.

That seems... less then ideal.

#

Especially considering in this case I'm lucky that neither DocumentSheetV2 or the HandlebarsApplicationMixin override _attachFrameListeners, but if anything in the chain would and calls super, I'd also have to implement everything else upstream, just to be able to overwrite ApplicationV2#onSubmitForm

#

Actually, considering every event in ApplicationV2#_attachFrameListeners is private I'm gonna have to copy paste every handler as well... Yeah, okay I'd like a core fix, lol.

#

whether the answer is changing some/all of those events in _attachFrameListeners to be protected instead of private, or adding a _preSubmit method or the like, I'll leave up to you all to decide.

#

it seems the handlebars mixin actually does have its own implementation but in _attachPartListeners, interestingly enough.

Edit: Ah. That's the one used if you have multiple forms, makes sense.

#

Question

Am I correct that there is no build-in resizability yet for AppV2?

eager hill
#

Building a handle to do that interactively is still a to-do

eager hill
#

You can replace onSubmitForm easily by defining an alternative form submission handler

#

DEFAULT_OPTIONS.form.handler iirc

austere shadow
eager hill
#

Im just on my phone now away from my pc so I’m not actually sure - that doesn’t sound right though

austere shadow
#

Let me see if I can show you... maybe I'm doing something weird, let me double check.

austere shadow
#

The reason this happens is because in _attachFrameListeners you aren't actually using the handler as a check:

// Form handlers
if ( this.options.tag === "form" ) {
  this.#element.addEventListener("submit", this.#onSubmitForm.bind(this));
  this.#element.addEventListener("change", this.#onChangeForm.bind(this));
}
#

So unless you are completely overriding _attachFrameListeners the private #onSubmitForm will be called.

#

And as the handler here is called after the FormDataExtended is created, the error is created, and stops propagating to my custom event.

#

the alternative solution would be to change the handlerCall to not have formData, that way I can manipulate the form element as needed and create the FormDataExtended myself in the handler call.

#

But as is there is no way to sanitize the element before new FormDataExtended(this.#element) is called, and that's the problem I'm running into.

#

How I've resolved it for my prototype is by altering that line to these two lines:

const element = this._preSubmit?.(this.#element) ?? this.#element;
const formData = new FormDataExtended(element);

So in my MyDocV2Sheet I can implement the _preSubmit

_preSubmit(element) {
    $(element).find("tags ~ input").each((_i, input) => {
        if (input.value === "") input.value = "[]";
    });
}
#

but well, editing foundry source code ain't really a permanent solution to solving my prototype 😛

ancient crescent
eager hill
ancient crescent
#

I'm fine with that

austere shadow
#

Has anyone tried using {{formField}} with a HTMLField yet? I just can't get the output to display anything useful in terms of the editor, no matter what I try.

jovial hatch
austere shadow
jovial hatch
#

What's your formField look like?

austere shadow
#

description: new fields.HTMLField({ required: false, nullable: true}),

#

ah wait the hbs

#

{{formField fields.description value=source.description}}

jovial hatch
#

{{formField fields.description value=description localize=true button=true}} is what works on my end (insofar as it shows something, not that it actually works as an editor 😄 ).

austere shadow
#

Ah let me try the button=true.

#

That does in fact display the button, however it doesn't come with any listeners, I suppose AppV1 had build-in listeners for this but AppV2 doesn't?

#

Ah yup, it sure does.

#

Ah, seems like there's a missing default on 200px height as well

eager hill
austere shadow
#

Awesome work!

vivid beacon
#

So I gave it a shot to start making my next FormApplication as an ApplicationV2 instead.
It renders, but the form doesn't seem to get hooked up, so I'm missing something 🤔

const {HandlebarsApplicationMixin, ApplicationV2} = foundry.applications.api;

export default class DhpLevelup extends HandlebarsApplicationMixin(ApplicationV2) {
    constructor(actor){
        super();
        this.data = {
            ...deepClone(actor.system.levels),
            class: '',
        };
    }

    static DEFAULT_OPTIONS = {
        classes: ["dhp", "application", "levelup"],
        position: {
            width: 480,
            height: 600,
        },
        actions: {},
        form: {
            closeOnSubmit: false,
            submitOnChange: false,
        }
    };

    static PARTS = {
        form: {
            id: "form",
            template: "systems/dhp/templates/application/levelup.hbs",
            forms: {
                ".dhp.application.levelup": DhpLevelup.submitForm,
            }
        }
    }

    async _updateObject(event, formData) {
        this.render();
    }

    async _prepareContext(_options) {
        return {
            ...this.data,
            config: CONFIG.dhp,
        };
    }

    static submitForm(){
        console.log('Submitted');
    }
}

Editing an input in the rendered template never triggers a rerender. I hooked it up with name property like I usually would like this simple example:

<form class="dhp application levelup" autocomplete="off">
    <select id="class-select" name="data.class">
        {{selectOptions config.DOMAIN.classMap selected=data.class labelAttr="name" localize=true blank=""}}
    </select>
</form>
eager hill
#

What you need to do is:

static PARTS = {
  form: {
    id: "form",
    template: "systems/dhp/templates/application/levelup.hbs",
    forms: {
      ".dhp.application.levelup": {
        handler: DhpLevelup.submitForm
      }
    }
  }
}
#

Alternatively, if you like the convenience of your entire application being a form, you can make the root element of your application a form instead of as a specific part:

static DEFAULT_OPTIONS = {
  tag: "form",
  classes: ["dhp", "application", "levelup"],
  position: {width: 480, height: 600},
  form: {handler: DhpLevelup.submitForm}
};

static PARTS = {
  form: {
    id: "form",
    template: "systems/dhp/templates/application/levelup.hbs"
  }
}
#

@vivid beacon hope this helps. Let me know if you have any questions!

vivid beacon
# eager hill <@95486257630556160> hope this helps. Let me know if you have any questions!

Hey, thanks a bunch for having a look. Wanted to get a hang of the upcoming way of things
Making changes, it's still not reacting to any changes inside the form though. I stripped down the application even more, but the handler function is never called.

const {HandlebarsApplicationMixin, ApplicationV2} = foundry.applications.api;

export default class DhpLevelup extends HandlebarsApplicationMixin(ApplicationV2) {
    constructor(){
        super({});
        this.data = {
            test: '',
        };
    }

    static DEFAULT_OPTIONS = {
        tag: "form",
        classes: ["dhp", "application", "levelup"],
        position: {width: 480, height: 600},
        form: { handler: DhpLevelup.submitForm }
    };
      
    static PARTS = {
        form: {
            id: "form",
            template: "systems/dhp/templates/application/levelup.hbs"
        }
    }

    async _prepareContext(_options) {
        return {
            data: this.data,
            config: CONFIG.dhp,
        };
    }

    static submitForm(){
        console.log('Submitted');
        this.render();
    }
}
<form class="dhp application levelup" autocomplete="off">
    <input type="text" name="data.test" value="{{data.test}}" />
</form>
eager hill
#

Are you submitting the form? Or just changing input elements within it?

#

I notice you don’t have a submit button

vivid beacon
#

No, I've stripped away everything to have a clean case, hopefully 😅
What I want is to build up an object this.data with a bunch of select inputs. There will be a submit button at the end that verifies the selections are good, and if they are it is not disabled and can be clicked.
I want this.data to be continually updated so that the data can be verified, and the submit button's disabled state controlled.

#

Issue at the moment is that this.data is never updated.

eager hill
#

As default behavior forms are only submitted using submit events. If you don’t trigger a submit event your handler will not be called

vivid beacon
#

I guess I'm more used to sheet behavior

eager hill
#

If you want your form to be auto-submitted when the value of an input changes you need to declare submitOnChange: true in your form configuration

#

Do you understand what im saying? Or do you need an example

vivid beacon
#

Hoh boy. No, I get it. I had submitOnChange as an option initially, but it fell away sometime along the way.
WIth that, I do get updates. I can work with this now 😁

eager hill
#

Hopefully it makes sense why this is not true by default. Most forms may not want this behavior

#

The most basic use case for a form is to use a submit button that is clicked to save changes

austere shadow
#

Is there an easy way to rerender just the header of an Appv2 app?

F.e. When you have a title set to "Item: ${this.document.name}" I'd like it to update when the item name changes

eager hill
austere shadow
eager hill
austere shadow
#

I suppose from a Foundry perspective that would be rather accurate, as a system developer it's 90% of the work 😛

austere shadow
#

Request: Can DialogV2 add a title property to DialogV2Configuration?

austere shadow
#

Nevermind, you can just pass window.title to DialogV2.prompt. I'm an idiot x)

vivid beacon
#

Welp, I'm sticking with AppV1 for sheets and apps that have portrait filepickers and editor fields for now.
I'm assuming listeners for [data-edit] and surrounding logic will get added to AppV2 in some fashion so we can have easy portrait selections and text editors like in V1 Developer

eager hill
#

although its easy to give your image a data-action to spawn a filepicker

vivid beacon
#

I did do that, but then there's wiring it up so onChangeSubmit picks up the choice - and doing the same to create a text editor makes it seem like unneccesary work if it will be added in from foundry side again

austere shadow
humble sorrel
#

A question on best practices for "part rendering" regarding _prepareContext.

Should part-dependent data be collected in prepareContext, gated by its inclusion in options.parts, like this?

if (options.parts.includes('composite')) addCompositeDataToContext(context);

Or is there a protected method in the Handlebars application mixin that I overlooked that compartmentalizes (potentially costly) data preparation between app parts?

austere shadow
#

Does it grab context per part or does it calculate once per render cycle SorWhat

humble sorrel
#

i believe its once per render cycle

#

for _prepareContext, at least

vivid beacon
#

I read somewhere up above that there was supposedly an option to render ApplicationV2 with V1 styling. I've scrubbed the class and I'm not finding it. Am I blind, or is it something not yet in there?

austere shadow
#

Iirc you change the css class "application" to "app" or other way around

eager hill
humble sorrel
eager hill
eager hill
#

You can build your own internal context preparation and delegation system

#

If you need such a thing

humble sorrel
#

oh, believe me, i know 🤣 and im trying to leave it behind with appv2

#

no issues at all, just checking my patterns before i go too far 👍 thanks

eager hill
#

It’s really only worth skipping something that is either an async process or a really slow sync process like a really complex high-dimensional array sort

humble sorrel
#

its the former, yea, doing some image manipulation very conditionally

eager hill
#

Try not to fall into the trap of restricting your context object to the minimum keys and values the rendered parts need. That path will usually be more complex than is merited from a performance POV

humble sorrel
#

and was just trying to ensure it ran only when needed for new data

#

good advice, thanks

eager hill
#

If in doubt, benchmark your application render and conditional re-renders using foundry.utils.benchmark to see whether extra logic in context prep makes any meaningful difference

humble sorrel
#

i have got to get into the habit of using the benchmark util; another good reminder 👍

eager hill
#

Good to compare against your appv1 case also if you have the ability to run them both side-by-side

humble sorrel
#

between the handlebar part rendering and the formfield helpers, appv2 is shaping up extremely well and ive gotten some moderately complex stuff up and running in record time

humble sorrel
#

whether its worth it or not is another question, heh

austere shadow
#

I've noticed some odd behavior with <string-tags> and AppV2.

if you enable "submitOnChange" the submit event will fire whenever the input value changes (before the on-click handler for the string-tags can resolve), in; if you cause a re-render; it'd empty the input box.

But besides that, clicking the 'Add Tag' button doesn't result in a change event getting fired.

Both of these things make sense in terms of what is happening, but it feels like undesired behavior.

The first issue is fixable, in _onSubmitForm we can do a simple

if(event.target?.parentElement?.nodeName === "STRING-TAGS") return;
return super._onSubmitForm(event);

for any AppV2 form with submitOnChange where we want to use <string-tags>

The latter however is more difficult to resolve, my idea was okay well, lets add a simple click event listener with a small timeout duration onto the 'Add Tag' button in _attachPartListeners, however it appears that <string-tags> is empty until it actually gets rendered into the DOM, therefore I can't add a listener to the button in _attachPartListeners.

Wondering if there's an easy solution I'm missing?

#

Ah, I can stick the click listener in _onRender.

#

Feedback: SetField#_toInput will render a HTMLStringsTagsElement if the element is a StringField but no choices/options are provided.

When doing so, the HTMLStringTagsElement gets created with no validation, f.e. allowing 'blank' strings. It'd be very useful if the validate function of the StringsField could be used here.

tender horizon
#

game.settings.registerMenu still enforces an instanceof FormApplication test, will (can) that be changed to allow appv2 menus or are those stuck on appv1 for v12?

austere shadow
#

I feel like I've requested this a while back but don't quite remember what the outcome was / whether it was scoped or not.

But would it be possible to add the PartId to the context on render in HandlebarsApplicationMixin#_renderHTML?

austere shadow
austere shadow
#

Awesome! Then ima just make that local edit so I can have it working now :B

humble sorrel
#

I did not expect these results, not that I am complaining at all.

[refactor/combined-appv1-appv2-subrendering e7a55a7] Refactoring for single app use.
 7 files changed, 215 insertions(+), 293 deletions(-)

I had an AppV1 built with a custom subrenderer. With v12 around the corner, I then built a second AppV2 for other functionality. I was curious if I could get the new AppV2 to render alongside my custom version of such. Not only was that initial process trivial, but after a little refactoring -- the above diff was produced.

Net reduction in LoC with improved functionality? Crazy stuff. Thanks Atro.

vivid beacon
#

Probably not news, but first time I tossed in a nav group in V2 - and at the moment the active tab is lost by default on rerender. Not hard to handle by tossing in something like class="tab {{#if (eq this.tab "the-tabs-name")}}active{{/if}}", but less convenient than V1 as is.

austere shadow
#

So far Atro's way of handling it as presented in Crucible has worked great for me.
Although it feels a little silly that it ain't a base implementation

vivid beacon
#

I'll give that a look. I just checked for what the V2 App did out of the box with data-action="tab" 😁

kindred minnow
#

Is there any reason we can't extend the function below to allow more path extensions other then html, handlebars, hbs. When you call a file with an extension outside of those, you get the following error. I'd like to be able to call .vue files.

You are only allowed to load template files with an extension in [html,handlebars,hbs]

game.socket.emit("template", path, resp => {
 if ( resp.error ) return reject(new Error(resp.error));
 return resolve(resp.html);
});

Started working on my VueApplicationMixin and this function punching is me in the face again. haha, I know I get get around it by just doing fetch statements like I have before. But it really sounded like the whole point of this Mixin thing was to make it so we could write our own... It would be nice to allow more file types.

humble sorrel
#

is that limitation coming from the handlebars mixin? or the base app class?

#

if you are adding a vue renderer, I would suspect you would mirror the handlebars mixin as needed for your rendering needs

kindred minnow
#

game.socket.emit("template", patch resp => {}) is part of the getTemplate function, which is outside of the handlebars mixin, but still clearly relies on handle bars.

austere shadow
#

I feel like getTemplate is related to Handlebars handling though, as in, it uses Handlebars caching isn't it? Or is it generic?

kindred minnow
#

I was hoping to keep to using similar functions when I don't need to write my own. I also, don't know if their is a benefit to using game.socket.emit("template", patch resp => {}) vs writing my own fetch

#

It does use handle bar stuff, but it appears to just be an object, I still could use its caching for my vue components, just ignoring everything else really.

austere shadow
#

It is, it calls Handlebars.registerPartial

#

and Handlebars.compile

#

so getTemplate won't be compatible with a vue implementation

kindred minnow
#

I guess my question is, whats the point of game.socket.emit("template", path, resp => {}) vs writing your own fetch command for files?

austere shadow
#

Ah I see, you're talking about the Server side getTemplate

kindred minnow
#

YES

#

I would prefer to fetch the files using the server side getTemplate command vs writing a client side fetch command. But honestly not entire sure of what the difference would be.

austere shadow
#

I mean effectively the question at that point would be is whether the HTTP request or the WS connection is faster or not (genuinely no idea for this scenario).

Either way the request is served by the Foundry Server just whether it's by a HTTP GET request or the WS connection. But I'll leave that one for staff to answer.

#

Personally, if it doesn't exist, I would 1000% +1 a "fetch file from local" that isn't using fetch.

#

(Especially if we can get a 'does file(s) exist' in the process)

kindred minnow
#

It appear either way for me to move forward with my VueMixin, I will need to do a fetch for the time being, I was just hoping to avoid rewriting a bunch of functions.

austere shadow
#

For my system I support multiple different auto-image resolution suffixes, so would be great to query the server once with an array of paths and know which exist to pick from there, than having to try a whole bunch of fetches until one succeeds.

kindred minnow
#

I use foundrys file system api thing to verify if the template file exists and throws an error if it does.

austere shadow
kindred minnow
#

Let me look it up, I think I use it in MM+ for checking if the a module has a CHANGELOG.md file

kindred minnow
#

Not sure if that will be super useful to your specific usecase, but its how I check to see if files exist before attempting to fetch them.

austere shadow
#

That's actually perfect for my case.

await FilePicker.browse('data', '/systems/path/to/folder/prefix*', {
    wildcard: true,
    extensions: ['.webp','.webm']
});

returns an array of found files.

kindred minnow
#

Its a useful little thing... I can't remember who suggested it to me, or who I stole it from...

strong stream
austere shadow
kindred minnow
#

Man... I am not looking forward to updating MM+ its been far to long since I've looked at that code, trying to remember everthing is super fuzzy

austere shadow
#

Either way Mouse, I still concur that this is a discussion to open up with Staff to see if the approach should be Fetch API, whether a different solution already succeeds, or if they need to add something server-side to be able to support non html/hbs content loading through sockets.

But should probably post it in #v12-feedback both for visibility, but also as it's not directly feedback to App V2, it's just the usecase you ended up finding it in x)

kindred minnow
#

I'll bring it up there. Thx

lapis field
kindred minnow
#

Yeah, I'll have to take a look at hot reloading when I get around to it. I kinda forgot about that. haha

austere shadow
#

hot reloading doesn't work for AppV2 atm anyways x)

#

should be fixed in dev v2 I believe

lapis field
#

One of these days I'll stop feeling burnt out and feel like working on a system again.

#

Would be nice to play with the new tools for AppV2.

austere shadow
#

I've been enjoying AppV2 quite a lot. Been building a new system from scratch for v12 so haven't really tried converting things over, but while it took some getting used to, independent part rendering is A+.

#

And love the new styling.

kindred minnow
#

Honestly before I get to carried away with the vue thing, I should build something using the Handlebars, just to get a feel for it.

vernal sand
humble sorrel
#

its not too hard to use, honestly, and its implementation would be a good example for alternate renderers

#

(the handlebars renderer, that is)

kindred minnow
lapis field
kindred minnow
#

I wouldn't suggest it for a whole production site... but for a handful of modules in foundry, it wont be the end of the world.

lapis field
#

Good for dev and side projects, bad for prod, imho.

kindred minnow
lapis field
#

Yea, I'd still compile it, no reason for everyone's computer to compile the templates every single time they load Foundry.

#

Great way for more users to complain about slow loading times.

kindred minnow
#

I do try to minify this by caching it and only loading the file when its needed. So it'll be slow the first time its called, but shouldn't be to bad after that.

And its optional so for something like KASPER, I would just use SFC, but my character sheet would probably used complied files as thats a lot more data to process.

#

And I haven't noticed any major issues, but then again, everyone I have tested with is mostly local, as in fairly close to the server. Might be worse if your in Germany and the server is in California

lapis field
#

Either way, don't mind me assuming the worst when it comes to things, they tend to happen given enough time and people.

ancient crescent
#

Has anyone figured out if it makes sense to do multiple parts for DocumentSheetV2, given the way Foundry's document data architecture works?

austere shadow
# ancient crescent Has anyone figured out if it makes sense to do multiple parts for `DocumentSheet...

I use it in all my DocumentSheetV2 apps. The answer comes down to 'what is the use case'

I have made my basic setup right now:

/* excluding specifics */ .window-content {
  display: grid;
  grid-template-areas:
    "header nav"
    "main main";
  /* In Document Sheets that don't submitOnChange I also add another row "footer footer". */
}

I've got at least 3 parts at this point, header, nav & "main", and sometimes also an additional footer.
"Main" however is generally multiple tabs, each of which is their own Part.

Now I'm doing this 80% for organizational purposes and 20% future-proofing. You could 100% stuff it all into 1 part, however this way allows me to selectively refresh certain tabs as necessary.

The biggest improvement is that I'm using the same header & nav for almost all of my DocumentSheetV2 apps, so I can re-use those parts without having to register them as partials.

ancient crescent
#

Do you have a public repo?

#

That's an interesting point though about parts just as a way to reuse even if they get re-rendered on document update

humble sorrel
kindred minnow
#

So besides having to rewrite the get/render/loadTemplate functions. It was way easier getting Vue to work with AppV2

austere shadow
ancient crescent
kindred minnow
#

I am now going to play around with the Handlebars AppV2 stuff. Because honestly one of the main reasons I preferred vue over AppV1 was being able re-render specific parts instead of the whole thing, which AppV2 already does.

strong stream
kindred minnow
# ancient crescent seems like it would be good to have a Vue repo that's setup as a library that ca...

The problem with that is that as things change people may need different versions.

My plan is to release it as a module and people can depend on it and use game.modules.get('fvtt-vue').api.v1 but I also plan to just let people include it and reference the core logic. Which will Expose Vue (with optional support for Pinia and SFC Loader), It would make their packages bigger, but it would also make their packages self sufficient without any dependencies.

ancient crescent
#

yeah dual-releasing as an Node and Foundry Library package makes sense

kindred minnow
#

The bigger problem with game.modules.get('fvtt-vue').api.v1 means each time I release a breaking change, it would mean adding game.modules.get('fvtt-vue').api.v2 which could get complex.

ancient crescent
#

does your node release also include a "just library types" setup?

kindred minnow
#

It would include the packages already to configure with the VueApplicationMixin along with other specific things to make peoples lives easier.

#

I have to build in module using this so I can work it out and test it before I get too far ahead

#

Also probably need to throw a warning about using SFC Loader, as it may cause performance issues if people are trying to load very large files or lots of components. haha

vernal sand
#

The trickier bit with having a Vue dependency is that it's tough to depend on a module for systems in particular. I originally used the VuePort module when we transitioned 13th Age to Vue and even with extra checks to ensure the module was enabled, it was significant pain point for our users. Things got a lot smoother when I rolled my own Vue solution directly into the system.

#

An npm package would be nice for sure though as a shared starting point. The main thing to sort out at that point would be particulars on things like Vue versions and SFC loader vs precompiled.

kindred minnow
#

Honestly for the node package, it will basically just be the custom VueApplicationMixin and maybe other things to help make your life easier, with instructions how to include what you need from vue.

#

Really the only reason to use my custom complied thing is for people like me who want to be lazy and don't want a build step. haha

wide lake
#

Welp, the styles are 100% broken and the types are mess of any and @ts-expect-error, but I can work on that - I've got DocumentSheetV2 rendering one of my React-based actor sheets 😄

eager hill
#

Nice!

ancient crescent
#

Seems like that goal of appv2 worked out

kindred minnow
# lapis field Either way, don't mind me assuming the worst when it comes to things, they tend ...

Alright, so after some of the most basic testing possible:

Loading Handlebars UserConfig takes about 20-25ms the first time, and then about 1.25-1.75ms every time after.

Loading a basic todo.vue with two other components inside them TodoItem.vue and UserProfile.vue using SFC takes about 350ms to 450ms the first time and then about 1.25ms to 1.75ms every time after that.

Loading that same component without using .vue file to fetch it but instead just grabing it with import todo.js takes about about 35-45ms the first time, and then about 1.25-1.75ms every time after.

Now I assume the todo.vue app is slightly more complex then the UserConfig app. But yes, fetching and compiling a vue app using SFC is much slower the first time, but about on par once cached. Just wanted to get some numbers as you said performance was one of the things you dealt with alot under #modules

Also I suspect that if you built your module using vite it would improve performance even more, but I am not using build steps for foundry, so I can't say one way or the other.

#

Also all of this is done on a localmachine for testing, so performace is going to be worse when fetching from a remote server.

#

But I think everyone pretty much knew SFC was going to be much slower. haha

#

I wonder if I could do some magic with sockets to shared cached components between users. meaning it would really only be slow for the first user to load the SFC file.

strong stream
kindred minnow
strong stream
#

Yeah, it's really a question of the size of the blob vs the size of the file and the compile time versus the extra round-trip time between another user and the server to send the cached blob over the socket

kindred minnow
# strong stream Yeah, it's really a question of the size of the blob vs the size of the file and...

You're probably right it might be longer but I am not sure and am curious so I'd like to do some testing. I think if you're not doing an SFC file, it's probably a lot slower as it'll already be compiled. But if you are doing an SFC file it might be faster.

But to be honest supporting SFC files is mostly because I am a jerk and like to experiment and tend to use foundry as my playground... Which is probably not all that great to people who have to answer questions like "hey foundry why is this devs module so weird and slow?". Hahahaha

kindred minnow
#

So not really sure where to share these, but if someone wanting to use Vue would like to take a look and give me some feedback, I'd appreciate it. I don't expect them to be perfect, but they appear to work.

I do plan on making Vue Versions of foundry's ApplicationV2, DialogV2, etc... But I figured I would start with HandlebarsApplicationMixin by making a VueApplicationMixin first and build up from there.

To any moderators if I should post this somewhere else, just let me know.

ancient crescent
kindred minnow
#

Valid, I just haven't really gotten that far. Basically all I've done is make sure I can do a Generic Vue ToDo App. I can setup a repo later today with an example the generic Vue ToDo App I've been testing with.

kindred minnow
ancient crescent
#

eh, this is a low traffic channel. People can check in later

wide lake
#

That’s a personal repo for shared stuff so don’t expect coherent anything.

kindred minnow
#

Why does that make me feel so lost (To be clear this remark has nothing to do with your code and all to do with me not working with react for like 5 years.). I will have to check it out later when I have time to read it, just as a double check to see if I am missing anything in my implementation of the ApplicationMixin function.

kindred minnow
#

I will work on fine tuning alot of this hopefully with feedback from others interested. Them demos are just the Grid from https://vuejs.org/examples/#grid and look like this

vernal sand
# kindred minnow Alright, so here you go: Just the files and a short rightup about what they do, ...

For your question in the repo about updating the existing app during replaceHTML(), my AppV1 version of a vue setup may be helpful. What I essentially did was add an updateContext() method to the vue app and then it iterates over the top level props and updates their values. https://gitlab.com/asacolips-projects/foundry-mods/vue3boilerplate/-/blob/main/module/sheets/actor-sheet.vue.mjs?ref_type=heads#L44

#

Given how props work in the vue, all you really need to do is make sure they get updated during the replaceHTML() step and vue should take care of the rest as long as you avoid replacing any HTML manually.

kindred minnow
#

Its why I use .provide(...) to give your component access to onSubmit and onChange events. Speaking of which, I should update the _attachPartListeners so that you can define more functions to attach the functions from the actions property as well...

vernal sand
#

As long as you're assigning the mounted app to something that can be tracked/updated, you can update it in the replaceHTML() method. In my case, I assigned the result of the .mount() call to this.vueRoot, and then I update the context in that vueRoot on re-render.

#

I'll have to look into the .provide() option. Still learning new things about Vue even years later and that looks super handy catjam

kindred minnow
#

To bad you cant create a thread inside of a thread... Not sure where else a conversation like this would belong though... maybe #module-development but tis not really specific to modules as it can be used in systems too. Just kinda feel icky bogging down this thread with my vue specific discussions... I guess technically its still all related to App V2...

But looking at your example, you are providing the html that includes has the component of <character-sheet ../> in which you are attaching your vue component. However, I am attaching the component you pass in as a standalone component to the <div data-application-part="${key}" /> which means I don't really have the ability to attach methods and data to the object you pass in... At least I don't think I can. I will have to experiment a little to find out.

Provide / Inject are pretty useful when you want to let any component or sub component of your Vue Instance to access something without having to pass it in as props each time. I am not sure if its the best way to do things, but it did seem like the perfect fit for what I wanted to do.

vernal sand
#

I've been holding off on updating my 13th Age system to use v12 (it's what my vue example repo was based on), but I'll try to set aside some time to experiment with your mixin this weekend and see if I can lend an assist on it 🍻

ancient crescent
#

Hey I've started a to-be-published page for ApplicationV2 on the community wiki - y'all are pretty cutting edge so I figured you might have some insights to contribute

kindred minnow
#

Well I am hoping to set some time aside to work on VueApplicationV2 as I would like to have the whole window be vue... But that adds alot of questions like, What happens if someone wants to use VueApplicationV2 with HandlebarsApplicationMixin. Also VueApplicationMixin would have to be updated, because if its using a Vue Frame vs the One from Foundry, I feel like inserting HTML wouldn't be the proper solution and instead I should attach the components dynamically properlly. But then the mixin needs to be updated so that it works with ApplicaitonV2 and VueApplicaitonV2...

Basically I started whiteboarding it and then got a headache tyring to wrap my mind aroudn it all and was like... Thats a Future Mouse Problem...

ancient crescent
#

E.g. It makes it work equally well with ActorSheet and ItemSheet

#

because it's just MyVueActorSheet extends VueApplicationMixin(foundry.applications.sheets.ActorSheet) and MyVueItemSheet extends VueApplicationMixin(foundry.applications.sheets.ItemSheet)

kindred minnow
#

I guess I have to look into it more. Maybe All I really need to do is overwrite the frame within the mixin to allow the frame to also be a Vue Instance... I haven't looked into it all that much yet.

#

Either way, I get more of a feeling of how well my Mixin works once I rewrite KASPER to use it. As that will be a real world example of it in use. Since that will work with Foundry Data, such as the drag and drop stuff along with actually save and update data.

austere shadow
ancient crescent
#

Related to the above issue - I'm not seeing any built-in drag and drop handling in DocumentSheetV2, which is a pretty important feature in AppV1 for actor & item handling

austere shadow
#

Ah that's still missing? That's a pity.

#

Was also waiting for that to do big UI work x)

ancient crescent
#

Also not seeing any re-implementation of AppV1 secrets?

eager hill
#

Good questions, I think some of these things will continue to improve before stable (without changes to existing API)

eager hill
austere shadow
#

I guess it didn't make it into dev2?

eager hill
eager hill
austere shadow
ancient crescent
austere shadow
ancient crescent
#

Just ItemSheet

#

It's in client-esm

eager hill
#

Should be foundry.applications.sheets.ItemSheetV2

ancient crescent
#

Next to userconfig

austere shadow
#

Ah.

#

may want to rename that to ItemSheetV2 as it's currently just called ItemSheet.

eager hill
#

I didn’t do any work on base item or actor sheets in dev2 simply put because I forgot. We had a ton of competing priorities for work to get done before the dev2 cutoff and I was bouncing around a lot. Didn’t think about it and there was no GitHub issue in the milestone to remind me :/ RIP

austere shadow
#

Absolutely no worries! Would you like me to open 1/2 issues to have it trackable?

eager hill
#

Yes certainly, please

#

Im not at my pc now

austere shadow
#

No worries, would you like them tracked separately or as one?

austere shadow
#

Is there a way to use FormField w/ HTMLField and show enriched content when displaying the editor's content, but the source when opening the editor?

I tried this:

{{formField fields.description value=descriptionHTML target=data.description localize=true toggled=true}}

but opening up the editor just gives me the enriched content link, instead of @UUID[uuid]

steel dome
#

I'm late to the party playing with AppV2. If I have a small form and want an event listener monitoring changes to, e.g., a select element, is it intended that event.currentTarget would be the entire form? Having static DEFAULT_OPTIONS.actions.steve: MyApp.steve, I wasn't expecting to see the entire form as currentTarget when I had data-action="steve" only on the relevant elements directly.

humble sorrel
#

check the...user configuration application if you havent already, as it uses this pattern

#

@steel dome

steel dome
#

Guess it is intended then.

eager hill
austere shadow
#

Sure thing

eager hill
steel dome
#

Not gonna lie, this feels a bit much, this isn't the correct way to start a tiny custom "FormApplication", is it?

export default class SkillConfig extends foundry.applications.api.HandlebarsApplicationMixin(foundry.applications.api.DocumentSheetV2) { ... }
austere shadow
#

although FormApplication you may just wanna take AppV2 rather than DocumentSheetV2

#

depending on whether the target is a document directly or not

strong stream
jovial hatch
lapis field
#

Also, you can do this:

export const HandlebarsV2App = HandlebarsApplicationMixin(ApplicationV2);
```in file a `utils.mjs` file or something, thus letting you extend from just `HandlebarsV2App`
strong stream
# humble sorrel It's really not.

Uh ... the character count of what's posted compared to the Application version of the same end result is pretty clearly much wordier. I get that it's being done for organization purposes, but nesting everything deep within assorted namespaces and scopes certainly makes it more painful to access for anyone who hasn't memorized exactly where everything goes (which is most devs, TBH)

steel dome
#

mom, dad, please dont fight.

Really the most major change - in this part - is the need for a mixin by default, unless one feels like reinventing those two html methods I forget the name of now, which was really the only reason I slapped on the same mixin as UserConfig (v2) did.

#

I ended up just doing const a = foundry.applications.api;.

humble sorrel
strong stream
humble sorrel
strong stream
humble sorrel
#

...the app i injected was the point, which was entirely appv2. Simply using appv2 reduced net loc.

Or dismiss first hand experience, either or.

steel dome
#

mom, dad, please dont fight.

humble sorrel
#

im just trying to reassure people that appv2 is not some complex beast, but is actually very easy to use. that is all.

vivid beacon
#

Thought I'd toss in classical V1 avatar image selection and make my next sheet in V2.
Copied over the handler code from V1 to throw up a filepicker and so on.
In the callback after selecting an image, V1 went ahead and made a form submission happen. How would you do that in V2? this._onSubmitForm expects the event's currentTarget to be the Form, and not an img element or something, so that doesn't seem to be the ticket.

  _updateImage(event, path){
    event.currentTarget.src = path;
    return this._onSubmitForm(this.options.form, event);
  }

EDIT: Actually, I probably just messed myself up by using an event from partListener instead of just using data-action. Doing that, the event is much more useful 😁

#

Nope, still an issue. I'm not too hot on the lifecycle of events, but what happens is that initially, the event has currentTarget set to the form element, which is what onSubmitForm expects. By the time the callback from the filepicker is called though, the event appearantly has currentTarget=null.

steel dome
#

tbh I just said screw it and made the function call document.update, I guess I never saw why it needed to use event at all

vivid beacon
#

That's fair. I figured I could do that. I just ended up interested in how one would actually trigger a form submission.

austere shadow
#

Not behind my pc so can't check source atm, but I don't think there's anything stopping you from calling the form submit handler manually either. Sure, its supposed to take a submit event, but at least DocumentSheetV2 iirc doesn't actually derive any data from the event and just grabs this.element to construct the FormDataExpanded from.

#

Either way, just manually dispatching a submit event works great for me personally

#

Just make sure to pass cancelable true as not every browser defaults to true

#

(so some people were enjoying onChange handling while others were having a fun time with a full on GET request being launched ashesweat)

kindred minnow
#

VueApplicationMixin v0.0.2 https://github.com/mouse0270/fvtt-vue

  • Added the Ability to Toggle the Console Logging
  • Added Expiremental Support for Props (Need feedback on how this should work)
  • Added the ability to unmount and remount instance using force option
  • Added the ability to update props (Sorta, need feedback on how this should work.)
class VueApplication extends VueApplicationMixin(ApplicationV2) {
  static DEFAULT_OPTIONS = foundry.utils.mergeObject(super.DEFAULT_OPTIONS, {
    id: `app-${Math.random().toString(36).substr(2, 9)}`,
    window: {
      title: `${Module.id}.title`,
      icon: "fa-solid fa-triangle-exclamation"
    },
    position: {
      width: 680,
      height: "auto"
    },
    actions: { }
  }, { inplace: false });

  static DEBUG = true;

  static PARTS = {
    app: {
      id: "app",
      component: App,
      props: {
        title: "Vue Application"
      }
    }
  }
}

Will now pass the props of {title: "Vue Application"} into the PART.app Any feedback on if this seems like a good or bad structure, please let me know.

Also you can do

const viteApp = await new VueApplication().render(true);
viteApp.render({parts: ["app"], force: true, props: { title: "Hello from Vue"}});

Currently you have to set force: true causing the whole part to be remounted. I can't seem to figure out why the props are not updating without unmounting the instance even though when inspecting the instance I can see the new props vaue

Also I am not sure if this is really a good structure. I don't really like that props: { ... } is not referencing the specific part meaning this will cause all parts to be updated to the new props. meaning if you do

viteApp.render({parts: ["app", "sidebar"], force: true, props: { title: "Hello from Vue"}});

Both the app and sidebar part will be updated to using props: { title: "Hello from Vue"}

Any feedback on how this should be structured would be greatly appreciated.

#

Also, if there is a better place for me to be posting this type of thing... please let me know. I asked last time but didn't really get any response that I can recall.

humble sorrel
#

fwiw, ive been following along (lightly) with your development and appreciate seeing real and unique example use cases

vernal sand
#

But my 13th Age system has been using Vue for several years now, so I'm definitely interested in testing out your approach and seeing what I can pull from it and what I can recommend as improvements from stuff we've already encountered and dealt with in Ye Olde Vue setup.

kindred minnow
# vernal sand I starred the repo and made a note for myself to look into it more, but haven't ...

I can't wait to get feedback. I am trying to make sure this Mixin Just Works So System or module developers can just drop it in as a replacement for the Handlebar Mixin. Its why I am going out of my way to make sure it works with SFC as well.

As for the props issue, I am clearly doing something wrong, but my brain just isn't registering it. I really shouldn't be all this hard to update the props but its driving me insane. I don't recall having a similar issue with the V1 app, but if I remember, I also wasn't creating and storing multiple vue instances to be rendered to try and match the V2 PARTS.

What System? Maybe I can take a look at your source code and see if I can get any ideas. I'd love to avoid any glaring potholes before hand instead of after.

vernal sand
# kindred minnow I can't wait to get feedback. I am trying to make sure this Mixin *Just Works* S...

13th Age: https://github.com/asacolips-projects/13th-age

  • Components are defined under src/vue/
  • Sheet classes are src/module/actor/actor-sheet-v2.js (and actor-npc-sheet-v2.js) and src/module/applications/compendium-browser.js
  • Repo isn't particularly well organized, as it's been in a perpetual state of just-get-X-thing-working since the earliest versions of Foundry. We transitioned to Vue using VuePort I think around 2020 or 2021, and then we switched to a custom Vue3 setup about a year after that.
#

My Vue3boilerplate repo was made as a stripped down and cleaned up version of the setup and can be found here: https://gitlab.com/asacolips-projects/foundry-mods/vue3boilerplate, along with more docs on the setup. It's only for Foundry v9, but the actual vue and sheet classes changed very little since then and is still useful as a reference.

kindred minnow
#

Honestly, until yesterday I didn't even know I could do createApp(App, props) maybe I'll have to switch to using createApp({...} and assigning stuff as needed.

vernal sand
#

That's a good point. I'm not as familiar with that aspect of Vue, but if it's anything like the non-root components, what I'm doing wouldn't actully be considered props since in a component props: and data() are two different things. Might be why I'm having an easier time with it.

#

With that said, I'm never reassigning the object that I'm using for props. The updateContext() method that I wrote and included in the vue app is used to iterate over its keys and update their values in place so that the object itself persists and reactivity continues to work.

kindred minnow
#

I am trying to make the props object reactive doing this.#props[part.id] = reactive(options?.props ?? part?.props ?? {});

And on the .render(...) I have tried:

foundry.utils.mergeObject(this.#props[part.id], options?.props ?? {}, { inplace: true, insertKeys: true});
Object.assign(this.#props[part.id], options?.props ?? {});
this.#props[part.id].title = options?.props?.title ?? "";

When doing console.log(this.#props[part.id]) It appears to have retained its reactive state, but its clearly not and its clearly not trigger the vue instance using it to understand the new values.

Though looking at the Vue Instance within the console is showing that its not being updated their. I forgot about the extension until now. So its either losing its reactivity or its not understanding it has changed.

wide lake
kindred minnow
wide lake
kindred minnow
#

If you have any questions, do feel free to ping me and if I can help I will.

wide lake
#

Personally I wouldn’t trust a foundry module solution but it could be useful for people with no build chain

kindred minnow
#

npm/copy code. Releasing it as a module will cause issues as modules amy require different versions of your module and users would have to install multiple variations. I feel like it adds more heacaches to end users then solves.

kindred minnow
#

The down side to it not being a module, is I suspect that new developers wont easily find Vue/React/Solid/Svelte Mixins and just default to handlebars as there wont be really any way to expose 3rd party mixins to the best of my knowledge other then word of mouth.

wide lake
vernal sand
#

Search engines should be able to pick up on that when people search for things like "Foundry VTT vue" and so on, and my Vue stuff is already decently easy to find with it. I'm also happy to include them as optional features in my Boilerplate starter system now that it has a generator CLI command.

#

And on that note, getting the mixins pinned in #system-development and #module-development would be good steps too, which has helped a lot with visibility for my system tutorial and the accompanying Boilerplate system.

kindred minnow
vernal sand
wide lake
kindred minnow
#

I still have to fix my props issue, and support for actions (using provide/inject) and add support for at least .use before I worry about people finding the mixin though. so no rush on my end.

ancient crescent
kindred minnow
ancient crescent
#

I mean, it's Markdown

#

probably the most straightforward docs language to exist

kindred minnow
#

sometimes you just have to ask someone smarter then you:

createApp({
  render: () => h(App, { ...VueProps })
})

I HAVE WORKING PROPS UPDATING WITHOUT DESTORYING THE WHOLE INSTANCE

Sorry, not sorry about the caps.. I was never going to figure that out on my own.

kindred minnow
#

I do still need to figure out a better way to pass in props on app.render({ parts: ["app", "sidebar"], props: {...}})

I guess I could do:

app.render({ parts: ["app", "sidebar"], props: {
  "app": { ... }
}});

Forcing you to have to specify the specific instances props you would like to update? Or do I just not allow you to define different props per part and only one props that gets applied to all parts?

I feel like having

class VueApplication extends VueApplicationMixin(ApplicationV2) {
  static PARTS = {
    app: {
      id: "app",
      component: App,
      props: {
        title: "Vue Application"
      }
    }
  }
}

is better then

class VueApplication extends VueApplicationMixin(ApplicationV2) {
  static PARTS = {
    app: {
      id: "app",
      component: App,
    }
  }
  static PROPS = {
    title: "Vue Application"
  }
}
vernal sand
#

I think my main worry with the parts setup is that it feels like it could get pretty heavy if you have multiple Vue applications per sheet, so if a client has say 5 NPC sheets open with 3 parts each, that's 15 Vue app instances to handle. I haven't done that level of performance testing to see if the overhead is significant enough for it to matter, but I would still lean towards something like a single Vue app per Foundry application, and then maybe use parts to handle updating specific nested properties within the map app's props.

kindred minnow
#

I am mostly looking for feature parity to the HandlebarsApplicationMixin. I didn't really think about having a character sheet with 3 parts being opened 5 times. Causing 15 Vue Instances to be running at once.

I wonder how impactful that will get. I could instead create one vue instance with using a programmatically generated template that binds all the parts to as a components for the instance.

#

I do expect most people to generally define one part... But I guess I can try to reduce the impact of users creating to many instances.

vernal sand
#

Right, I think this is just one case where feature parity may be tricky to do cleanly. With Handlebars, parts are a net gain since you're allowing the application to be semi-reactive and only re-render specific portions of the sheet as needed. With Vue though, each application has its own state, overhead, and so on that would have some level of impact. Vue itself is also already reactive, so parts aren't as beneficial for it IMO, though I could see them being useful for updating nested portion of the reactive props so that the whole app doesn't need its state tweaked.

wide lake
#

I was thinking about parts for the React mixin, and I couldn’t see a use case. My understanding is that it’s to avoid unnecessary re-renders with handlebars, but optimised rendering is one of the things that reactive renderers are already really good at.

#

Like, if you want a sheet with three parts, write a component with three children.

kindred minnow
#

So right now, I am basically using the parts system to allow you to force remount or update props for a specific component. I am not so sure if I could do the force remount if I make it all one instance, but I should be able to make the components use specific props.

vernal sand
#

Yeah, and reactive renders are good enough at it that you more or less get simulatenous editing of character sheets as a freebie (for regular fields, not just editors like ProseMirror) catjam

kindred minnow
#

Also, if anyone happens to have a simple idea I could create using Vue and Foundry I would appricate that. I am trying to think of something to show off that actually uses foundry. As the Vue examples I am going to be incorporating into my demos, are just the examples from Vuejs. I'd like to have a real example working with foundry api.

#

I guess technically KASPER will be my real example... Maybe I don't need one.

#

But I would rather have like an example, then a real module... I don't know maybe I am confusing myself. haha

vernal sand
#

Where’s it shined for me so far is on character sheets (reactive rendering is great for not losing editing context plus things like CSS animations on health bars) and big filtered lists/tables (like the compendium browser module which is currently getting a Vue rewrite based on my 13th Age compendium browser)

wide lake
#

I don’t want to write cheques my ass can’t cash, but it would be really cool to have a really simple but usable app written with every *AppMixin, like a Rosetta Stone of mixins.

vernal sand
#

Another one that’s not obvious are cases where being able to call JS in a template can be powerful. Handlebars logic is a pain sometimes, but with Vue you can do basically whatever you want in a v-if or v-for

vernal sand
wide lake
kindred minnow
vernal sand
#

In seriousness though, I updated Boilerplate to recently have data models as an optional feature via its CLI, and I’d like to extend that idea to include Handlebars, Vue, React, and Svelte mixins as they start to pop up.

#

The system is a really barebones starter point with examples of derived attributes, so it’s a good example use case.

karmic current
kindred minnow
strong stream
#

Yeah, that would be weird. Making a library module that could be used as a dependency would make more sense in that situation (even if you also make standalone/distributable versions of the codebase and link them from the package page)

ancient crescent
#

Library minimizes code duplication, which would be nice for modules

vernal sand
#

Yeah, if there's a package listing it also needs something like an actual library module to install. IMO, the best of both worlds approach would be a library module for shared dependencies, but with documentation and a repo for a standalone version for usage in systems in particular (and modules that don't want to rely on dependencies)

kindred minnow
vernal sand
#

Like with 13th Age, our system became much more stable when we moved away from the VuePort module and just built our own Vue dependency directly into the system.

strong stream
ancient crescent
kindred minnow
#

I think I am just going to leave it as an outside thing, and just rely on people saying "Oh you want to use vue... check out this github..."

vernal sand
#

Having it as a self-managed thing also takes away a significant amount of the maintenance burden for you!

#

Which is another reason I enjoy having Boilerplate as a starter system. Unlike my modules/systems, it's not the end of the world if it takes me longer to update it since it's ultimately just an example for others to build off of.

kindred minnow
#

Oh I probably should figure out tabs... or is that not a thing in V2 Apps anymore?

ancient crescent
#

there's an onClickTab action that calls it

kindred minnow
#

I'll have to check that out to make sure it all works as expected.

#

I haven't looked at the tabs in V2 kkinda forgot about them. I always ended up writing my own logic instead of using foundrys

wide lake
vernal sand
#

A listener that modifies the application's document are fine (item creation, drag n' drop, etc.) since those will ultimately just update the document's data and trigger reactive updates in your components. Things like adding CSS classes or modifying the DOM should happen directly in your components though.

ancient crescent
vernal sand
#

I'll look into that and see if it's viable, though it might be more effort than the component-based solution that I have currently. I wasn't aware of it at the time, though I wrote that tab class back in 2021 so it may not have been an option back then either.

#

If I was overriding changeTab, what I would shoot for would be updating something within the Vue app's props to let it know what the current tab is. Might be able to drop the tab components, but they were pretty straightforward to make and keep everything within Vue's virtual DOM.

ancient crescent
#

@vernal sand another good base appV2 function to investigate is the actions

#

for built-in click handlers

kindred minnow
#

Alright, so I moved everything to one Vue instance. Which massively cut down the code. But it completely broke part.forms as i was using vue.provide to provide the from handler events to the individual parts, but since there is now only one instance, the provide is providing the call handlers to all events. I am trying to work around this by modifying the #onChangeForm and #onSubmitForm to try and get the closest selector from the calling event but it seems overly complicated compared to the individual instances.

This also means you wont be able to attach app.use and app.directive to specific partsd and is now going to apply to the entire instance.

I really think I preferred having each PART registered as their own instance as it let you have more fine tune control over the individual parts vs everything grouped together and sharing an instance. But I fully understand the issue with performance if you end up with 15-25 vue instances running at once. Where this may keep you at 3-5 instances.

kindred minnow
#

Alright, I updated the mixin again to provide a fix for adjusting the size as the content of the vue instance updates (Probably not very optimized and will have to look at later)

I also update the https://github.com/mouse0270/fvtt-vue-vite to include examples from VueJs and will work on adding Foundry Specific examples later this week:

kindred minnow
#

I really think this is a good example, because it shows the use of vue over maybe a handlebars as it lets you easily animate the UI as it doesn't destroy and rebuild the ui.

kindred minnow
lapis field
#

Yea, that tracks.

#

Same reason I did it before.

steel dome
#

I used _saveScrollPosition and _restoreScrollPosition with Element#animate like some sort of savage. 😔

kindred minnow
#

Not sure if that's better or worse them implementing a whole framework on top of foundry to animate one simple thing... haha

steel dome
#

If your solution doesnt look jank if you re-render twice quickly, then it's better

kindred minnow
#

I am just hoping that all of my work creating this VueApplicationMixin, just makes it easier for other modules and systems to implement vue if they want to. Its been a fun project and I really hope people take advantage of it. Because I think I've heard about 3-5 people implement vue in their own way. VuePort, Foundry Vue, and I few custom implementations such as asacolips for their system.

kindred minnow
#

So if you really want that janky look and feel, just set force to true when you re-render. haha

lapis field
#

Will say, it was probably not nice to the backend of Foundry, I was doing everything as props, with each change being written back as a user was typing.

#

A slightly older demo, showing sync across clients.

wide lake
lapis field
#

Yea, this was a rawdog test environment where I did not care to add that.

#

I did it on a lark, then ended up being burnt out working on things, so I never got back to this.

kindred minnow
#

Not sure there is a good way to not treat foundrys backend like its been naughty via the mixin, going to leave that to individual developers to behave.

#

I do think all I have left is to provide a way to define app.use app.mixin and app.directive and the mixin should be pretty much feature complete... at least until someone uses it in production and breaks all my logic.

ancient crescent
#

Has anyone checked if ContextMenu still works in App V2?

kindred minnow
#

As in does it still have a _contextMenu? I don't think it does... I don't recall seeing anything when I wa looking for the Drag/Drop Events.

ancient crescent
#

feels like there's a lot of functionality that's getting dropped by AppV2

kindred minnow
#

I am sure you can still implement them yourselves. I feel like keep the application smaller, as not all windows will need to support Drag/Drop/ContextMenu, so maybe its not bad its missing.

But they should probably create some examples of how to add it yourself when you need it.

strong stream
ancient crescent
#

also, "you can implement it yourself" has basically been true for everything short of the database

#

the advantage of Foundry-provided is not just reducing module/system developer workload, but also common design language to make sure things from different packages work well together and look good together

kindred minnow
#

Though consistanct is a really really good point.

lapis field
#

Worth tapping the sign that AppV1 is not going to disappear for many major versions, and AppV2 can still be worked on between now and then to solve blockers for migrating to AppV2. Calling out things that are missing is always good to see if there was a good reason to not have something, call attention to something that was assumed to not be used, or to question if there is a better way now in AppV2 to do something in AppV1.

kindred minnow
#

Sorry I didn't mean to imply it shouldn't be questioned. I was just assuming why it might have been left out.

ancient crescent
kindred minnow
#

But you know what assuming does....

strong stream
ancient crescent
#

sorry forget that Spice is just mod

lapis field
#

Plus the staff are keeping eyes on here.

lapis field
humble sorrel
eager hill
#

I think that all the show-and-tell in this thread is really cool, but I do worry that I'm missing important questions from people lost in all the scroll...

I wonder if it would be better to split "show off what you have made using ApplicationV2" from "I have questions or need help with ApplicationV2".

humble sorrel
#

...this is a dev thread...good point 😅

ancient crescent
#

the core rendering seems to work great and I'm happy with that part of things

eager hill
kindred minnow
#

I am happy to post my show and tell somewhere else. I have asked twice where a better place would be. Id be happy to do so somewhere else if told the best place

austere shadow
#

Perhaps we can open a second thread on this channel for the purpose?

tender horizon
austere shadow
tender horizon
kindred minnow
eager hill
#

thank you @kindred minnow - I really appreciate you sharing your wisdom with the community. I also just want to make sure I can easily see what questions or issues people are encountering.

kindred minnow
#

So speaking of AppV2 Features... did anyone ever post an issue about Drag/Drop Missing? I thought Choas said they might... But I am curious as to that feature.

ancient crescent
#

I think I need to just make a demo AppV2 that checks all of the helper classes

quick sky
#

I'm poking into appv2 in order to figure out the types, and the parts based rendering for the handlebars plugin looks pretty similar (core idea wise) to what I set up for the party sheet in pf2e. I dunno if it was inspired by that or created independently, but I'm feeling super validated right now. This is def more fleshed out than I did though, obviously.

#

Regarding ApplicationV2 and the mixins to add a rendering engine, I'm curious on what the recommendation is when creating a sheet heirarchy (like a weapon sheet that extends a physical item sheet that extends a base item sheet). Is the idea that any individual point of the inheritance chain should decide what rendering engine is in play, or do you make the decision for the base and the rest is expected to follow in line? I'm expecting the latter, but correct me if its the former.

#

I don't plan to convert those things to ApplicationV2 yet, maybe way further down the line, but it will help me decide how to type them in typescript.

ancient crescent
#

so

const { api, sheets } = foundry.applications

BaseSystemActorSheet extends api.HandlebarsApplicationMixin(sheets.ActorSheetV2) {}

SubtypeActorSheet extends BaseSystemActorSheet {}
quick sky
#

That was my thought as well, because I'd imagine most of what would constitute a base actor sheet for a system would have make assumptions on the rendering.

eager hill
#

This is why we have, for example:

ApplicationV2
DocumentSheetV2 extends ApplicationV2
ActorSheetV2 extends DocumentSheetV2
CrucibleActorSheetV2 extends HandlebarsApplicationMixin(ActorSheetV2)
#

from the perspective of a system developer, you might be "adding the handlebars mixin right away"

#

but in terms of the larger picture it's actually the last thing to be added.

#

now of course you could go on to further subclass from there and those further subclasses would inherit handlebars behavior further downstream

#

When it comes time for us to tackle this for dnd5e though - I do think we might do something like this:

ApplicationV2
DocumentSheetV2 extends ApplicationV2
ActorSheetV2 extends DocumentSheetV2
ActorSheet5e extends ActorSheetV2
BaseHeroSheet5e extends ActorSheet5e
HeroSheet5e extends HandlebarsApplicationMixin(BaseHeroSheet5e)
#

because I think we want to make it easy for someone to do:

MyVueHeroSheet extends VueApplicationMixin(BaseHeroSheet5e)
#

adding the rendering backend as late as possible makes it easier for modules to provide alternative sheets.

ancient crescent
#

interesting perspective for system dev

kindred minnow
#

Interesting expirement, what legal issues would I run into making demos of AppV2 windows using my Mixin? Is that something I should only do locally for testing and not push out? as the idea is it should be a 1v1 recreation of your windows for example, UserConfig...

#

Or is it because its a different file even if it does the same stuff, its fine?

ancient crescent
#

Question regarding ActorSheetV2 - do you want tracking tickets for the other more advanced properties that didn't make it into Testing 1?

ancient crescent
#

Related, I'm finally reading how DragDrop works since I never really got into the guts of it with AppV1. It seems like it will work fine with App V2

#

just a question of rigging everything up

austere shadow
strong stream
#

Everything I've ever seen and heard says "make issues so stuff can get tracked and worked on and not slip through the cracks"

eager hill
austere shadow
#

Feels silly to copy paste an issue so hence asking what should be done.

eager hill
#

Yes, although I will say I am hoping - to a considerable degree - to have a “clean start” with ActorSheetV2. My goal is not to replicate every property/method/feature of the V1

austere shadow
#

Makes sense, I personally hope we'll at least have basic implementation for a TokenConfiguration header button as well as drag & drop support; most specifically for the item sorting.

#

I'm off to bed but I can open up a new issue(s) in the morning if you want and nobody else has gotten to it, night o/

eager hill
#

Yeah token configuration header button is a must-do

steel dome
kindred minnow
#

I am not a big user of the search thing or the ContextMenu that was built into things. For the most part when I needed a search I usually wrote my own, as for the ContextMenu, I have always found it being inlined tended to cause issues with position more then it helped... So I personally wont miss this... Howerver, I really do mis the drag and drop being default.

I suspect peoples opinion on which ones they miss are probably going to be based on the ones they used. haha

eager hill
#

That is a big assumption to force in core that is needlessly inefficient for a lot of systems

ancient crescent
#

Yeah cleaning up the context/data is a good improvement and I'm fine with having to implement my own _prepareContext

austere shadow
#

Am I just blind or does DocumentSheetV2 not support submitOnClose

#

And if I ain't blind, I think _preClose would be the best spot to add that handler, wouldn't it be?

eager hill
#

perhaps not, could consider adding it as another option

austere shadow
#

I feel like there was a specific case where onchange didn't fire when an on-close happened but I'm blanking on what the case was.

steel dome
#

The only time I have had an explicit need for submitOnClose (with app v1) has been when closing a sheet after writing in an input:text field without focusing away from it (eg by hitting Escape)

austere shadow
#

oh interesting

#

it doesn't work 'just fine'.

#

Some fields do some don't

#

let me see how that could be.

steel dome
#

multi-selects?

austere shadow
#

Okay so it seems to be related to the way this app of mine is setup.

I have one app which is just one form; this one if I am editing an input and hit escape launches the change event (aka submits) before close.

This other app I have has 2 forms, the app itself is a form, and has a sub-form.

The sub-form doesn't trigger on-change when I hit escape & close the app.

#

Could be to do with my implementation though.

strong stream
#

May also want to try clicking the X instead of hitting Esc and make sure that's captured properly too

austere shadow
#

Good point. Seems like that sub-form has just broken since Dev 1, haven't touched it since then snrk.

#

It seems that the problem has to do with it not scoping to the correct form, I've heard others complain about this

#

gotta love having a breakpoint in _onSubmitForm to debug this issue, as you refresh and foundry kicks you out, so trying to log back into the world hits the breakpoint twice LaughingSor

#

Alright, was on my end due to a change since an earlier v12 build that I hadn't bothered updating yet.

#

even in a sub form it works fine.

austere shadow
#

if I change the value of multi select it won't fire an on-change, so if I close the sheet the details won't be saved. If I edit another field first to fire said on-change, it however would.

#

Some goes for string-tags

#

So if anything it's a nice fall-back mechanism.

eager hill
#

Multi select should emit a change event, so they may sounds like a bug that can be fixed

austere shadow
#

I know I manually fixed it for string-tags in one of my apps

#
for(const stringTags of this.element.querySelectorAll("string-tags")) {
  const refresh = stringTags._refresh.bind(stringTags);
  stringTags._refresh = () => {
    refresh.call();
    this.element.dispatchEvent(new Event("submit", {cancelable: true}));
  }
}
steel dome
#

Probably a silly q, but what is the intended way to assign a change listener to eg a select element?
Is it intended to be done in an overridden _onRender method?

ancient crescent
#

yeah _onRender seems to be the best replacement for activateListeners

#

just use this.element instead of [html]

steel dome
#

Seems like, yeah. Not a lot of examples of it being used yet either.
On a related note, is there a way to add data- properties while using {{formField}}?

vivid beacon
#

attachPartListeners would also be useful, depending on the usecase.

humble sorrel
steel dome
#

Does that translate to hbs?

humble sorrel
#

that's the helper (or part of the callstack of the helper) for formField, so it should just be a matter of {{ formField .... dataset=contextDataset }}

steel dome
#

oh dear.

humble sorrel
#

you see value is contained there, which should look familiar: {{formField fields.groups value=source.groups}}

#

oh, no, just an object 🤣

#

like value

steel dome
#

lol ofc

#

yep, that is indeed all.

humble sorrel
#

dont be afraid to make formfield overrides for your data models 😎 its pretty neat -- that fields.groups is a datamodel that renders to a radio select

steel dome
#

currently neckdeep in getting rid of all appv1 instances and making custom effect application methods. This dialog im on atm has a datamodel in the background tho. Could probably just whip it into rendering itself tho tbqh

steel dome
#

Just, like, the most minor nitpick and not to quote Bruce Almighty here but are label.checkbox input elements... bigger in app v2?
With and without the .application class on the form shown here, seems like the checkbox is too big for its own container.

vernal sand
# kindred minnow Interesting expirement, what legal issues would I run into making demos of AppV2...

Obligatory not a lawyer, but at minimum you need to make sure the license of whatever you're trying to recreate as a demo allows it. For instance, an MIT licensed project would typically allow it as long as you follow the copyright notice requirements in the license, but even then a given sheet might include icons/assets that aren't part of the MIT license and would need to be replaced with alternatives.

kindred minnow
#

As I wouldn't be redistrubting any assets other then basically a .vue version of their .hbs file. I wouldn't be to worried about that, however, technically I would basically be releasing their .hbs file in pretty mucha 1 to 1 format with only slight modifications for .vue format.

vernal sand
#

Ah, gotcha, I misread that. Client side code from core has typically been used pretty liberally in modules/systems, but it's not open source or anything so I'll defer to staff on that.

austere shadow
#

Currently ApplicationV2#_updatePosition cannot take {height: "auto", width: "auto"} simultaneously, as when height is set to auto, the height property gets set to bounds.height and then during the implicit width conversion it instead gets set to bounds.height instead of being left empty.

Is this intentional; or more likely, is there a reason for me not to be using height & width auto on an Application(V2)?

Or is this an oversight?

eager hill
austere shadow
eager hill
#

I suppose the default JS behavior would be to expand horizontally first and then expand vertically once using additional horizontal width is no longer possible.

#

I'm not sure what type of app you would want that behavior for.

outer hound
#

I'm not sure what type of app you would want that behavior for.
I'm not sure that question should be asked. We've seen all kinds of whacky stuff. Who knows what people come up with

eager hill
outer hound
#

Then maybe I simply misinterpreted your statement/intent. Apologies

steel dome
#

Does AppV2 have a configuration somewhere to allow for more header buttons to appear in the regular header before it starts placing them in the collapsed menu?
Secondly, how do you make said menu appear correctly

ancient crescent