Templating Engine v9.3.4

Novaxjs2 v9.3.4 includes a powerful templating system with two rendering modes: HTML templates with custom syntax and JavaScript templates for maximum flexibility and control.

Setting Up Views


// Configure view engine (HTML mode)
app.setViewEngine('novax', {
  viewsPath: './views',  // Directory containing templates
  viewsType: 'html',     // 'html' or 'js'
  helpers: {             // Custom helper functions
    formatDate: (date) => new Date(date).toLocaleDateString(),
    uppercase: (str) => str.toUpperCase()
  }
});

// Or for JavaScript templates:
app.setViewEngine('novax', {
  viewsType: 'js',       // Uses JavaScript files for templates
  viewsPath: './templates'
});

// Add custom helpers programmatically
app.addHelper('reverse', (str) => str.split('').reverse().join(''));
app.addHelper('currency', (amount) => `$${amount.toFixed(2)}`);

// Add multiple helpers at once
app.addHelpers({
  truncate: (str, length) => str.length > length ? str.substring(0, length) + '...' : str,
  pluralize: (count, singular, plural) => count === 1 ? singular : plural
});
  

HTML Templates (New Syntax)

HTML templates use the new @ syntax for expressions and special tags for logic.

Basic Syntax

<!-- views/home.html -->
<!DOCTYPE html>
<html>
<head>
  <title>@title</title>
</head>
<body>
  <h1>@heading</h1>
  <p>Current time: @{new Date().toLocaleTimeString()}</p>
  <p>Formatted date: @formatDate(createdAt)</p>
</body>
</html>

Conditional Logic

<!-- If/Else statements -->
@if(user.isAdmin)
  <div class="admin-panel">Admin controls</div>
@else
  <p>Regular user view</p>
@end

<!-- Complex conditions -->
@if((user.age >= 18) && (user.status === 'active'))
  <p>Adult active user</p>
@end

<!-- Else if -->
@if(score >= 90)
  <p>Grade: A</p>
@elif(score >= 80)
  <p>Grade: B</p>
@else
  <p>Grade: C</p>
@end

Loops

<!-- Basic loop -->
<ul>
  @each(items)
    <li>@name - $@price</li>
  @end
</ul>

<!-- Accessing array index -->
@each(items)
  <div class="item-@index">
    <h3>@name</h3>
    <p>Position: @index of @{this.length}</p>
  </div>
@end

<!-- Accessing array items by index -->
@each(items)
  <p>Next item: @{this[index+1]?.name}</p>
@end

Nested Data & Object Access

<!-- Nested object access -->
<p>Shipping to: @user.address.city, @user.address.country</p>

<!-- Array access -->
<p>First item: @{items[0].name}</p>

<!-- JSON output -->
<script>
  const userData = @{JSON.stringify(user)};
</script>

<!-- Using helpers -->
<p>Price: @currency(product.price)</p>
<p>@truncate(description, 100)</p>
<p>@pluralize(items.length, 'item', 'items')</p>

Variable Declaration

<!-- Declare variables -->
@var fullName = user.firstName + ' ' + user.lastName
@var discount = user.isPremium ? 0.2 : 0.1

<p>Welcome, @fullName!</p>
<p>Your discount: @{discount * 100}%</p>

<!-- Object destructuring -->
@var {city, country} = user.address
<p>Location: @city, @country</p>

<!-- Array destructuring -->
@var [firstItem, secondItem] = items
<p>First: @firstItem.name, Second: @secondItem.name</p>

Template Includes

<!-- Include other templates -->
<!DOCTYPE html>
<html>
<head>
  <title>@title</title>
</head>
<body>
  @include('header.html', { user: user })
  
  <main>
    @include('content.html', { 
      content: pageContent, 
      sidebar: true 
    })
  </main>
  
  @include('footer.html')
</body>
</html>

JavaScript Templates (CommonJS style)

For more complex logic, you can use JavaScript templates that export a function or string:

// views/home.js
module.exports = function(data) {
  // Complex logic here
  const welcomeMessage = data.user.isAdmin 
    ? 'Welcome Administrator' 
    : `Welcome ${data.user.name}`;
  
  // Helper function within template
  function renderItems(items) {
    return items.map(item => `
      <div class="item">
        <h3>${item.name}</h3>
        <p>Price: $${item.price.toFixed(2)}</p>
        <p>${item.description}</p>
      </div>
    `).join('');
  }
  
  return `
    <!DOCTYPE html>
    <html>
    <head>
      <title>${data.title}</title>
      <style>
        body { font-family: Arial, sans-serif; }
        .item { border: 1px solid #ccc; padding: 1rem; margin: 1rem 0; }
      </style>
    </head>
    <body>
      <h1>${welcomeMessage}</h1>
      ${renderItems(data.items)}
      <footer>
        <p>Generated on ${new Date().toLocaleDateString()}</p>
      </footer>
    </body>
    </html>
  `;
};

Rendering Templates

// Basic rendering
app.get('/', async (req, res) => {
  const data = {
    title: 'Home Page',
    user: { 
      name: 'John Doe',
      isAdmin: false,
      address: {
        city: 'New York',
        country: 'USA'
      }
    },
    items: [
      { name: 'Product 1', price: 19.99, description: 'A great product' },
      { name: 'Product 2', price: 29.99, description: 'Another great product' }
    ],
    createdAt: new Date()
  };
  
  const html = await app.render('home', data);
  return html;
});

// With error handling
app.get('/profile', async (req, res) => {
  try {
    const userData = await getUserData(req.user.id);
    return await app.render('profile', userData);
  } catch (err) {
    return await app.render('error', { 
      message: 'Failed to load profile',
      error: err.message 
    });
  }
});

// Using with other response methods
app.get('/api/user/:id', async (req, res) => {
  const user = await getUserById(req.params.id);
  if (!user) {
    return res.status(404).render('404', { id: req.params.id });
  }
  return res.render('user-profile', { user });
});

Template Reference (New Syntax)

FeatureSyntaxDescription
Variables@variableOutput variable value
Expressions@{expression}JavaScript expressions
Conditionals@if(condition)...@endConditional rendering
Else/Elif@else
@elif(condition)
Else clauses for conditionals
Loops@each(array)...@endIterate over arrays with index available
Array Access@{this[index]}Access array items by index in loops
JSON Output@{JSON.stringify(data)}Convert objects to JSON
Helpers@helperName(arg)Use custom helper functions
Variable Declaration@var name = valueDeclare template variables
Template Includes@include('template.html', data)Include other templates
JavaScriptmodule.exports = function(data) {...}Full JavaScript templates when using viewsType: 'js'

Advanced Features

// Using third-party view engines
const pug = require('pug');
app.setViewEngine(pug, {
  viewsPath: './views',
  viewsType: 'pug',
  engineOptions: {
    pretty: true
  }
});

// Async helpers
app.addHelper('fetchData', async (url) => {
  const response = await fetch(url);
  return await response.json();
});

// Template inheritance (using JavaScript templates)
// base-template.js
module.exports = function(data, content) {
  return `
    <!DOCTYPE html>
    <html>
    <head>
      <title>${data.title}</title>
    </head>
    <body>
      <header>...</header>
      <main>${content}</main>
      <footer>...</footer>
    </body>
    </html>
  `;
};

// page.js
module.exports = async function(data) {
  const baseTemplate = require('./base-template');
  const pageContent = `<h1>${data.title}</h1><p>${data.content}</p>`;
  return baseTemplate(data, pageContent);
};

What's New in v9.3.4 Templating