Custom Components

Last updated 6 months ago

Child Components

In simple applications, the window consists of just one component. However, when the window becomes complex, it may be a good idea to split it into a few separate child components. For example:

<template>
<Window title="Example" width="640" height="480" margined>
<Box padded>
<WindowHeader/>
<WindowFooter/>
</Box>
</Window>
</template>
<script>
import WindowHeader from './WindowHeader'
import WindowFooter from './WindowFooter'
export default {
components: {
WindowHeader,
WindowFooter
}
}
</script>

This creates a window component which contains two child components, one under another.

The child components must be imported and declared using the components option of the parent component. Custom components declared like this can be used just like built-in components.

The root element of a child component can be a container, or even a single widget. For example:

<template>
<Box padded>
<Button>OK</Button>
<Button>Cancel</Button>
</Box>
</template>

Component Registration

Some components are used in many different places in the application, for example in different windows. Instead of importing them into every parent component that needs to use them, you can declare a global component.

This can be done by calling Vue.component() in the main script of the application:

main.js
import libui from 'libui-node'
import Vue from 'vuido'
import ButtonGroup from './ButtonGroup'
import MainWindow from './MainWindow'
Vue.component( 'ButtonGroup', ButtonGroup );
const window = new Vue( {
render: h => h( MainWindow )
} );
window.$mount();
libui.startLoop();

Now you can place the custom ButtonGroup component inside any other component.

See also Component Registration in the Vue.js documentation for more information.

Properties

You can use properties to pass data from the parent component to the child component. For example, let's define a custom component like this:

ButtonGroup.vue
<template>
<Box padded>
<Button>{{ okText }}</Button>
<Button>{{ cancelText }}</Button>
</Box>
</template>
<script>
export default {
props: {
okText: String,
cancelText: String
}
}
</script>

The props section specifies that this component has two properties, okText and cancelText, and their values must be strings. You can use other JavaScript types, like Boolean, Numeric, Array or Object to validate the property.

Now you can use the ButtonGroup component like this:

<ButtonGroup ok-text="OK" cancel-text="Cancel"/>

Note that the kebab-cased names of attributes are mapped to the corresponding camelCased names of properties.

A property can have a default value:

props: {
okText: { type: String, default: 'OK' },
cancelText: { type: String, default: 'Cancel' }
}

It also can be marked as required:

props: {
items: { type: Array, required: true }
}

You can pass a dynamic value to a property using v-bind:

<ButtonGroup v-bind:ok-text="okText" v-bind:cancel-text="cancelText"/>

When using a boolean or numeric property, you must use the v-bind directive even if you pass a constant value. Otherwise the value would be interpreted as a string. For example:

<BlogPost v-bind:likes="42" v-bind:is-published="true"/>

You can also omit the value to pass true to a boolean property:

<BlogPost is-published/>

See also Props in the Vue.js documentation for more information.

Custom Events

Child components can use custom events to notify their parent:

ButtonGroup.vue
<template>
<Box padded>
<Button v-on:click="okClick">OK</Button>
<Button v-on:click="cancelClick">Cancel</Button>
</Box>
</template>
<script>
export default {
methods: {
okClick() {
this.$emit( 'ok-click' );
},
cancelClick() {
this.$emit( 'cancel-click' );
}
}
}
</script>

The parent component can listen to these events using the v-on directive:

<ButtonGroup v-on:ok-click="accept" v-on:cancel-click="reject"/>

You can also use events to pass data to the parent component:

NameInput.vue
<template>
<Box>
<Text>Enter your name:</Text>
<TextInput v-bind:value="value" v-on:input="emitInput"/>
<Box>
</template>
<script>
export default {
props: {
value: String
},
methods: {
emitInput( newValue ) {
this.$emit( 'input', newValue );
}
}
}
</script>

The parent component can use the following code to update the value of its data property:

<NameInput v-bind:value="name" v-on:input="name = $event"/>

Note that the child component cannot simply modify the value property, because that will not automatically update the data in the parent component.

The v-model directive can also be used with custom components. By default, it binds the value property and the input event to the specified property of the parent component, so the above code is equivalent to:

<NameInput v-model="name"/>

When your window contains a complex hierarchy of custom components, passing data around using properties and events may become difficult to manage. In such case consider using a state management library, such as Vuex.

See also Custom Events in the Vue.js documentation for more information.

Slots

You can use slots to pass contents from the parent component to a child component. For example, let's assume that we have this simple child component:

NavigationButton.vue
<template>
<Button v-on:click="navigate">
<slot></slot>
<Button>
<template>

We can use it like this:

<NavigationButton>About</NavigationButton>

The "About" text is inserted in place of the <slot> element. The slot contents can include not just text, but also other elements, including built-in components and custom components.

See also Slots in the Vue.js documentation for more information.

Dynamic Components

The special <component> element can be used to dynamically switch between components. For example, you can replace the entire contents of a window without having to close it and create a new window:

<template>
<Window title="Example" width="640" height="480" margined>
<component v-bind:is="currentComponent"/>
</Window>
</template>

The currentComponent property can contain either the name of a registered component, or the options object which defines a component.

See also Dynamic Components in the Vue.js documentation for more information.