Ember.js with external handlebars template - javascript

So, I'm kind of new to Ember.js and it's been a couple of hours since I'm stuck with this. I'm playing with this bloggr client and what I want to accomplish is to load those handlebars templates from external files.
The "about" template should render when the user clicks the about page in the panel.
Here's the code in short so you dont have to dig that much (I believe it'll be easy for experienced users)
Template inside .html as shown in the example
<script type="text/x-handlebars" id="about">
<div class='about'>
<p>Some text to be shown when users click ABOUT.</p>
</div>
Now what I've done is to take that x-handlebar code away from the html page and placed it (without the <script type...>) and then put it in hbs/about.hbs
Now, inside the html page I've done something like this.
$.ajax({
url: 'hbs/about.hbs',
async: false,
success: function (resp) {
App.About = Ember.View.extend({
template: Ember.Handlebars.compile(resp),
});
}
});
The result of the ajax holds the content of the .hbs page, then I have to compile it so Ember can render it, right? Think that's what I did. But this is as far as I've come. Is what I've done right? What's the next move? I believe I have to append the content of the ajax call to the body or so.
Thanks in advance, after searching through SO I still wasn't able to make it run.

You can attach a template to the object of available templates simply like so:
Ember.TEMPLATES.cow = Ember.Handlebars.compile("I'm a cow {{log this}}");
Or in your case maybe something like this:
var url = 'hbs/about.hbs',
templateName = url.replace('.hbs', '');
Ember.$.ajax({
url: url,
async: false,
success: function (resp) {
Em.TEMPLATES[templateName] = Ember.Handlebars.compile(resp);
}
});
Here's a lazy example of it being done in the application ready:
http://emberjs.jsbin.com/apIRef/1/edit
To be honest precompiling the templates beforehand (server side) is more performant for the end user.
Precompiling takes the raw handlebars and turns it into a plethora of javascript statements for use when building your views.
When the DOM is ready Ember scans the DOM for script elements of type "text/x-handlebars" and calls compile on their contents. It then adds the results to the Ember.TEMPLATES object with the name from the data-template-name attribute. This can add some completely unnecessary load time to the application.
For example when we sent in "I'm a cow {{log this}}" it was converted into the following javascript method
function anonymous(Handlebars,depth0,helpers,partials,data /**/) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {};
var buffer = '', hashTypes, hashContexts, escapeExpression=this.escapeExpression;
data.buffer.push("I'm a cow ");
hashTypes = {};
hashContexts = {};
data.buffer.push(escapeExpression(helpers.log.call(depth0, "", {hash:{},contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data})));
return buffer;
}
minimized to something ugly like this:
function anonymous(e,t,n,r,i){this.compilerInfo=[4,">= 1.0.0"];n=this.merge(n,Ember.Handlebars.helpers);i=i||{};var s="",o,u,a=this.escapeExpression;i.buffer.push("I'm a cow ");o={};u={};i.buffer.push(a(n.log.call(t,"",{hash:{},contexts:[t],types:["ID"],hashContexts:u,hashTypes:o,data:i})));return s}
Depending on what your back-end is you can compile and bundle your templates before hand and send them down in the html so you can avoid spending time compiling the templates client side.

Related

Can't Render EJS Template on Client

