Props and events
A parent component often holds browser data that a child shows: an order page knows the current status, and a badge displays it. The other way round, a child often needs to tell its parent what the user did: a color picker reports which color was picked. Vue props carry values down to a child, and Vue events carry news back up, without a call to the server.
This page covers what you write on a child's component tag in the parent's template, and what the child declares in its $component({...}) options. For the data a component keeps for itself, see Component options.
Pass props to a child
A prop is a value the parent passes to a child in the browser. The child declares the props it takes in its $component({...}) options:
$component({
props: {
status: {
type: String,
required: true,
},
},
});
A short form, such as props: { status: String }, declares only the type.
The parent passes a browser value with : or v-bind:
<c-StatusBadge :status="currentStatus" />
currentStatus comes from the parent's data(), setup(), or js_data(). When the parent changes it, the child updates in the browser.
The editor checks the props you pass on a child's tag. It reports a prop the child does not declare, a missing required prop, and a value of a type the prop does not accept. citry check reports the missing and wrong-type cases too. See Child component props.
The : prefix is what makes status a Vue prop. A plain attribute, such as status="ok", is a Python input instead: Citry passes it to the child's Kwargs when the server renders the child, and it does not change later in the browser.
Listen to child events
An event is a message a child sends to its parent in the browser. The child declares the events it sends in emits and sends one with Vue's $emit:
$component({
emits: ["select"],
methods: {
choose(color) {
this.$emit("select", color);
},
},
});
<button type="button" @click="choose('#7f56d9')">Purple</button>
<button type="button" @click="choose('#12b76a')">Green</button>
The parent listens with @ on the child's tag:
<c-ColorPicker @select="chooseColor" />
chooseColor runs in the parent, the component whose template contains the <c-ColorPicker> tag. It receives the color the child passed to $emit.
This listener runs JavaScript in the browser. To run a Python handler on the server when the child emits select, write @c-select instead; see Bind events in templates.
Typed event payloads
The value a child passes with an event is its payload. To tell the editor what the payload looks like, write emits as an object. Each key is an event name, and the @type comment on the function's parameter gives the payload's type:
$component({
emits: {
"drop-task"(/** @type {{taskId: number}} */ payload) {
return true;
},
closed: null,
},
});
- A
nullvalue, as forclosed, allows any payload. - The array form, such as
emits: ["drop-task"], allows only the listed names, with any payload. - Without
emits, the child may emit any name, as in Vue.
The editor uses these types in the parent too. In <c-Lane @drop-task="move($event)" />, $event has the payload's type. So does the parameter of an inline function such as @drop-task="(payload) => move(payload)", and $event in an @c-drop-task binding on that tag. Inside this.$emit(""), completion offers the declared names.
The function only types the payload. The Vue build Citry loads does not call it while the page runs, so return true and do not rely on it to reject a value.
When emits is an array of strings, or an object whose keys are written out as names rather than computed, the editor and citry check also check event names:
- Emitting a name that
emitsdoes not list is an error:citry.browser.undeclared-emit. - Listening on a child for a name the child does not declare is a warning:
citry.browser.undeclared-component-event.
A prop named on<Event>, such as onPing for ping, also declares the event, as in Vue.
See Emitted event names for the details.
Vue on component tags
A Citry component tag accepts these Vue directives:
| Directive | What it does on a component tag |
|---|---|
:name, v-bind | Passes a Vue prop to the child. |
@event, v-on | Listens for an event the child emits. v-on="listeners" adds every listener in an object. |
v-if, v-else-if, v-else | Adds or removes the component. |
v-model | Passes a value and updates it when the child asks. |
v-show | Hides or shows the child's root element. |
| A custom directive | Runs on the child's root element. |
Other directives fail when the template loads, and the error says what to write instead:
- To pass content into the child, put
<c-fill name="...">inside the component tag, notv-slotor#name. See Content and slots. - To repeat a component, use
<c-for>, notv-for; seev-foron components. - For
v-htmlorv-text, pass a prop or a fill that the child renders. v-onceandv-memofail everywhere; seev-onceandv-memo.
v-model on a child
v-model on a component tag passes the value as the modelValue prop. It updates the value when the child emits update:modelValue. Here query is the parent's browser data:
<c-SearchField v-model="query" />
modelValue is a Vue prop, not a Python input, so SearchField declares it, along with the update:modelValue event, in $component({...}) in its js, not in Kwargs:
from citry import Component
class SearchField(Component):
template = """
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
"""
js = """
$component({
props: ["modelValue"],
emits: ["update:modelValue"],
});
"""
An argument names a different prop. Modifiers reach the child in a <name>Modifiers prop:
<c-TitleEditor v-model:title.trim="pageTitle" />
Here the child declares title and titleModifiers, and emits update:title. Vue applies .trim and .number itself. For .lazy or a modifier of your own, the child reads the modifiers prop and decides.
v-if on a child
v-if works on a component tag as on an element, and one chain can mix both:
<c-OrderSummary v-if="step === 'review'" />
<p v-else-if="step === 'empty'">Your cart is empty.</p>
<c-CheckoutForm v-else />
Python still renders every component in the chain, so the browser can switch between them without asking the server. When Python should decide whether a component exists at all, use <c-if>; see Choose <c-if> or v-if.
v-show on a child
v-show and custom directives act on the element at the root of the child's template. The parent registers a custom directive in its own js. Here OrderPanel registers tooltip and uses it on the StatusBadge in its template:
from citry import Component
class OrderPanel(Component):
template = """
<c-StatusBadge
v-show="expanded"
v-tooltip:top="statusHelp"
:status="currentStatus"
/>
"""
js = """
$component({
data() {
return {
expanded: true,
statusHelp: "Updated every hour",
currentStatus: "shipped",
};
},
directives: {
tooltip: {
mounted(el, binding) {
el.title = binding.value;
},
},
},
});
"""
If nothing registers the name, the page's Vue app stops and the browser console shows an error that names the directive and the component. To register a directive for every component, use a plugin; see Plugins and the Vue app.
The child's template must render exactly one root element, or the render fails; see v-show needs one root.
v-for on components
A natural first attempt at a list of components is v-for:
{# Fails: v-for cannot create Citry components #}
<c-StatusBadge
v-for="item in items"
:status="item.status"
/>
The template fails when it loads, with an error that says what to write instead. Repeat the component with <c-for> over a Python value, and pass each item's data as a Python input:
<c-for each="item in items">
<c-StatusBadge c-status="item.status" />
</c-for>
Pass HTML attributes
A plain attribute on a component tag is a Python input. To let the template that uses a component set HTML attributes such as class or aria-label, accept a mapping as an input and spread it onto the element that should get them with c-bind:
<c-Card c-attrs="{'class': 'featured', 'aria-label': label}" />
{# Inside Card #}
<article c-bind="attrs">
<c-slot />
</article>
This happens in Python, while the server renders the page. The mapping carries plain HTML attributes only, not Vue bindings. To combine it with the component's own attributes, see HTML attributes.
Forward attributes
Vue has its own way to pass attributes along, which works in the browser. Attributes and listeners that the parent writes on a child's tag, and that the child does not declare as props or events, are collected in the child's $attrs. By default, Vue adds them to the child's root element.
To place them on a specific element or component instead, set inheritAttrs: false and spread $attrs there:
$component({
inheritAttrs: false,
});
<header>Wrapper content</header>
<c-Child v-bind="$attrs" />
Declared props and declared events do not appear in $attrs.
To attach an object of Vue listeners, use v-on with the object:
<c-Child v-on="listeners" />
See also
- Content and slots for content you pass into a child, and which component's data it reads.
- Component options for
data(), methods, andjs_data(). - Bind events in templates for
@c-*handlers that run Python on the server. - VS Code for completion and checks in Vue code.