Skip to content

Adding a custom widget

Available since: brezel-spa@2.0.0

Custom widgets are a powerful way to extend the functionality of your Brezel instance. They can be used to display custom data, interact with external services, or provide additional functionality to your users.

Creating a custom widget

There are two ways to create a custom widget, and you need to choose the one that best fits your use case:

  1. Integrated Vue component: If you want to create a custom widget that is tightly integrated with your SPA. More performant on initial load and more fun to develop (hot module replacement etc.). This is the recommended way.
  2. Editable Vue widget: If you want to create a custom widget that can be edited in the interface by users such as admins. This has a more restrictive API and is less performant on initial load.

Integrated Vue component

To create an integrated Vue component, you need to create a new Vue component in your SPA repository, in your src/components directory.

  1. Create your component in src/components/MyWidget.vue:
src/components/MyWidget.vue
<script lang="ts">
import { defineComponent, type PropType } from 'vue'
import type { Product } from '@/types/modules/Products'
import { Module } from '@kibro/brezel-spa'
export default defineComponent({
name: 'MyWidget',
props: {
entity: {
type: Object as PropType<Product>,
required: true,
},
},
data () {
return {
products: [] as Product[],
}
},
mounted () {
this.fetchProducts()
},
methods: {
async fetchProducts () {
this.products = await Module.byIdentifier('products').getEntities()
},
},
})
</script>
<template>
<div class="my-widget">
Product: {{ entity.name }}
</div>
</template>
<style scoped lang="scss">
.my-widget {
}
</style>
  1. Register the component in your src/main.ts file:
src/main.ts
import { Brezel } from '@kibro/brezel-spa'
import App from './App.vue'
import MyWidget from './components/MyWidget.vue'
const brezel = new Brezel(import.meta.env.VITE_APP_API_URL, import.meta.env.VITE_APP_SYSTEM)
const app = brezel.bootstrap(App)
app.component('MyWidget', MyWidget)
export default app.mount('#app')
  1. Use your widget in your layout:
{
"tabs": [
{
"rows": [
{
"cols": [
{
"components": [
{
"type": "headline",
"options": {
"identifier": "product",
"icon": "mdi:format-list-bulleted"
}
},
{
"type": "widget",
"options": {
"widget": "custom",
"component": "MyWidget"
}
}
]
}
]
}
]
}
]
}

That’s it! Your custom widget is now available in your layout.

Import Generated Types

You can leverage Brezel’s generated types to get type safety and autocompletion in your custom widget. To do this, you need to enable the type generation in your systems/<system>/system.json file:

{
"bakery": {
"apply": {
"generate-types": true
}
}
}

Then you need to tell TypeScript where to find the types:

{
...
"compilerOptions": {
...
"paths": {
"@/types/*": [
"./src/types/*"
]
}
}
}

Editable Vue widget

To create an editable Vue widget, you need to create a new Vue component in your system directory, preferably under systems/<system>/widgets.

  1. Create your component in systems/<system>/views/widgets/MyWidget.vue:
<template>
<div class="my-widget">
</div>
</template>
<script>
import Module from 'module'
export default {
data () {
return {
products: []
}
},
mounted () {
this.fetchProducts()
},
methods: {
async fetchProducts () {
this.products = await Module.byIdentifier('products').getEntities()
},
},
}
</script>
<style scoped>
</style>
  1. Register the widget in your systems/<system>/widgets.bake.json file:
[
{
"resource_view": "my_widget",
"depends_on": "resource_module.views",
"resource": {
"module": "views",
"client": "${resource_client.global}",
"fields": {
"name": "MyWidget.widget",
"code": "{% verbatim %}\n${file('/views/widgets/MyWidget.vue')}\n{% endverbatim %}"
}
}
}
]
  1. Use your widget in your layout:
{
"tabs": [
{
"rows": [
{
"cols": [
{
"components": [
{
"type": "headline",
"options": {
"identifier": "product",
"icon": "mdi:format-list-bulleted"
}
},
{
"type": "widget",
"options": {
"widget": "custom",
"view": "MyWidget.widget"
}
}
]
}
]
}
]
}
]
}

That’s it! Your custom widget is now available in your layout.

Updating entity fields

Vue prohibits directly mutating props, so you need to emit an event to update the entity fields.

You must emit the event “event” with an object that contains the _type property set to “update-entity”, the field you want to update, and the new value.

<script lang="ts">
import { defineComponent, type PropType } from 'vue'
import type { Product } from '@/types/modules/Products'
export default defineComponent({
name: 'MyWidget',
props: {
entity: {
type: Object as PropType<Product>,
required: true,
},
},
emits: ['event'],
methods: {
updatePrice (value: number) {
this.$emit('event', {
_type: 'update-entity',
field: 'price',
value,
})
},
},
})
</script>
<template>
<div class="my-widget">
Product: {{ entity.name }}
<button @click="updatePrice(100)">Set price to 100</button>
</div>
</template>
<style scoped lang="scss">
.my-widget {
}
</style>