Vue

Die Komponenten Schritt für Schritt in eine Vue-3- oder Nuxt-Anwendung einbauen.

Vue 3 kommt von Haus aus mit Custom Elements zurecht: es setzt DOM-Properties, Objekte und Arrays brauchen also keinen Wrapper. Bekannt sein müssen ihm nur die Tags, die Custom Elements sind, sonst warnt der Compiler über unbekannte Komponenten.

1. Installieren

npm i tuix-webcomponents

2. Dem Compiler die Tags bekannt machen

Jedes Tag beginnt mit tuix-, eine Regel deckt also die ganze Bibliothek ab.

vite.config.ts
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
 
export default defineConfig({
    plugins: [
        vue({
            template: {
                compilerOptions: {
                    isCustomElement: (tag) => tag.startsWith('tuix-'),
                },
            },
        }),
    ],
})

3. Elemente registrieren

Die Bibliothek einmal dort importieren, wo die App erzeugt wird:

src/main.ts
import { createApp } from 'vue'
import 'tuix-webcomponents'
import 'tuix-webcomponents/dist/assets/tuix-styles.css'
import App from './App.vue'
 
createApp(App).mount('#app')

Das Stylesheet holt zugleich Rubik vom Tuix-CDN.

In Nuxt muss der Import auf dem Client bleiben, denn die Elemente erweitern HTMLElement, das es beim Server-Rendern nicht gibt:

plugins/tuix.client.ts
export default defineNuxtPlugin(async () => {
    await import('tuix-webcomponents')
})

Die Endung .client hält es aus dem Server-Bundle heraus. Das server-gerenderte Markup enthält die Tags weiterhin; sie upgraden, sobald das Plugin läuft.

4. Verwenden

src/components/InvoiceForm.vue
<script setup lang="ts">
import { computed, ref } from 'vue'
import { DescriptionType, IconName, MainVariant } from 'tuix-webcomponents'
 
const countries = [
    { name: 'Schweiz', value: 'CH' },
    { name: 'Deutschland', value: 'DE' },
]
 
const name = ref('')
const country = ref<string>()
const error = ref<string>()
 
const selected = computed(() => countries.find((option) => option.value === country.value))
 
const onNameChange = (event: CustomEvent<{ value: string }>) => {
    name.value = event.detail.value
    error.value = undefined
}
 
const onSelect = (event: CustomEvent<{ value: string | number | boolean }>) =>
    (country.value = String(event.detail.value))
 
const save = () => {
    if (!name.value) return (error.value = 'Ein Firmenname ist erforderlich')
    console.log(name.value, country.value)
}
</script>
 
<template>
    <tuix-input
        title="Firmenname"
        :value="name"
        :iconRight="IconName.BUILDING"
        :description="error"
        :descriptionType="error ? DescriptionType.ERROR : DescriptionType.TEXT"
        @on-change="onNameChange"
    />
 
    <tuix-dropdown title="Land" :options="countries" :value="selected" @select="onSelect" />
 
    <tuix-button :variant="MainVariant.PRIMARY" :iconLeft="IconName.SAVE" @customClick="save">Speichern</tuix-button>
</template>

Properties statt Attribute

Bei v-bind prüft Vue, ob der Name am Element existiert, und setzt ihn dann als Property. Gebunden wird deshalb mit der Schreibweise der Property:

<!-- Property: funktioniert für Strings, Objekte und Arrays gleichermassen -->
<tuix-dropdown :options="countries" :iconRight="icon" />
 
<!-- Statisches Attribut: kleingeschrieben, wie überall in HTML -->
<tuix-input iconright="search" descriptiontype="error" />

Der Modifier .prop erzwingt es, falls nötig: :options.prop="countries".

Events

Jedes Event bindet mit @ unter seinem eigenen Namen, camelCase eingeschlossen, sobald isCustomElement das Tag als Element ausweist:

<tuix-input @on-change="onChange" @on-blur="onBlur" />
<tuix-dropdown @select="onSelect" />
<tuix-checkbox @change="onToggle" />
<tuix-table @next-page="onNextPage" @page-size-change="onPageSize" />
<tuix-button @customClick="save">Speichern</tuix-button>

@customClick funktioniert, weil Vue die Schreibweise eines Listener-Namens mit Grossbuchstaben beibehält, sobald das Tag ein Element und keine Komponente ist: "these may be custom elements' custom events", wie es im Compiler steht. Alle anderen Namen sind ohnehin kleingeschrieben, keiner muss also anders geschrieben werden als in der Events-Referenz.

Ein dynamischer Name (@[clickEvent]) ist hier der falsche Weg: genau diese Form bricht. Ein dynamischer Name wird zu onCustomClick kompiliert, was Vue zu custom-click umschreibt, und der Handler läuft nie. @customClick wird direkt gebunden.

Eine Template-Ref geht genauso und liest sich besser, sobald mehrere Handler im Spiel sind:

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
 
const button = ref<HTMLElement>()
const save = () => console.log('gespeichert')
 
onMounted(() => button.value?.addEventListener('customClick', save))
onUnmounted(() => button.value?.removeEventListener('customClick', save))
</script>
 
<template>
    <tuix-button ref="button" variant="primary">Speichern</tuix-button>
</template>

v-model

v-model funktioniert an diesen Elementen nicht, denn sie emittieren on-change statt update:modelValue. Es bleiben der Wert plus der Handler oder eine kleine Vue-Komponente um das Element, die modelValue anbietet:

src/components/TuixInputField.vue
<script setup lang="ts">
defineProps<{ modelValue: string; title?: string }>()
const emit = defineEmits<{ 'update:modelValue': [string] }>()
</script>
 
<template>
    <tuix-input :title="title" :value="modelValue" @on-change="emit('update:modelValue', $event.detail.value)" />
</template>
<TuixInputField v-model="name" title="Firmenname" />

Fehlersuche

SymptomUrsache
Failed to resolve component: tuix-buttonisCustomElement ist für das verwendete Build-Tool nicht konfiguriert
HTMLElement is not defined beim Nuxt-BuildDie Bibliothek wird auf dem Server importiert; in ein .client-Plugin verschieben
Ein camelCase-Event feuert nieMit dynamischem Namen gebunden; @customClick direkt binden
Eine Array-Prop kommt als String anSie wurde als statisches Attribut statt mit : gebunden
Das Feld ändert sich nieDer Wert wird aus dem Change-Handler nicht zurückgeschrieben

Auf dieser Seite