emberjs component property not updated? - ember.js

i have two components in my template:
{{ property-pie-chart
models=model.hosts
defaultProp=""
filterByDate=filterByDate
chartData=[]
}}
{{ paged-filtered-list
data=model.hosts
dates=model.dates
page=page
pageSize=pageSize
filterByDate=filterByDate
pagerView=pagerView
initRouteAction=( action 'dateInit' )
dateFilterAction=( action 'filterByDate' )
termFilterAction=(action 'filterByTerm')
sortOrder=sortOrder
sortField=sortField
}}
I send action from paged-filtered-list component to controller, which triggers route transition with filterByDate as parameter:
import Ember from 'ember';
export default Ember.Controller.extend({
queryParams: [
'page',
'pageSize',
'sortField',
'sortOrder',
'filterByDate',
'filterByTerm'
],
filterByDate: "",
filterByTerm: "",
page: 1,
pageSize: 10,
pagerView: 4,
sortField: "",
sortOrder: 'asc',
lala: "",
actions: {
dateInit: function(sortedDates) {
if (!this.get('filterByDate')) {
let params = {
filterByDate: sortedDates.get('firstObject').get('key'),
page: this.get('page'),
pageSize: this.get('pageSize'),
pagerView: this.get('pagerView')
};
this.transitionToRoute('hosts', { queryParams: params});
}
},
filterByDate: function(value) {
if (value) {
let params = {
filterByDate: value,
page: 1,
pageSize: this.get('pageSize')
};
this.transitionToRoute('hosts', { queryParams: params});
}
},
filterByTerm: function(value) {
let params = {
filterByDate: this.get('filterByDate'),
page: 1,
pageSize: this.get('pageSize')
};
if (value) {
params['filterByTerm'] = value;
} else {
params['filterByTerm'] = "";
}
this.transitionToRoute('hosts', { queryParams: params});
}
}
});
Problem is that URL is updated and contains filterByDate, but first component property-pie-chart does not detect that filterByDate property is changed, altough i checked attributes in init/didUpdate methods and parameter is changed, can somebody help and explain what i am doing wrong?

Currently you are not setting filterByDate property in controller.
I would suggest the following approach,
You please declare the below property in corresponding route.js,
queryParams: { page: { refreshModel: true }, pageSize: { refreshModel: true },sortOrder: { refreshModel: true },filterByDate: { refreshModel: true },filterByTerm: { refreshModel: true }}
refreshModel denotes is whenever this property changed,then it will force to refresh the page.
and in controller.js, You don't need to call this.transitionToRoute('hosts', { queryParams: params}); instead you just set required queryParams participating property alone then transition will automatically taken care.
SideNote: It's good if you can change function name filterByTerm filterByDate by the way this is not related to problem
Update:
I am glad you sorted out the problem. but then I want to emphasize what are Computed Properties from ember guides.
In a nutshell, computed properties let you declare functions as properties. You create one by defining a computed property as a function, which Ember will automatically call when you ask for the property. You can then use it the same way you would any normal, static property.
https://guides.emberjs.com/v2.8.0/object-model/computed-properties/#toc_what-are-computed-properties

Related

Accessing model properties in Controller - Ember

