Update DS.RecordArray when changing routes - ember.js

ive searched for this and have not found an answer. I have 2 routes: "Index", which creates/updates an expense list, and "Charts", which charts the values of the expenses.
In order to handle the expense charts, I have the following function:
getData: function() {
var expenses = this.store.all('expense');
expenses.update();
var retarr = Ember.A();
expenses.forEach(function(expense) {
retarr.pushObject({
label: expense.get('name'),
value: expense.get('amount'),
group: 'expense'
});
});
return retarr;
}.property()
This is then passed to the ember-charts component in the Charts route.
<script type="text/x-handlebars" id='charts'>
<div class="chart-container">
{{horizontal-bar-chart data=getData}}
</div>
However, if I create/delete an expense in the "Index" route and hten transition to the "Charts" route, the DS.RecordArray doesn't update despite calling the "update()" function. As such, the chart does not reflect the created/deleted changes until the page is refreshed.
How do I fix this so the RecordArray auto updates along with the chart? I've broken my head for over two days trying different things. Thanks!

Your property getData should be bound to anything, if this something is an array you should use #each. For example as you can see here:
remaining: function() {
var todos = this.get('todos');
return todos.filterBy('isDone', false).get('length');
}.property('todos.#each.isDone')
I suggest you another approch, let's modify your model:
App.Chart = DS.Model.extend({
// fieds here...
label: function() {
return this.get("name");
}.property("name"),
value: function() {
return this.get("amount");
}.property("amount"),
group: function() {
return "expense";
}.property(),
)};
In your route set myCharts property:
App.ChartRoute = Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
var charts = this.store.find("chart");
controller.set("myCharts", charts);
}
});
Then you could use your horizontal chart:
<div class="chart-container">
{{horizontal-bar-chart data=myCharts}}
</div>
Note: I didn't tested this code but it should work

Related

Ember way to implement a search dialog

