Adding to plugin-outlets using a theme

In Plugin System Upgrades @eviltrout introduced a system for extending Discourse HTML using plugin outlets.

Plugin outlets are tagged areas in our application that allow you to inject a template. The template has access to the backing controller so it can be dynamic.

Our theme system in /admin/customize/themes allows you to define custom CSS and HTML.

When injecting HTML into your page you can also inject templates, this gives you a very simple mechanism for injecting content into various plugin outlets.

To inject into an outlet

  1. Find out the outlet name, you can do so by digging through our source or using the plugin outlet location theme.

  2. Define a handlebars template in the </head> section.

An example of this is:

<script type='text/x-handlebars' data-template-name='/connectors/discovery-list-container-top'>
Welcome {{currentUser.username}}. Please visit <a class="nav-link " href="http://google.com" target="_blank">My Site</a>
</script>

This will result in adding a navigation link in the header that only exists when the header is not in extra info mode (which happens when you scroll down a topic)

:mega: Naming is critical

Your template must have the data-template-name attribute identify the outlet. It is named like so:

/connectors/OUTLET-NAME/UNIQUE-ID

If you stray from that naming your template will not be picked up or injected.

Using this technique even business and standard customers can heavily extend Discourse without needing plugins.

Adding backing class to the outlet

If you wish to add a backing class to the outlet use api.registerConnectorClass eg:

api.registerConnectorClass("user-preferences-interface", "add-keyboard-selector", {
  shouldRender() {
    return require("discourse/lib/utilities").isAppleDevice();
  }
});

:warning: When possible always prefer solutions that inject into a plugin outlet, over solutions that override an entire template, that allows your theme to be far more robust and “future proof”.

Advanced usage

When using the Theme CLI, or a git-backed theme, you can split up your theme javascript into multiple files under /javascripts. More information can be found at Splitting up theme Javascript into multiple files

18 Likes

This is awesome. Here’s a small bit to add an icon to your home page in the navigation block. I’ve been wanting to try this out for a while.

In </head>:

<script type='text/x-handlebars' data-template-name='/connectors/header-before-notifications/home-page-link'>
    <a class="icon" href="http://example.com/" title="visit our home page"><i class="fa fa-home"></i></a>
</script>

In CSS:

.home-page-link {
    float: left;
}
.home-page-link .fa-home {
    padding: 0;
}
.d-header .icons .unread-private-messages {
    left: 122px;
}
.d-header .icons .flagged-posts {
    left: 80px;
}
15 Likes

Perfect, but how do you do with users without login?

Good point. I’m not sure. Here’s the file where the plugin outlet being utilized located:

https://github.com/discourse/discourse/blob/tests-passed/app/assets/javascripts/discourse/templates/header.hbs

header-after-home-logo could possibly be used instead but I’m not sure what’s possible with CSS, whether you’d be able to get the icon positioned as desired. Another outlet might be needed.

I was playing with this last night and found that an element with float:right inserted at header-after-home-logo would end up on the right of the existing icons. Not ideal, but if it’s a Home icon you’re inserting, that might be the best place for it. (I haven’t tested, but because this outlet is outside of the #if currentUser statement, it should appear for everyone)

CSS:

.home-page-link {
    float: right;
}
.home-page-link .fa-home {
    padding: 5px;
    color: #999999;
}

/head

<script type='text/x-handlebars' data-template-name='/connectors/header-after-home-logo/home-page-link'>
  <a class="icon" href="http://www.yourdomaingoeshere.org/" title="visit our home page"><i class="fa fa-home"></i></a>
</script>
4 Likes

Perfect, thanks! Is there a way to show stuff at such an outlet, if the user is not logged in only? And vice versa: Only if the user is logged in? Thanks!

1 Like

you can use Discourse.User.current() in your conditions. :smile_cat:

How can insert two icons?

Thanks a lot! Could you show me how an example code would look like with this condition? My programming skills are very modest. Cheers!

1 Like

https://github.com/discourse/discourse/blob/tests-passed/app/assets/javascripts/discourse/controllers/header.js.es6#L17-L19

Here you go :smile:

2 Likes

Sorry, I meant in the </head> section context. Is there something like

<script type='text/x-handlebars' data-template-name='/connectors/header-after-home-logo/add-header-links'>
  if user logged in
       <a class="nav-link " href="http://google.com" target="_blank">My Site</a>
  if user not logged in
       <a class="nav-link " href="http://google.com" target="_blank">Your Site</a>
</script>

Thanks so much!

1 Like
<script type='text/x-handlebars' data-template-name='/connectors/header-after-home-logo/add-header-links'>
  {{#if currentUser}}
     <a class="nav-link " href="http://google.com" target="_blank">My Site</a>
  {{else}}
     <a class="nav-link " href="http://google.com" target="_blank">Your Site</a>
  {{/if}}
</script>

Try this :smile:

4 Likes

Sorry to bother you again: Ist there a way to differentiate between members of groups, as in “A member of group X sees HTML 1, member of group Y sees HTML 2 …”?

1 Like

Does anyone know how I would pull a custom user field? Tried using the following code from the user.hbs template but cant seem to pull the custom user fields.

<script type='text/x-handlebars' data-template-name='/connectors/user-card-post-names/gamertags'>
            {{#if publicUserFields}}
              <div class="public-user-fields">
                {{#each uf in publicUserFields}}
                  {{#if uf.value}}
                    <div class="public-user-field">
                      <span class="user-field-name">{{uf.field.name}}</span>:
                      <span class="user-field-value">{{uf.value}}</span>
                    </div>
                  {{/if}}
                {{/each}}
              </div>
            {{/if}}
</script>

I have also tried using custom.user_field_1 at the field name as well. No Dice.

Edit: I know there is data here, as I am setting them with SSO and they do show up on the users profile. Trying to get them to show on the usercard as well.

I have this same question.

Does anyone know how to validate group membership in a template condition?

1 Like

If the data I want to pull is in another controller (UserController) from the one I want the data to display (UserCardController). Can I do this all within the site customization screen, or would I need to create a plugin and extend the UserCardController to include the data from UserController? What would would be the best way to pull it?

Thanks

What if the user had never visited the UserController? The card can be shown in many places.

What is the piece of data you want to pull?

I want to pull the publicUserField array that is created in userController. I am guessing if they never visited userController then I would need to create the array myself.

Yeah part of that field can be generated anywhere (the information about custom fields is in our Site object) but the other part requires the user_fields values which you’d have to make sure are available on the card somehow. I am not sure off the top of my head when the user card loads in the data that such fields are present.

3 Likes

does anyone know how to put an icon next to the search button?

1 Like