I have data that looks roughly like this:
"id": "1",
"slug": "WD",
"name": {
"en": "Working Draft",
"de": "Arbeitsentwurf",
"fr": "Version de travail",
"ja": "草案",
"ru": "Рабочий черновик"
}
And I am passing the name object to a component:
{{title-name name=model.name lang='en'}}
In the component template I would like to output the number of translations
<p>Translated {{translationCount}} times.</p>
I tried a few different things in my component to come up with this total but none of them work. How would I count the number of objects?
export default Ember.Component.extend({
// did not work:
translationCount: Ember.computed.alias('Object.keys(name).length'),
// did not work:
// translationCount: Ember.computed.alias('name.length'),
});
Being a little more explicit about it seems to work:
export default Ember.Component.extend({
translationCount: Ember.computed('name', function() {
return Object.keys('name').length;
})
});
Check out this ember-twiddle for an implementation of this.
Application Template
<h1>Welcome to the {{appName}}</h1>
{{title-name name=data.name lang='en'}}
{{outlet}}
Application Controller
import Ember from 'ember';
export default Ember.Controller.extend({
appName:'Stephanie Hobson App',
data: {
id: 1,
slug: 'WD',
name: {
en: 'Working Draft',
de: 'Arbeitsentwurf',
fr: 'Version de travail',
ja: '草案',
ru: 'Рабочий черновик'
}
}
});
title-name.js component
import Ember from 'ember';
var { computed, get } = Ember;
export default Ember.Component.extend({
translationCount: computed('name', function() {
var name = get(this, 'name');
return Object.keys(name).length;
})
});
title-name.hbs component template
{{yield}}
{{translationCount}}
Related
The app I've been putting together looks like a list of stores (with add/edit/delete options), and clicking on a store name takes you to the list of items in that store (again with add/edit/delete).
The router:
Router.map(function() {
this.route('about');
this.route('stores');
this.route('shop', function() {
this.route('items', { path: '/:shop_id/items' });
this.route('edit', { path: '/:shop_id/edit' });
});
});
The model:
// app/models/shop.js
import DS from 'ember-data';
export default DS.Model.extend({
shopName: DS.attr(),
shopDetails: DS.attr(),
items: DS.hasMany('item')
});
and:
// app/models/item.js
import DS from 'ember-data';
export default DS.Model.extend({
itemName: DS.attr(),
itemDetails: DS.attr(),
itemPrice: DS.attr(),
parentShop: DS.belongsTo('shop')
});
Route for the page that displays the list of items is:
// app/routes/shop/items.js
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.get('store').findRecord('shop', params.shop_id, {
include: 'items'
});
}
});
Now as far as I understand before adding an item to a shop I have to call peekRecord():
// app/controllers/shop/items.js
actions: {
saveItem() {
let currentShop = this.get('store').peekRecord('shop', /* shop id here */);
//rest of the code
}
}
from:
// app/templates/shop/items.hbs
<button type="submit" {{action 'saveItem'}}>Save</button>
The question is, how do I pass the shop id as the second argument for peekRecord()?
Figured it.
// app/templates/shop/items.hbs
<button type="submit" {{action 'saveItem' model}}>Save</button>
and
// app/controllers/shop/items.js
actions: {
saveItem(param) {
let currentShop = this.get('store').peekRecord('shop', param.id);
//rest of the code
}
}
I do 'server side validation'. In route in method 'catch' get errors from server. And I want pass this errors in template.
How to pass errors in template from route?
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Route.extend({
model() {
return this.store.createRecord('project');
},
actions: {
save(project) {
var router = this;
var errors = router.controllerFor('project.new').get('errors')
project.save().then(function(project){
router.transitionTo('projects.show', project);
}).catch(function(resp) {
// how to pass this errors in template ????
console.log(resp.errors);
});
}
},
});
From router.js
this.route('projects', function() {
this.route('new');
this.route('show', { path: '/:project_id' });
});
From Component
import Ember from 'ember';
import DS from 'ember-data'
export default Ember.Component.extend({
actions: {
save() {
this.project.set('colors', colors);
this.sendAction('save');
}
},
...
});
Closure Actions! (Assuming a recente version of - Ember 1.13+). Closure actions can have a return value, unlike regular actions.
On your template you do:
{{my-component mySave=(action 'save')}}
And in your component you do
import Ember from 'ember';
import DS from 'ember-data'
export default Ember.Component.extend({
actions: {
save() {
this.project.set('colors', colors);
let result = this.attrs.mySave();
//do something with result
}
},
...
});
And then in your controller:
import Ember from 'ember';
import DS from 'ember-data';
export default Ember.Controller.extend({
actions: {
save(project) {
var router = this;
var errors = router.controllerFor('project.new').get('errors')
project.save().then(function(project){
router.transitionTo('projects.show', project);
}).catch(function(resp) {
return resp.errors;
});
}
},
});
I would also recommend this article on Closure Actions which is very helpful.
EDIT: I initially replied with the action being on the route (as in your example) but #Kitler correctly reminded that closure actions communicate with the controller or another component. I don't know if that's an option for the OP?
I simply want to display my longitude on my app landing page/index/application.hbs. I am embarrassed to say how long I have been working at this! Can anyone help me?
//geoservice.js
import Ember from 'ember';
export default Ember.Service.extend({
longitude: function(position){
return position.coords.longitude;
},
latitude: function(position){
return position.coords.latitude;
}
});
//geo-component.js
import Ember from 'ember';
export default Ember.Component.extend({
geoservice: Ember.inject.service(),
myLongitude: function(){
if (navigator.geolocation) {
return this.get('geoservice').longitude(navigator.geolocation.getCurrentPosition());
} else {
return "Geolocation is not supported by this browser.";
}
}.on('init'),
});
//application.hbs
<h2 id="title">Welcome to Ember</h2>
{{outlet}}
{{geo-component.myLongitude}}
The problem wasn't consuming a service in a component, but that 1) the Geolocation API is asynchronous, and 2) you don't render a component like you did. This works:
app/components/geo-location.js
import Ember from 'ember';
export default Ember.Component.extend({
geo: Ember.inject.service(),
loading: true,
error: null,
latitude: null,
longitude: null,
setPosition: Ember.on('init', function() {
this.get('geo').getPosition().then((position) => {
this.set('latitude', position.latitude);
this.set('longitude', position.longitude);
this.set('loading', false);
}).catch((error) => {
this.set('error', error);
});
})
});
app/services/geo.js
import Ember from 'ember';
export default Ember.Service.extend({
getPosition() {
return new Ember.RSVP.Promise((success, error) => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(success, error)
} else {
error( new Error("Geolocation is not supported by this browser.") );
}
}).then((position) => {
return { latitude: position.coords.latitude, longitude: position.coords.longitude };
});
}
});
app/templates/components/geo-location.hbs
{{#if error}}
{{error}}
{{else if loading}}
Loading...
{{else}}
lat: {{latitude}}, long: {{longitude}}
{{/if}}
app/templates/application.hbs
<h2 id="title">Welcome to Ember</h2>
{{geo-location}}
I started working on a new ember project. I have never used ember before.
I am working with an api that does not conform with the JSON API spec and does not have a websocket.
So I poll the api to get the latest data.
I can get the latest data but it is rendered in the view on the bottom instead of the top. I have looked at 1, 2, 3 to no avail.
How do I get the new data to render at the top of the list?
//sample output after a new job fetched
2
1
3
//desired output
3
2
1
This is a new project so I don't want to use anything that will be depreciated in 2.0 (controllers, etc.). I am open to changing the model(s) if that works.
My route looks like this:
//routes query.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var interval = 1000; // every second
Ember.run.later(this, function() {
this.model().then(function(json) {
this.controller.set('model', json);
}.bind(this));
}, interval);
return Ember.RSVP.hash({
query: this.store.find('query'),
job: this.store.peekAll('job')
});
},
});
My models are:
//models query.js
import DS from 'ember-data';
import Ember from 'ember';
export default DS.Model.extend({
jobs: DS.hasMany('job'),
count: DS.attr('number'),
jobQ: ['took'],
jobsSorted: Ember.computed.sort('jobs', 'jobQ'), <-- this doesn't seem to work
});
//models job.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
submitter: DS.attr('string'),
submission_time: DS.attr('date'), // '2015-04-27 15:14:55', // date?
completion_time: DS.attr('date'), // '2015-04-27 15:15:08',
took: DS.attr('number'),
statuses: DS.attr('string'), // object
query: DS.belongsTo('query'),
});
So my serializer is:
// serializers query.js
import DS from 'ember-data';
import assign from 'npm:object-assign';
export default DS.RESTSerializer.extend({
isNewSerializerAPI: true,
normalizeResponse: function (store, primaryModelClass, payload) {
// delete things we don't want
delete payload.error;
delete payload.status;
// populate array
var jobs = [],
relationships = [];
// copies the jobs to a new var keep the payload clean
Object.keys(payload.jobs).forEach((key) => {
let attributes = {};
assign(attributes, payload.jobs[key]);
delete attributes.id;
jobs.push({ id: key, type: 'job', attributes: attributes});
relationships.push({ id: key, type: 'job'});
});
var c = payload.count;
//jobs
// [{
// id:
// type:
// attributes: {
// name:
// filter:
// ...
// },
// ...
// }]
return {
data: {
id: 1,
type: 'query',
relationship: {
job: {
data: relationships
}
},
attributes: { count: c }
},
included: jobs
};
}
});
A template that looks like this would work just fine:
{{#each model.query as |query|}}
{{#each query.jobsSorted as |job|}}
{{job.took}}
{{/each}}
{{/each}}
What does your template look like? Although to sort it on a descending order you would need to add :desc to the sort order:
export default DS.Model.extend({
jobs: DS.hasMany('job'),
count: DS.attr('number'),
jobQ: ['took:desc'],
jobsSorted: Ember.computed.sort('jobs', 'jobQ')
});
Here's a JSFiddle demonstrating that Ember.computed.sort works like you're trying to use it: http://emberjs.jsbin.com/towerukafa/3/edit?html,css,js,output
I'm new to ember and got a problem with a template.
My route
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(params) {
var pageNum = params.page || 1,
pageRows = 8;
return this.store.find('account', {
page: pageNum,
rows: pageRows
});
},
setupController: function(controller, model) {
controller.set('model', model);
controller.set('greeting', 'Hello World');
}
});
My Controller
import Ember from 'ember';
export default Ember.ArrayController.extend({
contentLength: function() {
// console.log(this);
// console.log('length: ' + this.get('content').length);
// return this.get('content').length;
return 'Test string';
},
actions: {}
});
Template
{{ greeting }}
{{ contentLength }}
The {{ greeting }} gets rendered correctly. But {{ contentLength }} gets rendered out as a string function..
Hello World function () { // console.log(this); // console.log('length: ' + this.get('content').length); // return this.get('content').length; return 'Test string'; }
Anyone who can help me solve this issue?
Thanks
You need to add .property() at the end of the contentLength function in order to display it in a template:
import Ember from 'ember';
export default Ember.ArrayController.extend({
contentLength: function() {
// console.log(this);
// console.log('length: ' + this.get('content').length);
// return this.get('content').length;
return 'Test string';
}.property(),
actions: {}
});
If you want the property to update whenever another property of your controller changes simply add it as a "parameter" of your property like this .property("thepropertytoobserve") and the length property of an arrayController is already aviable as {{length}} in the template.
Have a look at the doc for more details on computerd properties.
You can just use {{ length }} in your template as ArrayControllers already have that property.
The reason your contentLength function is not doing what you want is because it is not a computed property. You need to either use Ember.computed(function() { ..}) or append .property(...) to your contentLength function.
Eg:
contentLength: function() {
return this.get('content.length');
}.property('content.length')