init at work

This commit is contained in:
Mathias Schneider
2026-03-27 12:58:01 +01:00
parent 352c352056
commit 8d40597732
60 changed files with 16498 additions and 0 deletions
+747
View File
@@ -0,0 +1,747 @@
# Vue.js Introduction for Python Developers
A practical guide to Vue.js, written for developers who know Python.
---
## Table of Contents
1. [What is Vue?](#what-is-vue)
2. [The Big Idea: Reactivity](#the-big-idea-reactivity)
3. [Your First Vue Component](#your-first-vue-component)
4. [Reactive State with `ref`](#reactive-state-with-ref)
5. [Derived State with `computed`](#derived-state-with-computed)
6. [Templates: HTML with Vue Features](#templates-html-with-vue-features)
7. [Two-Way Binding with `v-model`](#two-way-binding-with-v-model)
8. [Events and User Interaction](#events-and-user-interaction)
9. [Components and Props](#components-and-props)
10. [Lifecycle Hooks](#lifecycle-hooks)
11. [Composables: Reusable Logic](#composables-reusable-logic)
12. [A Real-World Example: Filter Flow](#a-real-world-example-filter-flow)
13. [Vue Strengths, Weaknesses, and Use Cases](#vue-strengths-weaknesses-and-use-cases)
14. [Quick Reference](#quick-reference)
---
## What is Vue?
Vue.js is a JavaScript framework for building user interfaces. It focuses on the **view layer** - what the user sees and interacts with.
### Vue 2 vs Vue 3
This guide covers **Vue 3**, which introduced the Composition API (using `<script setup>`). If you see `ref()` and `computed()`, you're looking at Vue 3.
### Python Analogy
Think of Vue components as Python classes that combine:
| Python | Vue |
|--------|-----|
| Class with state | `<script setup>` with `ref()` values |
| Class methods | Functions defined in `<script setup>` |
| `__str__` or template engine | `<template>` section |
| Class styling | `<style>` section |
---
## The Big Idea: Reactivity
**Reactive** means the UI automatically updates when your data changes.
```python
# Python: you manually update the view
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
self.update_view() # you must call this manually
```
```vue
<!-- Vue: UI updates automatically -->
<script setup>
import { ref } from 'vue';
const count = ref(0);
function increment() {
count.value += 1; // UI updates automatically
}
</script>
<template>
<button @click="increment">Clicked {{ count }} times</button>
</template>
```
When `count.value` changes, Vue re-renders the button text. You never call `update_view()`.
---
## Your First Vue Component
A Vue **single-file component** (`.vue` file) has three sections:
```vue
<template>
<!-- 1. TEMPLATE: what to display -->
<p>{{ greeting }}</p>
<button @click="sayHello">Click me</button>
</template>
<script setup lang="ts">
// 2. SCRIPT: data and logic
import { ref } from 'vue';
const greeting = ref('Hello from Vue!');
function sayHello() {
console.log('Button clicked!');
}
</script>
<style scoped>
/* 3. STYLE: how it looks */
p { color: blue; }
</style>
```
### Mental Model
When reading Vue code, ask four questions:
1. **What values are reactive state?** → Look for `ref()` and `computed()`
2. **Which template elements use that state?** → Look for `{{ variable }}` and `v-model`
3. **Which functions change that state?** → Look for functions that modify `.value`
4. **Which computed values depend on that state?** → Look for `computed()`
---
## Reactive State with `ref`
### What is `ref`?
`ref()` wraps a value so Vue can track changes to it.
```ts
import { ref } from 'vue';
const count = ref(0); // reactive number
const username = ref('alice'); // reactive string
const isLoading = ref(false); // reactive boolean
const items = ref<string[]>([]); // reactive array
```
### Reading and Writing
In `<script>`, use `.value`:
```ts
count.value += 1; // increment
username.value = 'bob'; // change string
isLoading.value = true; // set to true
items.value.push('apple'); // modify array
```
In `<template>`, Vue unwraps refs automatically—use the variable name without `.value`:
```vue
<template>
<p>{{ count }}</p> <!-- reads count.value -->
<p>{{ username }}</p> <!-- reads username.value -->
<button :disabled="isLoading">Save</button>
</template>
```
### Python Comparison
```python
# Python
count = 0
count += 1
```
```ts
// Vue (script)
const count = ref(0);
count.value += 1;
```
The `.value` exists because `count` is a wrapper object, not the raw value.
### Example: Loading State
```vue
<template>
<button :disabled="isLoading" @click="save">
{{ isLoading ? 'Saving...' : 'Save' }}
</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const isLoading = ref(false);
async function save() {
isLoading.value = true;
try {
await fakeRequest();
} finally {
isLoading.value = false;
}
}
function fakeRequest() {
return new Promise((resolve) => setTimeout(resolve, 1000));
}
</script>
```
The button label and disabled state both react to `isLoading`.
---
## Derived State with `computed`
Use `computed()` for values that are calculated from other reactive values.
```ts
import { ref, computed } from 'vue';
const firstName = ref('Ada');
const lastName = ref('Lovelace');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
```
### Python Comparison
This is like a Python `@property`:
```python
class Person:
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
```
### Rule of Thumb
| Use `ref` when | Use `computed` when |
|---------------|---------------------|
| The value changes over time | The value is calculated from other values |
| The value is primary state | You don't want to duplicate state |
| Examples: user input, API data | Examples: full name from first + last |
### Common Mistake: Storing What Can Be Derived
```ts
// Bad: duplicate state, must update manually
const firstName = ref('Ada');
const lastName = ref('Lovelace');
const fullName = ref('Ada Lovelace'); // must keep in sync!
// Good: computed handles it automatically
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
```
### Real Example: Validation
```ts
const selectedStart = ref<string | null>(null);
const selectedEnd = ref<string | null>(null);
const hasValidRange = computed(() => {
if (!selectedStart.value || !selectedEnd.value) {
return true; // missing values are valid (no filter)
}
return new Date(selectedStart.value) <= new Date(selectedEnd.value);
});
```
---
## Templates: HTML with Vue Features
### Interpolation: `{{ }}`
Print values into HTML:
```vue
<p>{{ username }}</p>
<p>{{ 2 + 3 }}</p>
<p>{{ isLoading ? 'Loading...' : 'Done' }}</p>
<p>{{ items.length }} items</p>
```
### Conditional Rendering: `v-if` / `v-else`
Show elements based on conditions:
```vue
<p v-if="error">{{ error }}</p>
<p v-else>No errors</p>
<div v-if="isLoggedIn">
<p>Welcome, {{ username }}!</p>
</div>
<div v-else>
<p>Please log in.</p>
</div>
```
### Loops: `v-for`
Repeat elements for each item:
```vue
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
```
**Important:** Always use `:key` with `v-for` for proper list rendering.
### Attribute Binding: `:`
Bind HTML attributes to JavaScript expressions:
```vue
<button :disabled="isLoading">Save</button>
<img :src="imageUrl" />
<a :href="profileUrl">View Profile</a>
```
Short for `v-bind:disabled`.
### Class Binding
```vue
<div :class="{ active: isSelected, highlight: hasError }">
Content
</div>
```
---
## Two-Way Binding with `v-model`
`v-model` connects a form field and a reactive variable bidirectionally.
### Basic Input
```vue
<template>
<input v-model="email" />
<p>You typed: {{ email }}</p>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const email = ref('');
</script>
```
- User types → `email` updates
- Code changes `email` → input display updates
### Select Dropdown
```vue
<v-select
v-model="selectedCountry"
:items="countries"
item-title="title"
item-value="value"
/>
```
```ts
const countries = [
{ title: 'Germany', value: 'DE' },
{ title: 'France', value: 'FR' },
{ title: 'Spain', value: 'ES' },
];
const selectedCountry = ref<string | null>(null);
```
- `item-title`: what's shown to users
- `item-value`: what's stored in the variable
---
## Events and User Interaction
### Click Events: `@click`
```vue
<button @click="save">Save</button>
```
```ts
function save() {
console.log('saving...');
}
```
### Other Common Events
```vue
<input @input="onInput" /> <!-- typing -->
<form @submit.prevent="submitForm" /> <!-- form submission -->
<select @change="onChange" /> <!-- dropdown change -->
<div @mouseover="hovering = true" /> <!-- mouse events -->
```
The `.prevent` modifier calls `event.preventDefault()` automatically.
### Event with Parameters
```vue
<button @click="deleteItem(id)">Delete</button>
```
```ts
function deleteItem(id: number) {
console.log('deleting', id);
}
```
---
## Components and Props
### What is a Component?
A component is a reusable UI unit. Think of it like a Python function that returns HTML, but with its own state and logic.
### Parent Using Child
```vue
<!-- ParentComponent.vue -->
<template>
<UserCard :name="username" :age="34" />
</template>
<script setup lang="ts">
import UserCard from './UserCard.vue';
const username = 'Alice';
</script>
```
### Props: Data Passed In
Props are like function arguments for components.
```vue
<!-- UserCard.vue -->
<script setup lang="ts">
interface Props {
name: string;
age: number;
mode?: 'compact' | 'full'; // optional prop
}
const props = withDefaults(defineProps<Props>(), {
mode: 'full',
});
</script>
<template>
<div :class="props.mode">
<p>{{ props.name }} is {{ props.age }} years old.</p>
</div>
</template>
```
### Python Comparison
```python
# Python function
def user_card(name: str, age: int, mode: str = 'full'):
return f"{name} is {age} years old ({mode})"
```
### Child-to-Parent Communication
Props go down; events go up.
```vue
<!-- Parent -->
<template>
<FileUpload @upload="handleUpload" />
</template>
<script setup lang="ts">
function handleUpload(file: File) {
console.log('Uploaded:', file.name);
}
</script>
```
The child component calls `emit('upload', file)` when something happens. The parent decides what to do.
---
## Lifecycle Hooks
Code that runs at specific moments in a component's life.
### `onMounted`: When Component Appears
```ts
import { onMounted } from 'vue';
onMounted(async () => {
console.log('Component is on screen');
await fetchData();
});
```
This is like `__post_init__` or a setup function that runs after initialization.
### Other Common Hooks
| Hook | When It Runs |
|------|-------------|
| `onMounted` | Component is added to DOM |
| `onUnmounted` | Component is removed from DOM |
| `onUpdated` | Component re-renders |
| `onBeforeMount` | Just before first render |
| `onBeforeUnmount` | Just before removal |
---
## Composables: Reusable Logic
A **composable** is a reusable function containing Vue logic.
### Example: useCounter
```ts
// useCounter.ts
export function useCounter() {
const count = ref(0);
function increment() {
count.value += 1;
}
return { count, increment };
}
```
```vue
<!-- Using it -->
<script setup lang="ts">
import { useCounter } from './useCounter';
const { count, increment } = useCounter();
</script>
```
### Why Composables?
Without composables, components get too big. Composables let you:
- Reuse logic across components
- Separate data fetching from UI
- Keep components readable
### Real Examples from Projects
```ts
// Separate backend logic from UI
const { downloadFlexData } = useJsonDownload();
const rows = await downloadFlexData(payload);
// Authentication logic
const { isAuthenticated, login, logout } = useAuth();
```
### Python Comparison
Composables are like Python modules with business logic:
```python
# Python
from downloads import download_flex_data
rows = download_flex_data(payload)
# Vue
const { downloadFlexData } = useJsonDownload();
const rows = await downloadFlexData(payload);
```
---
## A Real-World Example: Filter Flow
This pattern appears frequently in data-driven applications.
### The Problem
User selects filter options, then clicks "Apply" to fetch data. Without separating "selected" and "applied" state, every dropdown change would trigger a backend request.
### State Structure
```ts
// What the user is currently editing
const selectedStart = ref<string | null>(defaultStart);
const selectedEnd = ref<string | null>(defaultEnd);
// What was last applied
const appliedStart = ref<string | null>(defaultStart);
const appliedEnd = ref<string | null>(defaultEnd);
```
### Validation
```ts
const isValidRange = computed(() => {
if (!selectedStart.value || !selectedEnd.value) return true;
return new Date(selectedStart.value) <= new Date(selectedEnd.value);
});
```
### Apply Action
```ts
async function applyFilters() {
if (!isValidRange.value) return;
appliedStart.value = selectedStart.value;
appliedEnd.value = selectedEnd.value;
await refreshData();
}
```
### Data Flow
```
User changes dropdown
selectedStart updates (draft state)
Validation recomputes
User clicks Apply
selected → applied (commit)
Build payload
Fetch from backend
Update table data
Table re-renders
```
### Why This Matters
- **Draft state**: what the user is currently editing (may not be valid)
- **Applied state**: what the system is actually using
This prevents excessive API calls and ensures exports match what's visible on screen.
---
## Vue Strengths, Weaknesses, and Use Cases
### Strengths
| Strength | Description |
|----------|-------------|
| **Gentle learning curve** | Approachable syntax; good docs; Vue 3 Composition API feels natural |
| **Reactivity system** | Automatic UI updates without manual DOM manipulation |
| **Single-file components** | Template, logic, and styles in one file—easy to understand |
| **Flexible architecture** | Use it for simple widgets or complex SPAs |
| **Great tooling** | Vite dev server, Vue DevTools, excellent TypeScript support |
| **Active ecosystem** | Vuetify (Material UI), Nuxt (SSR/SSG), Pinia (state management) |
### Weaknesses
| Weakness | Description |
|----------|-------------|
| **JavaScript ecosystem** | Requires understanding npm, build tools, ES modules |
| **Reactivity gotchas** | Objects/arrays need special handling; `.value` confusion |
| **Mobile/Web separation** | Vue itself is web-only (consider NativeScript or Capacitor for mobile) |
| **Smaller ecosystem than React** | Fewer third-party libraries |
### Typical Use Cases
| Use Case | Example |
|----------|---------|
| **Single-page applications (SPAs)** | Dashboards, admin panels, SaaS products |
| **Interactive UI components** | Data tables with filters, dynamic forms, real-time updates |
| **Progressive enhancement** | Add Vue to existing apps (like you might use jQuery) |
| **Prototyping** | Quick MVPs with Vuetify or Tailwind UI components |
---
## Quick Reference
### Template Syntax
```vue
{{ value }} <!-- interpolation -->
v-model="variable" <!-- two-way binding -->
@click="handler" <!-- click event -->
:prop="value" <!-- bind attribute -->
v-if="condition" <!-- conditional -->
v-for="item in items" <!-- loop -->
```
### Script Basics
```ts
import { ref, computed, onMounted } from 'vue';
const variable = ref(initialValue); // reactive state
const derived = computed(() => ...); // derived state
onMounted(() => { ... }); // lifecycle hook
```
### Common Patterns
| Pattern | Code |
|---------|------|
| Reading a ref | `variable.value` in script, `variable` in template |
| Writing a ref | `variable.value = newValue` |
| Defining props | `const props = defineProps<Props>()` |
| Optional props | `withDefaults(defineProps<Props>(), { optional: 'default' })` |
| Emitting events | `emit('eventName', data)` |
---
## Summary
Vue combines:
- **Reactivity**: UI updates automatically when state changes
- **Components**: Reusable pieces combining template, logic, and styles
- **Composables**: Shared logic extracted into testable functions
- **Props and Events**: Clear data flow between components
The key mental shift from Python:
| Python Thinking | Vue Thinking |
|----------------|--------------|
| Call functions to update view | Change state, view updates automatically |
| Classes hold state and methods | Components hold reactive refs and functions |
| Import modules for logic | Import composables for reusable logic |
---
## Further Reading
- [Vue 3 Documentation](https://vuejs.org/guide/)
- [Vue School Tutorials](https://vueschool.io/)
- [Vuetify Components](https://vuetifyjs.com/) (your project's UI framework)
- Project composables: `useJsonDownload`, `useJsonUpload`, `useAuth`
+215
View File
@@ -0,0 +1,215 @@
# Vue for Python Developers
This note gives you a practical mental model for Vue if you already know Python.
## The Big Idea
Vue components are small units that combine:
- template: what should be shown
- script: data and logic
- style: how it should look
A Vue single-file component often looks like this:
```vue
<template>
<p>{{ message }}</p>
<button @click="increment">Clicked {{ count }} times</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const message = 'Hello from Vue';
const count = ref(0);
function increment() {
count.value += 1;
}
</script>
```
## Python Analogy
Think of a Vue component like a Python object plus a small HTML view.
Python-style thinking:
```python
class CounterPage:
def __init__(self):
self.message = "Hello from Vue"
self.count = 0
def increment(self):
self.count += 1
```
Vue-style thinking:
```ts
const message = 'Hello from Vue';
const count = ref(0);
function increment() {
count.value += 1;
}
```
Difference:
- In Python, changing `self.count` changes object state.
- In Vue, changing `count.value` changes reactive state and the UI updates automatically.
## What "Reactive" Means
Reactive means the UI watches data. When the data changes, Vue updates the screen.
Example:
```vue
<template>
<p>{{ name }}</p>
<button @click="rename">Rename</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const name = ref('Mathias');
function rename() {
name.value = 'Mathias 2';
}
</script>
```
When `name.value` changes, the paragraph updates.
## Template vs Script
The template is not plain HTML. It is HTML with Vue features.
Example:
```vue
<template>
<p>{{ username }}</p>
<button @click="login">Login</button>
</template>
```
- `{{ username }}` means: print a variable into the HTML.
- `@click="login"` means: run the function when clicked.
The script defines the variables and functions used by the template.
```ts
<script setup lang="ts">
const username = 'alice';
function login() {
console.log('login clicked');
}
</script>
```
## Common Vue Building Blocks
### `ref`
Used for reactive single values.
```ts
const count = ref(0);
const username = ref('alice');
const isLoading = ref(false);
```
### `computed`
Used for values derived from other values.
```ts
const firstName = ref('Ada');
const lastName = ref('Lovelace');
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
```
### event handlers
Functions triggered by user actions.
```vue
<button @click="save">Save</button>
```
```ts
function save() {
console.log('saving');
}
```
### lifecycle hooks
Code that runs when a component starts.
```ts
onMounted(async () => {
await loadData();
});
```
This is roughly like running setup code after the component appears on screen.
## `v-model` in One Sentence
`v-model` is two-way binding between a form field and a reactive variable.
```vue
<input v-model="username" />
```
If the user types into the input, `username` changes.
If `username` changes in code, the input display changes.
## Example: Filter Form
```vue
<template>
<input v-model="searchText" />
<button @click="apply">Apply</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const searchText = ref('');
function apply() {
console.log('User searched for:', searchText.value);
}
</script>
```
This is very similar to your date filter selects in FlexibilityTable.
## Good Beginner Rule
When reading Vue code, ask these four questions:
1. What values are reactive state?
2. Which template elements use that state?
3. Which functions change that state?
4. Which computed values or API calls depend on that state?
If you answer those four, most Vue components become understandable.
## Related Notes
- [[01-Refs, Computed, and Reactivity]]
- [[02-Templates, v-model, and Events]]
- [[03-Components, Props, Lifecycle, and Composables]]
- [[04-FlexibilityTable Filter Flow]]
+252
View File
@@ -0,0 +1,252 @@
# Refs, Computed, and Reactivity
This note explains the most important Vue state concepts for a Python developer.
## `ref`: reactive storage for one value
A `ref` wraps a value so Vue can track it.
```ts
import { ref } from 'vue';
const count = ref(0);
const username = ref('mathias');
const isLoading = ref(false);
```
In script code, you read and write using `.value`.
```ts
count.value += 1;
username.value = 'new name';
isLoading.value = true;
```
In templates, Vue unwraps refs automatically.
```vue
<template>
<p>{{ count }}</p>
</template>
```
You write `count`, not `count.value`, in the template.
## Python comparison
Python:
```python
count = 0
count += 1
```
Vue script:
```ts
const count = ref(0);
count.value += 1;
```
The extra `.value` exists because `count` is a reactive wrapper object, not the raw number.
## Example: loading state
```vue
<template>
<button :disabled="isLoading" @click="save">
{{ isLoading ? 'Saving...' : 'Save' }}
</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const isLoading = ref(false);
async function save() {
isLoading.value = true;
try {
await fakeRequest();
} finally {
isLoading.value = false;
}
}
function fakeRequest() {
return new Promise((resolve) => setTimeout(resolve, 1000));
}
</script>
```
The button label and disabled state both react to `isLoading`.
## `computed`: derived state
A `computed` value is calculated from other reactive values.
```ts
import { ref, computed } from 'vue';
const firstName = ref('Ada');
const lastName = ref('Lovelace');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});
```
Use `computed` when a value can be derived instead of stored manually.
Bad pattern:
```ts
const firstName = ref('Ada');
const lastName = ref('Lovelace');
const fullName = ref('Ada Lovelace');
```
Now you must remember to update `fullName` yourself every time.
Better:
```ts
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
```
## Python comparison
This is similar to a property.
```python
class Person:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
```
Vue `computed` plays a similar role.
## Real example from your app
In FlexibilityTable, this pattern appears:
```ts
const hasValidPeriodRange = computed(() => {
if (!selectedPeriodStartFrom.value || !selectedPeriodStartUntil.value) {
return true;
}
return new Date(selectedPeriodStartFrom.value).getTime() <= new Date(selectedPeriodStartUntil.value).getTime();
});
```
This means:
- if one boundary is missing, the range is treated as valid
- if both are set, start must be before or equal to end
The component does not store `hasValidPeriodRange` manually. It derives it from the two selected dates.
## Another example: filtered list
```ts
const searchText = ref('');
const users = ref(['Alice', 'Bob', 'Charlie']);
const filteredUsers = computed(() => {
return users.value.filter((user) =>
user.toLowerCase().includes(searchText.value.toLowerCase()),
);
});
```
If `searchText` changes, `filteredUsers` updates automatically.
## Rule of Thumb
Use `ref` when:
- the value changes over time
- the UI should react to that change
- the value is primary state
Use `computed` when:
- the value is calculated from other reactive values
- you do not want to duplicate state
## Common Beginner Mistakes
### forgetting `.value` in script
Wrong:
```ts
count += 1;
```
Right:
```ts
count.value += 1;
```
### using `computed` for side effects
Bad:
```ts
const result = computed(() => {
console.log('side effect');
return count.value * 2;
});
```
A computed should mainly calculate and return a value.
### storing what can be derived
Bad:
```ts
const selectedFirst = ref('2026-01-01');
const selectedSecond = ref('2026-01-10');
const isValid = ref(true);
```
Better:
```ts
const isValid = computed(() => selectedFirst.value <= selectedSecond.value);
```
## Tiny Exercise
What should be `ref` and what should be `computed`?
Scenario:
- user types first name
- user types last name
- screen shows full name
- submit button disabled when first name is empty
Answer:
```ts
const firstName = ref('');
const lastName = ref('');
const fullName = computed(() => `${firstName.value} ${lastName.value}`.trim());
const isSubmitDisabled = computed(() => !firstName.value.trim());
```
## Related Notes
- [[00-Vue for Python Developers]]
- [[02-Templates, v-model, and Events]]
- [[04-FlexibilityTable Filter Flow]]
+252
View File
@@ -0,0 +1,252 @@
# 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]]
+250
View File
@@ -0,0 +1,250 @@
# Components, Props, Lifecycle, and Composables
This note explains how Vue code is organized into reusable pieces.
## Components
A component is a reusable UI unit.
Examples from your project:
- `FlexibilityTable.vue`
- `FileUpload.vue`
- `AppHeader.vue`
A parent component can render a child component.
```vue
<template>
<UserCard />
</template>
<script setup lang="ts">
import UserCard from './UserCard.vue';
</script>
```
## Props: inputs passed into a component
Props are like function arguments for components.
Example:
```vue
<template>
<UserCard :name="username" :age="34" />
</template>
```
Child component:
```vue
<script setup lang="ts">
interface Props {
name: string;
age: number;
}
const props = defineProps<Props>();
</script>
<template>
<p>{{ props.name }} is {{ props.age }} years old.</p>
</template>
```
## Python comparison
This is similar to passing constructor arguments or function arguments.
```python
render_user_card(name=username, age=34)
```
## Your real example
In FlexibilityTable:
```ts
interface FlexibilityTableProps {
title: string;
uploadMode?: 'fsp' | 'tso' | 'both' | 'none';
}
const props = withDefaults(defineProps<FlexibilityTableProps>(), {
uploadMode: 'none',
});
```
This means:
- the parent must give `title`
- the parent may give `uploadMode`
- if `uploadMode` is missing, use `'none'`
Then template code can use:
```vue
<v-toolbar-title>
{{ title }}
</v-toolbar-title>
```
and script code can use:
```ts
props.uploadMode
```
## Lifecycle: when should code run?
Sometimes you want code to run when the component appears.
```ts
import { onMounted } from 'vue';
onMounted(() => {
console.log('Component is mounted');
});
```
In your component:
```ts
onMounted(async () => {
await refreshTableDataFromBackend();
});
```
That means when the table component is loaded, it immediately fetches backend data.
## Composables
A composable is a reusable function that contains Vue logic.
Common pattern:
```ts
export function useCounter() {
const count = ref(0);
function increment() {
count.value += 1;
}
return {
count,
increment,
};
}
```
Use it inside a component:
```ts
const { count, increment } = useCounter();
```
## Why composables exist
Without composables, components get too big.
Instead of putting all API logic, auth logic, upload logic, and export logic directly in one component, you move reusable logic into composables.
Your project has examples:
- `useJsonDownload()`
- `useJsonUpload()`
- `useAuth()`
## Real example: `useJsonDownload`
In FlexibilityTable:
```ts
const { downloadFlexData, downloadRawFlexData } = useJsonDownload();
```
That means:
- this component imports data-loading logic from a composable
- the composable handles HTTP and backend transformation details
- the component only calls its functions
This is similar to separating Python business logic into a helper module.
Python style:
```python
from downloads import download_flex_data
rows = download_flex_data(payload)
```
Vue composable style:
```ts
const { downloadFlexData } = useJsonDownload();
const rows = await downloadFlexData(payload);
```
## Child-to-parent communication
Props go down from parent to child.
Events often go up from child to parent.
Example:
```vue
<FileUpload @upload="onFspUpload" />
```
This means the child component emits an `upload` event, and the parent runs `onFspUpload` when that happens.
## Example structure
```vue
<template>
<SearchBox @search="runSearch" />
</template>
<script setup lang="ts">
function runSearch(query: string) {
console.log('search query:', query);
}
</script>
```
The child says: "something happened".
The parent decides what to do.
## Rule of Thumb
Put code in a component when:
- it is very local to one UI element
- it mostly controls rendering
Put code in a composable when:
- it will be reused
- it handles data fetching
- it contains business logic
- it makes the component easier to read
## Reading Strategy
When you see a component, identify:
1. props coming in
2. local reactive state
3. composables being used
4. lifecycle hooks
5. events going to child components
## Related Notes
- [[00-Vue for Python Developers]]
- [[02-Templates, v-model, and Events]]
- [[04-FlexibilityTable Filter Flow]]
+332
View File
@@ -0,0 +1,332 @@
# FlexibilityTable Filter Flow
This note explains how your `selectedPeriodStartFrom` example interacts with the rest of the code.
## Goal of the feature
The table lets the user:
- choose a start period
- choose an end period
- click Refresh
- load matching backend data
- view and export the filtered result
## The important variables
From FlexibilityTable:
```ts
type PeriodFilterValue = string | null;
const periodStartOptions = getDownloadPeriodStartOptions();
const defaultPeriodStartFrom = periodStartOptions[0]?.value ?? null;
const defaultPeriodStartUntil = periodStartOptions[periodStartOptions.length - 1]?.value ?? null;
const selectedPeriodStartFrom = ref<PeriodFilterValue>(defaultPeriodStartFrom);
const selectedPeriodStartUntil = ref<PeriodFilterValue>(defaultPeriodStartUntil);
const appliedPeriodStartFrom = ref<PeriodFilterValue>(defaultPeriodStartFrom);
const appliedPeriodStartUntil = ref<PeriodFilterValue>(defaultPeriodStartUntil);
```
There are two groups of state here.
### selected values
These track what the user is currently editing in the UI.
```ts
selectedPeriodStartFrom
selectedPeriodStartUntil
```
### applied values
These track what was last confirmed with the Refresh button.
```ts
appliedPeriodStartFrom
appliedPeriodStartUntil
```
This separation is very important.
## Why both selected and applied exist
Without this split, every dropdown change could immediately trigger a backend request.
That would mean:
- too many requests
- surprising UI updates while the user is still choosing dates
- exports might not match the table state clearly
So the component uses a draft pattern.
Python analogy:
```python
form_input = {'start': '2026-03-01', 'end': '2026-03-31'}
applied_filters = {'start': '2026-02-01', 'end': '2026-02-28'}
```
The form input is what the user is editing.
The applied filters are what the system is currently using.
## Step 1: build options for the dropdown
```ts
const periodStartOptions = getDownloadPeriodStartOptions();
```
This comes from `jsonDownload.ts`.
That composable calculates valid backend week starts.
Each option looks roughly like this:
```ts
{
title: '2026-03-23',
value: '2026-03-22T23:00:00.000Z'
}
```
- `title` is for humans
- `value` is for the program
## Step 2: connect the dropdown to state
```vue
<v-select
v-model="selectedPeriodStartFrom"
:items="periodStartOptions"
item-title="title"
item-value="value"
label="Period start from"
/>
```
When the user chooses an option:
- Vuetify reads the chosen item
- Vue writes its `value` field into `selectedPeriodStartFrom`
- any code depending on `selectedPeriodStartFrom` can react
## Step 3: validate the selected range
```ts
const hasValidPeriodRange = computed(() => {
if (!selectedPeriodStartFrom.value || !selectedPeriodStartUntil.value) {
return true;
}
return new Date(selectedPeriodStartFrom.value).getTime() <= new Date(selectedPeriodStartUntil.value).getTime();
});
```
This computed reads the two selected values and checks if the range makes sense.
The Refresh button uses it:
```vue
<v-btn
:disabled="loading || !hasValidPeriodRange"
@click="applyDateFilters"
>
Refresh
</v-btn>
```
So if the user chooses an invalid range, the button becomes disabled.
## Step 4: user clicks Refresh
```ts
async function applyDateFilters() {
if (!hasValidPeriodRange.value) {
showMessage('Select a valid period range before refreshing.', 'error', true);
return;
}
appliedPeriodStartFrom.value = selectedPeriodStartFrom.value;
appliedPeriodStartUntil.value = selectedPeriodStartUntil.value;
await refreshTableDataFromBackend(
'Could not load data from backend.',
buildSelectedDownloadPayload(),
);
}
```
This does three things:
1. validate
2. copy selected values into applied values
3. fetch new backend data
## Step 5: build the request payload
```ts
function buildSelectedDownloadPayload(): DownloadPayload {
return buildDownloadPayload(
buildDateRange(selectedPeriodStartFrom.value, selectedPeriodStartUntil.value),
);
}
```
and:
```ts
function buildDateRange(periodStartFrom: PeriodFilterValue, periodStartUntil: PeriodFilterValue): DownloadDateRange {
return {
periodStartFrom: periodStartFrom ?? undefined,
periodStartUntil: periodStartUntil ?? undefined,
};
}
```
This turns UI values into backend filter input.
Important detail:
- `null` from the UI becomes `undefined` for the backend builder
- that means "no boundary selected"
## Step 6: call the composable
```ts
tableData.value = await downloadFlexData(payload);
```
This happens inside `refreshTableDataFromBackend()`.
The component does not itself know how to talk to the backend in detail. That logic lives in `useJsonDownload()`.
## Step 7: transform backend data into rows
Inside `jsonDownload.ts`, the composable:
- picks the correct endpoint
- sends the request
- receives backend offer data
- converts it into table rows and display rows
So the data flow is:
```text
Dropdown -> selectedPeriodStartFrom -> payload builder -> downloadFlexData -> tableData -> rendered table
```
## Step 8: render the table
The table rows come from:
```ts
const rows = computed<FlexDisplayRow[]>(() => tableData.value.displayRows ?? []);
```
Then more filtering happens:
```ts
const filteredRows = computed<FlexDisplayRow[]>(() =>
rows.value.filter((row) => {
const aggregator = row.senderId ?? row.aggregator;
const controlArea = row.receiverId ?? row.controlArea;
if (selectedAggregators.value.length && (!aggregator || !selectedAggregators.value.includes(aggregator))) {
return false;
}
if (selectedControlArea.value && controlArea !== selectedControlArea.value) {
return false;
}
return true;
}),
);
```
Notice something important:
- date filters affect what is fetched from the backend
- aggregator and control area filters affect what is shown locally in the frontend
That is a useful architectural distinction.
## Step 9: exports use applied filters
For JSON export:
```ts
const rawData = await downloadRawFlexData(buildAppliedDownloadPayload());
```
That uses applied values, not selected values.
Why?
Because if the user changed the dropdown but did not click Refresh yet, the table still reflects the old filters. Export should match what the user is actually seeing as the active state.
## Full mental model
Use this simple chain:
```text
1. Options are created
2. User picks a value
3. selectedPeriodStartFrom changes
4. Validation recomputes
5. User clicks Refresh
6. selected values become applied values
7. payload is built
8. backend data is fetched
9. tableData updates
10. table re-renders
```
## Tiny simplified example
```vue
<template>
<select v-model="selectedColor">
<option value="red">Red</option>
<option value="blue">Blue</option>
</select>
<button @click="apply">Apply</button>
<p>Selected: {{ selectedColor }}</p>
<p>Applied: {{ appliedColor }}</p>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const selectedColor = ref('red');
const appliedColor = ref('red');
function apply() {
appliedColor.value = selectedColor.value;
}
</script>
```
This is the same pattern as your date filters, just simpler.
## Key beginner lesson
When reading Vue, do not only ask "what is this variable".
Ask:
- who writes it?
- who reads it?
- is it draft state or applied state?
- does it affect frontend-only filtering or backend requests?
That is how the whole interaction becomes understandable.
## Related Notes
- [[00-Vue for Python Developers]]
- [[01-Refs, Computed, and Reactivity]]
- [[02-Templates, v-model, and Events]]
- [[03-Components, Props, Lifecycle, and Composables]]
+71
View File
@@ -0,0 +1,71 @@
# Vue Learning Path
Start here if you are new to Vue and coming from Python.
## Suggested reading order
1. [[00-Vue for Python Developers]]
2. [[01-Refs, Computed, and Reactivity]]
3. [[02-Templates, v-model, and Events]]
4. [[03-Components, Props, Lifecycle, and Composables]]
5. [[04-FlexibilityTable Filter Flow]]
## What each note covers
### Vue for Python Developers
The big picture:
- what a Vue component is
- template vs script
- what reactive means
- how to read Vue code without getting lost
### Refs, Computed, and Reactivity
State management basics:
- `ref`
- `.value`
- `computed`
- when to store data vs derive data
### Templates, v-model, and Events
Template mechanics:
- interpolation with `{{ ... }}`
- `v-model`
- `@click`
- `:disabled`
- how template code connects to script code
### Components, Props, Lifecycle, and Composables
Code organization:
- parent and child components
- props
- `onMounted`
- composables such as `useJsonDownload()`
### FlexibilityTable Filter Flow
Real project walkthrough:
- `selectedPeriodStartFrom`
- selected vs applied filters
- backend payload building
- table refresh flow
- export behavior
## Best way to study these notes
For each note:
1. Read the explanation slowly.
2. Copy one code example into a small test component.
3. Change one line and predict what the UI will do.
4. Run it and compare expectation with result.
That loop is the fastest way to get comfortable with Vue.