I am trying to use observers to observe a change on my model after XHR. This is because the earlier approach of extending a fn and calling super is not allowed any more.
Running into this weird issue where my observer doesn't fire:
App = Ember.Application.create({
ready: function () {
console.log('Ember Application ready');
this.topCampaignsController = Ember.ArrayController.create({
content: null
});
App.TopCampaignsModel.create({
// Calling super is no longer allowed in object instances
//success: function () {
// this._super();
// App.topCampaignsController.set('content', this.get('data'));
//},
onDataChange: function () {
console.log('data property on the object changed');
App.topCampaignsController.set('content', this.get('data'));
}.observes('data')
});
}
});
App.TopCampaignsModel = Ember.Object.extend({
data: null,
// this will be actually called from an XHR request
success: function () {
this.set('data', [5,10]);
},
init: function () {
console.log('TopCampaignsModel created');
this.success();
console.log(this.get('data'));
}
});
Jsfiddle here: http://jsfiddle.net/gdXfN/26/
Not sure why the console doesn't log "data property on the object changed". Open to alternative approaches on how I can override the 'success' fn in my instance.
After this commit in December last year, it is no longer possible to set observers during object creation. This resulted in a huge performance win.
To set observers on create you need to use:
var Object = Object.createWithMixins({
changed: function() {
}.observes('data')
});
Here's a fiddle demonstrating this.
The API documentation should be updated accordingly, something I will do later on.
However, I don't advise you to do that, but instead set observers during object definition. The same result can be achieved: http://jsfiddle.net/teddyzeenny/gdXfN/32/
That said, there are two things you are doing that go against Ember concepts:
You should not create controller instances yourself, you should let Ember create them for you:
App.TopCampaignsController = Em.Controller.extend({ content: null });
When the App is initialized, Ember will generate the controller for you.
Models should not be aware of controller existence. Controllers should access models not the other way round.
Models and Controllers will interact together through routes.
For the last two points, you can watch the tutorial at http://emberjs.com/guides/ to see how the Application, Controllers, Models, and Routes should interact. Since you're not using
Ember Data, just ignore DS.Model and imagine an Ember.Object instead. The tutorial can give you a pretty good overview of how objects should interact.
Related
I'm new to Ember.js and I've got some problems to understand its philosophy. I know actions up, data down but in real life, lets say I have Fotorama initialized in my-gallery component (I don't know if that is ok, but I did it in didInsertElement method). This library has its own events. They could look like this in plain JS:
$('.fotorama').on('fotorama:ready', function (e, fotorama) {});
or:
$('.fotorama').on('fotorama:show', function () {});
but I feel in Ember, those should be somehow mapped into actions in component.
My question is: how? I need to fire some actions (to be catched by another components or maybe a router) inside those actions. So I think it should be like this: this.sendAction('actionName', actionParams);
You can keep component reference to call sendAction method.
didInsertElement(){
this._super(...arguments);
var _this=this;
this.$('.fotorama').on('fotorama:show', function () {
_this.sendAction('actionName', actionParams);
});
}
willDestroyElement(){
this._super(...arguments);
this.$('.fotorama').off('fotorama:show')
}
If we find an better answer to this question. I will happily remove my answer.
I have a similar problem. I need to have a third party library talk to my ember app so I registered a custom event in my ember app:
const App = Ember.Application.extend({
customEvents: {
foo: 'foo',
},
});
Then triggered it from third party code:
<a href="..." onclick="$(this).trigger('foo'); return false;">
And, in a component, write the handler for that event according to the docs:
export default Ember.Component.extend({
foo(event) {
console.warn('event happened', event);
},
});
See:
* https://guides.emberjs.com/v3.0.0/components/handling-events/
* https://www.emberjs.com/api/ember/release/classes/Application/properties/customEvents?anchor=customEvents
I'm using this.store.push to push a record into the store from with the application controller (this action is being called from a socket service that is initialized in the application controller), using ember 2.2.1 I am achieving this like
var newStoreRecord = this.store.push({
data: {
id: id,
type: 'cart',
attributes: newCartItem
}
});
This adds this new item into the store but the template doesn't update to show the new item, I also tried adding something like this
this.get('cart.model').pushObject(newStoreRecord); assuming that I had something like cart: Ember.inject.controller(), at the top of the controller, might have had that one wrong anyway.
In the cart route I have my model being defined as so
model(params) {
this.set('routeParams',params.event_url);
return Ember.RSVP.hash({
event: null,
items: null
});
},
actions: {
didTransition() {
this.store.findRecord('event',this.get('routeParams')).then((result)=>{
this.controller.set('model.event',result);
});
this.controller.set('noItems',false);
this.store.query('cart',{auction_id:this.get('routeParams'),user:this.get('user.user.user_id'),combine:true}).then((result)=>{
if(!result.get('length')){
this.controller.set('noItems',true);
return null;
}else{
this.controller.set('model.items',result);
}
});
},
}
Not sure if I'm having troubles with getting the template to update because I'm not use the model hook? (btw, we're not using the model hook because of the bad performance on android we'd rather load an empty template with a loader and THEN load data rather than the other way around.
I have several thoughts here:
To answer your question specifically, when you set a variable from the store, like you're doing, it will only reference what was in the store at that time. It will not update automatically.
Your best bet is to add two new computed properties to your controller:
items: Ember.computed(function() {
return this.store.peekAll('cart');
}),
// You'll need to flesh this one out further
filteredItems: Ember.computed('items.#each.auction_id', function() {
return this.get('items').filter(...);
})
Reference filteredItems in your template and it should work.
Sidenote, I'd highly recommend refactoring a couple things.
I would use the setupController hook instead of didTransition. It runs after the model hook is complete so will be similar to what you're looking for
You can access the params at any time in the route, so you don't need to save them in the model hook
You don't need to return an a promise in the model hook if you're not doing any async data. Just return the object. You may need even need to do that.
Hope this helps.
I am unit testing my controller using mocha. My controller looks like:
AS.MyController = Ember.ObjectController.extend(Ember.Validations.Mixin, {
name: null,
description: null,
init: function () {
this._super();
this.get('store').find('something');
},
....
});
And my test begins like:
describe("MyControllerTest", function () {
//tried but didn't work
//delete AS.MyController.init;
var controller = AS.MyController.create();
.....
})
and the browser always throws error on "this.get('store')" call in init. I am not sure if I need to stub things out or there is a work around for it because my test case doesn't rely on store at all. In either case, I couldn't find much out there and would really appreciate any feedback.
Thanks, Dee
JSBIN : http://jsbin.com/aMASeq/3/
UPDATE :
There can be many ways to tackle this issue, but what I ended up doing is re-structuring the controller code a bit by putting all the function calls to store into separate actions and then in init I make calls to these action functions using this.send('actioName'). In my unit test, before instantiating the controller, I reopen the controller to modify these action functions(its easier to change action function than to change init function itself, when trying to change init I always got into some js error). Eg:
AS.MyController.reopen({actions: {setSomeActionThatUsesStore: function () {
//do something that doesn't involve using store
}}});
Controllers get access to the store from the container. You can create a mock container and instantiate the controller with it.
var mockContainer = new Ember.Container();
mockContainer.register('store:main', Ember.Object.extend({
find: function() { ... }
});
var controller = App.PostController.create({ container: mockContainer });
If you need access to the real store then you can just grab the controller from your App's container.
var controller = App.__container__.lookup('controller:post');
That will instantiate a PostController for you that has all of it's dependencies (such as store) wired together.
So, I am trying to get a simple propertyBinding to work with emberjs. Specifically, I have a controller with a content property, that gets updated under certain circumstances and a view, which needs that content array to draw some chart.
I have made the most basic example and it doesn't seem to work. My simple example is the following:
Appname.IndexController = Ember.Controller.extend({
value: 'bla'
});
Appname.IndexView = Ember.View.extend({
templateName: 'Index',
propertyBinding: 'Appname.IndexController.value',
didInsertElement: function() {
console.log('Indexview');
console.log(this.get('property'));
}
});
It is as simple as that, and it just does not work. What is really odd though, if I create another testcontroller (rather then extending it) e.g.
Appname.TestController = Ember.Controller.create({
value: 'jpopo'
});
the property binding works all of the sudden. But I just can not get it to work with the IndexController
(And in case the information is necessary, in the Applicaton.hbs I have an outlet)
Thanks for any help
Bindings work for instantiated objects, not for object definitions.
Appname.IndexController is the controller definition, not an instance. It is not what you want to bind to. The Ember.js app will create an instance of IndexController, and it's that created instance that you want to bind to:
To access the actual controller instance from its view, use controller.
Appname.IndexView = Ember.View.extend({
templateName: 'index',
propertyBinding: 'controller.value',
didInsertElement: function() {
console.log(this.get('property'));
}
});
Of course, that is if you follow Ember.js conventions.
Sample code for my question is here.
It's a simple Ember app that displays the SearchView containing a TextField by default.
When the user enters some text and hits Enter, I want to transition to another state (displayUserProfile) passing the value entered in the textbox.
At first, in the Textbox's insertNewline callback, I called the transitionTo method of the application's router, passing the value as part of the parameter object:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
App.router.transitionTo('displayUserProfile', {
username: this.get('value')
});
}
});
That works fine, but then I noticed that pangratz's answer on a question about infinite scrolling, uses a different approach. Instead he invokes a method on the view's controller, which in turn calls a method on the controller's target (which is the router).
This changes my code to:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
Em.tryInvoke(this.get('controller'), 'displayUserProfile', this.get('value').w());
}
});
App.SearchController = Em.Object.extend({
displayUserProfile: function(username) {
this.get('target').transitionTo('displayUserProfile', {
username: username
});
}
});
My question is: which approach is better?
Calling transitionTo directly from the view or delegating it to the view's controller?
I would recommend a different approach. insertNewLine should trigger an action that is handled by the router, which will then transition its state.
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
this.get('controller.target').send('showUser', {username: this.get('value')});
}
});
App.Router = Ember.Router.extend({
...
foo: Ember.Router.extend({
showUser: function(router, evt) {
router.transitionTo('displayUserProfile', evt);
});
}
});
You should put the showUser handler at the top-most route where it is valid in your app.
This approach follows the general pattern of events in Ember apps that views handle DOM-level events and where appropriate, turn them into semantic actions that are handled by the router.
Personally I think the second approach is better.
The first thing is that it's a bad idea to access the router statically. Then for me, you have to keep the views logic-less, so delegating to controller seems a good choice.
In your case this is only a call to the router, but you can imagine processing some algorithms on the textfield value. If you do this proccessing in you view, this will lead to a view, mixing UI code, and logic code. View should handle only UI code.