Mod Creation/Reusable Components with PetiteVue: Difference between revisions
Buttchouda (talk | contribs) |
m (Remove extra paren to fix syntax of line 16.) Tag: visualeditor |
||
(One intermediate revision by one other user not shown) | |||
Line 132: | Line 132: | ||
"load": ["static-templates.html"] | "load": ["static-templates.html"] | ||
}</syntaxhighlight> | }</syntaxhighlight> | ||
<small>''Comments in JSON are purely illustrative and not valid markup''</small> | |||
<syntaxhighlight lang="js" line>// setup.mjs | <syntaxhighlight lang="js" line>// setup.mjs | ||
Line 243: | Line 245: | ||
</template></syntaxhighlight> | </template></syntaxhighlight> | ||
<syntaxhighlight lang="js" line>// setup.mjs | <syntaxhighlight lang="js" line="1">// setup.mjs | ||
function Counter(props) { | function Counter(props) { | ||
return { | return { | ||
Line 258: | Line 260: | ||
// Save a reference here | // Save a reference here | ||
const counter = Counter({ count: 0 }); | const counter = Counter({ count: 0 }); | ||
ui.create(counter | ui.create(counter, document.getElementById('woodcutting-container')); | ||
// Manipulate here to reflect changes in the UI | // Manipulate here to reflect changes in the UI |
Latest revision as of 20:17, 1 November 2024
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"]
}
Comments in JSON are purely illustrative and not valid markup
// 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"]
}
Comments in JSON are purely illustrative and not valid markup
// 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"]
}
Comments in JSON are purely illustrative and not valid markup
// 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'));
Programmatically Manipulating Components
If you need to programmatically manipulate a component's (or store's) state, save the reference to the props
object being passed into ui.create
. The state should only be manipulated through methods on the object, not directly setting properties.
For example, using our Counter
from above:
<!-- templates.html -->
<template id="counter-component">
<span class="text-light">{{ count }}</span>
<button class="btn btn-secondary" @click="inc">+</button>
</template>
// setup.mjs
function Counter(props) {
return {
$template: '#counter-component',
count: props.count,
inc() {
this.count++;
}
};
}
export function setup({ onInterfaceReady }) {
onInterfaceReady(() => {
// Save a reference here
const counter = Counter({ count: 0 });
ui.create(counter, document.getElementById('woodcutting-container'));
// Manipulate here to reflect changes in the UI
// BAD: counter.count++;
// GOOD:
counter.inc();
});
}
PetiteVue Quick Reference
This is not an exhaustive rundown of PetiteVue features, but these are likely the most common to be used and examples of each.
Text Bindings
Render text within HTML using the double-curly braces notation {{ }}
.
Example
<template id="binding-example"><h1>{{ text }}</h1></template>
function BindingExample(props) {
return {
$template: '#binding-example',
text: props.text
};
}
ui.create(BindingExample({ text: 'Hello, Melvor!' }), host);
// -> <h1>Hello, Melvor!</h1>
Attribute Binding
Bind an attribute to props using v-bind
directive, or :
for short.
Example
<template id="attr-binding-example">
<span v-bind:class="`text-${(warning ? 'warning' : 'info')}`">
This message could be a warning or informational.
</span>
</template>
This notation accomplishes the same:
<template id="attr-binding-example">
<span :class="`text-${(warning ? 'warning' : 'info')}`">
This message could be a warning or informational.
</span>
</template>
Event Binding/Handling
Bind event handlers using the v-on
directive, or @
for short.
Example
<template id="event-binding-example">
<button v-on:click="onClick">Click Me!</button>
</template>
This notation accomplishes the same:
<template id="event-binding-example">
<button @click="onClick">Click Me!</button>
</template>
And would be used in the component like:
function EventBindingExample() {
return {
$template: '#event-binding-template',
onClick() {
alert('You clicked me!');
}
};
}
Input Value Binding
Input values can be bound using the v-model
directive.
Example
<template id="input-binding-example">
<input v-model="value" />
</template>
function InputBindingExample(props) {
return {
value: props.initialValue,
setValue(val) {
this.input = val;
}
};
}
const input = InputBindingExample({ initialValue: 'this is the initial value' });
ui.create(input, host);
// -> <input value="this is the initial value" />
input.setValue('now this value');
// -> <input value="now this value" />
// Assume the player changes the input in the UI to "new value"
console.log(input.value); // -> "new value"
Conditional Rendering
You can conditionally render elements using the v-if
, v-else
, and v-else-if
directives.
Example
<template id="conditional-example">
<span v-if="value % 15 === 0">FizzBuzz</span>
<span v-else-if="value % 3 === 0">Fizz</span>
<span v-else-if="value % 5 === 0">Buzz</span>
<span v-else>{{ value }}</span>
</template>
function ConditionalExample(props) {
return {
$template: 'conditional-example',
value: props.value
};
}
ui.create(ConditionalExample({ value: 6 }), host);
// -> <span>Fizz</span>
Melvor Idle version v1.3.1 (Released: 30th October 2024) |
---|
Error creating thumbnail: File missing Combat: Error creating thumbnail: File missing Attack • Error creating thumbnail: File missing Strength • Error creating thumbnail: File missing Defence • Error creating thumbnail: File missing Hitpoints • Error creating thumbnail: File missing Ranged • Error creating thumbnail: File missing Magic • Error creating thumbnail: File missing Prayer • Error creating thumbnail: File missing Slayer • Error creating thumbnail: File missing Corruption
|
Skills: Error creating thumbnail: File missing Farming • Error creating thumbnail: File missing Township • Error creating thumbnail: File missing Woodcutting • Error creating thumbnail: File missing Fishing • Error creating thumbnail: File missing Firemaking • Error creating thumbnail: File missing Cooking • Error creating thumbnail: File missing Mining • Error creating thumbnail: File missing Smithing • Error creating thumbnail: File missing Thieving • Error creating thumbnail: File missing Fletching • Error creating thumbnail: File missing Crafting • Error creating thumbnail: File missing Runecrafting • Error creating thumbnail: File missing Herblore • Error creating thumbnail: File missing Agility • Error creating thumbnail: File missing Summoning • Error creating thumbnail: File missing Astrology • Error creating thumbnail: File missing Alternative Magic • Error creating thumbnail: File missing Cartography • Error creating thumbnail: File missing Archaeology • Error creating thumbnail: File missing Harvesting
|
Other: Error creating thumbnail: File missing Beginners Guide • Guides • Error creating thumbnail: File missing Bank • Error creating thumbnail: File missing Combat • Error creating thumbnail: File missing Mastery • Error creating thumbnail: File missing Money Making • Error creating thumbnail: File missing Shop • Easter Eggs • Pets • Error creating thumbnail: File missing Golbin Raid • Error creating thumbnail: File missing Full Version • Throne of the Herald • Atlas of Discovery • Error creating thumbnail: File missing Into the Abyss
|
Reference Tables: Items, Equipment, Experience Table, Upgrading Items, Combat Areas, Slayer Areas, Dungeons, Strongholds, The Abyss, Monsters |