Using a Discourse component in a plugin

I have a question about reusing Discourse “core” Ember components in plugins. More particularly, I ran into trouble today trying to put a “signup” d-button into a plugin template.

Here is where I tried to do:
https://github.com/jgujgu/discourse-call-to-action/blob/master/assets/javascripts/discourse/templates/connectors/discovery-below/call-to-action.hbs#L14

And this is the console error it created:

I understand (very vaguely?) that showCreateAccount is a controller function passed along to different components that need it:
https://github.com/discourse/discourse/blob/adb73180f727be5b5bf4772c81f99e5ac9d10eea/app/assets/javascripts/discourse/templates/application.hbs#L3

Moreover, in order to get this modal to pop up in my component, do I need to fundamentally change the structure of my files? I don’t think my file structure is necessarily correct either. :sweat:

I just dropped this into a handlebars template out of curiosity:

{{d-button action="showCreateAccount" class="btn-primary sign-up-button" label="sign_up"}}

It worked like a charm. It gave me a “Sign Up” button that displayed the account creation modal. Note that this works when the template is specific to the plugin and not through a connector.

That said, if I do the same thing in a handlebars template that is using a plugin-outlet through a connector, it fails. Makes me think the connector templates aren’t inheriting actions from above. @eviltrout, is there any truth to that? That would be good to know. Even templates that have the action available and a plugin-outlet fail when the connector is used.

On another note, try inserting {{signup-cta}} in your handlebars file. See if that’s something you’re interested in.

4 Likes

As of Ember 2.10 we can’t just inherit the exact same context as the parent template, so outlets only have access to things that are explicitly passed into them, including actions.

We’ve tried to pass in all the data an outlet would need, but if you find a case where an action is needed in an outlet and it can’t call it, we can accept a PR to handle htat.

3 Likes

Is there a way to augment what’s passed through a plugin? That way we don’t create any extra bloat with PRs to add actions.

1 Like

No unfortunately the contract between the parent template and the outlet has to be explicit.

It is a little more limiting, but on the other hand it means we know exactly what objects / actions outlets can be using which helps us not break things in the future.

1 Like

I spent a little time digging through the Ember pieces of plugin-outlets and came up short. I didn’t see where the actions passed are explicitly defined. Are they passed through the component in buildArgs?

1 Like

buildArgs is only used for our widgets.

I did a quick search through our codebase and I couldn’t find an action being passed in either which is quite interesting, which tells me that most plugins just operate on the objects they are working on!

Having said that, it would look like this:

{{plugin-outlet 
  name="some-outlet" 
  args=(hash model=model someAction=(action "someAction"))}}

Then in your component you could do this.attrs.someAction()

1 Like

I tried altering a handful of plugin-outlets and managed to get the same error each time:

Assertion Failed: No application initializer named 'inject-discourse-objects'

The outlets don’t like it when I give them an action of any kind. I’m specifically looking at the static.hbs template since the action is already called there. That way I know it’s available.

I can’t say that I can think of an instance where I’ve tried to do this in any way. I’ve always used my own actions. But I could see the benefit of using them in the future. I just can’t think of an example right now.

Edit: Robin! That whole rm -rf tmp thing still gets me sometimes. I still get an error but it’s not the same:

Assertion Failed: <(subclass of Ember.Component):ember1016> had no action handler for: showCreateAccount

1 Like

You are getting that error when calling this.attrs.showCreateAccount() after passing it in?

It’s on the outlet itself when I change it to this:

{{plugin-outlet name="above-static" args=(hash model=model showCreateAccount=(action "showCreateAccount"))}}

How are you triggering the action though?

I don’t get that far. I didn’t add the action to the connector at all.

Oh I see, the error is trying to attach it! The error is because showCreateAccount doesn’t belong to the application controller, it’s part of the application route itself. Instead you can pass it the same way site-header does:

showCreateAccount=(action "appRouteAction" "showCreateAccount")

That gives me this:

An action named 'appRouteAction' was not found in <(subclass of Ember.Controller):ember1073>

Edit:
Here’s the exact outlet I’m working with now:

{{plugin-outlet name="above-static" args=(hash model=model showCreateAccount=(action "appRouteAction" "showCreateAccount"))}}

Ah, the static controller doesn’t have the appRouteAction helper that the application controller does. This is a bit of a complicated situation I promise, because it involves sending an action to something that is present on every page of the site :stuck_out_tongue:

Your best bet would be to create a new action in the static controller that simply calls this.send('showCreateAccount') and then bind that to the plugin outlet.

Ok. I added it to the static controller and now I can see it in the Ember inspector on the controller. :thumbsup: The trick is how to trigger that action from a d-button action within a connector. Wouldn’t I need another controller for that?

Nope, if it’s passed into the outlet the connector has access to it. You should be able to do {{d-button action=showCreateAccount}} and it’ll find it.

That’s what I thought but it creates a loop of some kind:

Here’s what I’m currently working with:

discourse/app/assets/javascripts/discourse/controllers/static.js.es6

showCreateAccount(){
   this.send("showCreateAccount");
}

discourse/app/assets/javascripts/discourse/templates/static.hbs

{{plugin-outlet name="above-static" args=(hash model=model showCreateAccount=(action "showCreateAccount"))}}

my-plugin/assets/javascripts/discourse/templates/connectors/above-static/temp.hbs

{{d-button action=showCreateAccount class="btn-primary btn-large sign-up-button" label="signup_cta.sign_up" }}

Looks like this.send is just caling the same showCreateAccount method over and over :slight_smile:

Perhaps use a different name for your showCreateAcount action.

2 Likes

Doh! That resolved it. It works! Thanks for sticking with me on this one.

Here’s a screen of this. You can see the sign up button in the background.

Just for clarification purposes and for anyone who wants to submit a PR for their specific case, this is the setup:

discourse/app/assets/javascripts/discourse/controllers/static.js.es6

showCreate(){
  this.send("showCreateAccount");
}

discourse/app/assets/javascripts/discourse/templates/static.hbs

{{plugin-outlet name="above-static" args=(hash model=model showCreateAccount=(action "showCreate"))}}

my-plugin/assets/javascripts/discourse/templates/connectors/above-static/temp.hbs

{{d-button action=showCreateAccount class="btn-primary btn-large sign-up-button" label="signup_cta.sign_up" }}
5 Likes