I want to implement simple ember app, where I have a search dialog, a list of results and a detailed view if I click on the results, something like this:
http://jsbin.com/tuyapabuhe/2/edit
The search method of the IndexController is doing an ajax request to populate the model, but I'm not sure if that is the best way to do it. I specially don't like the var self = this; part. Is there an ember way to do that search?
EDIT
I updated the example, now is doing an ajax request and is more realistic:
http://jsbin.com/wimogu/4/edit
The ajax call should be happening inside the model hook for the Index route. Instead of observes you can just use a property as follows:
App.IndexRoute = Ember.Route.extend({
model: function(){
return data; // your ajax call here...
}
});
App.IndexController = Ember.ArrayController.extend({
filtered: function() {
var name = this.get('name') || '';
var people = data.filter(function(el){
if(el.name.toLowerCase().indexOf(name)>-1)
return el;
});
return people;
}.property('name', 'model')
});
Then, in your template you can just do
{{#each user in filtered}}
{{#link-to 'person' user.id}}
<div>{{user.name}}</div>
{{/link-to}}
<hr/>
{{/each}}
Working solution here
Per my comment on another answer, I would suggest the following for AJAX calls based on one or more filters, complete with debouncing to limit the number of requests:
function handleSearch() {
this.set('model', this.store.find('user', this.get('query')));
}
App.IndexController = Ember.Controller.extend({
search: '',
sort: 'first_name',
direction: 'asc',
query: function() {
return {
search: this.get('search'),
sort: this.get('sort'),
direction: this.get('direction')
};
}.property('search'),
queryDidChange: function() {
Ember.run.debounce(this, handleSearch, 200);
}.observes('query').on('init'),
actions: {
clearSearch: function() {
this.set('search', '');
}
}
});
I have this running in the wild right now and it works perfectly.

emberjs | save state of routes and nested resources

i am trying to build my first emberjs app and i wonder how i can save the state of a nested route to rebuild that state when the top route is revisted in the current session.
To give an example:
Lets Say a user switches from /overview/item1 to /info and then returns to
/overview/ and want to redirect him to /overview/item1
HTML
<div id="navigation">
{{#link-to 'info' class='link' }}Info{{/link-to}}
{{#link-to 'overview' class='link'}} Overview {{/link-to}}
</div>
JS
App.Router.map(function(){
this.route('info');
this.resource('overview', function () {
this.resource('item', { path : '/:item_id'});
});
});
it would be really nice if somebody could give me a hint to the right approach of this.
There are various ways for achieving your goal. Basically, you need to store state of last visited overview/:item_id route in the parent route or controller. Then, you need to check this state before resolving model of overview route. If state is not null (user was selected some item from overview/:item_id), abort current transition and start the new one (to
overview/:selected_item_id).
Schematic solution in code:
// 1st approach
App.OverviewController = Ember.ObjectController.extend({
selectedItem: null
});
App.OverviewRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (this.get('controller.selectedItem')) {
transition.abort();
this.transitionTo('overview.item', this.get('selectedItem'));
}
}
});
App.OverviewItemRoute = Ember.Route.extend({
afterModel: function(model) {
this.controllerFor('overview').set('selectedItem', model);
}
});
// 2nd approach (less code)
App.OverviewRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (this.get('controller.selectedItem')) {
transition.abort();
this.transitionTo('overview.item', this.get('selectedItem'));
}
},
setupController: function(controller) {
controller.reopen({ selectedItem: null });
}
});
App.OverviewItemRoute = Ember.Route.extend({
afterModel: function(model) {
this.controllerFor('overview').set('selectedItem', model);
}
});
It's important to keep the item itself, not it's id, because it'll way more easier to load overview/:item_id route in the future (passing stored model in this.transitionTo('overview.item', item)).

Ember computed.alias in nested views

I'm trying to create a reusable generated element that can react to changing outside data. I'm doing this in an included view and using computed.alias, but this may be the wrong approach, because I can't seem to access the generic controller object at all.
http://emberjs.jsbin.com/nibuwevu/1/edit
App = Ember.Application.create();
App.AwesomeChartController = Ember.Object.extend({
data: [],
init: function() {
this.setData();
},
setData: function() {
var self = this;
// Get data from the server
self.set('data', [
{
id: 1,
complete: 50,
totoal: 100
},
{
id: 2,
complete: 70,
total: 200
}
]);
}
});
App.IndexController = Ember.Controller.extend({
needs: ['awesome_chart']
});
App.ChartView = Ember.View.extend({
tagName: 'svg',
attributeBindings: 'width height'.w(),
content: Ember.computed.alias('awesome_chart.data'),
render: function() {
var width = this.get('width'),
height = this.get('height');
var svg = d3.select('#'+this.get('elementId'));
svg.append('text')
.text('Got content, and it is ' + typeof(content))
.attr('width', width)
.attr('height', height)
.attr('x', 20)
.attr('y', 20);
}.on('didInsertElement')
});
And the HTML
<script type="text/x-handlebars">
<h2> Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Awesome chart</h2>
{{view App.ChartView width=400 height=100}}
</script>
For what it's worth, this didn't seem to work as a component, either. Is the ApplicationController the only place for code that will be used on multiple pages? The 'needs' seems to work, but the nested view can't access it. If I make a proper Ember.Controller instance to decorate the view, that doesn't seem to work either.
Any help is much appreciated.
Update:
I can't edit my comment below, but I found a good answer on how to use related, and unrelated, models in a single route.
How to use multiple models with a single route in EmberJS / Ember Data?
Firstly, your controllers should extend ObjectController/ArrayController/Controller
App.AwesomeChartController = Ember.Controller.extend({...});
Secondly when you create a view the view takes the controller of the parent, unless explicitly defined.
{{view App.ChartView width=400 height=100 controller=controllers.awesomeChart}}
Thirdly you already had set up the needs (needed a minor tweak), but just as a reminder for those reading this, in order to access a different controller from a controller you need to specify the controller name in the needs property of that controller.
App.IndexController = Ember.Controller.extend({
needs: ['awesomeChart']
});
Fourthly from inside the view your computed alias changes to controller.data. Inside the view it no longer knows it as AwesomeChart, just as controller
content: Ember.computed.alias('controller.data')
Fifthly inside your on('init') method you need to actually get('content') before you attempt to display what it is. content doesn't live in the scope of that method.
var content = this.get('content');
http://emberjs.jsbin.com/nibuwevu/2/edit
First, AwesomeChart does sound like it's gonna be a reusable self-contained component. In which case you should better user Ember.Component instead of Ember.View (as a bonus, you get a nice helper: {{awesome-chart}}).
App.AwesomeChartComponent = Ember.Component.extend({ /* ... */ });
// instead of App.ChartView...
Second, for AwesomeChart to be truly reusable, it shouldn't be concerned with getting data or anything. Instead, it should assume that it gets its data explicitly.
To do this, you basically need to remove the "content:" line from the awesome chart component and then pass the data in the template:
{{awesome-chart content=controllers.awesomeChart.data}}
Already, it's more reusable than it was before. http://emberjs.jsbin.com/minucuqa/2/edit
But why stop there? Having a separate controller for pulling chart data is odd. This belongs to model:
App.ChartData = Ember.Object.extend();
App.ChartData.reopenClass({
fetch: function() {
return new Ember.RSVP.Promise(function(resolve) {
resolve([
{
id: 1,
complete: 50,
total: 100
},
{
id: 2,
complete: 70,
total: 200
}
]);
// or, in case of http request:
$.ajax({
url: 'someURL',
success: function(data) { resolve(data); }
});
});
}
});
And wiring up the model with the controller belongs to route:
App.IndexController = Ember.ObjectController.extend();
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.ChartData.fetch();
}
});
Finally, render it this way:
{{awesome-chart content=model}}
http://emberjs.jsbin.com/minucuqa/3/edit

