Important changes to Plugin Outlets for Ember 2.10


(Robin Ward) #1

Shortly I will be merging a branch of Discourse into master that updates us to the latest stable version of Ember, which is 2.10. This is exciting because we’ve been behind Ember’s stable branch for a while and we’ve finally caught up! It also contains the Glimmer 2 template engine which is quite a bit faster so the application should be more responsive, and it also cuts down on our template file sizes so the app should be quicker to download.

However, there is a downside, and that I wasn’t able to get plugin outlets to work with 100% backwards compatibility due to internal changes in Ember.

What’s changed with Plugin Outlets

Previously, if your plugin made a connector to a {{plugin-outlet}}, you’d have the exact same scope in the handlebars document as where the outlet was declared. In the latest release, you will only have access to variables that are explicitly passed in to the outlet. I’ve gone through every plugin outlet in the codebase and passed in the main models and variables so if you were using those you should be OK. However, if you were using unusual or rare variables from the template you might have to update your code.

The most likely thing to break in your plugins is if you added data to a template by extending a controller and not a model, as they won’t be passed into your connector anymore. Fortunately, there’s a straightforward way to fix that:

New in Discourse: Connector Classes

You can now optionally define a class that will be associated with your connector.

Let’s say your connector was defined in my-plugin/templates/connectors/user-profile-controls/my-connector.hbs. You could create the following class:


export default {
  setupComponent(args, component) {
    component.set('today', new Date());

When your connector is inserted to the outlet, it will call the setupComponent method and you will get access to the plugin’s arguments (such as models) and you can call set on it to make variables available in your template.

A new feature is you can define a method called shouldRender which will determine if the connector will be inserted into the template. This is useful because previously all connectors had to be inserted, which made working with <ul> and <li> tags complicated:


export default {
  // if false the plugin won't be rendered
  shouldRender(args, component) {
     return component.siteSettings.my_plugin_enabled;

Finally, you can also declare actions in your component class. This is much simpler than using an initializer to extend the controller and defining the actions there. If you have added any actions to controllers you’ll need to move them into component classes.


export default {
  actions: {
    myAction() {
      console.log('my action triggered');

Upgrade Schedule

I’ve been reviewing all the officially supported plugins as well as many popular plugins to make sure they can be updated for this change. Once I’m confident that everything is working I’ll be merging this into the master / tests-passed branches of Discourse. This should happen either this afternoon or first thing Tomorrow morning. After that, we’ll keep a close eye on it before we merge it into our next beta.

The change is due to be in our next stable release, which should be available in early January.

The faster you upgrade your plugins, the better! If you need some help or find a bug in a common plugin just let me know and I’ll fix it as soon as possible.

Conditionally rendering a plugin outlet
Connector Classes and Actions
Able to use the 'extraNavItem' custom html outlet?
Dynamic Templates for plugin-outlet UIs
Site settings for custom plugin are ignored after discourse update
Reputation Plugin Version 0.2
Testing viewingSelf from plugin outlet
What is the difference between raw.hbs handlerbar files and only .hbs handlerbar files?
SetUpComponent not being called for topic list tags plugin outlet
(Robin Ward) #2

Update: We’re just going to deploy for meta for now. Ember 2.10 is running here so please let us know if you notice anything broken.

If you are feeling adventurous, you can deploy from the ember-2.10 branch on your discourses and try things out yourself!

(Leo McArdle) #3

Is it possible to create a connector class from a site customization?

(Robin Ward) #4

Not yet. There’s an internal way to register a class but it’s not exposed via plugin api yet.

(Leo McArdle) #5

Will it be exposed via the plugin api before ember-2.10 lands on master? I’d quite like to keep my user card/profile customizations as a site customization (rather than a full-blown-plugin), if possible

(Régis Hanol) #6

I’m afraid it’s too late. @eviltrout just merged it.

(Robin Ward) #7

Give me a few minutes, I’ll add it to the plugin api.

(Robin Ward) #8

Okay I’ve addeed a Plugin API (version 0.6) to register a connector class. If you’re writing a full plugin I recommend you don’t use this and let the connector class be automatically resolved, but if you’re using a customization this will work:


api.registerConnectorClass('user-profile-primary', 'my-connector', {
  shouldRender(args, component) {
    return component.siteSettings.my_plugin_enabled;

(Sam Saffron) #9

Just to expand a bit, looks like if you want to set a class on wrapping component you would use:

api.registerConnectorClass('user-profile-primary', 'my-connector', {
  setupComponent() {
    this.set('classNames', ['foo']);

With the new changes I am seeing a wrapping span @eviltrout which is a bit of a blocker for me:

Outlet is defined as:

 {{plugin-outlet name="user-activity-bottom"
                    args=(hash model=model)}}

Is there any way to remove the wrapping span?

Plugin admin button broken styling
(Robin Ward) #13

Unfortunately there’s no way to remove it. Maybe you can change the HTML to have two ul tags, where the second one is the outlet?

(Mittineague) #14

Unlike the posted example directory structure

I noticed that some plugins (eg, cakeday) are like


I’m guessing the directory structure is not important as long as the folder is named “connectors” and the choice is up to preference.

I am wondering how to differentiate between different but identically named plugin outlets. i.e.


(Robin Ward) #15

Yeah I realized a bunch of plugins did it one way or the other so both are supported. In the long term I’d like to standardize on one and warn plugins that do it the other way.

In those cases the outlet is effectively the same - you can’t see desktop or mobile at the same time, and you can’t see a tag/show as well as discovery.

If your plugin needs to be more specific, we might have to add another outlet.

(NTAuthority) #16

I’m trying to port a plugin to work with the Ember 2.10 port… however it seems topic-list-tags outlets actually don’t get output at all? I thought the context field didn’t exist anymore but the code tells me otherwise, however even if I’m using inline templates from customizations nothing seems to change in my topic list:

<script type='text/x-handlebars' data-template-name='/connectors/topic-list-tags/topic-test.raw'>

… yet, this does not show up in my actual topic list - if I remove the .raw I do get the usual error that would happen if a non-raw template gets used instead of a raw template (e.template being undefined), but this topic-list-tags connector doesn’t work from a plugin either.

@eviltrout - did you even verify the functioning of your new ‘raw’ plugin outlets? :slight_smile:

(Robin Ward) #17

This should fix it:

(Aman Jagga) #18

After the upgrade, my plugin is not able to route the URI to the correct template.Are there any changes in the Ember routing as well?

I am using this Ember route code to map this URI with the template main-page.hbs located in the home folder

export default {
    resource: 'home',
    path: '/home',
    map() {
        this.route('main-page', { path: 'page' });


git hub link to route file @route
Full source code for the plugin is available @github

Any leads or hints will be highly appreciated.

(Robin Ward) #19

I am not sure how that worked before - I think perhaps for a very short period of time we had a home resource, but that no longer exists. Since you are not embedding your route inside another one, you can just export a function and it should work:

export default function() {
  this.route('main-page', { path: '/home/page' });

(Aman Jagga) #20

Thanks, somehow I was confused with the resource part

(Shoshana Berleant) #23

I was unable to get actions working like it says above, but I found another way to do it:

In OP:

export default {
  actions: {
    myAction() {
      console.log('my action triggered');

What I did:

export default {
  setUpComponent(args, component) {
    component.set('actions', {})
    component.set('actions.myAction', () => { console.log('my action triggered') })

(Robin Ward) #24

That seems incorrect. How were you triggering your action?

(Shoshana Berleant) #25

In the handlebars file:

{{input type="checkbox" name="myCheckbox"
        change=(action "myAction")}}

It seems to work–is there a better/more correct way to do it?