Skip to main content

Developer guide

In this document is described some core concepts and how to use them.

Application structure​

Main entry​

The main.ts is the entry point of the application, whereas App.vue is the root component.

The App.vue component uses the <QoreAppWrapper />. This component is responsible for making features like dialogs, notifications etc. work globally. It also makes sure the globals are loaded when the user is authenticated, and will show a loader until everything is ready.

Layouts & App config​

By default, the layouts directory contains a MainLayout.vue and AuthLayout.vue. The MainLayout.vue, alongside other app configs, are passed on to the qore Vue plugin in main.ts:

const appConfig = new AppConfig(router, locale.global, import('@/layouts/MainLayout.vue'))
app.use(qorePlugin, appConfig)

This way, Qore will be aware of what the app is using, so it can extend it with its own features (for example appending translations).

PrimeVue​

The Qore Vue plugin will use the PrimeVue vue plugin, and register all its components globally. This means you can use PrimeVue components in your components without importing them (e.g. <Button>, <InputText> etc.).

For documentation, visit: https://primevue.org/

PassThrough props​

A common pattern in PrimeVue is to use the pt prop. This is a neat way to pass on props to the underlying component. For example, to pass on a class prop to a Button component:

<Button :pt="{ root: { class: 'p-2' } }" />

Icons​

PrimeVue supports a set of icons by default. You can add icons simply by adding:

<i class="pi pi-check"></i>

If you would like to use material icons instead, use:

<i class="material-icons">{{ iconName }}}</i>

More information can be found on their docs.

Pinia store​

Inside Qore, the following stores exist:

  • api Responsible for storing the most recent responses (e.g. validation messages) and showing backend dd error dialogs
  • auth Responsible for handling the authentication state (e.g. storing the auth user and authentication state)
  • globals Responsible for storing the menu, tenants, resources, modules and plugins
  • ui Responsible for theming (e.g. sidebar width, topbar height, dark mode etc.) and showing global dialogs/notifications

Some state will be stored in local storage, so it will be available after a page refresh.

Using the store​

Each store is made globally available in components for easy access in the $q object. For example, to access the auth store:

<Button
:label="$q.auth.user?.name"
/>

Similar to Quasar, you can also use it for media queries:

<AppLayoutConfig v-if="$q.ui.screen.gt.md" />

If you want to access the store inside composition functions, you can use the useStore function:

import { useAuthStore } from 'qore'

const authStore = useAuthStore()

console.log(authStore.user)

Adding or customizing stores​

Inside boot/store.ts you can add or extend stores. For example, to extend the auth store:

pinia.use(({ store }) => {
if (store.$id === 'auth') {
store.hello = 'world'
}
})

// Then later in components:
import { useAuthStore } from 'qore'
const auth = useAuthStore()
console.log(auth.hello)

For full documentation please visit: https://pinia.vuejs.org/introduction.html

I18n​

Translation messages will be loaded inside boot/locale.ts

Translations​

You can use translations in components by using the $t helper:

<Button
:label="$t('Send')"
/>

If you want to use translations inside composition functions, you can use the translate function:

import { translate } from 'qore'

console.log(translate('auth.Logout'))

Fields​

Fields can, like in front-end v1, be registered globally (e.g. in components.ts).

General usage​

Fields work a bit different compared to v1:

  • It is now compatible with v-model (and v-model is mandatory), so you are not required to listen to @setState event anymore
  • Each field expects a (required) config (type FieldConfig | object) prop, which is a JSON object that describes the field (e.g. name, label, tooltip etc.)
  • Each field accepts an optional formState (type FormState | object) prop, which is a JSON object that describes the form state (e.g. required fields, validation messages etc.)

Using fields outside forms​

A common frustration in front-end v1 was that fields could only be used inside forms. This is no longer the case. Below is an example of how to use a field outside a form.

<template>
<div :class="`flex flex-col gap-${$q.ui.fieldsGap}`">
<TextField
v-model="formState.values.name"
:formState="formState"
:config="nameFieldConfig"
/>

<BelongsToManyField
v-model="formState.values.hasMany"
:formState="formState"
:config="{
name: 'hasMany',
label: 'Has Many',
componentProperties: { multiple: true, options: hasManyOptions }
}"
/>

<pre class="mt-4">{{ formState.values }}</pre>
</div>
</template>
import { ref } from 'vue'
import { FieldConfig, FormState, translate } from 'qore'

// Define formstate
const formState = ref(new FormState<{ name: string; hasMany: Array<string> }>())

// Config for name field
const nameFieldConfig = ref<FieldConfig>(new FieldConfig('name'))
nameFieldConfig.value.label = translate('Name')
nameFieldConfig.value.toolTip = translate('Hello from tooltip!')
nameFieldConfig.value.hint = translate('Hello from hint!')
nameFieldConfig.value.attributes = {
autofocus: true,
autocomplete: 'username'
}

