Beginner (Exercises 1β4)
Exercise 1 β Traffic Light (β Beginner)
Build: A traffic light component that cycles through red β yellow β green β red on click.
Concepts: useState, t-att-class, t-on-click, conditional classes
Acceptance criteria:
- Three colored circles rendered with CSS. Only the active one is "lit" (full opacity / glow).
- A "Next" button cycles the state. The sequence must be: red β yellow β green β red.
- A text label below shows the current light name and the associated rule (Stop / Prepare / Go).
- Use
t-att-classwith an object for active/inactive state β do not use inline styles. - Use a getter
currentLightthat returns the active light object from the state array.
Exercise 2 β Shopping Cart (β Beginner)
Build: A product list + shopping cart with add/remove functionality.
Concepts: useState arrays, t-foreach, t-key, getters, state mutations, events in loops
Acceptance criteria:
- Hard-code 6 products in state:
{ id, name, price, category }. - Render product cards in a grid using
t-foreach. Each has an "Add to Cart" button. - Cart shows items with quantity controls (+ / β) and a remove button per item.
- Cart total and item count are computed via getters and update reactively.
- Adding an already-carted product increases its quantity (no duplicates in cart).
- Quantity cannot go below 1 (the β button is disabled at 1; remove button deletes the item).
Exercise 3 β Multi-Step Form (ββ Beginner-Intermediate)
Build: A 3-step wizard form with validation at each step.
Concepts: t-model (all types), t-if/elif/else, form validation, useState, static props
Acceptance criteria:
- Step 1: Name (text, required), Email (email, required, valid format), Password (min 8 chars).
- Step 2: Date of birth (date input), Country (select with 5+ options), Newsletter checkbox.
- Step 3: Review β display all entered data read-only with an "Edit" link per step.
- Each step validates on "Next" click. Errors show inline. Cannot advance with invalid data.
- Progress bar shows which step is active/complete. Back button available from step 2+.
- Submit shows a success screen with a summary of entered data.
Exercise 4 β Filterable & Sortable Table (ββ Beginner-Intermediate)
Build: A data table with search, column sort, and pagination.
Concepts: useState, getters (computed), t-foreach, t-model, t-att-class, dynamic attributes
Acceptance criteria:
- Hard-code 20 employee records:
{ id, name, department, salary, joinDate }. - A search input filters rows by name or department (case-insensitive, t-model.lazy).
- Click any column header to sort ascending; click again for descending. Show a β²/βΌ icon.
- Pagination: 5 rows per page. Show page X of Y. Previous/Next buttons disabled at limits.
- A getter chains filter β sort β paginate. All applied in correct order.
- Show "No results" when filters produce an empty set.
Intermediate (Exercises 5β8)
Exercise 5 β Component Library (βββ Intermediate)
Build: A mini UI component library: Button, Input, Card, Modal, Badge.
Concepts: static props, defaultProps, slots, t-att-class, t-att-style, component composition
Acceptance criteria:
- Button: props for variant (primary/secondary/danger), size (sm/md/lg), disabled, loading. Full prop declarations.
- Input: props for type, label, placeholder, error message. Binds value via callback props (not t-model internally).
- Card: header, body (default), footer slots. All slots have fallback content.
- Modal: uses Card internally. Props: isOpen, onClose. ESC key closes it (global listener in onMounted/onWillUnmount).
- Badge: props for label, color, dot (boolean β shows a colored dot before label).
- Demo page renders all components with all prop variants in a gallery layout.
Exercise 6 β Custom Hooks Library (βββ Intermediate)
Build: 5 custom hooks, each demonstrated in a component.
Concepts: Custom hooks, lifecycle composition, useState, onMounted, onWillUnmount
Acceptance criteria:
- useToggle(initial) β returns
{ value, on, off, toggle }. Demo: light switch UI. - useCountdown(seconds) β counts down from N, stops at 0, returns
{ remaining, start, reset }. Uses setInterval with onWillUnmount cleanup. Demo: quiz timer. - useLocalStorage(key, default) β persists state to localStorage. Demo: theme preference that survives page refresh.
- useWindowSize() β reactive
{ width, height }. Demo: show "mobile" / "tablet" / "desktop" based on width. - useKeyboard(bindings) β accepts
{ "Ctrl+S": saveFn, "Escape": closeFn }. Adds/removes global keydown listener. Demo: shortcut help panel.
Exercise 7 β Real-Time Feed (βββ Intermediate)
Build: A live activity feed that simulates real-time updates.
Concepts: onMounted/onWillUnmount, setInterval, scroll preservation (onWillPatch/onPatched), useRef
Acceptance criteria:
- In
onMounted, start asetIntervalthat appends a new fake event every 2 seconds. - Show maximum 50 events β splice older ones when limit is exceeded.
- Implement scroll preservation: if user is at the bottom, auto-scroll to new items. If scrolled up, preserve their scroll position so new items don't jump the viewport.
- A "Live" / "Paused" toggle button stops/resumes the interval.
- A "Jump to Latest" button appears when user has scrolled up. Clicking it scrolls to bottom.
- Clean up the interval in
onWillUnmount.
Exercise 8 β Kanban Board (βββ Intermediate)
Build: A multi-column Kanban board with drag-and-drop (or button-based move).
Concepts: Component composition, useState arrays (splice/push), sub-components, events, t-foreach in t-foreach
Acceptance criteria:
- Three columns: To Do, In Progress, Done. Each has its own cards array in state.
- Each card: title, description, priority badge (Low/Medium/High with colors).
- Cards split across two components:
KanbanColumnandKanbanCard. - "Move Right" / "Move Left" buttons on each card move it to adjacent column.
- Add card form at the bottom of each column. Validates non-empty title.
- Total card count displayed per column header. Overall count in the board header.
Advanced (Exercises 9β12)
Exercise 9 β Error Boundary Dashboard (ββββ Advanced)
Build: A dashboard with multiple independent widgets, each wrapped in an error boundary.
Concepts: onError, error boundaries, t-key remount, async error handling, retry patterns
Acceptance criteria:
- Build a reusable
ErrorBoundarywith retry support (increments t-key to remount child). - Four dashboard widgets: Sales Summary, Revenue Chart (simulated), User Stats, Activity Feed.
- Each widget has a 20% random chance of throwing an error in
onWillStart(to simulate failures). - Failed widgets show an error card with the error message, a "Retry" button, and a timestamp.
- Retry increments the t-key, forcing a fresh mount β the new instance re-runs
onWillStart. - Other widgets continue working when one fails β failures are isolated.
Exercise 10 β Async Search with Concurrency Control (ββββ Advanced)
Build: A search component that demonstrates safe concurrent async state management.
Concepts: AbortController, race condition prevention, debouncing, onWillUnmount cleanup
Acceptance criteria:
- Input with 400ms debounce using a custom
useDebouncehook. - Each search call uses AbortController. Previous request is cancelled when a new one starts.
- Simulate network delay with random jitter (200β800ms) to make race conditions visible without the fix.
- Show a loading indicator only while the current request is in flight (not during debounce wait).
- Show cached results from the previous search while loading the new ones (stale-while-revalidate).
- Clean up any pending request and debounce timer in
onWillUnmount.
Exercise 11 β Mini Odoo Field Widget (ββββ Advanced)
Build: A complete Odoo field widget registered in the fields registry.
Concepts: @odoo-module, registry, standardFieldProps, orm, record.update, __manifest__
Acceptance criteria:
- Build a
TagsWidgetfor many2many fields. Shows existing tags as removable chips. - An autocomplete input searches available tags via
orm.searchReadas the user types. - Selecting a suggestion adds it to the field. Clicking β on a chip removes it.
- Respects the
readonlyprop β no add/remove in readonly mode. - Registered in
registry.category("fields")forsupportedTypes: ["many2many"]. - Add to a
res.partnerform view many2many field and test in Odoo.
Exercise 12 β Complete Odoo Client Action (βββββ Advanced)
Build: A full-featured Odoo client action: a KPI dashboard with live data from the ORM.
Concepts: Client action registration, orm, notification, action, user, dialog services, error boundaries, lifecycle
Acceptance criteria:
- Register as a client action (
registry.category("actions")) and add anir.actions.clientrecord. - Load KPI data in
onWillStartvia 3 separateorm.searchReadcalls (sale orders, partners, invoices). - Display metric cards: total revenue this month, new customers this month, overdue invoice count.
- A date range filter (This Week / This Month / This Year) that re-fetches data on change.
- A "Confirm All Drafts" button that calls
orm.callwith a ConfirmationDialog first. - Wrap each metric section in an error boundary. Show a notification on successful bulk confirm.
π Exercise Completion Checklist
- Exercises 1β4: Core OWL (components, state, templates) β can complete after Core Concepts section
- Exercises 5β6: Component design and hooks β complete after Advanced OWL section
- Exercises 7β8: Lifecycle + composition β complete after Lifecycle section
- Exercises 9β10: Error handling + concurrency β complete after Advanced OWL section
- Exercises 11β12: Odoo integration β complete after Odoo Integration section