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?
Related
To speak specifically, I have a URL to one page on a website but the URL is somewhat long and ugly.
It might look a little bit like this
mywebsite.com/all-products/hats/golf-caps
however, my marketing team feels this URL is too long and wants me to add a "prettified" url like this:
mywebsite.com/golf-caps
But all it would really do is redirect to the original route.
Is there a way to do this in ember.js? Or is this a bad practice? Ideally, I would only need to modify the router file rather than create a completely new route in my application just to perform a redirect.
If you have control over the server then I would handle this there. It's nice to be able to send the correct 308 permanent redirect header so search indexing lines up and most backend routing solutions seem to have concept this built in.
As far as I'm aware of you need to create a new route with a beforeModel() hook containing the redirect. But I don't think that this adds a lot of complexity. It's basically ember generate route golf-caps and edit the created app/routes/golf-caps.js to contain:
export default class GolfCapsRoute extends Route {
beforeModel() {
this.transitionTo('all-products/hats/golf-caps');
}
}
You may want to delete the route's template app/templates/golf-caps.hbs as that won't be rendered in any case.
If you have more than one redirect, you may want to consider adding a wild-card route that catches all requests. This could do the redirects based on a map for multiple paths and render a not-found page if there isn't any redirect registered for the path.
Ember has resetNamespace (guides).
this.route('all-products', { resetNamespace: true, path: '/' }, function() {
this.route('categories', { resetNamespace: true, path: ':category_id' }, function() {
this.route('subcategories', { path: ':subcategory_id' });
});
});
I'm not sure how resetNamespace will work two levels deep like that though. Especially since you have something meaningful in the second level, i.e.,hats`. Your ember router needs that to know which 'category' to request from the server.
For example golf-caps 'subcategory' could be in two 'categories'. If someone navigated directly to mysite.com/golf-caps your server would have no way to know which golf-caps to show, the ones from hats or the ones from accessories.
The only way you'd be able to collapse them down is if all your subcategories were truly unique. In which case, then yes, do just get rid of the category level.
Assuming you still need the category, I think you'll just be able to get rid of all-products in your URL:
this.route('all-products', { resetNamespace: true, path: '/' }, function() {
this.route('categories', { path: ':category_id' }, function() {
this.route('subcategories', { path: ':subcategory_id' });
});
});
I have an ember app written in pre 1.0 ember. I want to re-write the app in ember 2.0.
In the old codebase I have an LanguageController that have english defintions of all langauge strings, and I have an ajax call to populate the language controller with strings from another language. I then have that controller available as a global variable available in all templates. An ember 2.0 template may look like this:
<label id="lblHomeNextAppointment">{{EEA.lang.home_next_appointment}}</label>
<div class="appointment">
{{model.nextAppointment.formattedDate}}
{{model.nextAppointment.title}}
</div>
I want to put the dynamic language string where it says EEA.lang.home_next_appointment.
How to structure controllers etc. to achieve this? Maybe use a service? Whats the fundamental difference between a controller and a service?
The old (pre 1.0) controller looks like this:
EEA.LanguageController = Ember.ArrayController.extend({
// Default values for language strings
"header_progress": 'Progress',
"header_planned": 'Planned',
"home_next_appointment": "Next Appointment",
...
init: function() {
this._super();
EEA.lang = this; // Shorter to write EEA.lang, than EEA.router.languageController
},
...
You should use library for internationalization. Ember-I18n does exactly what are you looking for.
In your template you can use {{t}} helper:
<label id="lblHomeNextAppointment">{{t 'lang.home_next_appointment'}}</label>
You just need file with translations:
export default {
lang: {
'home_next_appointment': 'whatever'
}
};
I have an ajax call to populate the language controller with strings
from another language.
With Ember-I18n you can define translations at runtime.
For example, you could setup dynamic loading of required translations in beforeModel() hook of application route:
i18n: Ember.inject.service(),
beforeModel() {
let lang = $.cookie('user-lang');
if (!lang) {
lang = 'pl';
}
this.set('lang', lang);
Ember.$.getJSON('/translations/' + lang + '.json', (json) => {
this.get('i18n').addTranslations(lang, json);
});
},
Of course, with approach above you need to have .json translation files under public translations URL path.
There are few approaches for solving this, however in my opinion it's best to use Ember.Service() for this, for instance by creating Translator service (See Service Guide). I would reason this using following arguments:
You could detach Translator service from app and use it in other one
You could connect such service on-demand when needed (components, controllers, other services etc.)
You could eagerly load such service globally by using object initializer if needed
While you still could do it, coupling Route with controller would not requirement anymore
In this scenario you could do something like this:
Translator service:
export default Ember.Service.extend({
lang: 't1', // For reference
current: {},
init: function() {
this.current = this.translations[this.lang];
},
changeLanguage: function(lang) {
this.set('lang', lang);
this.set('current', this.translations[lang]);
},
translations: {
t1: {a: 'T1:A', b: 'T1:B'},
t2: {a: 'T2:A', b: 'T2:B'}
}
});
Injecting in controller
export default Ember.Controller.extend({
translator: Ember.inject.service()
});
And usage in template:
<p>{{translator.current.a}}</p>
See full gist or live example.
Obviously, one could do better, for instance by splitting into Translator (for setup/configuration) and Dictionary (for exposing selected language translations), so that template use would be something like {{Dictionary.index.greeting}}, but it shows Service-based approach to the problem.
I'm trying to build a Tweetdeck-like UI to arrange items from a central library into categories. I really need help wrapping my head around the canonical way of using Ember's router.
Essentially, I have a search UI, which allows the user to open zero or more categories simultaneously. The categories show a list of items, which the user can add to from a central library on the right. By completely ignoring the router and the URL, I have managed to hack together a semi-working proof of concept. Now I want to go back and try to do it the Ember way. Below is a high level sketch of what I am trying to accomplish:
If I understand correctly, the desired URL scheme would be a comma-separate list of model IDs that are currently open. I got a good idea of how to approach that from another question, How to design a router so URLs can load multiple models?.
Unfortunately, there are a few concepts I do not understand:
How do I construct my templates and router, such that the library is displayed with its own model and controller? I assume a named {{outlet}} is the way to go, but I am completely lost when it comes to the renderTemplate configuration. Or perhaps I should use {{render}} instead? In either case, I do not understand the router's role in this situation.
EDIT 1/28: I've added an updated fiddle that includes a standalone library route/template and documents my attempts to render it into the categories template. How does Ember expect me to give the library template its model when I try to embed it into another route? I've tried both {{outlet}} with renderTemplate and {{render}}, but in both cases, I am stuck when it comes to specifying the model.
Using renderTemplate:
App.CategoriesRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('categories');
this.render("library", {
into: "categories",
outlet: "library",
controller: "library",
});
},
});
When my controller receives a request to open a category, how do I communicate that to the router? How is the hash path updated? Who is responsible for loading the appropriate model(s)? I assume I should start with transitionTo or transitionToRoute, but I do not understand the router's role here either. Specific questions:
How do I de-serialize multiple, comma-separated models from the URL? Do I just split on the comma or is there a better way?
Once I get the IDs from the URL, how do I make my model hook return multiple records? Do I just shove them all into an Ember array?
When the controller gets the ID of a new record to open, how do I communicate that to the router?
I've tried to work this out on my own and have read the Ember documentation many times, but I am afraid it is simply over my head. I put together a minimal (currently non-functional) fiddle to outline my thoughts and point out where I am stuck. I would appreciate any help anyone could offer.
this.render does not accept a model parameter, but you could pass the model through the controller property instead, this makes sense to do since the Controller is really a proxy for the model at any rate
App.IndexRoute = Ember.Route.extend({
var self = this,
notesController = self.controllerFor('notes').set('content', self.store.find('notes'));
renderTemplate: function() {
this.render('notes', {
controller: notesController,
into: 'index',
outlet: 'notes'
});
}
});
You could also try something like this from this link.
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
books: this.store.findAll('book'),
genres: this.store.findAll('genre')
});
},
setupController: function(controller, model) {
controller.set('books', model.books);
controller.set('genres', model.genres);
}
});
Here, they load multiple models into one route using Ember.RSVP.hash and then using setupController they set each model (Rails: instance variable?) individually.
I'm assuming using this method that you could load as many models as you needed.
I'm a newbie to Ember Data and all I've done to date is FIXTURE data but today I'm trying to graduate to the real deal and am realising that I don't know enough about how to connect the model and the API's call signature.
Specifically I'd like to be able to call an endpoint GET /activities/[:user_id]/[date]. This would load an array of "Activity" objects but only those for a given date. I know that I can offset the API's directory with:
DS.RESTAdapter.reopen({
namespace: 'api'
});
In my case the api prefix is appropriate. I think I should also be able to get the date component solved by setting up a route something like this:
this.resource('activities', { path: '/activities' }, function() {
this.route('by_date', {path: '/:target_date'});
});
The above is just an educated guess because I'm completely at a loss on how to get the user_id component into the URL signature. Can anyone help? Are there any good tutorials or examples of basic Ember Data use cases?
Also, because I know I'm going to run into this next ... how does one add parameters to the url string (aka, GET /foobar?something=value) versus parameters to the URL itself (like above)?
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
UPDATE
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
I've implemented the suggestions from #intuitivepixel but am still having some problems ...
First off I tried to hard code the values for userId and dateBy:
Router:
this.resource('activities', { path: '/activities' }, function() {
this.route('by_date', {path: '/:user_id/:by_date'});
});
Route:
App.ActivitiesByDateRoute = Ember.Route.extend({
serialize: function(activity) {
return {
userId: 1,
dateBy: "2013-07-01"
};
}
});
Sadly that did not work. I think I understand why -- although I don't have a quick way to fix this -- but more disturbing for me was that when I manually put in the parameters into the URL: http://restful.service.com/api/activities/1/2013-07-01. The results are quite surprising to me:
Initially the debugging messages suggest a success:
This however, is not correct as no network requests are actually made
If you reload the browser, it will now go out and get the Activities but to my surprise it also goes out to find the specified user. Hmmm. That's ok, the user 1 is pulled back successfully.
The Activity, however, is just a GET /activities call which fails because this endpoint needs the user and date qualifier to work. Why weren't these included in the request?
I know that I can offset the API's directory with:
You can also set a different URL of our API if it's the case:
DS.RESTAdapter.reopen({
url: 'http://myapihost.com',
namespace: 'api'
});
This would produce a URL like http://myapihost.com/api/
The above is just an educated guess because I'm completely at a loss on how to get the user_id component into the URL signature.
Following your example and adding the user_id dynamic segment, let's say you have this router map definition:
this.resource('activities', { path: '/activities' }, function() {
this.route('byDate', {path: '/:user_id/:target_date'});
});
and this is your {{linkTo}} helper:
{{#each activity in model}}
{{#linkTo 'activities.byDate' activity}}
{{/each}}
then to build your url out of multiple dynamic segments you could hook into your route's serialize function and returning a hash composed out of the dynamic segments your URL needs:
App.ActivitiesByDateRoute = Ember.Route.extend({
serialize: function(activity) {
return {
user_id: activity.get('userId'),
target_date: activity.get('targetDate')
}
}
});
The code above will generate a URL like /activities/[userId]/[targetDate] when the {{linkTo}} link is clicked. The example assumes that the properties you need to build your URL are available in your Activity model.
Also, because I know I'm going to run into this next ... how does one add parameters to the url string (aka, GET /foobar?something=value) versus parameters to the URL itself (like above)?
This kind of URL queries are not yet supported by the ember framework, but the are good workarounds/projects that try to deal with that missing feature like: https://github.com/alexspeller/ember-query, and if this PR get's merged some day then you will have them also soon in ember, but for the time beeing you could use the above mentioned library to have support for custom queries. For the current status whether the lib get's merged or not have look here: http://discuss.emberjs.com/t/query-string-support-in-ember-router there is a discussion going on.
Hope it helps.
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! :)