What is Panel plugin edits must be initiated from a supported UI event

What is a “supported UI event”? Is it possible to switch off this limitation as it doesn’t make any sense for users (if they don’t like plugin they can always drop it or complain on it) and just make development more tricky?

Case 1. I have a form with submit event handler where I need to save some data into the document but I’m facing the error as form submit seems to be not supported UI event.

<form (ngSubmit)="onSubmit()" #createForm="ngForm">
    <button uxp-variant="cta" type="submit"
	[disabled]="!createForm.form.valid || isCreating">Create</button>
</form>

Case 2. I made workaround and bind write function to the click but it doesn’t work again as I need to await for another operation:

<button uxp-variant="cta" type="submit"
  [disabled]="!createForm.form.valid || isCreating" (click)="onSubmit()">Create</button>
public async onSubmit(): Promise<void> {
try {
  // this works
  await this.service.setRecent({ id: 0, name: "noname" });
  console.log("Faked saved to the document");
} catch (ex) {
  console.log("Failed to save fake", ex);
}

try {
  this.isCreating = true;
  this.bundle.project = await this.api.createProject(newProject);
  try {
    // And this doesn't work
    await this.service.setRecent(this.bundle.project);
    console.log("Real saved to the document"); // never called
  } catch (ex) {
    console.log("Failed to save real", ex);
  }
} catch (ex) {}
}

Try something like

document.querySelector("form").addEventListener("submit", yourFunction);

This should work.

It still doesn’t work as I have to call awaitable function before edit (Case 2 above).

yourFunction can be an async function which has several async functions inside multiple awaits. What error are you seeing?

@stevekwak, here’s the case:

function createModify() {
  const panel = document.createElement("panel");
  let footer = document.createElement("footer");
  panel.appendChild(footer);

  let editButton = document.createElement("button");
  editButton.textContent = "Modify";
  editButton.onclick = async (e) => {
    const value = await doAsync();
    try {
      require("application").editDocument(() => { scenegraph.root.pluginData = { value }; } );    
    } catch (ex) {
      console.log("Failed", ex); // Failed [Error: Panel plugin edits must be initiated from a supported UI event]
    }
  };
  footer.appendChild(editButton);
  return panel;
}

function doAsync() {
  return new Promise((resolve, reject) => {
    setTimeout(() => { resolve("value"); }, 1000);
  });
}

Ah – so edits are allowed only on the same tick as the event that triggered your callback. If you want to do something asynchronous, you need to first call editDocument and then do your async operation so that XD knows to hold the document open longer.

For example:

editButton.onclick = (e) => {
    require("application").editDocument(() => {
        const value = await doAsync();
        try {
          scenegraph.root.pluginData = { value };
        } catch (ex) {
          console.log("Failed", ex); 
        }
    });
}

Thanks for idea, I will try it. As I understand in such usage editDocument must also be awaitable. How it will handle exceptions?

I missed something in that last example – let me update that:

editButton.onclick = (e) => {
    require("application").editDocument(async () => {
        const value = await doAsync();
        try {
          scenegraph.root.pluginData = { value };
        } catch (ex) {
          console.log("Failed", ex); 
        }
    });
}

You can only use editDocument once during a UI gesture. So it should be the last thing in your handler, and all your async logic should be contained within. If you need to catch errors from your async logic, you can use the regular try...catch semantics. Any errors that occur should be propagated back to your UI (how you do that is up to you).

1 Like

Good to know!

But, boy, wish this kind of answer would go directly into the documentation. This is such a murky and tricky area that it needs tons of light shone.

Basically, we need a fairly complete overview of using async code throughout a plugin’s lifecycle.

2 Likes

There was a big push of changes to the docs with XD 21, so it might be in there.

@peterflynn / @schenglooi / @stevekwak , if it isn’t, could you add it to the docs and push a new version?

Fantastic news; thanks very much!

Panel stuff has been in prerelease only so far. That’s why you haven’t seem many materials on it. But! we just pushed panel stuff to the docs site today so feel free to check that out. @peterflynn will post a summary post here soon today

Yes, I’ve been using the pre-release docs for panels.

Where would we find the kind of stuff that @kerrishotts mentions above in the just-released docs?

[…scrambling sounds…]

OK, I see, in Plugin Lifecycle.

It could still use (as Kerri says) some more explicit wording about async behavior in panel show()/hide() entries.

There are docs for editDocument() here:

Incidentally, the original example where it worked as long as it’s not async is a bug – in the next XD patch release, editDocument() will be required 100% of the time for your panel UI event handlers to be able to modify the document, whether async or not.

I suppose editDocument also must be async in order to correctly return execution context and let the code catch exceptions. Otherwise we will get unhandled exceptions in promise within doAsync().

I’m getting this error when setting the sceneNode.pluginData when using my panel.

I have a panel that exports on the panel update() method. So there is no plugin UI event that it originates from. But there is a design view UI event that it originates from.

I get this error intermittently. It might happen when I create a new artboard with the panel open.

1 Like

Hi barryallen1337,

Welcome to the community. That’s a good point. Maybe the restrictions could be extended from:

What is Panel plugin edits must be initiated from a supported UI event

to

What is Panel plugin edits must be initiated from a supported UI event or design view UI event

Unless you are doing copy to clipboard you should be able to edit the plugin data at any time. Make sure you have wrapped the assignment in editDocument() as mentioned above.

Just saw your last line. I’ve had it happen when creating an artboard. I posted about it recently but I’ve fixed it,

If you can post any steps to reproduce it post it there. It might be you just need to wrap it in editDocument.

Panels aren’t allowed to edit the document in response to update(), so the error message you’re seeing is correct. The pluginData field is part of the document – changes to it are Undoable, they mark the document as having unsaved changes, etc.

If you’re looking to store plugin state that doesn’t behave this way, writing a JSON file to the plugin’s data folder is your best bet.