📦 Variables in Views
Variables allow you to display dynamic data in your templates. Data is passed from controllers to views using Odac.set() and displayed using the <odac var> tag.
Passing Data from Controller
Use Odac.set() in your controller to pass data to views:
// Controller: controller/profile.js
module.exports = async function(Odac) {
// Set single variable
Odac.set('username', 'John Doe')
// Set multiple variables at once
Odac.set({
user: {
name: 'John Doe',
email: 'john@example.com',
role: 'admin'
},
pageTitle: 'User Profile'
})
Odac.View.skeleton('main').set('content', 'profile')
}
Displaying Variables
HTML-Safe Output (Recommended)
<odac var="username" />
<odac var="user.email" />
<odac var="product.price" />
This automatically:
- Escapes HTML to prevent XSS attacks
- Converts newlines (
\n) to<br>tags
Example:
// Controller
Odac.set('message', 'Hello\nWorld')
<!-- View -->
<odac var="message" />
<!-- Output: Hello<br>World -->
Raw HTML Output
When you need to display HTML content without escaping:
<odac var="htmlContent" raw />
<odac var="user.bio" raw />
Security Warning: Only use raw with trusted content. Never use it with user-generated content to prevent XSS attacks.
Example:
// Controller
Odac.set('content', '<strong>Bold text</strong>')
<!-- View -->
<odac var="content" raw />
<!-- Output: <strong>Bold text</strong> -->
Accessing Nested Properties
You can access nested object properties using dot notation:
// Controller
Odac.set('user', {
name: 'John',
profile: {
email: 'john@example.com',
address: {
city: 'Istanbul'
}
}
})
<!-- View -->
<p>Name: <odac var="user.name" /></p>
<p>Email: <odac var="user.profile.email" /></p>
<p>City: <odac var="user.profile.address.city" /></p>
String Literals
Display static text directly:
<odac>Hello World</odac>
<odac>Welcome to our site</odac>
This is useful when you want consistent syntax throughout your templates.
Accessing the Odac Object
You have full access to the Odac object within templates:
<!-- Authentication -->
<odac:if condition="Odac.Auth.check()">
<p>User ID: <odac var="Odac.Auth.user().id" /></p>
<p>Email: <odac var="Odac.Auth.user().email" /></p>
</odac:if>
<!-- Request Information -->
<p>Method: <odac var="Odac.Request.method" /></p>
<p>URL: <odac var="Odac.Request.url" /></p>
<p>IP: <odac var="Odac.Request.ip" /></p>
<!-- Configuration -->
<odac:if condition="Odac.Config.debug">
<div class="debug-info">Debug mode enabled</div>
</odac:if>
Practical Examples
User Profile Card
// Controller: controller/profile.js
module.exports = async function(Odac) {
// Fetch user from database
const userId = Odac.Request.get('id')
const user = await Odac.DB.users
.where('id', userId)
.first()
// Pass to view
Odac.set('user', {
name: user.name,
email: user.email,
bio: user.bio,
isVerified: user.verified
})
Odac.View.skeleton('main').set('content', 'profile')
}
<!-- View: view/content/profile.html -->
<div class="profile-card">
<h2><odac var="user.name" /></h2>
<p><odac var="user.email" /></p>
<odac:if condition="user.isVerified">
<span class="badge">✓ Verified</span>
</odac:if>
<div class="bio">
<odac var="user.bio" raw />
</div>
</div>
Product Display with Computed Values
// Controller: controller/product.js
module.exports = async function(Odac) {
const productId = Odac.Request.get('id')
const product = await Odac.DB.products
.where('id', productId)
.first()
// Compute values in controller
const hasDiscount = product.discount > 0
const finalPrice = product.price * (1 - product.discount / 100)
Odac.set({
product: product,
hasDiscount: hasDiscount,
finalPrice: finalPrice
})
Odac.View.skeleton('main').set('content', 'product')
}
<!-- View: view/content/product.html -->
<div class="product">
<h1><odac var="product.name" /></h1>
<odac:if condition="hasDiscount">
<p class="original-price">$<odac var="product.price" /></p>
<p class="final-price">$<odac var="finalPrice" /></p>
<span class="discount">-<odac var="product.discount" />%</span>
<odac:else>
<p class="price">$<odac var="product.price" /></p>
</odac:if>
<div class="description">
<odac var="product.description" />
</div>
</div>
Working with Arrays
// Controller: controller/products.js
module.exports = async function(Odac) {
const products = await Odac.DB.products
.where('active', true)
.get()
Odac.set({
products: products,
totalProducts: products.length
})
Odac.View.skeleton('main').set('content', 'products')
}
<!-- View: view/content/products.html -->
<h1>Products (<odac var="totalProducts" />)</h1>
<div class="products-grid">
<odac:for in="products" value="product">
<div class="product-card">
<h3><odac var="product.name" /></h3>
<p>$<odac var="product.price" /></p>
</div>
</odac:for>
</div>
Best Practices
- Always use Odac.set(): Pass all data through
Odac.set()for consistency - Set data before rendering: All
Odac.set()calls should come beforeOdac.View.set() - Compute in controller: Do calculations in the controller, not in views
- Use descriptive names:
pageTitle,userProfileinstead oftitle,data - Group related data: Use objects to organize related data
Good:
// Controller
const user = await Odac.DB.users.first()
const isAdmin = user.role === 'admin'
Odac.set({
user: user,
isAdmin: isAdmin
})
Avoid:
<!-- Don't do complex logic in views -->
<odac:if condition="user.role === 'admin' && user.verified && !user.banned">
...
</odac:if>
Error Handling
Always handle cases where data might not exist:
// Controller
module.exports = async function(Odac) {
const productId = Odac.Request.get('id')
const product = await Odac.DB.products
.where('id', productId)
.first()
if (!product) {
Odac.set('error', 'Product not found')
} else {
Odac.set('product', product)
}
Odac.View.skeleton('main').set('content', 'product')
}
<!-- View -->
<odac:if condition="error">
<div class="alert alert-danger">
<odac var="error" />
</div>
<odac:else>
<div class="product">
<h1><odac var="product.name" /></h1>
</div>
</odac:if>
Inline Syntax ({{ }} and {!! !!})
ODAC provides an inline interpolation syntax that is equivalent to the <odac var> tag. Both compile to the same engine output and are equally supported.
Use {{ }} when the variable appears inside HTML attributes or inline within text:
<!-- Inside attributes — {{ }} keeps markup clean -->
<img src="{{ product.image }}" alt="{{ product.name }}">
<a href="/user/{{ user.id }}" class="btn {{ isActive ? 'active' : '' }}">Profile</a>
<input type="text" name="q" value="{{ searchQuery }}">
<!-- Inline text interpolation -->
<p>Welcome, {{ user.name }}. You have {{ notifications }} new messages.</p>
<span>${{ product.price }}</span>
<!-- Raw inline output (trusted content only) -->
{!! htmlContent !!}
{!! user.bio !!}
Use <odac var> when the variable is the standalone content of an element:
<h1><odac var="pageTitle" /></h1>
<td><odac var="user.email" /></td>
<p><odac var="product.description" /></p>
Both syntaxes are XSS-safe by default (HTML-escaped). The choice is purely about readability. See Template Syntax Overview for the full selection guide.