253 lines
4.4 KiB
Markdown
Executable File
253 lines
4.4 KiB
Markdown
Executable File
# Templates, v-model, and Events
|
|
|
|
This note explains how Vue templates talk to your script code.
|
|
|
|
## Templates are HTML with Vue features
|
|
|
|
Example:
|
|
|
|
```vue
|
|
<template>
|
|
<p>{{ username }}</p>
|
|
<input v-model="username" />
|
|
<button @click="reset">Reset</button>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue';
|
|
|
|
const username = ref('mathias');
|
|
|
|
function reset() {
|
|
username.value = '';
|
|
}
|
|
</script>
|
|
```
|
|
|
|
The same variable is:
|
|
|
|
- shown inside `{{ username }}`
|
|
- edited by `<input v-model="username" />`
|
|
- changed by the `reset()` function
|
|
|
|
## `{{ ... }}`: show a value
|
|
|
|
```vue
|
|
<p>{{ username }}</p>
|
|
<p>{{ isLoading ? 'Loading...' : 'Done' }}</p>
|
|
<p>{{ 2 + 3 }}</p>
|
|
```
|
|
|
|
This is interpolation. It prints a value into the rendered HTML.
|
|
|
|
## `v-model`: two-way binding
|
|
|
|
`v-model` connects a form field and a reactive variable.
|
|
|
|
```vue
|
|
<input v-model="email" />
|
|
```
|
|
|
|
```ts
|
|
const email = ref('');
|
|
```
|
|
|
|
Two-way binding means:
|
|
|
|
- if the user types, `email` changes
|
|
- if code changes `email`, the input display changes
|
|
|
|
## Example with a select
|
|
|
|
```vue
|
|
<v-select
|
|
v-model="selectedCountry"
|
|
:items="countries"
|
|
label="Country"
|
|
/>
|
|
```
|
|
|
|
```ts
|
|
const countries = ['Germany', 'France', 'Spain'];
|
|
const selectedCountry = ref<string | null>(null);
|
|
```
|
|
|
|
When the user picks France, `selectedCountry.value` becomes `'France'`.
|
|
|
|
## Your real example
|
|
|
|
From FlexibilityTable:
|
|
|
|
```vue
|
|
<v-select
|
|
v-model="selectedPeriodStartFrom"
|
|
:items="periodStartOptions"
|
|
item-title="title"
|
|
item-value="value"
|
|
label="Period start from"
|
|
/>
|
|
```
|
|
|
|
This means:
|
|
|
|
- the dropdown shows options from `periodStartOptions`
|
|
- each option is an object
|
|
- `title` is shown to the user
|
|
- `value` is stored in `selectedPeriodStartFrom`
|
|
|
|
If one item is:
|
|
|
|
```ts
|
|
{ title: '2026-03-23', value: '2026-03-23T23:00:00.000Z' }
|
|
```
|
|
|
|
then the user sees:
|
|
|
|
```text
|
|
2026-03-23
|
|
```
|
|
|
|
but the stored value becomes:
|
|
|
|
```ts
|
|
selectedPeriodStartFrom.value = '2026-03-23T23:00:00.000Z';
|
|
```
|
|
|
|
## Events with `@click`
|
|
|
|
`@click` means: call a function when the element is clicked.
|
|
|
|
```vue
|
|
<button @click="increment">Add</button>
|
|
```
|
|
|
|
```ts
|
|
function increment() {
|
|
count.value += 1;
|
|
}
|
|
```
|
|
|
|
Other common events:
|
|
|
|
```vue
|
|
<input @input="onInput" />
|
|
<form @submit.prevent="submitForm" />
|
|
<select @change="onChange" />
|
|
```
|
|
|
|
## Binding attributes with `:`
|
|
|
|
The `:` shorthand means "bind this HTML or component prop to JavaScript".
|
|
|
|
```vue
|
|
<button :disabled="isLoading">Save</button>
|
|
```
|
|
|
|
Equivalent long form:
|
|
|
|
```vue
|
|
<button v-bind:disabled="isLoading">Save</button>
|
|
```
|
|
|
|
If `isLoading` is true, the button becomes disabled.
|
|
|
|
## Example: disable refresh button
|
|
|
|
In your component:
|
|
|
|
```vue
|
|
<v-btn
|
|
:disabled="loading || !hasValidPeriodRange"
|
|
@click="applyDateFilters"
|
|
>
|
|
Refresh
|
|
</v-btn>
|
|
```
|
|
|
|
This means:
|
|
|
|
- disable the button if data is loading
|
|
- also disable it if the selected date range is invalid
|
|
- when clicked, run `applyDateFilters`
|
|
|
|
## Conditional rendering
|
|
|
|
Show something only if a condition is true:
|
|
|
|
```vue
|
|
<p v-if="errorMessage">{{ errorMessage }}</p>
|
|
<p v-else>No errors</p>
|
|
```
|
|
|
|
## Loops with `v-for`
|
|
|
|
```vue
|
|
<li v-for="user in users" :key="user.id">
|
|
{{ user.name }}
|
|
</li>
|
|
```
|
|
|
|
This repeats the element for each item.
|
|
|
|
## Common beginner confusion: where does a template variable come from?
|
|
|
|
If you see this in a template:
|
|
|
|
```vue
|
|
<p>{{ total }}</p>
|
|
```
|
|
|
|
search the script for:
|
|
|
|
- `const total = ...`
|
|
- `let total = ...`
|
|
- `function total() ...`
|
|
- `computed(() => ...)`
|
|
- `defineProps(...)`
|
|
|
|
The template only knows values exposed by the script.
|
|
|
|
## Mental model for reading templates
|
|
|
|
For every template line, ask:
|
|
|
|
1. Is it displaying a value?
|
|
2. Is it binding a prop?
|
|
3. Is it listening for an event?
|
|
4. Which script variable or function does it connect to?
|
|
|
|
## Mini Example: search box
|
|
|
|
```vue
|
|
<template>
|
|
<input v-model="searchText" placeholder="Search users" />
|
|
<button :disabled="!searchText" @click="search">Search</button>
|
|
<p>You entered: {{ searchText }}</p>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue';
|
|
|
|
const searchText = ref('');
|
|
|
|
function search() {
|
|
console.log('Searching for', searchText.value);
|
|
}
|
|
</script>
|
|
```
|
|
|
|
Flow:
|
|
|
|
- user types into input
|
|
- `searchText` updates
|
|
- paragraph updates immediately
|
|
- button disable state updates immediately
|
|
- clicking Search uses current `searchText`
|
|
|
|
## Related Notes
|
|
|
|
- [[00-Vue for Python Developers]]
|
|
- [[01-Refs, Computed, and Reactivity]]
|
|
- [[03-Components, Props, Lifecycle, and Composables]]
|
|
- [[04-FlexibilityTable Filter Flow]]
|