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.

Differences between integrated and editable widgets

-Integrated widgetEditable widget
Importing Moduleimport { Module } from '@kibro/brezel-spa'import Module from 'module'
Importing Apiimport { Api } from '@kibro/brezel-spa'import Api from 'api'
Using componentscomponents: { MyComponent }Available components are predefined

If you want to convert an editable widget to an integrated widget, you can follow these steps:

  1. Move the widget to the src/components directory.

  2. Update the naming of the widget to match the Vue component naming conventions:

    • Rename the file to PascalCase.
    • Rename the component to PascalCase.
    • Update the component registration in src/main.ts to use PascalCase.
  3. Update the imports to use the @kibro/brezel-spa package.

  4. Declare the prop entity and any component you want to use in the widget explicitly.

  5. Update the widget registration in the layout to use the component property instead of the view property.

  6. Update the widget registration in the systems/<system>/widgets.bake.json file (or any other file where the widget is registered) to remove the widget registration.

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>