renderView Service
Interface
The service creates an async function that accepts two parameters:
await this.renderView(viewName, params)
Parameters
viewName
(string) - The name/path of the view to render. Can include path separators (/
) for nested views (e.g.,'_layout'
,'_pinstripe/_panel'
,'legal/_page'
)params
(object, optional) - Parameters to pass to the view. Defaults to{}
. These become available asthis.params
within the rendered view.
Return Value
Returns a Promise
that resolves to:
- The rendered content (typically an
Html
instance that can be converted to an HTTP response) undefined
if the view doesn't exist or the view mapping fails
Description
The renderView
service is the core view rendering mechanism in Pinstripe that:
- Resolves view names through the view mapping system (
this.viewMap
) to find the actual view file - Creates view contexts with isolated parameter scopes for each view
- Renders views asynchronously by calling the view's
render()
method - Supports nested rendering where views can render other views recursively
- Provides automatic context management ensuring each view has access to services and parameters
- Handles view resolution returning
undefined
for non-existent views (allowing graceful fallbacks)
The service is available on all views via this.renderView
and is automatically added to the client-side context for use in browser environments.
Examples
Basic View Rendering
// Render a simple view without parameters
export default {
async render() {
return this.renderView('_layout');
}
}
View with Parameters
// Pass parameters to a view
export default {
async render() {
return this.renderView('_layout', {
title: 'Welcome',
body: 'Hello World!'
});
}
}
Nested View Rendering
// Views rendering other views
export default {
async render() {
return this.renderView('_layout', {
title: 'My Page',
body: this.renderView('_section', {
title: 'Content Section',
body: this.renderHtml`<p>Section content here</p>`
})
});
}
}
Complex Nested Structure
export default {
async render() {
return this.renderView('_layout', {
title: 'Complex Page',
body: this.renderHtml`
<div class="container">
${this.renderView('_header')}
<main>
${this.renderView('_sidebar')}
${this.renderView('_content', {
body: this.renderMarkdown(content)
})}
</main>
${this.renderView('_footer')}
</div>
`
});
}
}
Conditional View Rendering
export default {
async render() {
const { user } = this.params;
const isAdmin = user?.role === 'admin';
return this.renderView('_layout', {
body: this.renderHtml`
${() => {
if (isAdmin) {
return this.renderView('_admin_panel', { user });
}
return this.renderView('_user_content', { user });
}}
`
});
}
}
Dynamic View Selection
export default {
async render() {
const { pageable } = this.params;
// Dynamically select view based on object type
const viewName = `_pageables/_${pageable.constructor.name}`;
const result = await this.renderView(viewName, {
[pageable.constructor.name]: pageable
});
// Fallback if specific view doesn't exist
if (result === undefined) {
return this.renderView('_404');
}
return result;
}
}
Async Data Loading with View Rendering
export default {
async render() {
const posts = await this.database.posts.published();
if (await posts.count() > 0) {
return this.renderView('_posts', {
posts,
showLoadMore: true
});
}
return this.renderView('_empty_state', {
message: 'No posts found'
});
}
}
Delegating to Pinstripe Components
export default {
render() {
// Delegate to built-in Pinstripe views
return this.renderView('_pinstripe/_panel', {
title: 'User Settings',
body: this.renderView('_pinstripe/_form', {
model: this.user,
fields: ['name', 'email']
})
});
}
}
Response Manipulation
export default {
async render() {
const response = await this.renderView('_layout', {
title: 'Not Found',
body: this.renderHtml`<p>Page not found</p>`
});
// Convert to response array and modify status
const [status, headers, body] = response.toResponseArray();
return [404, headers, body];
}
}
Integration with Forms and Actions
export default {
async render() {
return this.renderView('_layout', {
body: this.renderView('_panel', {
title: 'Edit Profile',
body: this.renderForm(this.user, {
fields: ['name', 'email', 'bio']
}),
footer: this.renderView('_button', {
href: '/profile',
body: 'Cancel'
})
})
});
}
}
Multi-View Composition
export default {
async render() {
const user = await this.session?.user;
return this.renderView('_layout', {
body: this.renderHtml`
<article>
<header>
${this.renderView('_navbar')}
</header>
<div class="content-wrapper">
<aside>
${this.renderViews('_sidebar/_*')}
</aside>
<main>
${this.renderView('_content', this.params)}
</main>
</div>
<footer>
${this.renderView('_footer')}
</footer>
</article>
`
});
}
}
Error Handling
The service handles missing views gracefully:
export default {
async render() {
// Try specific view first
let result = await this.renderView('custom/_special_layout');
// Fallback to default if view doesn't exist
if (result === undefined) {
result = await this.renderView('_layout');
}
return result;
}
}
View Resolution
Views are resolved through the view mapping system:
- View names are mapped through
this.viewMap[name]
- If no mapping exists,
renderView
returnsundefined
- Views can be organized in nested directories using
/
separators - Special prefixes like
_pinstripe/
refer to framework-provided views
Performance Considerations
- Async rendering: Always
await
renderView
calls when you need the result - Parameter passing: Only pass necessary parameters to avoid unnecessary data transfer
- View caching: The framework handles view instance caching automatically
- Nested efficiency: Prefer composing views over deeply nested HTML templates
Integration Patterns
With Database Models
const post = await this.database.posts.find(id);
return this.renderView('_pageables/_post', { post });
With Services
return this.renderView('_layout', {
body: this.renderTable(this.database.users, {
fields: ['name', 'email', 'role']
})
});
With Client-Side Features
// renderView is available client-side too
return this.renderView('_modal', {
body: await this.renderView('_form', formConfig)
});