Ember update property on the changes in array - ember.js

I have following in my controller, and facing issue while updating property with array change..
import Ember from 'ember';
export default Ember.Controller.extend({
imageIds: Object.keys(JSON.parse(localStorage.image_ids || "{}")),
// imageIds = ['gnffffffffjdf', 'hzfyfsidfulknm', 'euriekjhfkejh']
previewImageId: function() {
return this.imageIds.get('firstObject');
}.property('imageIds.[]'),
actions: {
addDetails: function() {
this.transitionToRoute('items.add_item');
},
removeImage: function(image_id) {
var uploaded = JSON.parse(localStorage.image_ids || "{}");
delete uploaded[image_id]
localStorage.image_ids = JSON.stringify(uploaded);
this.get("imageIds").removeObject(image_id);
// this.set("imageIds", Object.keys(JSON.parse(localStorage.image_ids || "{}")));
},
updatePreview: function(image_id){
this.set("previewImageId", image_id);
var uploaded = JSON.parse(localStorage.image_ids || "{}");
uploaded[image_id] = image_id;
localStorage.image_ids = JSON.stringify(uploaded);
// this.set("imageIds", Object.keys(JSON.parse(localStorage.image_ids)));
this.get("imageIds").pushObject(image_id);
}
},
init: function(){
var controller = this;
Ember.$('body').on('click', ".current_image", function() {
var public_id = Ember.$(this).attr('id');
controller.set("previewImageId", public_id);
});
}
});
Whenever there is any change in the imageIds array, previewImageId should be updated.
tried using pushObject, removeObject, .get and .set options.
But still no luck
Can anyone pls help me?
ANSWER:
import Ember from 'ember';
export default Ember.Controller.extend({
imageIds: function() {
return Object.keys(JSON.parse(localStorage.image_ids || "{}"));
}.property(),
previewImageId: function() {
return this.get("imageIds.firstObject");
}.property('imageIds.[]'),
actions: {
addDetails: function() {
this.transitionToRoute('items.add_item');
},
removeImage: function(image_id) {
var uploaded = JSON.parse(localStorage.image_ids || "{}");
delete uploaded[image_id]
localStorage.image_ids = JSON.stringify(uploaded);
this.get("imageIds").removeObject(image_id);
},
updatePreview: function(image_id){
var uploaded = JSON.parse(localStorage.image_ids || "{}");
uploaded[image_id] = image_id;
localStorage.image_ids = JSON.stringify(uploaded);
this.get("imageIds").unshiftObject(image_id);
}
},
init: function(){
var controller = this;
Ember.$('body').on('click', ".current_image", function() {
var public_id = Ember.$(this).attr('id');
controller.get("imageIds").removeObject(public_id);
controller.get("imageIds").unshiftObject(public_id);
});
}
});
Here previously I tried with setting value to previewImageId.. which was wrong way, as it overrides my computed property.

I could see that you are setting the previewImageId cp in a couple of places. You should make the computed property as a setter and getter aware.
Take a look here for an example
If the cp is implemented without a setter, then setting some value on the cp will overwrite its computed function.

Here is a working demo for your use case.
Basically I made the imageIds a property. Here is the code snippet:
App.IndexController = Ember.ArrayController.extend({
imageIds: function() {
return this.get("content");
}.property(),
previewImageId: function() {
return this.get("imageIds").get("firstObject");
}.property("imageIds.[]"),
actions: {
remove: function(item) {
this.get("imageIds").removeObject(item);
}
}
});
Hope this helps!

Related

Ember loading state not triggered on transitionTo