// Setting the formstate values
formState.value.values = {
name: 'Koen',
hasMany: ['one', 'two']
}

// Mark the name as required
formState.value.meta.required = ['name']

// The options for hasMany field
const hasManyOptions = [
{
label: 'One',
value: 'one'
},
{
label: 'Two',
value: 'two'
},
{
label: 'Three',
value: 'three'
}
]
info

For a working example that communicates with the back-end, you might take a look at views/auth/LoginView.vue

Creating a form field​

To create a custom form field, you can use the following example. Each field should be wrapped inside the FieldWrapper component, which will take care of the form state, validation messages, label etc.

<template>
<FieldWrapper :config="config" :formState="formState">
<InputText
v-model="localValue"
@input="$emit('update:modelValue', localValue)"
v-bind="inputProps"
v-focus="config.autofocus"
/>
</FieldWrapper>
</template>

<script setup lang="ts">
import { FieldConfig, FormState, useField } from 'qore'

const props = defineProps<{
modelValue: string | null
config: FieldConfig
formState?: FormState
}>()

const emit = defineEmits(['update:modelValue'])

const { localValue, inputProps } = useField(props, emit)
</script>

The useField exposes multiple props that might be useful for your custom field:

{
localValue,
inputProps,
fieldMeta,
fieldData,
callAction,
fieldHasValidationMessages,
fieldValidationMessages,
fieldFirstValidationMessage
}

If your field will use options (e.g. for a Dropdown), you might also want to use:

import { useFieldSelectOptions } from 'qore'
const { selectInputProps } = useFieldSelectOptions(props.config) // Contains the options, filter etc.

Notify & Dialog & Confirm​

Qore exposes helper functions to show notifications and dialogs via composition api.

Showing a notification​

Below is an example of how to show a notification:

import { notify } from 'qore'
notify({
severity: 'success',
detail: apiStore.lastSuccesfulResponseAsString
})

Showing a dialog​

The BaseDialog component can be used to show a dialog:

<Button @click="dialog = !dialog">Show base dialog</Button>
<BaseDialog header="Hello from dialog" v-model="dialog">
<p>Hello world</p>
</BaseDialog>

A dialog is usually included inside the component you are working on (see PrimeVue documentation for more information), however you can also do it via the showDialog helper:

import { markRaw } from 'vue'
import { showDialog } from 'qore'
import MyDialogComponent from '@/components/dialogs/MyDialogComponent.vue'
showDialog({
component: markRaw(MyDialogComponent),
options: {
props: {
header: translate('Hello from dialog'),
modal: true,
style: {
width: '90vw'
},
dismissableMask: true
}
}
})

Confirmation popup​

You can show a confirmation popup by using the showConfirmPopup helper:

<Button @click="(e) => showConfirmPopup(e, { accept: () => notify('Confirmed') })">
Show confirm popup
</Button>
import { showConfirmPopup } from 'qore'

API​

V2 will use axios again, however the syntax is a bit different

Doing requests​

You can start doing api requests by using the qore helper:

import { useApi } from 'qore'

const api = useApi()

api.post('reset-password', { email: ''})
.then(() => {

})
.catch(() => {

})

The last validation messages and responses are stored in the api store.

Router​

The router works mostly the same as v1. The router is booted inside boot/router.ts, and the routes exist in routes.ts.

General usage​

To navigate to a route:

import { useRouter } from 'vue-router'

const router = useRouter()

router.push({ name: 'home' })

To get the current route:

import { useRouter } from 'vue-router'

const router = useRouter()

console.log(router.currentRoute.value)

Types​

General usage​

Most types you'd expect can be imported from qore, for example:

import { AuthStatus, type User, type QoreResource, type Tenant, type Model, ... } from 'qore'

defineProps<{
user: User
}>()

Helpers​

General usage​

All helpers from v1 should eventually be available in v2. You can use them like this:

import { onMounted } from 'vue'
import { reloadGlobals } from 'qore'

onMounted(() => {
reloadGlobals()
})

VueUse​

Some helpers however are also already available by the vueuse package, so it is recommended to use those instead. For example:

import { watchDebounced, isObject, useArrayUnique } from '@vueuse/core'

watchDebounced(
() => [formState.value.values.password, formState.value.values.password_confirmation],
validatePassword,
{
debounce: 300
}
)

console.log(isObject(['hello']), useArrayUnique(['hello', 'hello']))

String case change​

For string case change, you can use the @vueuse/integrations package:

import { useChangeCase } from '@vueuse/integrations/useChangeCase'

console.log(useChangeCase('hello_world', 'camelCase').value)

Uuid​

You can generate uuid:

import { v4 as uuidv4 } from 'uuid'

console.log(uuidv4())