How to load external JS file in component using ember-cli - ember.js

I have created an ember app with a component, and I am trying to figure out how I can load a external JS file that is stored in the vendor dir of the app within the component. The code for the component looks like the following,
// app/components/bubbles-page.js
import Ember from 'ember';
export default Ember.Component.extend({
didInsertElement: function() {
// this.$() is a scoped selector to the current view
// bubbles();
// window.onload = bubbles();
// the below line gives the following error,
this.$().bubbles();
// ERROR
//TypeError: this.$(...).bubbles is not a function
// END ERROR
}
});
And the ember-cli-build.js looks like the following,
/* global require, module */
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
module.exports = function(defaults) {
var app = new EmberApp(defaults, {
// Add options here
//
});
// Use `app.import` to add additional libraries to the generated
// output files.
//
// If you need to use different assets in different
// environments, specify an object as the first parameter. That
// object's keys should be the environment name and the values
// should be the asset to use in that environment.
//
// If the library that you are including contains AMD or ES6
// modules that you would like to import into your application
// please specify an object with the list of modules as keys
// along with the exports of each module as its value.
app.import( app.bowerDirectory + '/d3/d3.min.js');
app.import('vendor/bubbles.js');
return app.toTree();
};

Your ember-cli-build.js seems ok.
As for your component file, you can use Ember.run.scheduleOnce('afterRender', callback) to wait for the DOM to render and then call your jQuery plugin.
Also, normally with jQuery plugins, you would have to pass a jQuery selector with which to call the function.
Try the following:
// app/components/bubbles-page.js
import Ember from 'ember';
export default Ember.Component.extend({
didRender() {
this.$('.my-bubbles-container').bubbles();
}
});
Replace .my-bubbles-container with your jQuery selector. I am not familiar with the jQuery plugin you're trying to use but normally that's how jQuery plugins would be used.
You can read more about how to initialize a jQuery component on [this blog post][1].
Updated answer
After learning that your bubbles.js file wasn't a jQuery plugin and that it was a function that operates on the global window, you can call it like this:
// app/components/bubbles-page.js
import Ember from 'ember';
export default Ember.Component.extend({
didRender() {
bubbles();
}
});
If your function is a global function, you will need to add /* global bubbles */ to your component file, or add bubbles to the predef array in the .jshintrc file to avoid the JSHint errors.

This might be a silly question but did you restart your ember serve after adding the import to ember-cli-build? Changes to the Brocfile or ember-cli-build won’t be picked up until you restart.
Another thing you could try is to add { type: 'vendor'} to your import statement: app.import('vendor/bubbles.js', {type: 'vendor'});

Related

How do I add a CSS class to the application view in Ember 2.7 or higher?

