Ember-cli uses ES6 syntax to import modules.
This is how you import Ember-Data:
import DS from 'ember-data'
How does Ember-cli know where to import Ember-Data from? This case doesn't seem to fit the naming conventions explained in Using Modules and the Resolver docs. (Or maybe I'm missing something.)
The ‘ember-cli-ember-data’ node module adds ember-data to the generated Ember CLI output (via vendor.js). If you look at this module’s index.js, in the EmberCLIED.prototype.included function, you will see the following references to ember-data in the vendor directory:
EmberCLIED.prototype.included = function included(app) {
this.app = app;
var options = {
exports: {
'ember-data': [
'default'
]
}
};
if (this.app.env === 'production') {
this.app.import('vendor/ember-data/ember-data.prod.js', options); // <--
} else {
this.app.import('vendor/ember-data/ember-data.js', options); // <--
}
};
That’s how Ember CLI knows where to find ember-data.
The bit 'ember-data' refers to, in the case of a base install of Ember-CLI vendor/ember-data/ember-data.js.
This is exactly the same as: import Ember from 'ember'; which refers to vendor/ember/ember.js.
What you call it in the import doesn't matter. That is just a reference to what you are importing.
Related
I recently tried extended the EmberRouter to include the following piece of information.
router.js
import EmberRouter from '#ember/routing/router';
const Router = EmberRouter.extend({
lastVisitedURL: null,
init() {
this._super(...arguments);
this.on('routeWillChange', () => {
this._super(...arguments);
this.lastVisitedURL = this.currentURL;
});
}
// Router.map code (not important)
})
I would now like to extract lastVisitedURL from a controller file. However, I'm not sure how to do it. Some of the things I've tried include importing the EmberRouter directly (I.E.):
import Router from '../router';
export default Controller.extend({
someFunction() {
console.log(Router.lastVisitedURL); // returns undefined
}
});
I'm not perfectly sure what the problem is with this approach, but it appears to be returning me some sort of other object or function that doesn't truly contain the state of the router.
So the next approach, that seems to be a little more accepted, was to try to use the RouterService object that I believe is meant to provide an API to the EmberRouter.
import Router from '../router';
export default Controller.extend({
router: service(),
someFunction() {
console.log(this.router.lastVisitedURL) // returns undefined
}
});
The problem I encountered with this solution though is that even though the routerService can store the state of the EmberRouter, it doesn't store my specific new variable. So I now need a way to add this specific pice of data to the RouterService as well as the EmberRouter.
I'm not really sure how to do this or if there is a better approach to the problem I'm trying to solve. Any thoughts appreciated!
I'm a little bit confused about your use case to be honest. The current URL is available on the RouterService, which is shipped with Ember by default. You could access it like this:
import Controller from '#ember/controller';
import { inject as service } from '#ember/service';
export default Controller.extend({
router: service(),
someFunction() {
console.log(this.router.currentURL);
}
});
It seems like you are trying to reinvent that feature.
If you want to go with declaring a property on the EmberRouter instance and use it at other places you need to look up the router on the container. It's available as router:main. You can't import it directly as it's neither a service nor a controller. The code would look like:
import Controller from '#ember/controller';
import { getOwner } from '#ember/application';
export default Controller.extend({
someFunction() {
let owner = getOwner(this);
let router = owner.lookup('router:main');
console.log(router.currentURL);
}
});
I would not recommend such a pattern. I don't think it's officially supported. As far as I'm aware router:main is private API. So it might be broken in a minor release.
This could be way better addressed by a service:
// app/services/location-history.js
import Service from '#ember/service';
import { inject as service } from '#ember/service';
import { action } from '#ember/object';
export default Service.extend({
router: service(),
updateRoute: action(function() {
this.visitedURLs = [...this.visitedRoutes, this.router.currentURL];
}),
init() {
this.router.on('routeDidChange', this.updateRoute);
this.set('visitedURLs', []);
},
willDestroy() {
this.router.off('routeDidChange', this.updateRoute);
}
});
If you find that syntax hard to read I recommend switching to native ECMAScript classes, which is the default syntax since Ember Octance:
// app/services/location-history.js
import Service from '#ember/service';
import { inject as service } from '#ember/service';
import { action } from '#ember/object';
export default class LocationHistoryService extends Service {
#service router;
visitedURLs = [];
#action
updateRoute() {
this.visitedURLs = [...this.visitedRoutes, this.router.currentURL];
}
constructor() {
this.router.on('routeDidChange', this.updateRoute);
},
willDestroy() {
this.router.off('routeDidChange', this.updateRoute);
}
});
I'd like to reopen Ember or Ember Data framework classes. Using Ember CLI, where is the right place to put these so that they get initialized property? Here's an example of something I'd like to do:
import DS from 'ember-data';
DS.Model.reopen({
rollback: function() {
this._super();
// do some additional stuff
}
});
I think the best way to execute modules that have side effects would be to create an initializer. Something like this:
// app/initializers/modify-model.js
import DS from 'ember-data';
let alreadyRun = false;
export default {
name: 'modify-model',
initialize() {
if (alreadyRun) {
return;
} else {
alreadyRun = true;
}
DS.Model.reopen({
// ...
});
}
};
Initializers are automatically run by Ember-CLI, so there's no need to call them yourself.
EDIT: As Karim Baaba pointed out, it's possible for initializers to run more than once. For an easy way around that, I've included an alreadyRun flag.
Using an initializers is sufficient but isn't a good practice for writing tests as they're ran multiple times.
Here is an example of how to reopen the text field view to clear the input when focusIn is triggered
app/overrides/textfield.js:
import Ember from 'ember';
export default Ember.TextField.reopen({
focusIn: function(evt) {
this._super(evt);
this.set('value', '');
}
});
app/app.js
import './overrides/textfield';
The pattern is very simple and can easily be used for DS.Model
Export your content as an ES6 module:
import DS from 'ember-data';
export default DS.Model.reopen({
rollback: function() {
this._super();
// do some additional stuff
}
});
Put the file with your reopen content somewhere like app/custom/model.js, then import the file in app/app.js like this:
import SuperModel from './custom/model';
Now all your models have the custom code.
I don't understand how I'm supposed to include my custom authenticator and custom authorizor with ember cli.
Where to put it and what to include and how to do it. The cli example for simple-auth provided unfortunately does not cover custom authorizer and authenticator.
The build is successfully, but when running it in the browser, I get the error
TypeError: SimpleAuth.Authenticators is undefined
I'm aware that I'm doing something wrong, but could you please guide me or point me to the right documentation on how to do this, I can't find anything :(
My initializer looks like this:
import Ember from 'ember';
import CustomAuthenticator from "../models/customauthenticator";
export default {
name : 'authentication',
before : 'simple-auth',
initialize : function(container) {
container.register('authenticator:custom', CustomAuthenticator);
//container.register('authorizer:custom', CustomAuthorizer);
}
};
My authenticator looks like this
import Ember from "ember";
import App from '../app';
import SimpleAuth from "simple-auth/authenticators/base";
App.CustomAuthenticator = SimpleAuth.Authenticators.Base.extend({
tokenEndpoint: '/api/RestUser.php/users/core/access/',
restore: function(data) {
[...]
},
authenticate: function(credentials) {
[...]
},
invalidate: function() {
[...]
}
});
What am I missing? Thanks in advance!
Change that to:
...
import Base from "simple-auth/authenticators/base";
export default Base.extend({
...
Can somebody point me to a resource on how to implement a test helper with ember-cli?
Or else a simple explanation?
I know the helpers go in the test/helpers directory, but how do you load them into the integration tests?
Thanks
The only way I found to do this is:
// tests/helpers/controller.js
import Ember from 'ember';
Ember.Test.registerHelper('controller', function (app, name) {
return app.__container__.lookup('controller:' + name);
});
then in my acceptance test:
// acceptance/index-test.js
import Ember from 'ember';
// import our helper (this might be done within helpers/start-app.js to always import all helpers)
import '../helpers/controller';
import startApp from '../helpers/start-app';
// your tests using the helper(s)
But there might be some better way of doing.
I have imported moment.js into my project and it seems to work just fine in my controllers but for some reason it is not working in my routes.
Controller:
// controllers/users.js
import Ember from 'ember';
export default Ember.Controller.extend({
date: function() {
alert(moment().format('X'));
}.property()
...
});
Route:
// routes/users.js
// (Error: /routes/users.js: line 5, col 29, 'moment' is not defined.
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var data = { start: moment().startOf('month').startOf('day').format('X') };
return this.store.find('event', data);
}
});
Brocfile:
var app = new EmberApp();
app.import('vendor/moment/moment.js');
I guess this is a JsHint Error. You may want to add the following comment to your Route code.
/* global moment:true */
import Ember from "ember";
....
Also (from ember-cli documentation):
If you want to use external libraries that write to a global namespace (e.g. moment.js), you need to add those to the predef section of your project’s .jshintrc file and set its value to true. If you use the lib in tests, need to add it to your tests/.jshintrc file, too.
Then you don't have to do it to every file your using moment.js in.