# Custom Events I was fixing a bug in [Buttons](https://github.com/shabegom/buttons) and ran into an issue that required a custom event. This is how I fixed the problem. This might not be the best solution, but it worked for me! ## The Problem Inline Buttons are rendered using a `registerMarkdownPostProcessor`. The post processor looks for codeblocks in the note that start with the word `button`. It then uses the button's ID to look up the parent button in a store. The store is indexed when Obsidian is first loaded. The problem is that the post processor was trying to render the inline button before the store had finished indexing. ## The Janky Solution My first solution was to wrap the rendering of the inline button in a `setTimeout`. This delayed the store lookup until after the indexing had completed. I shipped this initially, but realized the time it takes to index can really depend on how many buttons someone has. It also feels like a janky hack and not an elegant solution. I took to the OMG #plugin-development Discord channel and @javalent pointed my towards using an event. ## Events Class This Obsidian API exposes and [Events](https://github.com/obsidianmd/obsidian-api/blob/bde556afa033e909ebfb9fcee8f5ef288276f78f/obsidian.d.ts#L859) class that can be extended to add custom events. You'll notice that many other parts of the API are already extending Events for their own event handlers. Events is a pretty simple class that has everything needed to trigger a custom event: ```javascript export class Events { on(name: string, callback: (...data: any) => any, ctx?: any): EventRef; off(name: string, callback: (...data: any) => any): void; offref(ref: EventRef): void; trigger(name: string, ...data: any[]): void; tryTrigger(evt: EventRef, args: any[]): void; } ``` ## Adding an `index-complete` Event If I needed to add anything custom to the `Events` class I can extend it: ```javascript export class StoreEvents extends Events { constructor() { super(); } } ``` Because I just need the methods in `Events` itself and don't want to add anything custom, I'm going to instantiate `Events` directly: ```js private storeEvents = new Events(); private storeEventsRef: EventRef ``` `StoreEventsRef` is going to be our reference to the event listener so that we can turn it off if the plugin is unloaded. I passed `storeEvents` to my `initializeButtonStore` function: ```js initializeButtonStore(this.app, this.storeEvents); ``` and triggered the `index-complete` event when `initializeButtonStore` had finished indexing: ```js localStorage.setItem("buttons", JSON.stringify(blocksArr)); buttonStore = blocksArr; storeEvents.trigger('index-complete') ``` From here I just needed to setup my event listener to listen for an `index-complete` event and then render my inline button only if indexing had finished: ```js this.storeEventsRef = this.storeEvents.on('index-complete', async () => { const args = await getButtonById(this.app, id); if (args) { ctx.addChild(new InlineButton(codeblock, this.app, args, id)) } }) ``` To clean up properly, I needed to remove the event listener inside my `onunload`: ```js onunload(): void { this.storeEvents.offref(this.storeEventsRef); } ``` ## One Problem There's one issue with my code. The inline button now renders beautifully on first loading of the vault, but if I navigate away from and back to the note with an inline button...it doesn't render! This is because I'm waiting for 'index-complete' to trigger the rendering. My simple fix was to add a `indexComplete` boolean and wrap the whole thing in an `if` statement: ```js if (!this.indexComplete) { this.storeEventsRef = this.storeEvents.on('index-complete', async () => { this.indexComplete = true; const args = await getButtonById(this.app, id); if (args) { ctx.addChild(new InlineButton(codeblock, this.app, args, id)) } }) } else { const args = await getButtonById(this.app, id); if (args) { ctx.addChild(new InlineButton(codeblock, this.app, args, id)) } } ``` I'll probably go back and remove the duplication of code here, but it runs and does what I wanted, so good enough. Ship it! ## Conclusion So this is how I created a custom Event and used it to know when it was safe to render my Inline Buttons. What did I do wrong? How could it be better? What is still confusing? Let me know in the OMG discord.