Mod Creation/Reusable Components with PetiteVue: Difference between revisions

From Melvor Idle
(Created page with "Melvor Idle ships with [https://github.com/vuejs/petite-vue PetiteVue] for mods to use to create reusable HTML components. The documentation from the [https://github.com/vuejs/petite-vue official GitHub page] in addition to the [https://vuejs.org/guide/introduction.html full Vue.js documentation] (for clarity on definitions and what the PetiteVue directives do - there are many full Vue.js features that are unavailable) may assist in using the PetiteVue library. However,...")
(No difference)

Revision as of 03:49, 17 October 2022

Melvor Idle ships with PetiteVue for mods to use to create reusable HTML components. The documentation from the official GitHub page in addition to the full Vue.js documentation (for clarity on definitions and what the PetiteVue directives do - there are many full Vue.js features that are unavailable) may assist in using the PetiteVue library. However, there are also helper functions for making it easier for mods to interact with PetiteVue.

Helper Functions

These are the functions provided by Melvor Idle to interact with PetiteVue. For the sake of avoiding edge cases and oddities surrounding how mods are loaded, you should use these instead of interacting with the PetiteVue global object directly.

ui.create(props: ComponentProps, host: HTMLElement): HTMLElement

Creates an instance of a component and mounts it within the HTML.

Parameters

props: ComponentProps The PetiteVue component function that you want to instantiate.

host: HTMLElement The element that the component should be appended to.

Returns

HTMLElement The host element.

Example

<!-- templates.html -->
<template id="counter-component">
  <span class="text-light">{{ count }}</span>
  <button class="btn btn-secondary" @click="inc">+</button>
</template>
// manifest.json
{
  "load": ["templates.html"]
}
// setup.mjs
function Counter(props) {
  return {
    $template: '#counter-component',
    count: props.count,
    inc() {
      this.count++;
    }
  };
}

export function setup({ onInterfaceReady }) {
  onInterfaceReady(() => {
    // Create and append a Counter component to the bottom of the Woodcutting page
    ui.create(Counter({ count: 0 }), document.getElementById('woodcutting-container'));
  });
}

ui.createStore(props: Record<string, unknown>): ComponentStore

Creates a PetiteVue store for sharing state amongst components.

Parameters

props: Record<string, unknown> The props that the store should contain.

Returns

ComponentStore The PetiteVue store that can be shared between components.

Example

In the above example for ui.create, if you created a second Counter component, it would contain its own state and clicking the incrementing button on one would have no effect on the other. By using a store, you can share state in the following way:

<!-- templates.html -->
<template id="counter-component-using-store">
  <span class="text-light">{{ store.count }}</span>
  <button class="btn btn-secondary" @click="store.inc">+</button>
</template>
// manifest.json
{
  "load": ["templates.html"]
}
// setup.mjs
function CounterUsingStore({ store }) {
  return {
    $template: '#counter-component-using-store',
    store
  };
}

export function setup({ onInterfaceReady }) {
  onInterfaceReady(() => {
    const store = ui.createStore({
      count: 0,
      inc() {
        this.count++;
      }
    });

    // Create and append a CounterUsingStore component to the bottom of the Woodcutting page
    ui.create(CounterUsingStore({ store }), document.getElementById('woodcutting-container'));
    // Create and append another CounterUsingStore component to the bottom of the Firemaking page
    ui.create(CounterUsingStore({ store }), document.getElementById('firemaking-container'));
  });
}

Now in this example, both the counter on the Woodcutting page and the Firemaking page should stay in sync with the current count.

ui.createStatic(template: string, host: HTMLElement): HTMLElement

Creates an instance of a static component (no PetiteVue bindings) and mounts it within the HTML. This helper function doesn't use PetiteVue but should be preferred if you only need to create a reusable static piece of HTML.

Parameters

template: string The selector string for the template you want to clone. For example, to target <template id="static-component"></template>, you would use '#static-component'.

host: HTMLElement The element that the component should be appended to.

Returns

HTMLElement The host element.

Example

<!-- static-templates.html -->
<template id="my-static-component">
  <h3>Hello, this is static HTML</h3>
</template>
// manifest.json
{
  "load": ["static-templates.html"]
}
// setup.mjs
export function setup({ onInterfaceReady }) {
  onInterfaceReady(() => {
    // Create the static component and place it at the bottom of the Woodcutting page
    ui.createStatic('#my-static-component', document.getElementById('woodcutting-container'));
  });
}

Nesting Static Components

In order to nest static components, child component templates need to be referenced by using a s-template attribute on the host element.

For example, given the following templates:

<!-- static-templates.html -->
<template id="static-parent">
  <h3>Hello, this is static HTML from the parent</h3>
  <div s-template="#static-child"></div>
</template>

<template id="static-child">
  <p>And this HTML is from a static child.</p>
</template>

You could create the parent component using the following:

// setup.mjs
export function setup({ onInterfaceReady }) {
  onInterfaceReady(() => {
    ui.createStatic('#static-parent', document.getElementById('woodcutting-container'));
  });
}

Which results in the following HTML being appended to the bottom of the Woodcutting page:

<h3>Hello, this is static HTML from the parent</h3>
<div>
  <p>And this HTML is from a static child.</p>
</div>

Useful Patterns

Nesting Components

PetiteVue components may be nested to create larger reusable components. This pattern, likely combined with a PetiteVue store, can be followed all the way to creating the entire UI for your mod in a single parent component (which would be preferred, rather than calling ui.create many times).

Consider the following templates:

<!-- templates.html -->
<template id="block-component">
  <div class="block>
    <div class="block-header" v-scope="BlockHeader(headerProps)"></div>
    <div class="block-content" v-scope="BlockContent(contentProps)"></div>
  </div>
</template>

<template id="block-header">
  <h3 class="block-title>{{ title }}</h3>
</template>

<template id="block-content">
  <p v-for="line in lines">{{ line }}</p>
</template>

And defined components:

function Block(props) {
  return {
    $template: '#block-component',
    BlockHeader,
    BlockContent,
    headerProps: props.header,
    contentProps: props.content
  };
}

function BlockHeader(props) {
  return {
    $template: '#block-header',
    title: props.title
  };
}

function BlockContent(props) {
  return {
    $template: '#block-content',
    lines: props.lines
  };
}

A complete block component can be created with the following:

ui.create(Block({
  header: { title: 'My Block Component' },
  content: { lines: ['My first paragraph.', 'My second paragraph.'] }
}), document.getElementById('woodcutting-container'));