How to refresh Ember view

I have a template where I represent a User which hasMany usertags. The values are there after I hit F5, I'm not sure how to automatically refresh the view. I've looked into the ember observer, but it only fires after the DOM load - anyway I'm not sure if observers are the answer yet so looking for a fresh opinion on how to do this.
{{username}}
<span {{action 'addusertag' selectedTag}}>Add</span>
{{#each tag in model.usertags}}
{{/each}}
App.UserRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controller.set('model', this.get('store').find('user',model.id));
},
actions: {
addusertag: function(params){
var tag = this.get('store').createRecord('usertag', {tag_id: params.id, user_id: this.currentModel.id});
tag.save();
}
}
});
Thanks!
Check out this answer.
After you save, you want to add your usertag to the user.usertags array.
addusertag: function(params){
var context = this;
var tag = this.get('store').createRecord('usertag', {tag_id: params.id, user_id: this.currentModel.id});
tag.save().then(function (tag) {
context.controller.get('content.usertags').pushObject(tag);
});
}

Ember.js - How to clear the form data after saving the record?

I'm using Ember App Kit. I have a form that takes a student name and I can save the data into a database without any problem. The problem is the data (student name) retains on the form whenever I get transition back to this route (http://localhost:8000/#/students/new) from another page. If I refresh the screen, then the data will be cleared out and I will get a fresh form. What am I doing wrong?
Also, if I decide not to save or add the record and go to see the list of students, I see an empty record on screen. That record is not in the database though. How can I prevent that?
//--------------------------------------------
// Controller
var StudentsNewController = Ember.ObjectController.extend({
init: function() {
var newSystem = this.store.createRecord('student');
this.set('newStudent', newStudent);
this._super();
},
actions: {
acceptChanges: function () {
var self = this;
self.get('newStudent').save().then(function(student){
self.transitionToRoute('students');
});
}
}
});
export default StudentsNewController;
//--------------------------------------------
// Model
var Student = DS.Model.extend({
name: DS.attr('string'),
major: DS.belongsTo('major')
});
export default Student;
//--------------------------------------------
// Template
<form {{action 'updateSystem' on="submit"}}>
<fieldset>
<div>
<label>Student Name</label>
{{input value=newStudent.name size="50"}}
</div>
<button {{action 'acceptChanges'}}>Add</button>
</fieldset>
</form>
Try to setup the controller in you route (here) instead of using the init method in the controller. It's one of the routes responsibilities.
I think the problem is that you assume that every time you transition to the StudentsNewRoute a new StudentsNewController is created, and thus the init method is called.
The truth is Ember creates the controller once and changes it's content based on the model and the setupController hooks. So the init method of the controller it's called once and you end up with the same record every time you transition to the route.
To solve this you'd do something like this:
var StudentsNewController = Ember.ObjectController.extend({
newStudent: null,
actions: {
acceptChanges: function () {
this.get('newStudent').save().then((student) => {
this.transitionToRoute('students');
});
}
}
});
//--------------------------------------------
var StudentsNewRoute = Ember.Route.extend({
model: function() {
return this.store.createRecord('student');
},
setupController: function(controller, model) {
controller.set('newStudent', model);
},
});
I hope this helps you!
actions: {
acceptChanges: function () {
var self = this;
self.get('newStudent').save().then(function(student){
self.set('newStudent','');
self.transitionToRoute('students');
});
}
}