Ember Communication Between Components - ember.js

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.

Related

Ember.js Best Practices - Can/Should I override sendAction?

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 :)

how to deal with custom events in ember.js component?

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

Handling network connectivity and promise rejections in Ember

I am trying to implement an application-level error handler to catch failed promises within my application. Most of the suggestions I've seen around this have to do with error logging, but I actually want to trigger a modal window to let my users know they're no longer connected to the internet + can only read data.
I'd like to trigger an action from within the RSVP.onerror handler. The only way I've managed to do this so far is with something like this:
Ember.RSVP.configure('onerror', function(e) {
window.App.__container__
.lookup('controller:application')
.send('showModal', 'disconnected');
});
but it seems a bit hacky to me.
Is there somewhere more appropriate to write this code? More generally, are there any best practices around notifying users of a lost internet connection?
Ember has a built in error route (docs here).
Depending on how you want to handle other kinds of errors in your app, you could add views/error.js and run a method on willInsertElement in that view that tests whether the user is offline and, if true, sends the showModal action.
Alternatively, failed promises can automatically be caught in the original promise and trigger some method that has been injected into the controller or whatever instance is making the promise:
Controller:
this.set('thing').then(function() {
// Success
}, this.offlineError()); // Failure
Initializer (note the syntax is for ember-cli):
export default {
name: 'offline-error',
initialize: function(container, app) {
var offlineError = function() {
// or something like this
container.lookup('controller:application').send('showModal', 'disconnected');
};
app.register('offlineError', offlineError, { instantiate: false });
// Inject into routes, controllers, and views.
Em.A(['route', 'controller', 'view']).forEach(function(place) {
app.inject(place, 'offlineError', 'offlineError:main');
});
}
};
The above code makes the offlineError method available in all routes, controllers, and views but the beauty of it is that the method has access to the container without using the hacky private property.
I ended up wrapping Offline.js in a service. I'm using ember-cli.
// services/connection.js
/* global Offline */
import Ember from 'ember';
// Configure Offline.js. In this case, we don't want to retry XHRs.
Offline.requests = false;
export default Ember.Object.extend({
setup: function() {
if (Offline.state === 'up') {
this._handleOnline();
} else {
this._handleOffline();
}
Offline.on('down', this._handleOffline, this);
Offline.on('up', this._handleOnline, this);
}.on('init'),
_handleOffline: function() {
this.set('isOffline', true);
this.set('isOnline', false);
},
_handleOnline: function() {
this.set('isOnline', true);
this.set('isOffline', false);
}
});
I injected the service into controllers and routes:
// initializers/connection.js
export default {
name: 'connection',
initialize: function(container, app) {
app.inject('controller', 'connection', 'service:connection');
app.inject('route', 'connection', 'service:connection');
}
};
Now in my templates, I can reference the service:
{{#if connection.isOffline}}
<span class="offline-status">
<span class="offline-status__indicator"></span>
Offline
</span>
{{/if}}
(Offline.js also has some packaged themes, but I went with something custom here).
Also, in my promise rejection handlers I can check if the app is offline, or if it was another unknown error with the back-end, and respond appropriately.
If anyone has any suggestions on this solution, chime in!

Difference between send() and triggerAction()

I'm sending an event from a view to its parent view. Now I have two solutions that work:
App.View = Ember.View.extend({
somethingHappened: function() {
this.get('parentView').send('anAction');
})
});
or
App.View = Ember.View.extend(Ember.ViewTargetActionSupport, {
somethingHappened: function() {
this.triggerAction({
action: 'anAction',
target: this.get('parentView')
});
})
});
What is the difference in the two methods? I can't figure out from the API in what cases I should use triggerAction from the ViewTargetActionSupport mixin.
If you look at the source for triggerAction you will see that it uses send() internally. In my opinion it seems that triggerAction is mostly a nice wrapper around send, though I haven't used it before and just relied on send.
triggerAction bubbles an event. send and sendAction require an action coming up from the component itself.
triggerAction in the component's controller will work with only {{component-name}} in the template.
Where as, send requires something like {{component-name onConfirm=(action='doItNow')}}

Parent Route Not Asked to Handle Event?

The guide says that when an action is triggered, Ember first looks for a handler in the current controller, then if it can't find it in the controller it looks in the current route, then the parent route, etc. I'm not seeing that happen.
My routes:
App.Router.map(function() {
// Creates 'products' and 'products.index' routes
this.resource('products', function(){
// ...
});
});
My super trivial products.index template;
<span {{action fooBar}}>Run fooBar</span>
To test this, I'm currently at /#/products in the browser, and Ember logs "Transitioned into 'products.index'" saying I'm currently in the products.index route, as I expect. Now if click on the action, Ember should look for a handler in:
ProductsIndexController
ProductsIndexRoute
ProductsRoute
My observations:
If I put the handler in ProductsIndexController, it works.
If I put the handler in ProductsIndexRoute, it works.
However, if I put the handler in ProductsRoute, it's never called:
.
App.ProductsRoute = Ember.Route.extend({
events: {
fooBar: function(){
alert("alarm!");
}
}
});
Instead I see the error:
*Error: Nothing handled the event 'fooBar'.*
What am I missing?
One of my other javascript files was also setting/creating App.ProductsRoute (and doing nothing with it), which was causing a conflict. Silly mistake.