In my Ember app, I have a JSON file filled with data in my public directory (I.E. public/data/articles.json.
I have an article route where I would like to load in this data and display it in the front-end. How could I do this successfully? I am willing to use the model hook, however I don't know of a solution off-hand.
I currently have the following code in my controller. However, this doesn't work (most likely because the data is loaded in after it is rendered).
import Controller from '#ember/controller';
import $ from 'jquery';
export default Controller.extend({
init() {
this._super(...arguments);
$.getJSON("/data/articles.json", function (data) {
console.log("test");
console.log(data);
this.articleData = data;
}
})
You can literally just write return $.getJSON("/data/articles.json"); in your routes model hook and then access the data as model in your template/controller.
A bit more elegant would be to use fetch:
async model() {
const res = await fetch('/data/articles.json');
return res.json();
}
Are you accessing that file somewhere else? I would recommend putting the file inside your app folder and importing it. It could be inside app/utils/sample-articles.js or another folder for "static data". That way you don't have to do that extra request.
// app/utils/sample-articles.js
export default {
// Articles
};
Then in your controller you can just import it like:
import Controller from '#ember/controller';
import sampleArticles from 'your-app/utils/sample-articles';
export default Controller.extend({
articleData: sampleArticles
});
Also in your example, you're assigning the value of data to a variable in an inner scope. You would need to refactor that like:
import Controller from '#ember/controller';
import { set } from '#ember/object';
import $ from 'jquery';
export default Controller.extend({
init() {
this._super(...arguments);
$.getJSON("/data/articles.json", (data) => {
console.log("test");
console.log(data);
set(this, "articleData", data); // Use `set`
}
});
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 have the following route that will poll a model and refresh the data at a given interval. What I'm trying to do is trigger an alert when a new record is available in the model. I'm new to this, so I'm having some trouble figuring out how to trigger an alert site-wide without simply triggering it each time the model refreshes. I tried using 'didCreate' in the model, but it doesn't seem to recognize new records.
import Route from '#ember/routing/route';
import Ember from 'ember'
export const pollInterval = 8000 // time in milliseconds
export default Route.extend({
model() {
return Ember.RSVP.hash({
pat: this.store.findAll('pat'),
appt: this.store.findAll('appt')
})
},
getSMS () {
return this.get('store').findAll('smstext')
},
onPoll () {
return this.getSMS()
.then((users) => {
this.set('currentModel', users)
})
},
afterModel () {
let smsPoller = this.get('smsPoller')
if (!smsPoller) {
smsPoller = this.get('pollboy').add(this, this.onPoll, pollInterval)
this.set('smsPoller', smsPoller)
}
},
setupController(controller, models) {
controller.set('huddle', models.huddleappt);
controller.set('pat', models.pat);
}
})
I would recommend to use a service for this use case. You can inject your service wherever you need the data, and in the service you can handle the polling.
You can then display your data like this.
In your component file:
import Component from '#ember/component';
import { inject as service } from '#ember/service';
import { computed } from '#ember/object';
export default Component.extend({
smsService: service(),
smsData: computed('smsService.data')
// ...
And in your template you can access your data with the computed property from your components js file
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.$()
}
});
I'm build a list-view, which renders a list of records in a table. The list-view is build as a reusable mixin, and has a reusable template as well. I want the list-view to be as easy to use as possible, and not have to write too much code, to make it work - but only overwrite what I want to change.
Idealy I only want to tell the controller (or perhaps even better) the router, that it's going to render a list-view, and only render custom template, if I have one defined.
Example:
import Ember from 'ember';
import MixinList from '../../mixins/mixin-list';
export default Ember.Route.extend(MixinList, {
model: function() {
return this.store.find('category');
}
});
Currently I have to write this code, to make the list-view work:
Categories route:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('category');
}
});
Categories controller:
import Ember from 'ember';
import MixinList from '../../mixins/mixin-list';
export default Ember.Controller.extend(MixinList, {
actions: {
itemAction: function(actionName, item) {
if (actionName === 'edit') {
this.transitionToRoute('categories.edit', item.get('id'));
}
}
}
});
Categories template:
<h1>Categories</h1>
{{partial 'mixin-list'}}
Is it possible to setup conventions, so routes which are using a specific mixin, are given a default controller and template, if they arent added to the project by the user?
After some further research (and some fresh eyes), I found the solution:
import Ember from "ember";
export default Ember.Mixin.create({
renderTemplate: function() {
var currentController = this.container.lookup('controller:' + this.routeName);
if (currentController.isGenerated) {
currentController.reopen(MixinList);
this.render('mixin-list-view');
}
else {
this.render();
}
}
});
That allows me to only define the route, and include the mixin, and let that mixin do the magic:
import Ember from 'ember';
import MixinList from '../../mixins/mixin-list';
export default Ember.Route.extend(MixinList, {
model: function() {
return this.store.find('category');
}
});
The important part here, is the renderTemplate method, and the lookup to the currentController. The currentController exposes a property, that tells if it's autogenerated (not explicitly created by the user). In that case, we can overwrite the rendered template, and even add functionallity to the controller - for example by adding a mixin to the controller (.reopen(...)).
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.