I'm developing an app with Ember 1.0.rc3 and I'm loading my precompiled Handlebars template files from my backend server on demand via Ajax requests. To be able to handle this, I created my own template manager, which is pulling the templates from the server and stores them in Ember.TEMPLATES.
Now, this is working pretty good, but for the sake of lazyness I wonder if there is a possibility to hook into some code within the Ember framework and put my template manager as a proxy in front so that every time when Ember does it's magic, its doing this by accessing my manager first?
EDIT:
Here's some code, basically my template manager is just a class, which - as I said - loads the precompiled templates from the server and places them into the Ember.TEMPLATES template cache.
fetchTemplate: function (templateName, templateAlias) {
var alias = templateAlias || templateName;
var retString = '';
// check if the template already exists
if (typeof Ember.TEMPLATES[alias] === 'undefined') {
// create the ajax request object
$.ajax({
type: 'GET',
data: { templateName: templateName },
url: 'template/request',
success: function(data) {
if (typeof data.template === 'string' && data.template !== '') {
var escapedTemplateString =
data.template.replace(/\\n/g, "\\n").replace(/\\r/g, "\\r").replace(/\\t/g, "\\t");
escapedTemplateString = escapedTemplateString.replace(/\s+/g, " ");
// load the template into the Ember.TEMPLATES template cache
Ember.TEMPLATES[alias] = Ember.Handlebars.template(eval("(" + escapedTemplateString + ")"));
retString = alias;
} else {
retString = '';
}
}
});
} else {
retString = alias;
}
return retString;
},
releaseCache: function() {
Ember.TEMPLATES = {};
}
Implementing a custom resolver (or subclassing the default resolver) would let you tap into Ember's process for finding templates. However, I don't think you would be able to successfully integrate your approach because resolving is expected to be synchronous.
Related
I'm trying to refactor my Ember acceptance tests to not use the deprecated authorize method, as it is throwing a warning:
The `authorize` method should be overridden in your application adapter
I checked the docs, and numberous other sources, but they don't actually explain how to migrate my code. Here's what I've got at the moment:
// projectname/app/pods/login/controller.js (excerpt)
export default Controller.extend({
session: service(),
sessionToken: null,
onSuccess: function(res) {
res = res.response;
this.set('sessionToken', res.session);
if (res.state === "authenticated") {
document.cookie = "token="+res.session+";path=/;";
var authOptions = {
success: true,
data : {
session : res.session,
}
};
this.get('session').authenticate("authenticator:company", authOptions);
}
}
});
And this must be the part that I'm meant to get rid of:
// project/app/adapters/application.js (excerpt)
export default DS.RESTAdapter.extend(DataAdapterMixin, {
authorize(xhr) { // This is deprecated! I should remove it
let sessionToken = this.get('session.data.authenticated.session');
if (sessionToken && !isEmpty(sessionToken)) {
xhr.setRequestHeader('Authorization', "Token " + sessionToken);
}
},
});
And here is my test:
import { test, module } from 'qunit';
import { visit, currentURL, find, click, fillIn } from '#ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import { authenticateSession} from 'ember-simple-auth/test-support';
module('moduleName', function(hooks) {
setupApplicationTest(hooks);
test('moduleName', async function(assert) {
// await authenticateSession(this.application); // Never works
// await authenticateSession(); // Never works
await authenticateSession({
authenticator: "authenticator:company"
}); // Works slightly more?
await visit('/my/other/page');
await assert.equal(currentURL(), '/my/other/page');
});
});
REMOVING the authorize method and attempting either of the commented out methods yields:
Error: Assertion Failed: The `authorize` method should be overridden in your application adapter. It should accept a single argument, the request object.
If I use the authenticator block as an arg, then regardless of the presence of the authorize method, I simply get:
actual: >
/login
expected: >
/my/other/page
Which, I assume, is because it did not login.
Leaving the authorize method there, and trying the commented methods yields:
Error: Browser timeout exceeded: 10s
Per the docs you linked above: To replace authorizers in an application, simply get the session data from the session service and inject it where needed.
Since you need the session data in your Authorization header, a possible solution for your use case may look like this:
export default DS.RESTAdapter.extend(DataAdapterMixin, {
headers: computed('session.data.authenticated.session', function() {
const headers = {};
let sessionToken = this.get('session.data.authenticated.session');
if (sessionToken && !isEmpty(sessionToken)) {
headers['Authorization'] = "Token " + sessionToken;
}
return headers;
})
});
This should allow you to dynamically set the Authorization header, without doing so via the authorize method.
Ember Simple Auth, has an excellent community and quickly created a guide on how to upgrade to v3.
The latest version fixes this problem completely - If anyone is having this problem, upgrading to 2.1.1 should allow you to use the new format in your application.js:
headers: computed('session.data.authenticated.session', function() {
let headers = {};
let sessionToken = this.get('session.data.authenticated.session');
if (sessionToken && !isEmpty(sessionToken)) {
headers['Authorization'] = "Token " + sessionToken;
}
return headers;
}),
This problem was only present in 2.1.0.
I am working on a user profile page and I am trying to pass data from the controller to a template helper. Here's my controller:
usersDetailController = RouteController.extend({
waitOn: function () {
Meteor.subscribe('userProfileExtended', this.params._id);
},
data: function(){
console.log('info is ' + this.params._id);
var a = Meteor.users.findOne(this.params._id);
console.log(a);
return Meteor.users.findOne(this.params._id);
},
action: function() {
this.render('Users');
}
});
Here's my template helper:
Template.Users.helpers({
user: function() {
//define user based on the data context from the route controller
}
});
Can someone offer me some guidance on how to pass data that I defined in the controller in the template helper??
Thanks!!
Get rid of the helper and use this pattern instead :
data: function(){
return {
user: Meteor.users.findOne(this.params._id)
};
}
This way you'll be able to reference user in your template because the data context will be set to the result of the route data function.
I have a view that looks like this:
App.StarRatingView = Ember.View.extend({
template: function() {
return new Ember.Handlebars.compile('test')
}
})
This is supposed to insert test in the page, but instead it inserts the definition of the compile() function:
function (context, options) { options = options || {}; var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); var compilerInfo = container.compilerInfo || [], compilerRevision = compilerInfo[0] || 1, currentRevision = Handlebars.COMPILER_REVISION; if (compilerRevision !== currentRevision) { if (compilerRevision < currentRevision) { var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision], compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision]; throw "Template was precompiled with an older version of Handlebars than the current runtime. "+ "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+")."; } else { // Use the embedded version info since the runtime doesn't know about this revision yet throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+ "Please update your runtime to a newer version ("+compilerInfo[1]+")."; } } return result; }
Any ideas why this is happening?
It doesn't expect a function. This should do the trick
App.StarRatingView = Ember.View.extend({
template: Ember.Handlebars.compile('test')
})
Or even better:
App.StarRatingView = Ember.View.extend({
templateName: 'test'
})
Ember will now render the view using the given template name.
You can just set the template property to the contents of the compiled output:
App.StarRatingView = Ember.View.extend({
template: Ember.Handlebars.compile('test')
})
http://emberjs.jsbin.com/uYOvUWU/1/edit
Also see the emberjs docs on using templates in views
My Emberjs app is running slowly so I wanted to precompile my template to ease the runtime a bit. However I'm lost on how to proceed. I read http://handlebarsjs.com/precompilation.html and Emberjs introduction but no, all I could do was just creating a template file as instructed on the site, and I cannot figure out what and how to do with this template file in Emberjs.
How can I precompile templates in Emberjs? What should I do with the template file to use it in Emberjs?
To clarify, Thomas' example as-written is still doing the template compilation at run-time. I think his point, though, was that after you've loaded your precompiled Ember-Handlebars templates you can do this:
MyApp.MyView = Ember.View.extend({
template: Ember.TEMPLATES.mytemplate,
})
The problem with using Handlebars' built-in precompiler is that Ember's Handlebars implementation adds some functionality on top of what Handlebars itself provides, so you'll want to install the ember-precompile package, which provides basically the same interface as the handlebars command-line utility, but using Ember's Handlebars implementation.
This will avoid you having to change all your templateNames to templates and having to add in the Ember.TEMPLATES... in each view, since it automatically updates Ember's built-in template cache.
So, assuming you've already loaded your pre-complied templates.js file as output from ember-precompile templates/*.handlebars -f templates/templates.js, here's a more complete example snippet of a worker import/initialization order:
<script src="/lib/handlebars-1.0.0.beta.6.js"></script>
<script src="/lib/ember-1.0.pre.js"></script>
<script src="/lib/ember-data-latest.js"></script>
<script>
var App = Ember.Application.create();
</script>
<script src="/templates/templates.js"></script>
<script src="/js/models.js"></script>
<script src="/js/views.js"></script>
<script src="/js/controllers.js"></script>
<script src="/js/router.js"></script>
<script>
App.initialize();
</script>
You could also use Grunt.js and a handlebars template compiler. I've used the "grunt-ember-templates" plugin and it works well.
http://gruntjs.com/
https://npmjs.org/package/grunt-ember-templates
Here is a gist showing how to precompile handlebars templates and add the result to the Ember.TEMPLATES object, which Ember consults to resolve named templates.
https://gist.github.com/2013669
I'm using Gulp for builds, and precompiling templates looks like this:
var handlebars = require('gulp-ember-handlebars');
var concat = require('gulp-concat');
var SRC = {
TEMPLATES: ['app/templates/**/*.{hbs,html}']
};
gulp.task('templates', function() {
return gulp.src(SRC.TEMPLATES)
.pipe(handlebars({outputType: 'browser'}))
.pipe(concat('templates.js'))
.pipe(gulp.dest(DEST.SCRIPTS));
});
Then I use the Handlebars runtime library rather than the full version.
Ember-Handlebars: https://www.npmjs.org/package/gulp-ember-handlebars
You can precompile in the client's browser, as Thomas Bartelmess stated.
You can also precompile using handlebars via nodejs (taken from my very own Jakefile):
var Handlebars = require('handlebars');
precompile = (function () {
//Lovingly extracted from Ember's sources.
var objectCreate = Object.create || function (parent) {
function F() {}
F.prototype = parent;
return new F();
},
Compiler = function () {},
JavaScriptCompiler = function () {};
Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
Compiler.prototype.compiler = Compiler;
JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
JavaScriptCompiler.prototype.compiler = JavaScriptCompiler;
JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";
JavaScriptCompiler.prototype.initializeBuffer = function () {
return "''";
};
JavaScriptCompiler.prototype.appendToBuffer = function (string) {
return "data.buffer.push(" + string + ");";
};
Compiler.prototype.mustache = function (mustache) {
if (mustache.params.length || mustache.hash) {
return Handlebars.Compiler.prototype.mustache.call(this, mustache);
} else {
var id = new Handlebars.AST.IdNode(['_triageMustache']);
if (!mustache.escaped) {
mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
}
mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
return Handlebars.Compiler.prototype.mustache.call(this, mustache);
}
};
return function precompile(string) {
var ast = Handlebars.parse(string);
var options = {
knownHelpers : {
action : true,
unbound : true,
bindAttr : true,
template : true,
view : true,
_triageMustache : true
},
data : true,
stringParams : true
};
var environment = new Compiler().compile(ast, options);
return new JavaScriptCompiler().compile(environment, options, undefined, true);
};
}());
strPrecompiledTemplate = item.handlebarsTemplateFolders.map(function (dir) {
console.info("\tProcessing " + dir);
return readdirRecursiveSync(dir).map(function (file) {
console.info("\t\t" + file);
var content = fs.readFileSync(file, 'utf-8');
content = Handlebars.precompile(content);
file = file.replace(/\.[^\.]+$/, '').replace(/^src\//g, '').substr(dir.length).replace(/^\/+/, '');
// Pay attention: The wrap in Ember.Handlebars.template() is important!
return "Ember.TEMPLATES['"+file+"'] = Ember.Handlebars.template("+content+");";
}).join("\r\n");
}).join("\r\n");
You can set the precompiled handlebars output to the template property (not templateName) on you ember view. This is what ember also does under the hood
MyApp.MyView = Ember.View.extend({
templateName: "myViewWhatever",
template: Ember.Handlebars.compile('<p>{{blah}}</p>'),
})
I would like to load additional templates on the fly. Is it possible?
You can register new templates in Ember.TEMPLATES. They will then be available to views.
An excerpt from my code (jQuery Ajax handler):
success: function(data) {
$(data).filter('script[type="text/x-handlebars"]').each(function() {
templateName = $(this).attr('data-template-name');
Ember.TEMPLATES[templateName] = Ember.Handlebars.compile($(this).html());
});
}
That's it.
I was just looking for the same thing and am about to have a play with the snippet below
credit: borismus on github https://gist.github.com/2165681
<script>
/*
* Loads a handlebars.js template at a given URL. Takes an optional name, in which case,
* the template is added and is reference-able via templateName.
*/
function loadTemplate(url, name, callback) {
var contents = $.get(url, function(templateText) {
var compiledTemplate = Ember.Handlebars.compile(templateText);
if (name) {
Ember.TEMPLATES[name] = compiledTemplate
} else {
Ember.View.create({ template: compiledTemplate }).append();
}
if (callback) {
callback();
}
});
}
</script>
I am using requirejs along with text plugin to load handlebar templates dynamically.
r.js optimizer will compile the handlerbar template to text file, which can be loaded easily using requirejs or even ajax