Is there a way to pass an array to currentWhen in EmberJS? - ember.js

I'm trying to make a link stay 'active' on multiple routes, such as /#/users and /#/user.
Any ideas?

You can reopen Ember's LinkView and do something like this (allows currentWhen to contain space delimited values)
Ember.LinkView.reopen({
active: function() {
// we allow link-to's currentWhen to specify multiple routes
// so we need to check each one of them
var loadedParams = Ember.get(this, 'loadedParams');
var currentWhen = this['current-when'] || this.currentWhen;
currentWhen = currentWhen || (loadedParams && loadedParams.targetRouteName);
if (currentWhen && currentWhen.indexOf(' ') >= 0) {
var currents = currentWhen.split(' ');
router = Ember.get(this, 'router');
for (var i = 0; i < currents.length; i++) {
var isActive = router.isActive.apply(router, [currents[i]]);
if (isActive)
return isActive;
}
return false;
}
return this._super();
}.property('resolvedParams', 'routerArgs')
});

This is a total hack, but it works in Ember 1.0.0. To make links to users stay active when the user route is active:
App.UserRoute = Ember.Route.extend({
activate: function() {
setTimeout(function() {
$('[href="#/users"]').addClass("active");
}, 0);
}
});

Related

What gets to be a model in Ember?

I find myself writing a search function for hotels. When searching for hotels, you would want to specify the rooms & amount of people per room. Where would the class for the room-query best reside? As a model, even though it isn't fetched from the data store?
I currently have placed it as a utility, i.e.
App.RoomQuery = Ember.Object.extend({
travelers: null
});
but this doesn't feel like the correct approach. I am using it as
Ember.ObjectController.extend({
model: function() {
return {
rooms: [RoomQuery.create({travelers: ['25', '28']})]
}
}.property(),
roomString: function() {
var rooms = this.get('rooms');
var roomStringArray = [];
for(var i = 0; i < rooms.length; i++) {
var travelers = rooms[i].get('travelers').toArray();
roomStringArray.push(travelers.join("-"));
}
return roomStringArray.join(",");
},
actions: {
addRoom: function() {
this.get('rooms').pushObject(RoomQuery.create({}));
},
add: function(room) {
room.addTraveler();
}
}
});

Ember update property on the changes in array

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!

Emberjs how can I make collection arrangedContent and searchResults work together?

I have a controller that observes a search field like so:
Scrolls.IndexController = Ember.ArrayController.extend({
searchResult: function() {
var that = this;
this.get('model').set('content', this.store.filter('scroll', function(item) {
var searchTerm = that.get('searchCard');
var regExp = new RegExp(searchTerm, 'i');
return regExp.test(item.get('name'));
}));
}.observes('searchCard')
});
Which works great, but once I add a method that overrides arrangedContent to limit the returned items, it stops re-rendering.
Scrolls.IndexController = Ember.ArrayController.extend({
arrangedContent: Ember.computed('content', function() {
var count = 0;
return this.get('content').filter(function() {
count++;
return count <= 3;
});
}),
searchResult: function() {
var that = this;
this.get('model').set('content', this.store.filter('scroll', function(item) {
var searchTerm = that.get('searchCard');
var regExp = new RegExp(searchTerm, 'i');
return regExp.test(item.get('name'));
}));
}.observes('searchCard')
});
How can I get make what I'm doing to behave nicely with each other?
I see a few things here that jump out to me. First one being, in the context of a controller, content and model are the same thing so in the observer, when you do:
this.get('model').set('content'
You're setting a property of 'content' on the model when I think you actually intend to set the content directly on the controller, like this:
this.set('content',
I also kind of wonder whether you really need to override the content and arrangedContent properties (not sure what the calling code looks like). I suspect that might cause some bugs later. Instead, I wonder if you could set it up like this:
Scrolls.IndexController = Ember.ArrayController.extend({
firstThreeSearchResults: function() {
var count = 0;
return this.get('searchResults').filter(function() {
count++;
return count <= 3;
});
}.property('searchResults'),
searchResults: function() {
var searchTerm = this.get('searchCard');
return this.store.filter('scroll', function(item) {
var regExp = new RegExp(searchTerm, 'i');
return regExp.test(item.get('name'));
});
}.property('searchCard')
});
Final possible problem is the use of the filter function called on the store. According to the docs, this function: "returns a live RecordArray that remains up to date as new records are loaded into the store or created locally." The problem being, though the filter might update as new results are added, it might not cause the computed property that looks for the first three results to update. That is, the binding on that computed property might not fire. One way to get around this would be to do something like this:
Scrolls.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find();
}
});
Scrolls.IndexController = Ember.ArrayController.extend({
firstThreeSearchResults: function() {
var count = 0;
return this.get('searchResults').filter(function() {
count++;
return count <= 3;
});
}.property('searchResults'),
searchResults: function() {
var searchTerm = this.get('searchCard');
return this.get('content').filter(function(item) {
var regExp = new RegExp(searchTerm, 'i');
return regExp.test(item.get('name'));
});
}.property('searchCard', 'content.length')
});

How can I programmatically add/remove models to a controller?

This shouldn't be too hard.
I have a datepicker UI widget, and each time the user clicks on a month, I want to add or remove that month from the MonthsController (an ArrayController). The MonthsController is not associated with a route, so in my ApplicationTemplate I simply have
{{render months}}
A simplified version of my datepicker view is
App.DatepickerView = Ember.View.extend({
click: function(e) {
var id = $(this).datepicker().data('date').replace(" ", "-");
this.get('controller.controllers.months').toggleMonth(id);
}
});
and I handle the event in my MonthsController:
App.MonthsController = Ember.ArrayController.extend({
toggleMonth: function(id) {
var month = App.Month.find(id),
index = this.indexOf(month);
if (index === -1) {
this.pushObject(month);
} else {
this.removeAt(index);
}
}
});
I thought I had this working, but then I realized that month in the last snippet wasn't really an App.Month, it was just (I suppose) an anonymous object.
How can I programmatically add/remove models to a controller?
Your App.Month.find(id) will return a promise. If that month hasn't loaded yet you would also be loading this data from the server. You need to wrap your code in the promise's then.
toggleMonth: function(id) {
var _this = this;
App.Month.find(id).then(function(month) {
var index = _this.indexOf(month);
if (index === -1) {
_this.pushObject(month);
} else {
_this.removeAt(index);
}
});
}

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.