So, I'm trying to access my model properties in controller.
Controller:
dashobards: [
{ id: 12, name: 'test' },
{ id: 17, name: 'test2' },
];
In route I have model named dashboards
return Ember.RSVP.hash({
dashboards: this.store.findAll('dashboard'),
}).then((hash) => {
return Ember.RSVP.hash({
dashboards: hash.dashboards
});
}, self);
I wanna have result in controller like this:
dashboards: [
{ id: 12, name: 'test' },
{ id: 17, name: 'test2' },
{ id: 17, name: 'test1' },
{ id: 20, name: 'test20' },
];
In controller I am trying to access this model like this:
this.dashborads = this.get(model.dashobards)
And it's not working, is there any other way of doing that?
Another update How to access complex object which we get it from server in ember data model attibute,
Created twiddle to demonstrate
define attribute with DS.attr(),
export default Model.extend({
permissions:DS.attr()
});
route file,
model(){
return this.store.findAll('dashboard');
}
Your server response should be like,
data: [{
type: 'dashboard',
id: 1,
attributes: {
permissions: {'name':'role1','desc':'description'}
}
}]
hbs file,
{{#each model as |row| }}
Name: {{row.permissions.name}} <br/>
Desc: {{row.permissions.desc}} <br />
{{/each}}
Update:
Still I am not sure about the requirement, Your twiddle should be minimalized working twiddle for better understanding..anyway I will provide my observation,
1.
model(params) {
this.set('id', params.userID);
const self = this;
return Ember.RSVP.hash({
dashboards: this.store.findAll('dashboard'),
user: this.store.findRecord('user', params.userID)
}).then((hash) => {
return Ember.RSVP.hash({
user: hash.user,
dashboards: hash.dashboards
});
}, self);
}
The above code can be simply written like
model(params) {
this.set('id', params.userID);
return Ember.RSVP.hash({
dashboards: this.store.findAll('dashboard'),
user: this.store.findRecord('user', params.userID)
});
}
Its good to always initialize array properties inside init method. refer https://guides.emberjs.com/v2.13.0/object-model/classes-and-instances/
For removing entry from array,
this.dashboard.pushObject({ 'identifier': '', 'role': '' }); try this this.get('dashboard').pushObject({ 'identifier': '', 'role': '' });.
if possible instead of plain object you can use Ember.Object like
this.get('dashboard').pushObject(Ember.Object.create({ 'identifier': '', 'role': '' }));
For removing entry.
removeDashboard(i) {
let dashboard = Ember.get(this, 'dashboard');
Ember.set(this, 'dashboard', dashboard.removeObject(dashboard[i]));
}
The above code can be written like, since i is an index
removeDashboard(i) {
this.get('dashboard').removeAt(i)
}
Just do return this.store.findAll('dashboard'); in route model hook, and dont override setupController hook, then in hbs you should be able to access model that will represent RecordArray. you can have a look at this answer for how to work with this.

Ember: computed property (object) not updating in view

I'm new to ember and am creating a search filtering app. I have my search filter "buckets" set up as controller properties and they are bound nicely to query parameters.
I'm looking to create a "your selected filters" component that summarizes what filters the user has currently active. I'm thinking maybe a computed property is the way to do this? In my controller I created one called selectedFilters:
export default Ember.Controller.extend(utils, {
queryParams: ['filter_breadcrumb','filter_price','filter_size_apparel','filter_color'],
filter_breadcrumb: [],
filter_price: [],
filter_size_apparel: [],
filter_color: [],
selectedFilters: Ember.computed('this{filter_breadcrumb,filter_price,filter_size_apparel,filter_color}', function() {
let filterContainer = {};
for (let bucket of this.queryParams) {
let bucketArray = this.get(bucket);
if (bucketArray.length > 0) { // only add if bucket has values
filterContainer[bucket] = {
'title' : cfg.filterTitles[bucket], // a "pretty name" hash
'values' : bucketArray
};
}
}
return filterContainer;
})
});
The contents of selectedFilters would look something like this when a user has chosen filters:
{
filter_breadcrumb: { title: 'Category', values: [ 'Home > Stuff', 'Garage > More Stuff' ] },
filter_price: { title: 'Price', values: [ '*-20.0' ] },
filter_color: { title: 'Color', values: [ 'Black', 'Green' ] }
}
And then the template would be:
<h1>Selected Filters</h1>
{{#each-in selectedFilters as |selectedFilter selectedValues|}}
{{#each selectedValues.values as |selectedValue|}}
<strong>{{selectedValues.title}}</strong>: {{selectedValue}} <br>
{{/each}}
{{/each-in}}
This actually works (kind of). The view is not updating when filters are added and removed. When I hard-refresh the page, they do show up. I'm wondering why they aren't updating even though the "input" properties to selectedFilters do?
I'm thinking either I'm doing it wrong or perhaps there's a better way to do this. Any help appreciated!
You can't use this for computed property dependent key because it's undefined in that scope.
Arrays and objects defined directly on any Ember.Object are shared across all instances of that object. so initialize it in init(). refer initializing instances ember guide
init(){
this._super(...arguments);
this.set('filter_breadcrumb',[]);
}
For definining computed properties using arrays as dependant key refer ember guide
In your case if you want your computed property to recalculate based array item added/removed or changed to different array then use .[]
export default Ember.Controller.extend(utils, {
queryParams: ['filter_breadcrumb', 'filter_price', 'filter_size_apparel', 'filter_color'],
init(){
this._super(...arguments);
this.set("filter_breadcrumb",[]);
this.set("filter_price",[]);
this.set("filter_size_apparel",[]);
this.set("filter_color",[]);
},
selectedFilters: Ember.computed('filter_breadcrumb.[]','filter_price.[]','filter_size_apparel.[]','filter_color.[]', function() {
let filterContainer = {};
for (let bucket of this.queryParams) {
let bucketArray = this.get(bucket);
if (bucketArray.length > 0) { // only add if bucket has values
filterContainer[bucket] = {
'title': cfg.filterTitles[bucket], // a "pretty name" hash
'values': bucketArray
};
}
}
return filterContainer;
})
});
In case if you want computed property to recalculate based on each individual item change then consider filter_price.#each.price
Figured it out. It appears the brace expansion doesn't work on this. I tried:
selectedFilters: Ember.computed('this{filter_breadcrumb,filter_price,filter_size_apparel,filter_color}', function() {
and
selectedFilters: Ember.computed('this.{filter_breadcrumb,filter_price,filter_size_apparel,filter_color}', function() {
This works tho:
selectedFilters: Ember.computed('filter_breadcrumb', 'filter_price', 'filter_size_apparel', 'filter_color', function() {
But I'm still wondering if this is the recommended way of accomplishing my "filter summary" task.

Delete a key (and value) from ember object

Ember newbie and I have a feeling I am doing something very wrong here. Basically, I am trying to use an action to delete a key from a custom ember object. The action takes the key as the parameter (though that's a very jquery way to do it - perhaps I am missing a more "ember" way to do it?)
I created a twiddle
I am able to set the value to null which kind of works, but I'd prefer to remove the key and the value entirely. I would think .removeObject(key) would be the ticket, but it doesn't work. The console complains with:
Uncaught TypeError: thisData.removeObject is not a function
So I think I am using it in the wrong context.
Here's my example controller:
import Ember from 'ember';
const UrlObj = Ember.Object.extend({});
export default Ember.Controller.extend({
urlData: UrlObj.create({
queryParams: {
filter_breadcrumb: [
'Jewelry > Rings',
'Clothing and Accessories > Sweaters'
],
filter_price: ['100.0-200.0'],
filter_size: ['S','L'],
paging: 18,
q: 'gold',
search_sort: 'relevance'
}
}),
actions: {
deleteStuff(key) {
alert("deleteStuff called with " + key);
let thisData = Ember.get(this.urlData, 'queryParams');
//thisData.removeObject(key); // doesn't work, wrong context?
//Ember.get(this.urlData, 'queryParams').removeObject(key); // doesn't work, wrong context?
//delete thisData[key]; // this deletes it from the object but it's JS so ember is not aware of it
Ember.set(thisData, key, null); // this kind of works, but I'd like to remove the key AND the value
}
}
});
I commented out the lines that don't work.
Here's my template:
here's the data list:<br>
{{#each-in urlData.queryParams as |key value|}}
<a href="#" {{action "deleteStuff" key}}>{{key}}: {{value}}</a><br>
{{/each-in}}
Any help appreciated!
You need to notify property changed.
And no need to use Ember.Object
export default Ember.Controller.extend({
urlData: {
queryParams: {
filter_breadcrumb: [
'Jewelry > Rings',
'Clothing and Accessories > Sweaters'
],
filter_price: ['100.0-200.0'],
filter_size: ['S','L'],
paging: 18,
q: 'gold',
search_sort: 'relevance'
}
},
actions: {
deleteStuff(key) {
alert("deleteStuff called with " + key);
let thisData = Ember.get(this.urlData, 'queryParams');
delete thisData[key];
this.notifyPropertyChange('urlData');
}
}
});
No need to use "notifyPropertyChange"
export default Ember.Controller.extend({
urlData: {
queryParams: {
filter_breadcrumb: [
'Jewelry > Rings',
'Clothing and Accessories > Sweaters'
],
filter_price: ['100.0-200.0'],
filter_size: ['S','L'],
paging: 18,`enter code here`
q: 'gold',
search_sort: 'relevance'
}
},
actions: {
deleteStuff(key) {
alert("deleteStuff called with " + key);
let thisData = this.get('urlData').queryParams;
delete thisData[key];
console.log(this.get('urlData')); //u ll get deleted data
}
}
});

Acceptance tests aren't resetting

I have some acceptance tests that test a component. If I run each test separately, they pass just fine. However, when I run the tests together, they fail because they're retaining the values from the previous tests.
Here is my code:
filter-test.js
module('Integration - Filter', {
beforeEach: function() {
App = startApp();
server = setupPretender();
authenticateSession();
},
afterEach: function() {
Ember.run(App, 'destroy');
server.shutdown();
}
});
test('filters can be saved and selected via the dropdown', function(assert) {
visit('/status');
fillIn('.filter-status', 'Not Completed');
fillIn('.filter-id', '444');
andThen(function() {
assert.ok(find('.status-title').text().includes('2 of 7'), 'the new filter filters the results');
});
});
test('only saved filters can be edited', function(assert) {
visit('/status');
fillIn('.filter-id', 'not an id');
click('.update-filter');
andThen(function() {
assert.equal(find('.alert').text(), 'Not a Saved Filter×');
});
});
test('filter values can be cleared', function(assert) {
visit('/status');
fillIn('.filter-id', '444');
fillIn('.filter-status', 'Completed');
click('.clear-filters');
andThen(function() {
// this fails because `.filter-id` is set to 'not an id':
assert.equal(find('.filter-id').val(), '', 'filter for was reset to its initial value');
// this also fails because `.filter-status` is set to 'Not Completed':
assert.equal(find('.filter-status').val(), 'Everything', 'status dropdown was reset to its initial value');
});
});
ps-filter/component.js
export default Ember.Component.extend({
classNames: ['panel', 'panel-default', 'filter-panel'],
currentFilter: null,
initialValues: null,
didInsertElement: function() {
this.set('initialValues', Ember.copy(this.get('filterValues')));
},
actions: {
saveFilter: function(name) {
var filters = this._getFilterList();
var filterValues = this.get('filterValues');
if (!Ember.isEmpty(name)) {
filters[name] = filterValues;
this.sendAction('updateFilter', filters);
this.set('currentFilter', name);
}
},
updateFilter: function() {
var filterValues = this.get('filterValues');
var currentFilter = this.get('currentFilter')
var filters = this.get('userFilters');
filters[currentFilter] = filterValues;
this.sendAction('updateFilter', filters);
},
clearFilters: function() {
this.set('currentFilter', null);
this.set('filterValues', Ember.copy(this.get('initialValues')));
}
}
});
status/controller.js
export default Ember.ArrayController.extend({
filterValues: {
filterStatus: 'Everything',
filterId: 'id',
},
userFilters: Ember.computed.alias('currentUser.content.preferences.filters')
});
status/template.hbs
<div class="row">
{{ps-filter
filterValues=filterValues
userFilters=userFilters
updateFilter='updateFilter'
}}
</div>
From what I gathered, it seems that it sets the initialValues to the filterValues left over from the previous test. However, I thought that the afterEach was supposed to reset it to its original state. Is there a reason why it doesn't reset it to the values in the controller?
Note that the component works normally when I run it in development.
Ember versions listed in the Ember Inspector:
Ember : 1.11.3
Ember Data : 1.0.0-beta.18
I'm running Ember CLI 0.2.7.
Edit
I don't think this is the issue at all, but here is my pretender setup:
tests/helpers/setup-pretender.js
export default function setupPretender(attrs) {
var users = [
{
id: 1,
name: 'ttest',
preferences: null
}
];
var activities = [
{
id: 36874,
activity_identifier: '18291',
status: 'Complete'
}, {
id: 36873,
activity_identifier: '82012',
status: 'In Progress'
}, {
id: 35847,
activity_identifier: '189190',
status: 'In Progress'
}, {
id: 35858,
activity_identifier: '189076',
status: 'Not Started'
}, {
id: 382901,
activity_identifier: '182730',
status: 'Not Started'
}, {
id: 400293,
activity_identifier: '88392',
status: 'Complete'
}, {
id: 400402,
activity_identifier: '88547',
status: 'Complete'
}
];
return new Pretender(function() {
this.get('api/v1/users/:id', function(request) {
var user = users.find(function(user) {
if (user.id === parseInt(request.params.id, 10)) {
return user;
}
});
return [200, {"Content-Type": "application/json"}, JSON.stringify({user: user})];
});
this.get('api/v1/activities', function(request) {
return [200, {"Content-Type": "application/json"}, JSON.stringify({
activities: activities
})];
});
this.put('api/v1/users/:id', function(request) {
var response = Ember.$.parseJSON(request.requestBody);
response.user.id = parseInt(request.params.id, 10);
var oldUser = users.find(function(user) {
if (user.id === parseInt(request.params.id, 10)) {
return user;
}
});
var oldUserIndex = users.indexOf(oldUser);
if (oldUserIndex > -1) {
users.splice(oldUserIndex, 1);
users.push(response.user);
}
return [200, {"Content-Type": "application/json"}, JSON.stringify(response)];
});
});
}
When I run the tests, it fails because it reset the value to the one in the previous test. For example, when I run 'filter values can be cleared', the .filter-id input has the same .filter-id value from 'only saved filter can be edited. If I change the value in 'only saved filters can be edited'back to '', the 'filter values can be cleared' test passes.
Basically, the component sets the initialValues property when it first inserts the element. It's set to a copy of the filterValues property, so it should be set to the controller's filterValues property, and shouldn't change. However, it seems that the modified filterValues property is carried over to the next test, which means that initialValues is set to that modified property when it rerenders. So, the test rerenders the templates, but retains the modified values in the controller and component.
I can make the tests pass by creating an initialValues property in the controller and passing that into the component, but that'd mean having duplicate properties in the controller (since filterValues and initialValues would have the same values).
I could modify the user record in the component, but I thought we're supposed to only modify records in the controller or router. Besides, isn't the afterEach hook supposed to reset the app?

Ember JS: Highcharts.js into a component

Updated 03/17/2016 to better reflect current best practices for EmberJS v1.13.0 and up.
Problem
I am rendering Highcharts into a component and i almost have it working, but the binding of a property into the component is getting lost somewhere.
This is how I call the component:
//templates/index.hbs
{{pie-chart data=pieData}}
This is what the data property looks like (currently set in a controller):
//controllers/index.js
import Ember from 'ember';
export default Ember.Controller.extend({
init() {
this._super(...arguments);
this.pieData = [
['0 - 30', 2.5],
['31 - 60', 7.5],
['61 - 90', 12.5],
['91 - 120', 77.5]
];
}
});
And here is the component logic:
//components/pie-chart.js
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['chart'],
renderChart() {
return this.$().highcharts({
chart: {
height: 275
},
title: null,
plotOptions: {
pie: {
dataLabels: {
enabled: false
}
}
},
series: {
type: 'pie',
data: this.get('data')
},
colors: ['#777777', '#888888', '#999999', '#aaaaaa', '#bbbbbb', '#cccccc', '#dddddd', '#eeeeee'],
credits: {
enabled: true
}
});
},
didUpdateAttrs() {
let chart = this.$().highcharts();
let series = this.get('data');
chart.series[0].setData(series);
},
didInsertElement() {
this._super(...arguments);
this.renderChart();
},
willDestroyElement() {
this.$().highcharts().destroy();
}
});
i got the idea from this blog and i am trying to adapt it to make more charts.
the chart renders onto the screen, but it is blank... there are no errors to report... the only thing i can think is the data property is not being handled correctly in order to plot the graph?
I am not sure if this is my wrong use of Ember code, or wrong use of Highcharts code?
Solution
the series property inside the highchart was supposed to be an array of objects.. but I mistakenly defined just an object:
the fix:
series: [{
type: 'pie',
data: this.get('data')
}],
So, that is how you render a Highchart.js into a component :)

Categories