ember store.find promise not being fulfilled in time - ember.js

I'm using an ArrayController with an ItemController but I'm having problems with getting my store.find promise fulfilled before the hotelName and hotelAddress properties in my ItemController are used. Any idea on how I can unsure the store find is fulfilled before returning? I know the store.find works. I'm able to see the results but the timing is off. Thank you.
export default Ember.ArrayController.extend({
itemController: ...,
});
export default Ember.ObjectController.extend({
init: function() {
this._super();
},
hotelName: function() {
var self = this;
self.store.find('hotel', this.get('hotelId')).then(function(hotel) {
self.set('hotelName', hotel.get('name'));
});
}.property('hotelId'),
hotelAddress: function() {
var self = this;
self.store.find('hotel', this.get('hotelId')).then(function(hotel) {
self.set('hotelAddress', hotel.get('street'));
});
}.property('hotelId')
});

Related

Ember, mixin to detect click outside of view/component

I'm writing a Mixin to handle when user clicks outside of a view/component.
This is the mixin:
App.ClickElsewhereMixin = Ember.Mixin.create({
onClickElsewhere: Ember.K,
didRender: function() {
this._super.apply(this, arguments);
return $(document).on('click', this.get('onClickElsewhere'));
},
willDestroyElement: function() {
this._super.apply(this, arguments);
$(document).off('click', this.get('onClickElsewhere'));
},
});
I use it in my component:
onClickElsewhere: function() {
this.send('exitEditMode');
},
But when I run it, I get:
TypeError: this.send is not a function
How can I keep the this context?
Solution:
just to make it easier for the reader, here the working Mixin:
App.ClickElsewhereMixin = Ember.Mixin.create({
onClickElsewhere: Ember.K,
setupListener: Ember.on('didRender', function() {
// Set an event that will be fired when user clicks outside of the component/view
return $(document).on('click', $.proxy(this.get('onClickElsewhere'), this));
}),
removeListener: Ember.on('willDestroyElement', function() {
// Clean the previously defined event to keep events stack clean
return $(document).off('click', $.proxy(this.get('onClickElsewhere'), this));
}),
});
The current answer doesn't check whether the click was actually outside of the element – a click on the component will also trigger the callback.
Here's an updated version:
export default Ember.Mixin.create({
onOutsideClick: Ember.K,
handleOutsideClick: function(event) {
let $element = this.$();
let $target = $(event.target);
if (!$target.closest($element).length) {
this.onOutsideClick();
}
},
setupOutsideClickListener: Ember.on('didInsertElement', function() {
let clickHandler = this.get('handleOutsideClick').bind(this);
return Ember.$(document).on('click', clickHandler);
}),
removeOutsideClickListener: Ember.on('willDestroyElement', function() {
let clickHandler = this.get('handleOutsideClick').bind(this);
return Ember.$(document).off('click', clickHandler);
})
});
Greg answer have a mistake, that makes removing the clickHandler event not working. Which means that your clickevent will fire even if you destroy the component.
Here is proper version
import Ember from 'ember';
export default Ember.Mixin.create({
onOutsideClick: Ember.K,
handleOutsideClick: function(event) {
let $element = this.$();
let $target = $(event.target);
if (!$target.closest($element).length) {
this.onOutsideClick();
}
},
setupOutsideClickListener: Ember.on('didInsertElement', function() {
let clickHandler = this.get('handleOutsideClick').bind(this);
return Ember.$(document).on('click', clickHandler);
}),
removeOutsideClickListener: Ember.on('willDestroyElement', function() {
let clickHandler = this.get('handleOutsideClick').bind(this);
return Ember.$(document).off('click', Ember.run.cancel(this, clickHandler));
})
});
The ember way of doing it is Ember.run.bind. This takes care of binding and the run loop.
App.ClickElsewhereMixin = Ember.Mixin.create({
onClickElsewhere: Ember.K,
setupListener: Ember.on('didRender', function() {
this.set('clickHandler', Ember.run.bind(this, this.onClickElsewhere));
Ember.$(document).click(this.get('clickHandler'));
}),
removeListener: Ember.on('willDestroyElement', function() {
Ember.$(document).off('click', this.get('clickHandler'));
}),
});
You have two options:
Use a closure
Use bind
Closure
App.ClickElsewhereMixin = Ember.Mixin.create({
onClickElsewhere: Ember.K,
didRender: function() {
this._super.apply(this, arguments);
return $(document).on('click', function(this){ return this.get('onClickElsewhere'); }(this));
},
willDestroyElement: function() {
this._super.apply(this, arguments);
$(document).off('click', function(this){ return this.get('onClickElsewhere'); }(this));
},
});
Bind
App.ClickElsewhereMixin = Ember.Mixin.create({
onClickElsewhere: Ember.K,
didRender: function() {
this._super.apply(this, arguments);
return $(document).on('click', this.get('onClickElsewhere').bind(this));
},
willDestroyElement: function() {
this._super.apply(this, arguments);
$(document).off('click', this.get('onClickElsewhere').bind(this));
},
});
However, not all browsers support bind yet.
Also, I think you need to use sendAction instead of send in the component (http://guides.emberjs.com/v1.10.0/components/sending-actions-from-components-to-your-application/)
Edit:
jQuery.proxy uses call/apply underneath the covers. See this post for a discussion of call/apply vs bind.
You can use the lib ember-click-outside. Worked for me.

Ember: ArrayController computed property based on array item properties

I have an ArrayContoller on which I want to set a boolean property based on the properties of its contents.
Plain-language description of the logic:
If the array contains any items with a property of isRetired equal to true, set the retiredShoes property of the ArrayController to true, otherwise, set the ArrayController retiredShoes property to false.
It seems like this should be a simple matter, but I haven't found a solution anywhere, and I'm still pretty new at this.
I'll put together a jsfiddle if necessary.
Here are the controllers for the array and the object:
App.ApplicationController = Ember.ArrayController.extend({
sortProperties: ['title'],
itemController: 'shoe',
retiredShoes: function() {
//how do I compute this sucker?
}
});
App.ShoeController = Ember.ObjectController.extend({
needs: ['application'],
actions: {
delete: function() {
var shoe = this.get('model'),
runs = shoe.get('runs');
shoe.deleteRecord();
shoe.save();
},
toggleRetired: function() {
var shoe = this.get('model');
shoe.toggleProperty('isRetired');
shoe.save();
}
}
});
Off top of my head, without jsbin. If there's a problem/bug, drop me a comment and I'll look it over again.
App.ApplicationController = Ember.ArrayController.extend({
retiredShoes: function() {
return this.get("model").isAny("isRetired", true);
}.property("model.#each.isRetired")
});

Ember.js: Computed property involving other controller not refreshing upon load of model

I have the following code:
App.UserController = App.EditableController.extend({
needs: 'application',
test: function() {
return this.get('controller.application.me.email');
}.property('controller.application.me.email'),
});
App.ApplicationController = Ember.Controller.extend({
isPublic : true,
me : null,
init: function () {
this.set('me', App.User.find(1));
this._super();
}
});
But the computed property doesn't seem to update once the model loads (from the console):
> App.__container__.lookup('controller:Application').get('me.email')
"test#example.com"
> App.__container__.lookup('controller:User').get('test')
undefined
Am I missing something?
Assuming your App.EditableController is of type Ember.ObjectController then this should work:
App.UserController = Ember.ObjectController.extend({
needs: 'application',
// notice the we use here plural 'controllers' to have access to the
// controllers defined with the 'needs' API
contentBinding: 'controllers.application',
test: function() {
return this.get('content.me.email');
}.property('content')
});
In the case that your App.EditableControlleris of type Ember.Controller than this should do the job:
App.UserController = Ember.Controller.extend({
needs: 'application',
// notice the we use here plural 'controllers' to have access to the
// controllers defined with the 'needs' API
controllerBinding: 'controllers.application',
test: function() {
return this.get('controller.me.email');
}.property('controller')
});
Now doing App.__container__.lookup('controller:User').get('test') in the console should output:
"test#example.com" // or whatever your example mail is
Hope it helps.

Ember Router transitionTo nested route with params

App.Router.map(function() {
this.resource('documents', { path: '/documents' }, function() {
this.route('edit', { path: ':document_id/edit' });
});
this.resource('documentsFiltered', { path: '/documents/:type_id' }, function() {
this.route('edit', { path: ':document_id/edit' });
this.route('new');
});
});
And this controller with a subview event that basically transitions to a filtered document
App.DocumentsController = Ember.ArrayController.extend({
subview: function(context) {
Ember.run.next(this, function() {
//window.location.hash = '#/documents/'+context.id;
return this.transitionTo('documentsFiltered', context);
});
},
});
My problem is that this code works fine when Hash of page is changed.
But when I run the above code NOT w/ the location.hash bit and w/ the Ember native transitionTo I get a cryptic
Uncaught TypeError: Object [object Object] has no method 'slice'
Any clues?
Thanks
UPDATE:
App.DocumentsFilteredRoute = Ember.Route.extend({
model: function(params) {
return App.Document.find({type_id: params.type_id});
},
});
{{#collection contentBinding="documents" tagName="ul" class="content-nav"}}
<li {{action subview this}}>{{this.nameOfType}}</li>
{{/collection}}
The problem is that your model hook is returning an array, while in your transitionTo you are using a single object. As a rule of thumb your calls to transitionTo should pass the same data structure that is returned by your model hook. Following this rule of thumb i would recommend to do the following:
App.DocumentsController = Ember.ArrayController.extend({
subview: function(document) {
var documents = App.Document.find({type_id: document.get("typeId")});
Ember.run.next(this, function() {
return this.transitionTo('documentsFiltered', documents);
});
}
});
Note: I assume that the type_id is stored in the attribute typeId. Maybe you need to adapt it according to your needs.

Why model is not accessible inside controller while accessible in handlebar template?

I have a model of patient object
App.Router.map (function () {
this.resource('patients');
this.resource('patient', {path: ':patient_id'}, function(){
this.resource('dashboard', function() {
this.route('summary');
});
});
});
App.PatientRoute = Ember.Route.extend({
model: function(params) {
return App.Patient.find(params.patient_id);
},
setupController: function(){
console.log("Menu Items:" + App.PatientMenuItem.find() );
this.controllerFor('patient').set('menuItems', App.PatientMenuItem.find());
}
});
App.DashboardSummaryRoute = Ember.Route.extend({
setupController: function(){
this.controllerFor('dashboard.summary').set('content', this.controllerFor('patient').get('model'));
this.controllerFor('dashboard.summary').getPatient();
}
});
App.DashboardSummaryController = Ember.ObjectController.extend({
getPatient:function(){
console.log(this.content.get_allergies);
}
});
App.PatientController = Ember.ObjectController.extend({
menuItems:[],
});
<script type="text/x-handlebars" data-template-name="dashboard/summary">
Summary{{this.content.get_allergies}}
</script>
In the above I am not able to access the same get_allergies in DashboardSummaryController but I am able to access it in handlebars, Can anyone help me what is the mistake ?
Thanks in advance
I don't know if this alone solves the problem, but always use the get() and set() methods when accessing properties. So i would suggest to try this in your getPatient() method:
App.DashboardSummaryController = Ember.ObjectController.extend({
getPatient:function(){
console.log(this.get("content.get_allergies"));
}
});
Why does the template work? The Handlebars expression you have there is automatically translated into the call, i have suggested for your controller method.