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
Related
In my Ember App, I have a large number of modal dialog components that I render in my Application route like so:
{{component modalComponent options=modalOptions}}
All dialog components extend from a single base class, where, for convenience, I have overridden sendAction. The point of the override is to always trigger some action on the target, as opposed to sendAction's default behavior of "if the property is undefined, do nothing". Here is what that looks like:
sendAction: function (actionName) {
if (Em.isEmpty(this.get(actionName))) {
this.set(actionName, actionName);
}
this._super(...arguments);
},
This seems to work as I would expect: always triggering an action on the target that will then bubble up the stack. What I'm wondering is...
Are there any implications/side-effects of overriding sendAction that I am not aware of?
Currently, one of the more accepted ways to handle actions in components is through closure actions:
In template:
{{do-button id="save" clickHandler=(action "storeEvent") contextMenuHandler=(action "logEvent") buttonText="Store It"}}
In component:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
clickHandler(event) {
this.get('clickHandler')(event);
},
contextMenuHandler(event) {
event.preventDefault();
this.get('contextMenuHandler')(event);
}
}
});
And finally, an excerpt from the controller:
actions: {
doStuff(event) {
alert(event);
},
logEvent(event) {
console.log(event);
},
So basically, you are taking the action passed into the component and calling it, passing in whatever arguments you want to from the component. Closure actions are pretty sweet, and they make working with actions a lot easier. Hope this gets your wheels turning :)
I'm developing a webapp to display and search documents. I've laid out the main wrapper div like this:
<div class="wrapper">
{{searchComponent searchComponent=searchComponent}}
{{pdfViewer pdfSearchComponent=searchComponent}}
</div>
This allows me to later add other types of viewers in there, like so:
{{otherViewer otherSearchComponent=searchComponent}}
The outer wrapper is an ember component as well. So it's controller looks like:
Ember.controller.extend({
searchComponent: null,
.
.
otherProperties,
actions: { }
});
And the searching component binds itself on itialization, as inspired from this source: http://www.samselikoff.com/blog/getting-ember-components-to-respond-to-actions/
Ember.controller.extend({
searchComponent: null,
.
.
onStart: function(){
this.searchComponent = this;
}.on('init'),
.
.
actions: {
someAction: function() {
//do something
}
}
}
So I can now reference the component from the main pdfViewer like this:
this.get('searchComponent').send('someAction')
To get the response, right now I bind another property to all the controllers / templates, and then watch for a change on that property in the viewer controller, after which I can place the results where they need to be.
Is there a way to send a 'message' from my 'pdfViewer' to my 'searchComponent' and receive a 'response' without explicitly binding them together, as above?
You could consider using pub/sub through a Service event bus, in which your searchComponent and pdfViewer both emit and listen for messages, so can talk to each other. Sure, there is a dependency on the service, but from what I see your components are pretty application-specific anyway.
Something like:
_listen: function() {
this.get('eventBus').on('message', this, 'handleMessage');
}.on('init'),
actions: {
click() { this.get('eventBus').trigger('message', message); }
}
A few weeks ago I evaluated several approaches to parent-children component communication: http://emberigniter.com/parent-to-children-component-communication/, perhaps this helps somewhat.
I have a component that represent a map and after an action in my controller I want to call a method on the component to center the map. The code looks like this
App.PlacesController = Ember.Controller.extend({
actions : {
centerMap : function () {
// how to call from here to GoogleMapComponent.centerMap ??
}
}
});
App.GoogleMapComponent = Ember.Component.extend({
centerMap : function () {
}
});
template
{{google-map}}
<button {{action "centerMap"}}>Center Map</button>
I have found a workaround but I don't think this is the Ember way of doing this.
{{google-map viewName="mapView"}}
<button class="center-map">Center Map</button>
App.PlacesView = Ember.View.extend({
didInsertElement : function () {
this.$(".center-map").click(this.clickCenterMap.bind(this));
},
clickCenterMap : function () {
this.get("mapView").centerMap();
}
});
In Ember, views (Components are glorified views) know about their controller, but controllers do NOT know about views. This is by design (MVC) to keep things decoupled, and so you can have many views that are being "powered" by a single controller, and the controller is none the wiser. So when thinking about the relationship, changes can happen to a controller and a view will react to those changes. So, just to reiterate, you should never try to access a view/component from within a controller.
There are a few options I can think of when dealing with your example.
Make the button part of your component! Components are meant to handle user input, like button clicks, so you may want to consider making the button a part of the map component and handle clicks in the actions hash of your component. If this buttons is always going to accompany the map component, then I certainly recommend this approach.
You could have a boolean property on your controller like isCentered, and when the button is clicked it's set to true. In your component you can bind to that controller's property, and react whenever that property changes. It's a two-way binding so you can also change your locally bound property to false if the user moves the map, for example.
Controller:
...
isCentered: false,
actions: {
centerMap: {
this.set('isCentered', true);
}
}
...
Component:
...
isCenteredBinding: 'controller.isCentered',
onIsCenteredChange: function () {
//do your thing
}.observes('isCentered'),
...
Jeremy Green's solution can work if you mix in the Ember.Evented mixin into the controller (which adds the pub/sub trigger and on methods)
You can use on to have your component listen for an event from the controller, then you can use trigger in the controller to emit an event.
So in your component you might have something like this:
didInsertElement : function(){
this.get('controller').on('recenter', $.proxy(this.recenter, this));
},
recenter : function(){
this.get("mapView").centerMap()
}
And in your controller you could have :
actions : {
centerMap : function () {
this.trigger('recenter');
}
}
Bind a component property to the controller property in the template:
{{google-map componentProperty=controllerProperty}}
Then observe the component property in the component:
onChange: function () {
// Do your thing
}.observes('componentProperty')
Now every time controllerProperty is changed in the controller, onChange in the component will be called.
From this answer, second paragraph.
I think it's OK to have a reference in your controller to your component. It's true that your component encapsulates it's own behaviour, but public methods like reload etc. are perfectly fine.
My solution for this is to pass the current controller to the component and set a property on the controller within the component.
Example
template.hbs:
{{#component delegate=controller property="refComponent"}}
component.js:
init: function() {
this._super.apply(this, arguments);
if (this.get("delegate")) {
this.get('delegate').set(this.get("property") || "default", this);
}
}
Now in your controller you can simply get a reference to your component with this.get("refComponent").
Steffen
Inside of your component call:
var parentController = this.get('targetObject');
See: http://emberjs.com/api/classes/Ember.Component.html#property_targetObject
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.
I have a mixin which automatically recalculates and sets the height of a div on page resize.
It works but it seems silly to me to be binding to a jQuery event and triggering an Ember event manually every time it is called.
Is there a way to bind to window events directly in Ember?
I have a simplified JSFiddle here
This is the code:
App.windowWrapper = Ember.Object.create(Ember.Evented,
resizeTimer: null
init: ->
#_super()
object = this
$(window).on 'resize', ->
window.clearTimeout(object.resizeTimer)
object.resizeTimer = setTimeout( App.windowWrapper.resize, 100)
true
resize: ->
App.windowWrapper.fire('resize')
)
And the mixin that is calling it.
App.Scrollable = Ember.Mixin.create
classNames: "scrollable"
init: ->
Ember.assert("Scrollable must be mixed in to a View", this instanceof Ember.View)
#_super()
didInsertElement: ->
#_super()
#calculateHeight()
App.windowWrapper.on('resize', this, 'calculateHeight')
willDestroyElement: ->
App.windowWrapper.off('resize', this, 'calculateHeight')
#_super()
calculateHeight: ->
offset = #$().offset().top
windowHeight = $(window).height()
#$().css('height', windowHeight - offset)
I know I'm a few years late, but I was looking for a solution to the same problem and found this:
jQuery(window).on('resize', Ember.run.bind(this, this.handleResize));
(source: Ember.js ofiicial)
**** P.S.
About the implementation of this little trick - I don't know if it's possible to use it in a controller method and trigger on init (as denis.peplin commented), I'm actually binding the event in the route's 'setupController' function. I don't know if it's best-practice, but it works like a charm in my app.
There's nothing wrong with registering your own event handler with jQuery for something like this.
Ember doesn't have any window event handling currently.
I'd also suggest trying to come up with a solution that didn't rely on globals.
And sample code for Itay Hamashmid's answer:
App.ApplicationController = Ember.Controller.extend({
handleResize: function() {
console.log('resized');
},
bindResizeEvent: function() {
jQuery(window).on('resize', Ember.run.bind(this, this.handleResize));
}.on('init')
});
I don't think it's possible with Ember.js - the only place where it provides built in DOM event handling is for Ember.View (click etc.) and maybe in other view related classes.
We also used similar approaches to your solution to handle special cases like this!
For the ones who searching by newest answers or just get rid of jquery...
In my case I just want to know if window resized to calc width of element.
import Component from '#ember/component';
export default Component.extend({
size: window.innerWidth,
init() {
this._super(...arguments);
window.addEventListener('resize', ()=> this.set('size', window.innerWidth));
}
});
Yeah, I know there is no way to unbind this kind of anonymous function, but I've searched and I can't find good answers...
I thinking to refactor this to use jquery but in a restricted scope like:
import Component from '#ember/component';
import $ from 'jquery';
export default Component.extend({
size: window.innerWidth,
init() {
this._super(...arguments);
$(document.querySelector('#something'))
.on('resize', (e)=> this.set('size', e.target.innerWidth));
}
});
And this will listener will live until this component lives!