I'm trying out Ember.JS and I'm having a really tough time using it with Require.JS so far, even with a (pretty) basic example.
First of all, I'd like to say that Require.JS is supposed to (I think) improve two weak points I see in Ember.JS :
Organizing the app, especially in separate js files
Not loading unnecessary code
I'm basically trying to display an app with header/content/footer. So, when I'm creating my App I'm binding a ApplicationController and an ApplicationView, and the view handles the template. This works great in displaying (pretty easily) the header and the footer.
Then, I'm trying to render a template for the index (for example), and I would like to dynamically load IndexView/IndexController (for example) and bind it with a route. That's where I'm having a tough time.
I found an easy way to do this by setting IndexView directly as App.IndexView, but the problem with this solution is that if I load IndexView, I'm also loading the index template file content (using text.js plugin). That would be okay for my example, BUT since I'm trying to build a complex website, that would mean loading all the templates when loading the website, which is exactly what Require.JS was trying to avoid.
Where am I wrong here? How do I dynamically load the template depending on the routing?
EDIT: It's not really needed to declare a placeholder in the main html document as it's injected using view.append().
I've been struggling with the same thing, and I finally came up with a way to split over router, controller, views and templates loading them dynamically.
This is my main "embermain.js" file:
window.MyRanks = Ember.Application.create();
MyRanks.Router.map(
function() {
this.route('about');
}
);
MyRanks.AboutRoute = Ember.Route.extend({
setupController: function(controller, model) {
require(['app/controller/AboutController'], function(controller) {
});
}
});
Here is my AboutController:
require(
['app/view/AboutView'],
function (view) {
var controller = MyRanks.AboutController = Ember.Controller.extend({
});
return controller;
});
Here is my AboutView:
define(
['text!app/templates/about.html'],
function (template) {
var view = Ember.View.create({
template: Ember.Handlebars.compile(template),
templateName: 'about',
variable: 'my value',
didInsertElement: function() {
console.log( "Yes the view was included");
}
});
view.append();
return view;
}
);
And here is the template about.html
This is the template {{view.variable}}
Hope it helps! :)
Related
I'm trying to setup a way to define routes, their template and model dynamically. (Ember 2.16.0)
So far I have this:
// app/router.js
Router.map(function () {
let routes = [
{route: "page_one", templateName: "default.hbs"},
{route: "page_two", templateName: "default.hbs"},
{route: "page_three", templateName: "custom.hbs"}
];
routes.forEach(function (key) {
this.route(key.route);
}, this);
});
This works, and allows me to load pages if their template file exists.
I want to take this a step further and define the template name here, for example I want them all to use "default.hbs" as well as load the model for them dynamically.
Is this something that's possible in EmberJS? If so which documentation should I be looking at to do so.
I'm aware you can specify templates in their corresponding route file "app/routes/page_one.js" but this wouldn't be dynamic.
By default, Ember's approach to routing and templates is to use built-in templates that handle layout of data that comes from a backend. It's perfectly possible do something like this however and end up with your CMS pages being displayed at /page/:slug:
// app/router.js
Router.map(function () {
this.route('page', { path: '/:post_id' });
});
Doing the above would then allow you to set your page route to handle retrieving the appropriate data from your CMS and then display it in your page.hbs (with the option to wrap it with any additional HTML to make it work well in Ember).
Here's one example of an Ember add-on that does something like that. https://github.com/oskarrough/ember-wordpress It also has a working test app that is designed to work against a Wordpress backend that you can study here: https://github.com/oskarrough/ember-wordpress/tree/master/tests/dummy
There are other approaches you could take as well, but this is probably the simplest one. Does that help?
I have been looking to a solution to this for about a week now with no luck. We have an ember application which has a sidebar that is present on all routes which displays a list of user posts. It is important that the posts update in real-time as they are submitted as well as sort with the newest post at the top of the list, which from what I've read will require an array controller. The problem is, I cant find any way (or rather dont understand) to use an array controller and specific model that is not directly referenced to the current route. I have tried rendering the sidebar with the following code in the application route:
Destination.ApplicationRoute = Ember.Route.extend({
model: function(model) {
var self = this;
return new Em.RSVP.Promise(function (resolve, reject) {
new Em.RSVP.hash({
post : self.store.find('post')
}).then(function (results) {
resolve({
post: results.post
});
});
});
},
renderTemplate: function(controller, model) {
this.render();
this.render('sidebars/postBar', {
outlet: 'postbar',
into: 'application',
controller: 'posts',
model: 'post'
});
}
Then I have the following code for my array controller
Destination.PostsController = Ember.ArrayController.extend({
itemController: 'post',
sortProperties: ['id'],
sortAscending: false
});
However this doesnt work at all and I'm having trouble finding any examples of how to accomplish this.
The approach you can use is to load whatever models you need for the entire application in the ApplicationRoute. You don't have to create the RSVP.Promise as you have done, simply return an RSVP.all or RSVP.hash as follows:
Destination.ApplicationRoute = Ember.Route.extend({
model: function(model) {
return Em.RSVP.Hash({
post : self.store.find('post')
// fetch other models as required
});
}
});
Now there are two options for the controller setup and rendering.
Option 1: Outlets and route based controller setup.
The next thing is to setup the appropriate controller and render the view. Assuming you have defined an {{outlet 'sidebar'}} in your application template, the ApplicationRoute can render the sidebar as follows:
Destination.ApplicationRoute = Ember.Route.extend({
setupController: function(controller, model, transition) {
// perform default application controller setup
this._super(controller, model, transition);
// setup sidebar controller model
this.controllerFor('side-bar').set('model', model.posts);
// setup other controllers as required...
},
renderTemplate: function(controller, model) {
// render `posts` template into `side-bar` outlet with `side-bar` controller.
var c = this.controllerFor('side-bar');
this.render('side-bar', { outlet: 'side-bar, controller: c });
// other top level outlet rendering as required...
}
});
Option 2: View helper based controller setup and rendering.
Instead of using additional outlets, we can avoid the need to override setupController or renderTemplate in the route entirely. We can use the handlebars render helper to specify both the model and controller to use directly from our template.
So given your application controller will be setup with the result of the RSVP hash by default, it will contain a 'posts' property on its model/content. Just add the following to your application template:
{{render 'side-bar', posts}}
The above will render the sidebar template and setup the singleton SideBar controller using the posts model for you. I think this is cleaner than messing about with outlets given it doesn't sound like you going to be rendering different views into the sidebar based on your question.
API documentation on the render helper is here, with an overview of the rendering helpers here.
Note I have used Ember-cli resolver naming conventions which use a dasherized naming convention. If you're not using Ember CLI (which I highly recommend) then you may have to use the PascalCased string names ie 'SideBar' instead of 'side-bar'.
Ive got little basic problem with ember.
Here is app: http://emberjs.jsbin.com/qivamuzu/5 (click test - working like a charm, because model is in memory - loaded in index page)
But when you try page #test directly http://emberjs.jsbin.com/qivamuzu/5#/test all the data disappear (and that's bad - does not fired index route and load model). I follow this question Why isn't my ember.js route model being called? but doesn't help me.
I need to use template with model in other templates - I use {{render index}} but I'm not sure what to use and how. Please help me I am stuck.
I'm a little unclear on exactly what you're trying to do.
If you're just trying to use the same model data with a different route (and template) then you can explicitly set the model data to be the same in the route definition:
App = Ember.Application.create();
App.Router.map(function() {
this.route('test');
});
var myModelData = ['red', 'yellow', 'blue'];
App.IndexRoute = Ember.Route.extend({
model : function(){
return myModelData;
}
});
App.TestRoute = Ember.Route.extend({
model : function(){
return myModelData;
}
});
Here's a working JSBin example:
http://emberjs.jsbin.com/qivamuzu/8/edit
EDIT: Additional Information That May Help
Okay, one more stab at it =) When you navigate directly to the test page there is no data because the TestRoute is using the model data from the IndexRoute which hasn't been loaded yet. What you can do is force the creation of the IndexController and model by initializing it from the ApplicationRoute which will always be invoked when you first go to any route in your application.
First you have to generate the controller since it doesn't exist yet.
this.generateController('index');
Then you can get the controller and set its model data:
this.controllerFor('index').set('model', ['red','green','blue']);
Here's a working fiddle and I actually tested it this time to make sure it works when you go straight to #/test. I removed the extra routes and things that aren't actually needed from my previous example.
http://emberjs.jsbin.com/qivamuzu/11#/test
Trying to move over to Ember 1.0.0-rc2, this I'm yet to solve (syntax below is CoffeeScript):
App.Router.map(() ->
#route('EditPrices', path: '/redigera')
)
How would I specify that this route should use the App.Views.EditPrices.EditPricesView, rather than just App.EditPricesView? I've tried specifying 'Views.EditPrices.EditPrices' as the first parameter to the this.route() method, but this gives me absolutely nothing - no error message, no warning, but it doesn't render any content...
One incredibly ugly workaround I've come up with is this:
App.EditPricesView = App.Views.EditPrices.EditPricesView
...but clearly, there must be a better way? And please, don't tell me to put all my views in the root (App) object; that is simply not an option...
Thanks in advance.
Yes, Ember expects the Views to be placed in the App root. Your workaround is the best, i can think of. The only other option would be to use render in the renderTemplate hook of your route, but this would require even more code:
App.PostRoute = App.Route.extend({
renderTemplate: function() {
this.render('myPost', { // the view to render
into: 'index', // the template to render into
outlet: 'detail', // the name of the outlet in that template
controller: 'blogPost' // the controller to use for the template
});
}
});
When rendering a template through a Backbone view you will often end up with some code that looks something like this:
ShirtView = {
template: JST["/templates/shirt_template"],
el: ".shirt-element"
render: function() {
var html = this.template({color: this.model.color, size: this.model.size});
this.$el.html(html);
}
}
This is all well and good and your template will render with the attributes you wanted. But if this.model.color changes then it will not be reflected in the view. You can then use something like modelbinder to explicitly bind elements in the view to your model, but this means introducing extra code to your view.
What I am wondering is if there are any templating engines, like Moustache or Handlebars, that automatically updates the elements belonging to the fields in the attributes object as the model changes, without me having to specify it in the view?
As the comments have suggested, there are several libraries you can use for this ... but I'd like to suggest that you don't need to. I work on a Backbone-powered site with thousands (heck, probably tens or hundreds of thousands) of lines of code, and all we use is our own custom base class.
In essence, all you need to do is:
var TemplatedView = Backbone.View.extend({
render: function() {
this.renderTemplate();
}.
renderTemplate: function() {
this.$el.html(this.template(this.model.toJSON()));
}
});
then you can make any new view a templated one with just:
var ShirtView = TemplatedView.extend({
template: JST["/templates/shirt_template"],
el: ".shirt-element"
});
or, if you have some custom render logic, you just need to call renderTemplate:
var AdvancedShirtView = TemplatedView.extend({
template: JST["/templates/shirt_template"],
el: ".shirt-element",
render: function() {
this.model.fold();
this.renderTemplate();
this.model.press();
}
});
Now we have a few enhancements beyond that (eg. if someone specifies a "rawTemplate" property on one of our views, our renderTemplate will compile it in to a proper template), but that's the beauty of rolling your own solution for something like this: you get exactly what you want.
Or you can use a library :-) But personally, for something that's both so simple and so integral to your site, I don't see why you'd want to.