Ember.js, EmberCLI - removing the hash ( # ) from the URL - ember.js

So according to Ember's documentation Ember defaults to using the hashchange event. Thats why we have the fancy #/some/url setup. We can also set it to use the browser's history API.
I've noticed that most (if not all) sites listed on Built with Ember apparently use the history API. Which makes sense because it make the URL look more natural.
All that is to say I (sorta) understand where, how, and why the # gets tacked on.
My question relates specifically to EmberCLI. I've noticed that when I create a simple app the # is not in the URL. Is that because I havent deployed it yet? Or does the CLI default to the history api? If so, where is this set? I cant find an instance of
App.Router.reopen({
location: 'history'
});

The first one I clicked used hash history ;) https://fnd.io/
By default Ember uses the hash change event, mostly due to cross browser compatibility. http://caniuse.com/history
In ember-cli it uses auto by default. http://emberjs.com/api/classes/Ember.Location.html#toc_autolocation
If you look in router.js you'll notice
var Router = Ember.Router.extend({
location: YourAppENV.locationType
});
which pulls its settings from config/environment.js
module.exports = function(environment) {
var ENV = {
baseURL: '/',
locationType: 'auto',
EmberENV: {
....
Just as a quick plug, location history is a tad more difficult to set up, since you
essentially have to tell your server to serve from the base page whenever it's hit, and ignore anything after that, but it's really just a one time setup.

For locationType: 'auto'
Your routes will be http://localhost:4200/login
For locationType: 'hash'
Your routes will be http://localhost:4200/#/login
That's it.

Related

Ember CLI and Google Analytics

According to the docs, you just need to embed the analytics code and reopen the Router. The latter seems unclear to me.
I've placed the embedded code on index.html, then created the following
app/reopens/router.js
import Router from '../router';
Router.reopen({
notifyGoogleAnalytics: function() {
return ga('send', 'pageview', {
'page': this.get('url'),
'title': this.get('url')
});
}.on('didTransition')
});
app/app.js: Added the import...
import './reopens/router';
This results in a fatal error when viewing the site: Uncaught ReferenceError: ga is not defined
How do you make the ga function visible to this observer?
The problem is that on the first run through of the didTransition, the method is missing as that part of the script has not been executed.
Is this a problem? Actually no. The purpose of didTransition in this instance is to capture when a transition occurs as the supplied javascript from Google with capture to initial local of the page. All is needed is a check to see if 'ga' exists.
Add the recommended javascript to the index.html of your ember app from Google (https://developers.google.com/analytics/devguides/collection/analyticsjs/).
Modify your code to include a check to see if ga is undefined:
import Router from '../router';
Router.reopen({
notifyGoogleAnalytics: function() {
if (typeof ga != 'function') { return; }
return ga('send', 'pageview', {
'page': this.get('url'),
'title': this.get('url')
});
}.on('didTransition')
});
This is variation of what I have for an ember-cli based app. Let me know if it works.
After looking, exhaustively, for a way to plug Google Analytics into Ember, I built my own. For all those still struggling with this issue and not finding the Ember.js v1.0.0 answer helpful, here is how I got it to work on v4.2:
import Route from '#ember/routing/route';
import { action } from '#ember/object';
export default class ApplicationRoute extends Route {
#action
didTransition() {
gtag('set', 'page_path', window.location.pathname);
gtag('event', 'page_view');
return true;
}
}
Forgive my ignorance, as I am new to Ember, but the existing documentation makes it look like didTransition is no longer supported at the router level. That makes +99% of the solutions I found on the internet outdated.
Instead, I invoked didTransition at the route level. By extending Route as ApplicationRoute, I extended the main behavior of every Route. In this example, I execute the gtag event for triggering a page view after setting the page URL. The gtag code from Google simply goes in the index.html file in the head section.
Hopefully, this saves someone from hours of searching.

How to setup development environment for Ember.js + Express

I'm in the process of splitting into two different projects an Ember.js app and its Express REST API counterpart. I assumed that things would be cleaner this way.
Until then, my Express app was both serving REST endpoints and all static files like index.html and app.js. But now, ember-cli is in charge of serving the static files and the Express app handles authentication + REST.
The last issue I'm having is that I now have two different ports: ember-cli uses http://localhost:4200 and express uses http://localhost:3333. When I get the session cookie from Express upon authentication, it's never being sent on subsequent request because of same origin policy (see: How do I send an AJAX request on a different port with jQuery?).
Now if I understand correctly I have two solutions:
Setup Express to support JSONP and make sure Ember uses it too
Install a local Nginx or Apache and setup a proxy pass
The first solution is not ok because after deployment both apps will use the same domain/port. The second solution would probably work but it seems a bit ridiculous to ask developers to install a local web server to simply run an app.
I'm sure many of you have encountered that issue before. What would you suggest to make development easy?
Thanks!
Hmm. Seems like I found another solution:
Following instructions found there: http://discuss.emberjs.com/t/ember-data-and-cors/3690/2
export default DS.RESTAdapter.extend({
host: 'http://localhost:3333',
namespace: 'api',
ajax: function(url, method, hash) {
hash = hash || {}; // hash may be undefined
hash.crossDomain = true;
hash.xhrFields = { withCredentials: true };
return this._super(url, method, hash);
})
});
You will also need to add the following headers in the Express app:
// Add support for cross-origin resource sharing (localhost only)
app.all('*', function(req, res, next) {
if (app.get('env') === 'development') {
res.header('Access-Control-Allow-Origin', 'http://localhost:4200');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
}
next();
});
That's it! Last step is to make sure that Ember uses CORS only in the dev environment.
UPDATE
Ember-cli now has an integrated proxy feature that makes all the above obsolete.
From documentation: "Use --proxy flag to proxy all ajax requests to the given address. For example ember server --proxy http://127.0.0.1:8080 will proxy all your apps XHR to your server running at port 8080."

How to handle invalid routes without slash in Ember.js

At the moment, I handle invalid routes in Ember.js like this:
this.route('invalid', { path: '*path' }
This works and applies to routes like:
https://www.appname.com/#/misspelled_name
However, when using Dropbox Datastores API, I am having some problems. After an authentication request, Dropbox redirects me to:
https://www.appname.com/#access_token=...
Is there a way to handle this route? Without the slash before the route name? In this case, the 'invalid' route is not applied and I receive an error 'The route access_token=... was not found'. How should I handle this response in Ember?
UPDATE
I don't think it is possible to handle this. The only working solution for me was to do authentication before Ember was even loaded. After successful authentication, I load my ember app:
window.dropboxClient = new Dropbox.Client({
key: 'some_key'
});
dropboxClient.authenticate({ interactive: true }, function(error) {
if (error) {
return console.log('Error during authentication');
}
});
yepnope([{
test : window.dropboxClient.isAuthenticated(),
yep : ['my-ember-app.js']
}])
I don't have direct experience with EmberJS, but one possibility might be to run client.authenticate({ interactive: false }); before the EmberJS script is even loaded.
Another alternative is to specify a different redirect URI (not the same page as the rest of your app) and not load EmberJS on that page. (That page would then presumably redirect back to the main app when it was done.) You could also forgo redirects altogether and use the pop-up auth driver instead.
Before the #/, you aren't actually inside of the ember app. As a result, none of the ember code will have any impact on this.

Ember data 1.0.0 Beta: RESTAdapter endpoint customization no longer works

I am converting a project for use with Ember data 1.0.0 Beta 1 (just released). I have a REST adapter listening on a specific endpoint and thus need to customize the endpoint.
This is how it worked in Ember data 0.13:
App.Adapter = DS.RESTAdapter.extend({})
DS.RESTAdapter.reopen({
url: 'https://api.example.com'
});
In Ember data 0.13, the URL became: https://api.example.com/authors
In Ember data 1.0.0, the url becomes: http://192.168.0.108:51939/authors
with /192.168.0.108:51939 the url on which the webapp is running.
It thus looks like the url setting on .reopen of a RESTAdapter no longer works ?
I have the same problem with other customizations of the URL (such as namespace) ...
Hope somebody can help.
Marc
Looks like this was updated soon after #cyclomarc's answer (check the PR https://github.com/emberjs/data/pull/1145). In ember data 'url' is now 'host'. 'namespace' stills works.
DS.RESTAdapter.reopen({
host: 'http://google.com',
namespace: 'api'
});
Sends requests to http://google.com/api/*
Ember v1.0.0-7
Ember Data v1.0.0-beta.1-17
EDIT: This is now documented in TRANSITION.md:
https://github.com/emberjs/data/blob/master/TRANSITION.md#host-and-namespace-configuration
Ember-Data 1.0 beta is a complete rewrite of the API, see the transition guide, which details the changes made
The transition guide mentions that the Adapter API has changed, and adapters will have to be rebuilt. This is likely a breaking change, and the documentation is forthcoming on the endpoint customization
Seems to be a regression. A PR is registered by Paul Chavard.
See https://github.com/emberjs/data/pull/1145
In the meantime, overriding the buildUrl is a solution (see answer from #intuitivepixel)
https://github.com/emberjs/data/blob/master/TRANSITION.md
http://emberjs.com/guides/models/connecting-to-an-http-server/
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'http://api.example.com',
namespace: 'admin'
})
Refer to the links above.
Note that with the current ember-data beta you must call your custom adapter "App.ApplicationAdapter".
Doesn't work if you try "App.Adapter".
Hope that helps!
It seems the RESTAdapter in beta1 has quite a few regressions. I'm looking at it now and so far I see missing:
namespace/url configuration
camelCase to lower_with_underscore attribute mapping
query params on GET url
Non of the above is mentioned in the transition guide (unless I completely missed it).
Having looked into the transition guide, still no mention about that url and namespace are removed from the RESTAdapter, further reading in the source code inline comments still refer it can be used as mentioned in the question. But as #cyclomarc mentioned in his comment (referring to what #tchak13 said that one should now use buildURL), so this is how you could do it overriding the buildURL function:
App.Adapter = DS.RESTAdapter.extend({
buildURL: function(type, id) {
var url = "/" + Ember.String.pluralize(type.typeKey);
if (id) { url += "/" + id; }
return 'https://api.example.com' + url;
}
});
Hope it helps.

what is the role of router in a single page application

I am new to Ember-js, I was recently going through some blog entries and also saw the video of Ember-js introduction by Tom dale.
to summarize they say that Router Api is newly introduced and it the best thing that happened to Ember-js,Router Api is used to manage the state of the application and each state is identified with a URL, now for a single page application where in we use only one URL, what is the role of the router, will there be only one routeing entry which is mapped to '/'(index)? If yes, then we lose the advantage provided by the Router api right?
now for a single page application where in we use only one URL, what is the role of the router, will there be only one routeing entry which is mapped to '/'(index)?
Typically a single page application will still use the url. For example watch url change when using gmail. So in this case single page application means the browser doesn't fetch a new page as url changes. Like gmail, a typical ember single-page application will change url as user navigates to various parts of the application. The ember router takes care of this automatically.
If yes, then we lose the advantage provided by the Router api right?
If you decide not to use the url, and really want it to just stay "/" the whole time, you can still use the router. Just set the router's location type to "none"
See http://emberjs.com/guides/routing/specifying-the-location-api/
I understand that routing here means managing states, but at any point of time user can be in a set of states for instance take gmail the user would be in login state and compose state, how to manages multiple states existing together?
For sure that is true. The ember router is basically a statechart where routes (leaf nodes) are nested under some number of resources. So in the case of gmail for example only a logged in user can be in the compose state.
GMail URL: https://mail.google.com/mail/u/0/?shva=1#inbox
// Gmail Routes:
* /mail - the mail application
* /u/0 - connected account index 0 for the current user
* ?shva=1 - http://stackoverflow.com/questions/1692968/what-is-shva-in-gmails-url
* inbox - folder name
EmberMail Version: https://mail.ember.com/mail/u/0/inbox
// EmberMail Routes
this.resource('mail', { path: '/mail' }, function() {
this.resource('currentUser', { path: '/u' }, function() {
this.resource('account', { path: '/:account_id' }, function() {
this.route('folder', { path: '/:folder_id' });
});
});
});
can you point me to a example application which uses routing extensively?
The best example I know of is discourse. Check out the following for example of how a large ember application uses the ember router:
Discourse Application routes
Discourse Admin routes