Views are gone since Ember 2.0.0, but you could do this:
// app/views/application.js or app/application/view.js
import Ember from 'ember';
export default Ember.Component.extend({
classNames: []
});
Since Ember CLI 2.7 this workaround no longer works, looks like the views folder is now being ignored. However, the Ember inspector still shows this for the application view:
view:foobar#view:toplevel
And the HTML is:
<div id="ember420" class="ember-view">
<h2>application</h2>
</div>
It still is a view, there must be a way to customize it.
You can use jQuery (via Ember.$) to manually add the class at some point in the application's startup. This will work:
// in app/routes/application.js
export default Ember.Route.extend({
// ...
activate() {
this._super(...arguments);
let root = Ember.getOwner(this).get('rootElement');
Ember.$(root).addClass('my-custom-class');
},
// ...
});
Since you are asking about the application route, there is no need to clean up after this in the deactivate hook.
This is a specialization of something I've done to facilitate tweaking of styles depending on the current route. Here's an instance initializer that will add a route--xyz class to the root element for the current route hierarchy:
import Ember from 'ember';
function toCssName(routeName) {
return `route--${routeName.dasherize().replace(/\./g, '-')}`;
}
export function initialize(appInstance) {
Ember.Route.reopen({
activate() {
this._super(...arguments);
Ember.$(appInstance.rootElement).addClass(toCssName(this.routeName));
},
deactivate() {
this._super(...arguments);
Ember.$(appInstance.rootElement).removeClass(toCssName(this.routeName));
}
});
}
export default {
name: 'css-route-name',
initialize
};
With this initializer, the root element will always have the class route--application in addition to any other active routes.
Problem can be solved via css:
body > .ember-view {
height: 100%;
}
As jquery selector 'body > .ember-view' should work too
Seems like the best option is to add a component to the application template. What makes it the best solution is that you don't need extra addons or hacks.
More context: https://github.com/emberjs/ember.js/issues/11486
// application template
{{#app-view-substitute}}
{{outlet}}
{{/app-view-substitute}}
Note: Not convenient for a large app where other developers have already made assumptions about the level of nesting of the elements. This adds one more level to every single element and CSS, even when carefully crafted, might break. An alternative is to: https://stackoverflow.com/a/40187809/7852
What I ended up doing is:
// top level component rendered in the application template
didInsertElement: function() {
this._super(...arguments);
this._addIdAndCSSClassToApplicationView();
},
_addIdAndCSSClassToApplicationView: function() {
let root = Ember.getOwner(this).get('rootElement'); // Ember >= 2.3
let applicationView = root.querySelector('.ember-view:first-child');
let idclass = 'myRequiredName';
applicationView.id = idclass;
let classes = applicationView.className;
applicationView.className = classes + ' ' + idclass;
}
Wrapping the outlet is cleaner, this is a hack, but it makes sense in a large app where other have already made assumptions about the levels of nesting in the app.
With Ember 2.2 I had to change the root line to something like this:
let root = Ember.$('.ember-application');

How to include javascript files using 'import name from "module-name"' syntax in Ember.js?

I see that Ember.js includes files using 'import name from "module-name"' syntax. For example, in app.js:
import Ember from 'ember';
import Resolver from 'ember/resolver';
import loadInitializers from 'ember/load-initializers';
import config from './config/environment';
I want to include my JS files using this method. But I don't know how to do that. Where should I put my JS files? Or should I do something else?
Here is an example.
My component:
//file app/components/small-logo.js
import Ember from 'ember';
import Logo from 'library/logo';
export default Ember.Component.extend({
mouseEnter: function() {
var logo = new Logo();
logo.changeColor();
}
});
Logo - is a big class, that has many functions. On mouseEnter event I want change the logo's color. Also, I want to start animation when this component shows, but I don't know how to execute a function at this time, it will be another one question.
Here is my component's template:
//file app/templates/components/small-logo.hbs
{{#link-to 'index'}}
<img alt="Logo" src="img/pixel.gif" class="logo">
{{/link-to}}
Here is my js file with class Logo. I don't know how to include it:
//file app/library/logo.js
var Logo = function() {
...
}
Logo.prototype.changeColor = function() {
...
}
In order to be able to import a function/module you must export it in the file, adding an export default Logo; will make it available for import though.
I also recommend using absolute paths to reference it, in your case applicationName/library/logo.
Another more ember-ish way to do what you are trying to accomplish would be to move the logic into the component.
export default Ember.Component.extend({
mouseEnter: function() {
this.changeColor();
},
changeColor: function() {
// your color change logic, you can access the element via this.$()
}
});
And while we're at it, since you are using the ember-cli you can use ES6 syntax:
const { Component } = Ember;
export default Ember.Component.extend({
mouseEnter() {
this.changeColor();
},
changeColor() {
// your color change logic, you can access the element via this.$()
}
});

How to create a inject helper for something other than service?

https://github.com/emberjs/ember.js/blob/5fd2d035b30aa9ebfe73de824b3b283ec8e589cc/packages/ember-runtime/lib/system/service.js#L31
In the line I reference above the ember-core team imports this createInjectionHelper and uses it to add a clean/simple api for injecting services like so
App.ApplicationRoute = Ember.Route.extend({
authManager: Ember.inject.service('auth'),
model: function() {
return this.get('authManager').findCurrentUser();
}
});
How can I create something like this myself for a non service?
Your example usage will change slightly from what you have above. We will cover what the injectRepositories does in a little bit.
import injectRepositories from 'app/utils/inject';
export default Ember.Route.extend({
repository: injectRepositories('person'),
model: function() {
var repository = this.get('repository');
return repository.find();
}
});
The initializer can be improved with the following changes:
import registerWithContainer from "ember-cli-auto-register/register";
export function initialize(_, application) {
registerWithContainer("repositories", application);
application.inject("repositories", "store", "store:main");
}
export default {
name: "repositories",
after: "store",
initialize: initialize
};
Let's break down what is happening in each line.
registerWithContainer("repositories", application);
In the line above, we are deferring to the ember-addon ember-cli-auto-register. This addon will take a directory, in this situation, the repositories directory and register each object into the Ember container to able to be accessed via a lookup. They will be inserted as if doing the following:
application.register("repositories:person", PersonRepository);
Then we add a function to do the injection using the ember-addon ember-cli-injection:
// app/utils/inject.js
import inject from "ember-cli-injection/inject";
var injectRepositories = inject("repositories");
export default injectRepositories;
This then allows us the opportunity to use the newly created function above to access these objects with the code below:
import injectRepositories from 'app/utils/inject';
export default Ember.Route.extend({
repository: injectRepositories('person'),
model: function() {
var repository = this.get('repository');
return repository.find();
}
});
Since each object is now in the container, we can look it up and inject at runtime instead of during the initialization of the application. We register the repositories key in the function and this then returns a computed property (see code below from ember-cli-injection). We do this as a computed property to allow lazy loading. The object is not fetched from the container until the property is accessed.
import Ember from 'ember';
var injection = function(key) {
return function(name) {
return Ember.computed(function(propertyName) {
var objectName = name || propertyName;
return this.container.lookup(key + ':' + objectName);
});
};
};
export default injection;
We also allow for a name to passed to the repositories function, for example repository: injectRepositories('person'). This allows you to name your object whatever you would like when injecting it.
If you would like to just name the object the same as the name of the repository injected into the container you can alternatively do person: injectRepositories(). This will pass the person key to the computed property as the propertyName and since the name was left null when injecting, the objectName will instead be person. This matches the API produces similar results but is not the same as that of the Ember.inject.service and Ember.inject.controller API that is available as of Ember 1.10.
I don't think it's their intention for you to use it this way. The standard way is to use App.inject() if you're using plain ember, or do this in an initializer if you're using ember-cli.
In your case:
// Register the service
App.register('service:auth', {
findCurrentUser: function() {
// Do your magic
}
}, { instantiate: false });
App.inject('route', 'auth', 'service:auth');
Then in your model hook you can use this.auth.findCurrentUser();. You can also inject this into controllers and components if you need. Also note, to keep it clean, that you might want to include a separate module instead of defining your auth module in the service registration.
More info here:
http://emberjs.com/guides/understanding-ember/dependency-injection-and-service-lookup/#toc_dependency-injection-with-code-register-inject-code
NOTE
A service is also not a "special" thing. You can inject anything you want into pretty much anything you want using this method.

Ember CLI: where to reopen framework classes

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.

Ember CLI - Error when using moment.js in route

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.