If I use a transitionTo on a route with a slow model hook, the loading.hbs state never gets triggered (I have loading.hbs files at all of the levels -- cluster, cluster.schedule and cluster.schedule.preview_grid). I tried renaming the one at cluster.schedule preview_grid-loading.hbs with no luck.
On the transitionTo, there is no model or model id passed in, just the route:
viewPreviewGrid: function() {
this.transitionTo('cluster.schedule.preview_grid');
},
I also have a loading action defined as follows:
loading(transition) {
var controller = this.controller;
if (!Ember.isNone(controller)) {
this.controller.reset();
}
transition.promise.finally(function() {
NProgress.done();
});
}
During the transitionTo call the page just stays on the previous route until the promises in the model hook resolve, and then it transitions to the other route. If I refresh the page, the loading state gets triggered just fine. Is this a known behaviour for transitionTo?
This is my model hook:
model: function (/*params*/) {
var socialProfile = this.modelFor('cluster.schedule').get('firstObject');
if (!socialProfile.get('isInstagram')){
throw new Error("Attempted to access preview with non-ig profile: " + socialProfile.get('id'));
}
var accessToken = socialProfile.get('token');
var self = this;
return Ember.RSVP.hash({
igPosts: new Ember.RSVP.Promise(function(resolve) {
self.getUsersRecentMedia(accessToken).then(function(response) {
var igPosts = Ember.A([]);
response.data.forEach(function(data) {
igPosts.pushObject(self.igPostFromResponse(data, socialProfile));
});
resolve(igPosts);
});
}),
posts: new Ember.RSVP.Promise(function(resolve) {
self.store.query('gram', { type: 'preview', social_profile_id: socialProfile.get('id'), limit: self.get('postLimit') }).then(function(grams) {
var filteredGrams = grams.filter(function(gram) {
return (gram.get('scheduledInFuture')) && (gram.belongsTo('socialProfile').id() === socialProfile.get('id')) && (gram.get('active'));
});
resolve(filteredGrams);
});
}),
igUser: new Ember.RSVP.Promise(function(resolve) {
self.getSelf(accessToken).then(function(response) {
resolve(self.igUserFromResponse(response.data, socialProfile));
});
})
});
},
You need to return true at the end of the loading() hook to tell Ember to go ahead and show the default loading route (loading.hbs).
loading(transition) {
var controller = this.controller;
if (!Ember.isNone(controller)) {
this.controller.reset();
}
transition.promise.finally(function() {
NProgress.done();
});
return true;
},

How would one extend multiple mixins when creating a new mixin in Ember.js