I'm coding an application on express, and I'm using ejs as a view/template engine.
At path /artists, I'm rendering the view artists.ejs which has artists covers. When clicking on a cover, I want an AJAX call to retrieve the corresponding data, place it in my template/view for artist artist.ejs and display this template in my HTML under the cover.
I've seen this related question but it has not solved my use case.
Everything seems clear, but I can't render the data with the template. I would like to compile the template server-side, send it to the client ready to use, and then fill it in when needed with the data received from the AJAX call.
What I've done:
When calling /artists, compile on server-side using ejs.compile(str, opt):
router.get('/artists', function(req, res) {
// Compile artist template
fs.readFile('views/artist.ejs', "utf-8", function(err, template) { // Convert template file to string
artist_template = ejs.compile(template); // Compile template
res.render('artists.ejs', {template: artist_template}); // render page with compiled template
});
I took care of converting the file into String, as ejs compiler only works with String (compared to Jade .compileFile)
Then on client-side, I grab the function:
<script>
var template = <%= template %>
</script>
Then on another script, I retrieve the data with an AJAX call:
$.get('/artists/'+artist_name, function(data) {
var html = template({artist: data});
$('#artist-page').html(html);
}
But when I make the call, I receive:
Uncaught ReferenceError: fn is not defined
When I call the template, fn, I receive:
Uncaught ReferenceError: opts is not defined.
Is the function fn hard-coded? I've read the EJS and Jade documentation but there was little relevant information in regards to my issue.
Do I perhaps need the template on client-side also?
I eventually found a workaround to my question, and I understood with your answer that you could proceed in 2 different ways:
1) What I did: read and save template as a string, then render it client-side with ejs Runtime script.
// In controller.js
var templates = {};
templates.template1 = fs.readFileSync(filePath1, 'utf-8'); // Read template as a string
templates.template2 = fs.readFileSync(filePath2, 'utf-8');
...
res.render('app.ejs', {templates: templates}); // Send templates in view
// In view app.ejs
<script type="text/javascript">
var templates = <%- JSON.stringify(templates) %>; // Get templates object (object of strings)
</script>
<script type="text/javascript" src="/JS/ejs.min.js"></script> <!-- Load ejs RunTime -->
// In site.js - javascript client/public file
$.get('/artists', function(data) {
var html = ejs.render(templates.template1, data); // Render ejs client side with EJS script (template1 corresponds to the artists template)
$('#artists-wrapper').html(html); // Sets HTML
});
Thus, I send all my templates on first page load, and then I render the requested page on the client side. The interest, according to what I've read, is that you only send JSON object (your data) through AJAX calls, and not the entire page, making your request light. Only the first load is heavy with all your templates.
2) What I would like to do according to #RyanZim answer: compiling templates server side into functions, send them, and then call them on the client side : template(data). If I understood well, there is no need of EJS client library in this case, and my templates are no longer strings but functions:
// In controller.js
var templates = {};
templates.template1 = ejs.compile(fs.readFileSync(filePath1, 'utf-8'), {client: true}); // Get template as a function
templates.template2 = ejs.compile(fs.readFileSync(filePath2, 'utf-8'), {client: true});
...
res.render('app.ejs', {templates: templates}); // Send templates in view
However, I can't get them in my view:
<script type="text/javascript">
var templates = <%- JSON.stringify(templates) %>; // Get templates object (object of functions)
</script>
is not working. they are functions on the server before I send them, but I don't know how to recover them. Do you have an idea ?
I tried a workaround, by changing them into String before sending them:
templates.template1 = templates.template1.toString();
Send them and then client side, transform them back in functions:
var template = new Function(templates.template1);
$.get('/artists', function(data) {
var html = template(data);
$('#artists-wrapper').html(html); // Sets HTML
});
But that won't work either.
Do you have an idea what I'm missing here?
And last, do you agree that compiling them server side before using the functions is better in terms of computation than rendering each template client-side?
Thanks for the help, and hope that will help anybody else!
You need to use the client option on the server side when you are compiling for the client. From the docs:
client When true, compiles a function that can be rendered
in the browser without needing to load the EJS Runtime
https://github.com/mde/ejs#options
Your server-side code snippet should be:
// Compile artist template
fs.readFile('views/artist.ejs', "utf-8", function(err, template) {
artist_template = ejs.compile(template, {client: true}); // Use client option
res.render('artists.ejs', {template: artist_template});
});

Dynamic use of g:message in Javascript

is there an easy way to use the g:message functionality in a dynamic way in Javascript, e.g.
function get_i18n( myAttr ) {
return "${message(code:'" + myAttr + "')} ";
}
so that I can perform the function call
pl_get_i18n( "xyz" )
for the predefined i18 attribute xzy ?
Like here, but dynamic: https://stackoverflow.com/a/8296812/1779814
PS: The JS code is included in the GSP file.
The short answer is "no". GSP tags can only be executed on the server-side, not by the browser (i.e. JavaScript).
However, I would expect there is at least one Grails plugin that does the following:
creates a JavaScript object containing the messages defined in your messages*.properties file(s)
provides a JavaScript function that enables you to resolve messages from this object
So although it's not possible to execute GSP tags in the browser, it doesn't seem terribly difficult to provide equivalent functionality in JavaScript. I would be amazed if there isn't already a Grails plugin that does this.
Here is a very simplistic example of how you can use AJAX to fetch a message code from the server.
// AjaxMessageController.groovy
package example
import grails.converters.JSON
class AjaxMessageController {
def index() {
render [message: message(code: param.code)] as JSON
}
}
Then within your page you can just use an ajax call (jQuery based) in this example to look up a message code:
var someMessageCode = 'something.you.want';
$.ajax({
dataType: 'json',
url: '${createLinK(controller: "ajaxMessage", action: "index"}',
data: {code: someMessageCode},
success: function(data) {
window.alert(data.message);
}
});

Piping data from a file to a rendered page in Sails.js

My application needs to read in a large dataset and pipe it to the client to manipulate with D3.js. The problem is, on large datasets, the reading/loading of the file contents could take a while. I want to solve this using streams. However, I'm unsure of how to do so in the context of the Sails framework.
What I want to do is read the contents of the file and pipe it to a rendered page. However, I can't figure out how to pipe it through if I use something like res.view('somePage', { data: thePipedData });.
I currently have something like this:
var datastream = fs.createReadStream(path.resolve(DATASET_EXTRACT_PATH, datatype, dataset, dataset + '.csv'));
datastream.pipe(res);
...
return res.view('analytics', { title: 'Analytics', data: ??? });
What's the best way to approach this?
Based on your example it seems like the best course of action would be to set up a separate endpoint to serve just the data, and include it on the client via a regular <script> tag.
MyDataController.js
getData: function(req, res) {
/* Some code here to determine datatype and dataset based on params */
// Wrap the data in a Javascript string
res.write("var theData = '");
// Open a read stream for the file
var datastream = fs.createReadStream(
path.resolve(DATASET_EXTRACT_PATH, datatype, dataset, dataset + '.csv')
);
// Pipe the file to the response. Set {end: false} so that res isn't closed
// when the file stream ends, allowing us to continue writing to it.
datastream.pipe(res, {end: false});
// When the file is done streaming, finish the Javascript string
datastream.on('end', function() {
res.end("';");
});
}
MyView.ejs
<script language="javascript" src="/mydata/getdata?datatype=<%=datatype%>&etc.."></script>
MyViewController.js
res.view('analytics', {datatype: 'someDataType', etc...});
A slight variation on this strategy would be to use a JSONP-style approach; rather than wrapping the data in a variable in the data controller action, you would wrap it in a function. You could then call the endpoint via AJAX to get the data. Either way you'd have the benefit of a quick page load since the large data set is loaded separately, but with the JSONP variation you'd also be able to easily show a loading indicator while waiting for the data.

Hiding HTML template from client side

I want to keep my client-side code as clean and minimal as possible. My project uses Knockout JS and this template engine: https://github.com/blueimp/JavaScript-Templates. I've created the template at the bottom of my source code with the computed values rendering in a "pre" section per the teamplate (that I don't really want visible). Is there a way to hide the template and call it when the DOM loads?
To load your templates from the server, put them in their own little .html files and then on the client, you can use jquery to fetch them into a variable:
// The variable that we're going to put the template in
var myTemplate;
$.ajax({
async: false,
type: "GET",
url: "/templates/myTemplate.html",
success : function(data) {
myTemplate = data;
}
});
Then when you want to use the template, you can call templ() with the template:
var result = tmpl(myTemplate, data);
Note: I've forced jquery to be synchronous using async:false because you want to make sure the template is there when you call on it.

Javascript to read file names under a directory

I have placed some XML files under a directory. I need to populate a drop down using javascript to display all those XML file names. How do I do that?
You've been way too broad when it comes to circumstances and situation, so I'll be broad back with an answer.
Given the following:
The web-page with the <select> you need to populate is hosted on the same server as the file list.
The server has the ability to use a server-side language (e.g. PHP, ASP)
You don't mind, or can at least decipher jQuery code (makes what I'm about to post more about the concept than the practice)
You will need something like the following setup:
Create a server-side file that dumps a list of file names
You're going to need to look up some way to retrieve and dump the list of the files. This is so JavaScript & AJAX can go fetch this list and dump in in to the drop-down list. Example output of said script (which I'm aliasing as /server-side-file-list in the JavaScript below)
file-001.xml
file-002.xml
file-003.xml
file-004.xml
Setup the <select> on your page
<!-- Somewhere in the page -->
<select id="xml-file-list" name="xml-file-list"></select>
Setup the JavaScript/Ajax code
<!-- This should go in the <head></head> portion of your page with the select -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
$.ajax({
url: '/server-side-file-list',
success: function(d){
$.each(d.split(/[\r\n]+/),function(i,e){
$('<option />',{ value: e }).text(e).appendTo('#xml-file-list');
});
}
});
</script>
Basic work-flow:
HTML page loads up with an empty <select>
jQuery takes over and fetches a list of files from that /server-side-file-list script using AJAX (behind the scenes)
The results are returned and placed in to the <select> as <option>s.
Done.
--
Food for thought:
A better method may be to load your file list in to the page at run time (if possible). That is to say, if the page you're working on is an ASP or PHP or other type of server-side language page, you can retrieve the file list when the page is called upon and load it at that time (and avoid using javascript altogether).
Assuming you're talking about local files, you need a browser that supports the W3C FileSystem API.
You can test for compatibility at www.html5test.com
If you're completely agnostic about the name of xml file you will need a server-side language to get a list of file of a server directory.
Otherwise, if you're in control of filenames, a workaround could be give them a progressive name (e.g. 1.xml, 2.xml...) and try to make some chained ajax HEAD calls.
If the ajax request of 1.xml returns a 200 server status, then ask for 2.xml and so on, until you get a 404 error. For each ajax call you can add the name to an array and then use it to create a dynamic select.
Of course, for a matter of performance, this should be intended only as a workaround and it's not reliable at all, since a 404 could occur even if the resource exists, so I strongly suggest to use a server side language
anyway this is a jQuery 1.5+ example explaining the workaround. Suppose you have 1.xml, 2.xml and 3.xml. The HTML is just an empty select
<select id="xmlfiles"></select>
Javascript/jQuery
<script>
$(document).ready(function() {
/**
* we need to only know if the resource exists or not
*/
$.ajaxSetup({ type : "head" });
/**
* Function reading xml file. When deferred has been resolved
* (when first 404 occurs) an array of filename is passed.
*/
function getXMLFile(path) {
var deferred = $.Deferred(),
xmlFiles = [];
(function getFile( i ) {
var url = [path, i, '.xml'].join('');
$.when($.ajax(url))
.done(function() {
xmlFiles.push(url);
getFile( ++i );
})
.fail(function() {
deferred.resolve(xmlFiles);
});
}(1));
return deferred.promise();
};
/**
* when we have read xmlfiles in "./" then for each file
* retrieved append an option
*/
$.when(getXMLFile('./')).then(function(file) {
var select = $('#xmlfiles');
/* create options */
$.each(file, function(i, filename) {
var option = $('<option></option>');
option.html(filename).attr('value', filename).appendTo(select)
})
});
});
</script>
and this shows a select with options
./1.xml
./2.xml
./3.xml
(Sorry for my verbosity)

Categories