ViewComponents are a special kind of View that is designed to be easily composable,
making it simple to add child views and build complex user interfaces.
Unlike views, which are render-agnostic, components have a specific set of rendering
guidelines that allow for a more declarative development style.
Components are defined with the Component.create static method, which takes a tagged template string or a function that returns another component.
Extends: View
See: Component.create
| Param | Type | Description |
|---|---|---|
| options | object |
Object containing options. The following keys will be merged to this: model, state, key, onDestroy, onHydrate, onBeforeRecycle, onRecycle, onBeforeUpdate, onUpdate, onCreate, onChange. Any additional options not in the component or view options list will be automatically extracted as props and stored as this.props. |
Properties
| Name | Type | Description |
|---|---|---|
| [key] | string |
A unique key to identify the component. Components with keys are recycled when the same key is found in the previous render. Unkeyed components are recycled based on type and position. |
| [model] | Model |
A Model or any emitter object containing data and business logic. The component will listen to change events and call onChange lifecycle method. |
| [state] | Model |
A Model or any emitter object containing data and business logic, to be used as internal state. The component will listen to change events and call onChange lifecycle method. |
| [props] | Model |
Automatically created from any options not merged to the component instance. Contains props passed from parent component as a Model. The component will listen to change events on props and call onChange lifecycle method. When a component with a key is recycled during parent re-render, new props are automatically updated and any changes trigger a re-render. |
Example
import { Component, Model } from 'rasti';
// Create Timer component.
const Timer = Component.create`
<div>
Seconds: <span>${({ model }) => model.seconds}</span>
</div>
`;
// Create model to store seconds.
const model = new Model({ seconds : 0 });
// Mount timer on body.
Timer.mount({ model }, document.body);
// Increment `model.seconds` every second.
setInterval(() => model.seconds++, 1000);
ViewSafeHTMLComponentComponentComponentSubscribes to a change event on a model or emitter object and invokes the onChange lifecycle method.
The subscription is automatically cleaned up when the component is destroyed.
By default, the component subscribes to changes on this.model, this.state, and this.props.
Kind: instance method of Component
Returns: Component - The current component instance for chaining.
| Param | Type | Default | Description |
|---|---|---|---|
| model | Object |
The model or emitter object to listen to. | |
| [type] | string |
"'change'" |
The event type to listen for. |
| [listener] | function |
this.onChange |
The callback to invoke when the event is emitted. |
PartialTagged template helper method.
Used to create a partial template.
It will return a Partial object that preserves structure for position-based recycling.
Components will be added as children by the parent component. Template strings literals
will be marked as safe HTML to be rendered.
This method is bound to the component instance by default.
Kind: instance method of Component
Returns: Partial - Partial object containing strings and expressions.
| Param | Type | Description |
|---|---|---|
| strings | TemplateStringsArray |
Template strings. |
| …expressions | any |
Template expressions. |
Example
import { Component } from 'rasti';
// Create a Title component.
const Title = Component.create`
<h1>${({ props }) => props.renderChildren()}</h1>
`;
// Create Main component.
const Main = Component.create`
<main>
${self => self.renderHeader()}
</main>
`.extend({
// Render header method.
// Use `partial` to render an HTML template adding children components.
renderHeader() {
return this.partial`
<header>
<${Title}>${({ model }) => model.title}</${Title}>
</header>
`;
}
});
stringRender the component as a string.
Used internally on the render process.
Use it for server-side rendering or static site generation.
Kind: instance method of Component
Returns: string - The rendered component.
Example
import { Component } from 'rasti';
const Button = Component.create`
<button class="button">Click me</button>
`;
const App = Component.create`
<div>
<${Button}>Click me</${Button}>
</div>
`;
const app = new App();
console.log(app.toString());
// <div data-rst-el="r1-1"><!--rst-s-r1-1--><button class="button" data-rst-el="r2-1">Click me</button><!--rst-e-r1-1--></div>
console.log(`${app}`);
// <div data-rst-el="r1-1"><!--rst-s-r1-1--><button class="button" data-rst-el="r2-1">Click me</button><!--rst-e-r1-1--></div>
ComponentRender the Component.
First render (when this.el is not present):
This is the initial render call. The component will be rendered as a string inside a DocumentFragment and hydrated,
making this.el available. this.el is the root DOM element of the component that can be applied to the DOM.
The onHydrate lifecycle method will be called.
Note: Typically, you don't need to call render() directly for the first render. The static method Component.mount()
handles this process automatically, creating the component instance, rendering it, and appending it to the DOM.
Update render (when this.el is present):
This indicates the component is being updated. The method will:
The onBeforeUpdate lifecycle method will be called at the beginning, followed by the onUpdate lifecycle method at the end.
Child component handling: When rendering child components, they can be either recreated or recycled:
Recreation: A new component instance is created, running the constructor again. This happens when no matching component is found for recycling.
Recycling: The same component instance is reused. Recycling happens in two ways:
Components with a key are recycled if a previous child with the same key exists
Unkeyed components are recycled if they have the same type and position in the template or partial
When a component is recycled:
The onBeforeRecycle lifecycle method is called when recycling starts
The component's this.props is updated with the new props from the parent
The onRecycle lifecycle method is called after props are updated
A recycled component may not use props at all and remain unchanged, or it may be subscribed to a different model (or even the same model as the parent) and update independently in subsequent render cycles.
Kind: instance method of Component
Returns: Component - The component instance.
Lifecycle method. Called when the component is created, at the end of the constructor.
This method receives the same arguments passed to the constructor (options and any additional parameters).
It executes both on client and server.
Use this method to define models or state that will be used later in onHydrate.
Kind: instance method of Component
| Param | Type | Description |
|---|---|---|
| …args | * |
The constructor arguments (options and any additional parameters). |
Lifecycle method. Called when model emits change event.
By default calls render method.
This method can be extended with custom logic.
Maybe comparing new attributes with previous ones and calling
render when needed.
Kind: instance method of Component
| Param | Type | Description |
|---|---|---|
| model | Model |
The model that emitted the event. |
| changed | object |
Object containing keys and values that has changed. |
| […args] | any |
Any extra arguments passed to set method. |
Lifecycle method. Called when the component is rendered for the first time and hydrated in a DocumentFragment. This method only executes on the client and only during the first render. Use this method for client-only operations like making API requests or setting up browser-specific functionality.
Kind: instance method of Component
Lifecycle method. Called before the component is recycled and reused between renders.
This method is called at the beginning of the recycle method, before any recycling operations occur.
A component is recycled when:
key and a previous child with the same key existskey but has the same type and position in the template or partialUse this method to perform operations that need to happen before the component is recycled, such as storing previous state or preparing for the recycling.
Kind: instance method of Component
Lifecycle method. Called when the component is recycled and reused between renders.
A component is recycled when:
key and a previous child with the same key existskey but has the same type and position in the template or partialDuring recycling, the component instance is reused and its props are updated with new values. The component's element may be moved in the DOM if the new template structure differs from the previous one.
Kind: instance method of Component
Lifecycle method. Called before the component is updated or re-rendered.
This method is called at the beginning of the render method when the component's state, model, or props change and trigger a re-render.
Use this method to perform operations that need to happen before the component is updated,
such as saving previous state or preparing for the update.
Kind: instance method of Component
Lifecycle method. Called when the component is updated or re-rendered. This method is called when the component's state, model, or props change and trigger a re-render. Use this method to perform operations that need to happen on every update.
Kind: instance method of Component
Lifecycle method. Called when the component is destroyed. Use this method to clean up resources, cancel timers, remove event listeners, etc.
Kind: instance method of Component
| Param | Type | Description |
|---|---|---|
| …args | * |
Options object or any arguments passed to destroy method. |
SafeHTMLMark a string as safe HTML to be rendered.
Normally you don't need to use this method, as Rasti will automatically mark string literals
as safe HTML when the component is created and when
using the Component.partial method.
Be sure that the string is safe to be rendered, as it will be inserted into the DOM without any sanitization.
Kind: static method of Component
Returns: SafeHTML - A safe HTML object.
| Param | Type |
|---|---|
| value | string |
Helper method used to extend a Component, creating a subclass.
Kind: static method of Component
| Param | Type | Description |
|---|---|---|
| object | object | function |
Object containing methods to be added to the new Component subclass. Also can be a function that receives the parent prototype and returns an object. |
ComponentMount the component into the DOM. Creates a new component instance with the provided options and optionally mounts it into the DOM.
Mounting modes:
If el is not provided, the component is instantiated but not mounted (the same as using new Component(options)). You can mount it later by calling render() and appending the element (this.el) to the DOM.
Kind: static method of Component
Returns: Component - The component instance.
| Param | Type | Default | Description |
|---|---|---|---|
| [options] | object |
{} |
The component options. These will be passed to the constructor and can include model, state, props, lifecycle methods, and any other component-specific options. |
| [el] | node |
The DOM element where the component will be mounted. If provided, the component will be rendered and appended to this element. If not provided, the component is created but not mounted. | |
| [hydrate] | boolean |
false |
If true, enables hydration mode for server-side rendering. The component will assume the DOM already contains its HTML structure and will only hydrate it. If false (default), the component will be rendered from scratch and appended to el. |
Example
import { Component, Model } from 'rasti';
const Button = Component.create`
<button class="${({ props }) => props.className}">
${({ props }) => props.label}
</button>
`;
// Normal mount: render and append to DOM.
const button = Button.mount({
label: 'Click me'
}, document.body);
// Create without mounting (mount later).
const button2 = Button.mount({ className : 'secondary', label : 'Save' });
// Later, render and append it to the DOM.
document.body.appendChild(button2.render().el);
// Hydration mode: hydrate existing server-rendered HTML
// Assuming document.body already contains the HTML structure of the button.
const hydratedButton = Button.mount({
className : 'primary',
label : 'Click me'
}, document.body, true);
ComponentTakes a tagged template string or a function that returns another component, and returns a new Component class.
const Button = Component.create`<button class="button">Click me</button>`;
null, undefined, false, or an empty string, the interpolation won't render any content.const Button = Component.create`
<button class="${({ props }) => props.className}">
${({ props }) => props.renderChildren()}
</button>
`;
Attach DOM event handlers per element using camel-cased attributes.
Event handlers are automatically bound to the component instance (this).
Internally, Rasti uses event delegation to the component's root element for performance.
Attribute Quoting:
Quoted attributes (onClick="${handler}") evaluate the expression first, useful for dynamic values
Unquoted attributes (onClick=${handler}) pass the function reference directly
Listener Signature: (event, component, matched)
event: The native DOM event object
component: The component instance (same as this)
matched: The element that matched the event (useful for delegation)
const Button = Component.create`
<button
onClick=${function(event, component, matched) {
// this === component
console.log('Button clicked:', matched);
}}
onMouseOver="${({ model }) => () => model.isHovered = true}"
onMouseOut="${({ model }) => () => model.isHovered = false}"
>
Click me
</button>
`;
If you need custom delegation (e.g., {'click .selector': 'handler'}),
you may override the events property as described in View.delegateEvents.
attribute="${() => true}". false attributes won't be rendered. true attributes will be rendered without a value.const Input = Component.create`
<input type="text" disabled=${({ props }) => props.disabled} />
`;
// Create a button component.
const Button = Component.create`
<button class="button">
${({ props }) => props.renderChildren()}
</button>
`;
// Create a navigation component. Add buttons as children. Iterate over items.
const Navigation = Component.create`
<nav>
${({ props }) => props.items.map(
item => Button.mount({ renderChildren : () => item.label })
)}
</nav>
`;
// Create a header component. Add navigation as a child.
const Header = Component.create`
<header>
${({ props }) => Navigation.mount({ items : props.items})}
</header>
`;
// Create a button component.
const Button = Component.create`
<button class="button">
${({ props }) => props.renderChildren()}
</button>
`;
// Create a navigation component. Add buttons as children. Iterate over items.
const Navigation = Component.create`
<nav>
${({ props, partial }) => props.items.map(
item => partial`<${Button}>${item.label}</${Button}>`
)}
</nav>
`;
// Create a header component. Add navigation as a child.
const Header = Component.create`
<header>
<${Navigation} items="${({ props }) => props.items}" />
</header>
`;
this.el will be a reference to that child component's element.// Create a button component.
const Button = Component.create`
<button class="${({ props }) => props.className}">
${({ props }) => props.renderChildren()}
</button>
`;
// Create a container that renders a Button component.
const ButtonOk = Component.create`
<${Button} className="ok">Ok</${Button}>
`;
// Create a container that renders a Button component, using a function.
const ButtonCancel = Component.create(() => Button.mount({
className : 'cancel',
renderChildren : () => 'Cancel'
}));
Kind: static method of Component
Returns: Component - The newly created component class.
| Param | Type | Description |
|---|---|---|
| strings | string | function |
HTML template for the component or a function that mounts a sub component. |
| …expressions | * |
The expressions to be interpolated within the template. |
Emitter is a class that provides an easy way to implement the observer pattern
in your applications.
It can be extended to create new classes that have the ability to emit and bind custom named events.
Emitter is used by Model and View classes, which inherit from it to implement
event-driven functionality.
The Emitter class includes "inverse of control" methods (listenTo, listenToOnce, stopListening)
that allow an object to manage its own listening relationships. Instead of:
// Traditional approach - harder to clean up
otherObject.on('change', this.myHandler);
otherObject.on('destroy', this.cleanup);
// Later you need to remember to clean up each listener
otherObject.off('change', this.myHandler);
otherObject.off('destroy', this.cleanup);
You can use:
// Inverse of control - easier cleanup
this.listenTo(otherObject, 'change', this.myHandler);
this.listenTo(otherObject, 'destroy', this.cleanup);
// Later, clean up ALL listeners at once
this.stopListening(); // Removes all listening relationships
This pattern is particularly useful for preventing memory leaks and simplifying cleanup in component lifecycle management.
Example
import { Emitter } from 'rasti';
// Custom cart
class ShoppingCart extends Emitter {
constructor() {
super();
this.items = [];
}
addItem(item) {
this.items.push(item);
// Emit a custom event called `itemAdded`.
// Pass the added item as an argument to the event listener.
this.emit('itemAdded', item);
}
}
// Create an instance of ShoppingCart and Logger
const cart = new ShoppingCart();
// Listen to the `itemAdded` event and log the added item using the logger.
cart.on('itemAdded', (item) => {
console.log(`Item added to cart: ${item.name} - Price: $${item.price}`);
});
// Simulate adding items to the cart
const item1 = { name : 'Smartphone', price : 1000 };
const item2 = { name : 'Headphones', price : 150 };
cart.addItem(item1); // Output: "Item added to cart: Smartphone - Price: $1000"
cart.addItem(item2); // Output: "Item added to cart: Headphones - Price: $150"
functionAdds event listener.
Kind: instance method of Emitter
Returns: function - A function to remove the listener.
| Param | Type | Description |
|---|---|---|
| type | string |
Type of the event (e.g. change). |
| listener | function |
Callback function to be called when the event is emitted. |
Example
// Re render when model changes.
this.model.on('change', this.render.bind(this));
functionAdds event listener that executes once.
Kind: instance method of Emitter
Returns: function - A function to remove the listener.
| Param | Type | Description |
|---|---|---|
| type | string |
Type of the event (e.g. change). |
| listener | function |
Callback function to be called when the event is emitted. |
Example
// Log a message once when model changes.
this.model.once('change', () => console.log('This will happen once'));
Removes event listeners with flexible parameter combinations.
Kind: instance method of Emitter
| Param | Type | Description |
|---|---|---|
| [type] | string |
Type of the event (e.g. change). If not provided, removes ALL listeners from this emitter. |
| [listener] | function |
Specific callback function to remove. If not provided, removes all listeners for the specified type. Behavior based on parameters: - off() - Removes ALL listeners from this emitter - off(type) - Removes all listeners for the specified event type - off(type, listener) - Removes the specific listener for the specified event type |
Example
// Remove all listeners from this emitter
this.model.off();
Example
// Remove all 'change' event listeners
this.model.off('change');
Example
// Remove specific listener for 'change' events
const myListener = () => console.log('changed');
this.model.on('change', myListener);
this.model.off('change', myListener);
Emits event of specified type. Listeners will receive specified arguments.
Kind: instance method of Emitter
| Param | Type | Description |
|---|---|---|
| type | string |
Type of the event (e.g. change). |
| […args] | any |
Optional arguments to be passed to listeners. |
Example
// Emit validation error event with no arguments
this.emit('invalid');
Example
// Emit change event with data
this.emit('change', { field : 'name', value : 'John' });
functionListen to an event of another emitter (Inverse of Control pattern).
This method allows this object to manage its own listening relationships,
making cleanup easier and preventing memory leaks. Instead of calling
otherEmitter.on(), you call this.listenTo(otherEmitter, ...) which
allows this object to track and clean up all its listeners at once.
Kind: instance method of Emitter
Returns: function - A function to stop listening to the event.
| Param | Type | Description |
|---|---|---|
| emitter | Emitter |
The emitter to listen to. |
| type | string |
The type of the event to listen to. |
| listener | function |
The listener to call when the event is emitted. |
Example
// Instead of: otherModel.on('change', this.render.bind(this));
// Use: this.listenTo(otherModel, 'change', this.render.bind(this));
// This way you can later call this.stopListening() to clean up all listeners
functionListen to an event of another emitter and remove the listener after it is called (Inverse of Control pattern).
Similar to listenTo() but automatically removes the listener after the first execution,
like once() but with the inverse of control benefits for cleanup management.
Kind: instance method of Emitter
Returns: function - A function to stop listening to the event.
| Param | Type | Description |
|---|---|---|
| emitter | Emitter |
The emitter to listen to. |
| type | string |
The type of the event to listen to. |
| listener | function |
The listener to call when the event is emitted. |
Example
// Listen once to another emitter's initialization event
this.listenToOnce(otherModel, 'initialized', () => {
console.log('Other model initialized');
});
Stop listening to events from other emitters (Inverse of Control pattern).
This method provides flexible cleanup of listening relationships established with listenTo().
All parameters are optional, allowing different levels of cleanup granularity.
Kind: instance method of Emitter
| Param | Type | Description |
|---|---|---|
| [emitter] | Emitter |
The emitter to stop listening to. If not provided, stops listening to ALL emitters. |
| [type] | string |
The type of event to stop listening to. If not provided, stops listening to all event types from the specified emitter. |
| [listener] | function |
The specific listener to remove. If not provided, removes all listeners for the specified event type from the specified emitter. Behavior based on parameters: - stopListening() - Stops listening to ALL events from ALL emitters - stopListening(emitter) - Stops listening to all events from the specified emitter - stopListening(emitter, type) - Stops listening to the specified event type from the specified emitter - stopListening(emitter, type, listener) - Stops listening to the specific listener for the specific event from the specific emitter |
Example
// Stop listening to all events from all emitters (complete cleanup)
this.stopListening();
Example
// Stop listening to all events from a specific emitter
this.stopListening(otherModel);
Example
// Stop listening to 'change' events from a specific emitter
this.stopListening(otherModel, 'change');
Example
// Stop listening to a specific listener
const myListener = () => console.log('changed');
this.listenTo(otherModel, 'change', myListener);
this.stopListening(otherModel, 'change', myListener);
EmitterA Model manages an internal table of data attributes and triggers change events when any of its data is modified.
Models may handle syncing data with a persistence layer. To design your models, create atomic, reusable objects
that contain all the necessary functions for manipulating their specific data.
Models should be easily passed throughout your app and used anywhere the corresponding data is needed.
preinitialize() is called with all constructor argumentsthis.defaults are resolved (if function, it's called and bound to the model)parse() is called with all constructor arguments to process the datathis.attributes is built by merging defaults and parsed dataExtends: Emitter
| Param | Type | Default | Description |
|---|---|---|---|
| [attributes] | object |
{} |
Primary data object containing model attributes |
| […args] | * |
Additional arguments passed to preinitialize and parse methods |
Properties
| Name | Type | Description |
|---|---|---|
| defaults | object | function |
Default attributes for the model. If a function, it's called bound to the model instance to get defaults. |
| previous | object |
Object containing previous attributes when a change occurs. |
| attributePrefix | string |
Static property that defines a prefix for generated getters/setters. Defaults to empty string. |
Example
import { Model } from 'rasti';
// User model
class User extends Model {
preinitialize() {
this.defaults = { name : '', email : '', role : 'user' };
}
}
// Order model with nested User and custom methods
class Order extends Model {
preinitialize(attributes, options = {}) {
this.defaults = {
id : null,
total : 0,
status : 'pending',
user : null
};
this.apiUrl = options.apiUrl || '/api/orders';
}
parse(data, options = {}) {
const parsed = { ...data };
// Convert user object to User model instance
if (data.user && !(data.user instanceof User)) {
parsed.user = new User(data.user);
}
return parsed;
}
toJSON() {
const result = {};
for (const [key, value] of Object.entries(this.attributes)) {
if (value instanceof Model) {
result[key] = value.toJSON();
} else {
result[key] = value;
}
}
return result;
}
async fetch() {
try {
const response = await fetch(`${this.apiUrl}/${this.id}`);
const data = await response.json();
// Parse the fetched data and update model
const parsed = this.parse(data);
this.set(parsed, { source : 'fetch' });
return this;
} catch (error) {
console.error('Failed to fetch order:', error);
throw error;
}
}
}
// Create order with nested user data
const order = new Order({
id : 123,
total : 99.99,
user : { name : 'Alice', email : 'alice@example.com' }
});
console.log(order.user instanceof User); // true
// Serialize with nested models
const json = order.toJSON();
console.log(json); // { id: 123, total: 99.99, status: 'pending', user: { name: 'Alice', email: 'alice@example.com', role: 'user' } }
// Listen to fetch updates
order.on('change', (model, changed, options) => {
if (options?.source === 'fetch') {
console.log('Order updated from server:', changed);
}
});
// Fetch latest data from server
await order.fetch();
EmitteranyModelobjectobjectCalled before any instantiation logic runs for the Model.
Receives all constructor arguments, allowing for flexible initialization patterns.
Use this to set up defaults, configure the model, or handle custom constructor arguments.
Kind: instance method of Model
| Param | Type | Default | Description |
|---|---|---|---|
| [attributes] | object |
{} |
Primary data object containing model attributes |
| […args] | * |
Additional arguments passed from the constructor |
Example
class User extends Model {
preinitialize(attributes, options = {}) {
this.defaults = { name : '', role : options.defaultRole || 'user' };
this.apiEndpoint = options.apiEndpoint || '/users';
}
}
const user = new User({ name : 'Alice' }, { defaultRole : 'admin', apiEndpoint : '/api/users' });
Generate getter/setter for the given attribute key to emit change events.
The property name uses attributePrefix + key (e.g., with prefix 'attr', key 'name' becomes 'attrname').
Called internally by the constructor for each key in this.attributes.
Override with an empty method if you don't want automatic getters/setters.
Kind: instance method of Model
| Param | Type | Description |
|---|---|---|
| key | string |
Attribute key from this.attributes |
Example
// Custom prefix for all attributes
class PrefixedModel extends Model {
static attributePrefix = 'attr_';
}
const model = new PrefixedModel({ name: 'Alice' });
console.log(model.attr_name); // 'Alice'
// Disable automatic getters/setters
class ManualModel extends Model {
defineAttribute() {
// Empty - no getters/setters generated
}
getName() {
return this.get('name'); // Manual getter
}
}
anyGet an attribute from this.attributes.
This method is called internally by generated getters.
Kind: instance method of Model
Returns: any - The attribute value.
| Param | Type | Description |
|---|---|---|
| key | string |
Attribute key. |
ModelSet one or more attributes into this.attributes and emit change events.
Supports two call signatures: set(key, value, ...args) or set(object, ...args).
Additional arguments are passed to change event listeners, enabling custom behavior.
Kind: instance method of Model
Returns: Model - This model instance for chaining
Emits: change Emitted when any attribute changes. Listeners receive `(model, changedAttributes, ...event:args)`, change:attribute Emitted for each changed attribute. Listeners receive `(model, newValue, ...event:args)`
| Param | Type | Description |
|---|---|---|
| key | string | object |
Attribute key (string) or object containing key-value pairs |
| [value] | * |
Attribute value (when key is string) |
| […args] | * |
Additional arguments passed to event listeners |
Example
// Basic usage
model.set('name', 'Alice');
model.set({ name : 'Alice', age : 30 });
// With options for listeners
model.set('name', 'Bob', { silent : false, validate : true });
model.on('change:name', (model, value, options) => {
if (options?.validate) {
// Custom validation logic
}
});
objectTransforms and validates data before it becomes model attributes. Called during construction with all constructor arguments, allowing flexible data processing. Override this method to transform incoming data, create nested models, or handle different data formats.
Kind: instance method of Model
Returns: object - Processed data that will become the model's attributes
| Param | Type | Default | Description |
|---|---|---|---|
| [data] | object |
{} |
Primary data object to be parsed into attributes |
| […args] | * |
Additional arguments from constructor, useful for parsing options |
Example
// Transform nested objects into models
class User extends Model {}
class Order extends Model {
parse(data, options = {}) {
// Skip parsing if requested
if (options.raw) return data;
// Transform user data into User model
const parsed = { ...data };
if (data.user && !(data.user instanceof User)) {
parsed.user = new User(data.user);
}
return parsed;
}
}
// Usage with parsing options
const order1 = new Order({ id : 1, user : { name : 'Alice' } }); // user becomes User model
const order2 = new Order({ id : 2, user : { name : 'Bob' } }, { raw : true }); // user stays plain object
objectReturn object representation of the model to be used for JSON serialization.
By default returns a copy of this.attributes.
You can override this method to customize serialization behavior, such as calling toJSON recursively on nested Model instances.
Kind: instance method of Model
Returns: object - Object representation of the model to be used for JSON serialization.
Example
// Basic usage - returns a copy of model attributes:
const user = new Model({ name : 'Alice', age : 30 });
const json = user.toJSON();
console.log(json); // { name : 'Alice', age : 30 }
// Override toJSON for recursive serialization of nested models:
class User extends Model {}
class Order extends Model {
parse(data) {
// Ensure user is always a User model
return { ...data, user : data.user instanceof User ? data.user : new User(data.user) };
}
toJSON() {
const result = {};
for (const [key, value] of Object.entries(this.attributes)) {
if (value instanceof Model) {
result[key] = value.toJSON();
} else {
result[key] = value;
}
}
return result;
}
}
const order = new Order({ id : 1, user : { name : 'Alice' } });
const json = order.toJSON();
console.log(json); // { id : 1, user : { name : 'Alice' } }
EmitterA View is an atomic unit of the user interface that can render data from a specific model or multiple models.
However, views can also be independent and have no associated data.
Models must be unaware of views. Views, on the other hand, may render model data and listen to the change events
emitted by the models to re-render themselves based on changes.
Each View has a root element, this.el, which is used for event delegation.
All element lookups are scoped to this element, and any rendering or DOM manipulations should be done inside it.
If this.el is not present, an element will be created using this.tag (defaulting to div) and this.attributes.
Extends: Emitter
| Param | Type | Description |
|---|---|---|
| options | object |
Object containing options. The following keys will be merged into the view instance: el, tag, attributes, events, model, template, onDestroy. |
Properties
| Name | Type | Description |
|---|---|---|
| el | node | function |
Every view has a root DOM element stored at this.el. If not present, it will be created. If this.el is a function, it will be called to get the element at this.ensureElement, bound to the view instance. See View.ensureElement. |
| tag | string | function |
If this.el is not present, an element will be created using this.tag and this.attributes. Default is div. If it is a function, it will be called to get the tag, bound to the view instance. See View.ensureElement. |
| attributes | object | function |
If this.el is not present, an element will be created using this.tag and this.attributes. If it is a function, it will be called to get the attributes object, bound to the view instance. See View.ensureElement. |
| events | object | function |
Object in the format {'event selector' : 'listener'}. It will be used to bind delegated event listeners to the root element. If it is a function, it will be called to get the events object, bound to the view instance. See View.delegateEvents. |
| model | object |
A model or any object containing data and business logic. |
| template | function |
A function that returns a string with the view's inner HTML. See View.render. |
| uid | number |
Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually. |
Example
import { View, Model } from 'rasti';
class Timer extends View {
constructor(options) {
super(options);
// Create model to store internal state. Set `seconds` attribute to 0.
this.model = new Model({ seconds : 0 });
// Listen to changes in model `seconds` attribute and re-render.
this.model.on('change:seconds', this.render.bind(this));
// Increment model `seconds` attribute every 1000 milliseconds.
this.interval = setInterval(() => this.model.seconds++, 1000);
}
template(model) {
return `Seconds: <span>${View.sanitize(model.seconds)}</span>`;
}
render() {
if (this.template) {
this.el.innerHTML = this.template(this.model);
}
return this;
}
}
// Render view and append view's element into the body.
document.body.appendChild(new Timer().render().el);
EmitternodeArray.<node>ViewViewnodeViewViewViewViewstringIf you define a preinitialize method, it will be invoked when the view is first created, before any instantiation logic is run.
Kind: instance method of View
| Param | Type | Description |
|---|---|---|
| options | object |
The view options. |
nodeReturns the first element that matches the selector,
scoped to DOM elements within the current view's root element (this.el).
Kind: instance method of View
Returns: node - Element matching selector within the view's root element (this.el).
| Param | Type | Description |
|---|---|---|
| selector | string |
CSS selector. |
Array.<node>Returns a list of elements that match the selector,
scoped to DOM elements within the current view's root element (this.el).
Kind: instance method of View
Returns: Array.<node> - List of elements matching selector within the view's root element (this.el).
| Param | Type | Description |
|---|---|---|
| selector | string |
CSS selector. |
ViewDestroy the view.
Destroy children views if any, undelegate events, stop listening to events, call onDestroy lifecycle method.
Kind: instance method of View
Returns: View - Return this for chaining.
| Param | Type | Description |
|---|---|---|
| options | object |
Options object or any arguments passed to destroy method will be passed to onDestroy method. |
onDestroy lifecycle method is called after the view is destroyed.
Override with your code. Useful to stop listening to model's events.
Kind: instance method of View
| Param | Type | Description |
|---|---|---|
| options | object |
Options object or any arguments passed to destroy method. |
ViewAdd a view as a child.
Children views are stored at this.children, and destroyed when the parent is destroyed.
Returns the child for chaining.
Kind: instance method of View
| Param | Type |
|---|---|
| child | View |
Call destroy method on children views.
Kind: instance method of View
Ensure that the view has a unique id at this.uid.
Kind: instance method of View
Ensure that the view has a root element at this.el.
You shouldn't call this method directly. It's called from the constructor.
You may override it if you want to use a different logic or to
postpone element creation.
Kind: instance method of View
nodeCreate an element.
Called from the constructor if this.el is undefined, to ensure
the view has a root element.
Kind: instance method of View
Returns: node - The created element.
| Param | Type | Default | Description |
|---|---|---|---|
| tag | string |
"div" |
Tag for the element. Default to div |
| attributes | object |
Attributes for the element. |
ViewRemove this.el from the DOM.
Kind: instance method of View
Returns: View - Return this for chaining.
ViewProvide declarative listeners for DOM events within a view. If an events object is not provided,
it defaults to using this.events. If this.events is a function, it will be called to get the events object.
The events object should follow the format {'event selector': 'listener'}:
event: The type of event (e.g., 'click').selector: A CSS selector to match the event target. If omitted, the event is bound to the root element.listener: A function or a string representing a method name on the view. The method will be called with this bound to the view instance.By default, delegateEvents is called within the View's constructor. If you have a simple events object,
all of your DOM events will be connected automatically, and you will not need to call this function manually.
All attached listeners are bound to the view, ensuring that this refers to the view object when the listeners are invoked.
When delegateEvents is called again, possibly with a different events object, all previous listeners are removed and delegated afresh.
Listener signature: (event, view, matched)
event: The native DOM event object.view: The current view instance (this).matched: The element that satisfies the selector. If no selector is provided, it will be the view's root element (this.el).If more than one ancestor between event.target and the view's root element matches the selector, the listener will be
invoked once for each matched element (from inner to outer).
Kind: instance method of View
Returns: View - Returns this for chaining.
| Param | Type | Description |
|---|---|---|
| [events] | object |
Object in the format {'event selector' : 'listener'}. Used to bind delegated event listeners to the root element. |
Example
// Using prototype (recommended for static events)
class Modal extends View {
onClickOk(event, view, matched) {
// matched === the button.ok element that was clicked
this.close();
}
onClickCancel() {
this.destroy();
}
}
Modal.prototype.events = {
'click button.ok': 'onClickOk',
'click button.cancel': 'onClickCancel',
'submit form': 'onSubmit'
};
// Using a function for dynamic events
class DynamicView extends View {
events() {
return {
[`click .${this.model.buttonClass}`]: 'onButtonClick',
'click': 'onRootClick'
};
}
}
ViewRemoves all of the view's delegated events.
Useful if you want to disable or remove a view from the DOM temporarily.
Called automatically when the view is destroyed and when delegateEvents is called again.
Kind: instance method of View
Returns: View - Return this for chaining.
Viewrender is the core function that your view should override, in order to populate its element (this.el), with the appropriate HTML. The convention is for render to always return this.
Views are low-level building blocks for creating user interfaces. For most use cases, we recommend using Component instead, which provides a more declarative template syntax, automatic DOM updates, and a more efficient render pipeline.
If you add any child views, you should call this.destroyChildren before re-rendering.
Kind: instance method of View
Returns: View - Returns this for chaining.
Example
class UserView extends View {
render() {
if (this.template) {
const model = this.model;
// Sanitize model attributes to prevent XSS attacks.
const safeData = {
name : View.sanitize(model.name),
email : View.sanitize(model.email),
bio : View.sanitize(model.bio)
};
this.el.innerHTML = this.template(safeData);
}
return this;
}
}
stringEscape HTML entities in a string. Use this method to sanitize user-generated content before inserting it into the DOM. Override this method to provide a custom escape function. This method is inherited by Component and used to escape template interpolations.
Kind: static method of View
Returns: string - Escaped string.
| Param | Type | Description |
|---|---|---|
| value | string |
String to escape. |
Reset the unique ID counter to 0. This is useful for server-side rendering scenarios where you want to ensure that the generated unique IDs match those on the client, enabling seamless hydration of components. This method is inherited by Component.
Kind: static method of View