functions in ejs

Joshua picture Joshua · Apr 15, 2013 · Viewed 25.9k times · Source

What I'd like to have is something like this:

app.js (node process, includes etc excluded for brevity but using ejs as rendering engine):

app.get('/', function(req, res){

    var ejsVariables = {
        title : 'ideal ejs function example',
        listData1 : {
            listTitle : 'my list',
            listItems : [
                { name : 'first item', class : 'foo' },
                { name : 'second item', class : 'bar' },
                { name : 'last item', class : 'foo' }                ]
        },
        listData2 : {
            listTitle : 'your list',
            listItems : [
                { name : 'a widget', class : 'foo' },
                { name : 'another widget', class : 'baz' }
            ]
        }
    };

    res.render('index', ejsVariables);
});

index.ejs:

<html>
    <head>
        <title><%= title %></title>
    </head>
    <body>
        <h1><%= title %></h1>

        <% makeList(listData1) %>

        <p>lorem ipsum</p>

        <% makeList(listData1) %>
    </body>
</html>

???


result:

<html>
    <head>
        <title>ideal ejs function example</title>
    </head>
    <body>
        <h1>ideal ejs function example</h1>

        <ul>
            <li class="foo">first item</li>
            <li class="bar">second item</li>
            <li class="foo">another item</li>
        </ul>

        <p>lorem ipsum</p>

        <ul>
            <li class="foo">a widget</li>
            <li class="baz">another widget</li>
        </ul>
    </body>
</html>

Question : what goes in the ??? section and/or should be changed above?


What I've tried so far

Attempt 1

- I really want this to work, it just doesn't

index.ejs

<html>
    <head>
        <title><%= title %></title>
    </head>
    <body>
        <h1><%= title %></h1>
        <% var makeList; %>
        <!-- this variable is declared here as those declared within the include are not
        accessible from the file from which it is called, I'm guessing some sort of 
        function scope is coming into play here -->

        <% include makeList %>

        <% makeList(listData1) %>

        <p>lorem ipsum</p>

        <% makeList(ListData2) %>
    </body>
</html>

makeList.ejs

<% function makeListItem(itemData){ %>
        <li class="<%= itemData.class %>" ><%= itemData.name %></li>
<% } %>

<% makeList = function(data){ %>
    <% if(data){ %>
        <ul>
            <% data.map(makeListItem) %>
        </ul>
    <% } %>
<% } %>

In this situation both makeListItem and makeList are being called, it just appears that due to scoping or something else, when they come to be called they are unable to actually output to the template.


Attempt 2

- this actually works, I just dislike the way I end up using includes instead of some sort of function call.

index.ejs

<html>
    <head>
        <title><%= title %></title>
    </head>
    <body>
        <h1><%= title %></h1>

        <% var data = listData1 %>
        <% include makeList %>

        <p>lorem ipsum</p>

        <% var data = listData2 %>
        <% include makeList %>
    </body>
</html>

makeList.ejs

<% function makeListItem(itemData){ %>
        <li class="<%= itemData.class %>" ><%= itemData.name %></li>
<% } %>

<% if(data){ %>
    <ul>
        <% data.map(makeListItem) %>
    </ul>
<% } %>

Attempt 3

- this basically involved messing around with ejs-locals, however I found the functionality somewhat lacking.

If I'm going to include another npm module in my application I'd really want it to be a more complete solution, but even then in an ideal world I'd rather do without and write it myself.


Apologies for the length of this post, it got out of hand quite quickly. I commend you if you've got this far.

Any input would be greatly appreciated.

Answer

robertklep picture robertklep · Apr 15, 2013

How about this:

// app.js
var ejs = require('ejs');
var fs  = require('fs');

app.locals({
  makeList  : function(list) {
    var template = fs.readFileSync('makeList.ejs', 'utf-8');
    return ejs.render(template, list);
  }
});

// index.ejs
<%- makeList(listData1) %>
  ^ important!

// makeList.ejs
<ul>
  <% listItems.forEach(function(item) { %>
    <li class="<%= item.class %>"><%= item.name %></li>
  <% }); %>
</ul>