I have previously discovered it is possible to extend mixins when creating a new mixin like such:
App.SomeNewMixin = Ember.Mixin.create(App.SomeOldMixin, {
someMethod: function() { return true; }
});
Now I am attempting to use two existing mixins, but it seems Mixin.create only supports 2 parameters.
App.SomeNewMixin = Ember.Mixin.create(App.SomeOldMixinOne, App.SomeOldMixinTwo, {
someMethod: function() { // No access to methods defined in SomeOldMixinOne }
});
This seems like a serious limitation of Ember Mixins. The Ember docs have little to no coverage of Ember.Mixin, so I'm not really sure how to proceed. I've tried using Ember.Mixin.apply within the init function of SomeNewMixin, also to no avail.
App.SomeNewMixin = Ember.Mixin.create({
init: function() {
this._super();
this.apply(App.SomeOldMixinOne);
this.apply(App.SomeOldMixinTwo);
}
someMethod: function() { return true; }
});
Any insight on possible solutions would be greatly appreciated!
Creating a mixin which extends multiple other mixins should work fine.
For example look at this:
var App = Ember.Application.create();
App.SomeOldMixin = Ember.Mixin.create({
someOldMethod: function() { return 'old'; },
someOldMethod2: function() { return 'old2'; }
});
App.SomeNewMixin = Ember.Mixin.create({
someNewMethod: function() { return 'new'; }
});
App.SomeNewerMixin = Ember.Mixin.create({
someNewerMethod: function() { return 'newer'; }
});
App.SomeNewestMixin = Ember.Mixin.create(App.SomeOldMixin, App.SomeNewMixin, App.SomeNewerMixin, {
someOldMethod: function() {
return this._super() + ' ' + this.someOldMethod2();
},
someNewestMethod: function() { return 'newest'; }
});
App.ApplicationController = Ember.Controller.extend(App.SomeNewestMixin, {
test: function() {
console.log(this.someOldMethod());
console.log(this.someNewMethod());
console.log(this.someNewerMethod());
console.log(this.someNewestMethod());
}.on('init')
});

Ember promise not resolved when I expect it to be

I have a custom component that expects data and not a promise, but I am unsure if they way that I am obtaining the data is the right way.
Is this the right way to do it?
component hbs
{{x-dropdown content=salutations valuePath="id" labelPath="description" action="selectSalutation"}}
Doesn't work
controller (this is the way I expect things to work
import Ember from 'ember';
export default Ember.Controller.extend({
bindSalutations: function() {
var self = this;
this.store.find('salutation').then(function(data) {
self.set('salutations', data);
});
}.on('init'),
components/x-dropdown.js
import Ember from 'ember';
export default Ember.Component.extend({
list: function() {
var content = this.get('content');
var valuePath = this.get('valuePath');
var labelPath = this.get('labelPath');
return content.map(function(item) {
return {
key: item[labelPath],
value: item[valuePath],
};
});
}.property('content'),
This works
controller
bindSalutations: function() {
var self = this;
this.store.find('salutation').then(function(data) {
self.set('salutations', data.get('content')); // pass the content instead of just the data
});
}.on('init'),
component
...
list: function() {
var content = this.get('content');
var valuePath = this.get('valuePath');
var labelPath = this.get('labelPath');
return content.map(function(item) {
return {
key: item._data[labelPath], // access through the _data attribute
value: item._data[valuePath],
};
});
}.property('content'),
Ember Data returns a Proxy Promise. This means you can use the promise as if it were a collection or model itself, as long as you aren't dependent on the property being completely populated when you use it. If you really want the promise resolved, you should probably be setting it up in the route.
If you want it on your controller, you can be lazy and do it like so:
Controller
salutations: function() {
this.store.find('salutation');
}.property(),
Component
...
list: function() {
var content = this.get('content'),
valuePath = this.get('valuePath'),
labelPath = this.get('labelPath');
return content.map(function(item) {
return {
key: item.get(labelPath),
value: item.get(valuePath),
};
});
}.property('content.[]'),
Template
{{x-dropdown content=salutations valuePath="id" labelPath="description" action="selectSalutation"}}
The real trick is to watch if the collection is changing. Hence you'll see I changed the property argument to content.[]

model returns null on controller

i'm working with a a router and a controller, and i need to complete some operations on the controller, this is my model code
AcornsTest.StockRoute = Ember.Route.extend({
model: function(params) {
"use strict";
var url_params = params.slug.split('|'),
url = AcornsTest.Config.quandl.URL + '/' + url_params[0] + '/' + url_params[1] + '.json',
stockInStore = this.store.getById('stock', url_params[1]),
today = new Date(),
yearAgo = new Date(),
self = this;
yearAgo.setFullYear(today.getFullYear() - 1);
today = today.getFullYear()+'-'+today.getMonth()+'-'+today.getDate();
yearAgo = yearAgo.getFullYear()+'-'+yearAgo.getMonth()+'-'+yearAgo.getDate();
if(stockInStore && stockInStore.get('data').length) {
return stockInStore;
}
return Ember.$.getJSON(url,{ trim_start: yearAgo, trim_end: today, auth_token: AcornsTest.Config.quandl.APIKEY })
.then(function(data) {
if(stockInStore) {
return stockInStore.set('data', data.data);
} else {
return self.store.createRecord('stock', {
id: data.code,
source_code: data.source_code,
code: data.code,
name: data.name,
description: data.description,
display_url: data.display_url,
source_name: data.source_name,
data: data.data,
slug: data.source_code+'|'+data.code
});
}
});
}
});
and this is my controller
AcornsTest.StockController = Ember.ObjectController.extend({
init: function() {
"use strict";
this.send('generateChartInfo');
},
actions: {
generateChartInfo: function() {
"use strict";
console.log(this.model);
console.log(this.get('model'));
}
}
});
from the controller i'm trying to get access to the model to get some information and format it, and send it to the view
but this.model or this.get('model') always returns null, how can i successful get access to the model from the controller? thanks
You are overriding the init method, but its broken, do this:
AcornsTest.StockController = Ember.ObjectController.extend({
init: function() {
"use strict";
this._super();
this.send('generateChartInfo');
});
You need to call the parent method.
See this test case: http://emberjs.jsbin.com/gijon/3/edit?js,console,output
The model is not ready at init time. If anyone has official docs please share.

Delete associated model with ember-data

I have two models:
App.User = DS.Model.create({
comments: DS.hasMany('App.Comment')
});
App.Comment = DS.Model.create({
user: DS.belongsTo('App.User')
});
When a user is deleted, it also will delete all its comments on the backend, so I should delete them from the client-side identity map.
I'm listing all the comments on the system from another place, so after deleting a user it would just crash.
Is there any way to specify this kind of dependency on the association? Thanks!
I use a mixin when I want to implement this behaviour. My models are defined as follows:
App.Post = DS.Model.extend(App.DeletesDependentRelationships, {
dependentRelationships: ['comments'],
comments: DS.hasMany('App.Comment'),
author: DS.belongsTo('App.User')
});
App.User = DS.Model.extend();
App.Comment = DS.Model.extend({
post: DS.belongsTo('App.Post')
});
The mixin itself:
App.DeletesDependentRelationships = Ember.Mixin.create({
// an array of relationship names to delete
dependentRelationships: null,
// set to 'delete' or 'unload' depending on whether or not you want
// to actually send the deletions to the server
deleteMethod: 'unload',
deleteRecord: function() {
var transaction = this.get('store').transaction();
transaction.add(this);
this.deleteDependentRelationships(transaction);
this._super();
},
deleteDependentRelationships: function(transaction) {
var self = this;
var klass = Ember.get(this.constructor.toString());
var fields = Ember.get(klass, 'fields');
this.get('dependentRelationships').forEach(function(name) {
var relationshipType = fields.get(name);
switch(relationshipType) {
case 'belongsTo': return self.deleteBelongsToRelationship(name, transaction);
case 'hasMany': return self.deleteHasManyRelationship(name, transaction);
}
});
},
deleteBelongsToRelationship: function(name, transaction) {
var record = this.get(name);
if (record) this.deleteOrUnloadRecord(record, transaction);
},
deleteHasManyRelationship: function(key, transaction) {
var self = this;
// deleting from a RecordArray doesn't play well with forEach,
// so convert to a normal array first
this.get(key).toArray().forEach(function(record) {
self.deleteOrUnloadRecord(record, transaction);
});
},
deleteOrUnloadRecord: function(record, transaction) {
var deleteMethod = this.get('deleteMethod');
if (deleteMethod === 'delete') {
transaction.add(record);
record.deleteRecord();
}
else if (deleteMethod === 'unload') {
var store = this.get('store');
store.unloadRecord(record);
}
}
});
Note that you can specify via deleteMethod whether or not you want to send the DELETE requests to your API. If your back-end is configured to delete dependent records automatically, then you will want to use the default.
Here's a jsfiddle that shows it in action.
A quick-and-dirty way would be to add the following to your user model
destroyRecord: ->
#get('comments').invoke('unloadRecord')
#_super()
I adapted the answer of #ahmacleod to work with ember-cli 2.13.1 and ember-data 2.13.0. I had an issue with nested relationships and the fact that after deleting an entity from the database its id was reused. This lead to conflicts with remnants in the ember-data model.
import Ember from 'ember';
export default Ember.Mixin.create({
dependentRelationships: null,
destroyRecord: function() {
this.deleteDependentRelationships();
return this._super()
.then(function (model) {
model.unloadRecord();
return model;
});
},
unloadRecord: function() {
this.deleteDependentRelationships();
this._super();
},
deleteDependentRelationships: function() {
var self = this;
var fields = Ember.get(this.constructor, 'fields');
this.get('dependentRelationships').forEach(function(name) {
self.deleteRelationship(name);
});
},
deleteRelationship (name) {
var self = this;
self.get(name).then(function (records) {
if (!records) {
return;
}
var reset = [];
if (!Ember.isArray(records)) {
records = [records];
reset = null;
}
records.forEach(function(record) {
if (record) {
record.unloadRecord();
}
});
self.set(name, reset);
});
},
});
Eventually, I had to set the relationship to [] (hasMany) or null (belongsTo). Else I would have run into the following error message:
Assertion Failed: You cannot update the id index of an InternalModel once set. Attempted to update <id>.
Maybe this is helpful for somebody else.