How to manually add contents to pluginOutlet after ajax call? And how to rerender or refresh a pluginOutlet?

Hi, :wave:

I am new to Discourse theming and emberjs, and currently working on a theme that has navigation items from the main website.

The nav items json is coming from an ajax request and I need to display it on 2 locations.

1st location is header-buttons:before. I managed to do it with api.decorateWidget and triggering appEvents site-header:force-refresh.

ajax( settings.site_navigation_links_endpoint ).then((result) => {

	if (!result.length) {
		return;
	}

	result.map((customHeaderLinksArray) => {

		const anchorAttributes = {
			title  : customHeaderLinksArray.title,
			href   : customHeaderLinksArray.url,
			target : "self"
		};

		headerLinks.push(
			h(
				`li.custom-header-nav-item`,
				h( "a.custom-header-nav-item-link", anchorAttributes, customHeaderLinksArray.title )
			)
		);

	});

	headerNav = h("ul.custom-header-nav", headerLinks);

	if( settings.site_navigation_position == "inline" ) {
		api.decorateWidget("header-buttons:before", (helper) => {
			return headerNav;
		});
	}

	appEvents.trigger("site-header:force-refresh");

	// To display on location (2nd location, see below )
	// ...
	// ...
}

The 2nd location is via pluginOutlet, below-site-header.
I believe I can’t do the same as the above, using api.decorateWidget, because this is pluginOutlet not a widget(?).

My questions are:

  1. How to manually insert ajax content to the pluginOutlet?
  2. I would like to know also how to compile a virtual-dom on the fly after my ajax call? eg. the variable above, headerNav, I’d like to get a compiled HTML markup of it. Not sure which lib / function to use.
  3. If possible, how to also rerender a pluginOutlet? similar to appEvents.trigger("site-header:force-refresh");

Thanks in advance! :man_bowing:

2 Likes

Correct. There are two ways to go about it…

  1. You can make your customization a widget, and then mount the widget in the plugin outlet like this:

    {{mount-widget widget="widget-name"}}
    

    You can see an example of how a widget is created in Discourse, for example the home logo: discourse/home-logo.js at 2dbcea9eeeb816dda347027497b3a49634ef851f · discourse/discourse · GitHub

    In a theme you’d add your widget-name.js file to a javascripts/discourse/widgets directory

    There’s more about widgets in A tour of how the Widget (Virtual DOM) code in Discourse works, but note that we’re going to gradually phase out widgets so anything you learn in this process won’t be useful long-term.

  2. Keep your widget decorator as-is, and create a separate Ember component that does what you want in the plugin outlet. We’ve been phasing out widgets in favor of Ember components (which is what most of Discourse is built on).

    This process would look like:

    1. Create a component JS and HBS (template) file in your theme’s javascripts/discourse/components directory.
    • component-name.js

    • component-name.hbs

    1. Build your Ember component… unfortunately this step is essentially “learn Ember” (Ember Guides) … but I think this might give you a rough idea to start:
    • In component-name.js:
    import Component from "@glimmer/component";
    import { tracked } from "@glimmer/tracking";
    import { action } from "@ember/object";
    
    const endpoint = settings.site_navigation_links_endpoint;
    
    export default class ComponentName extends Component {
      @tracked navLinks = null;
    
      @action
      async fetchNavLinks() {
       try {
          const response = await fetch(endpoint);
          const data = await response.json(); // assuming this is json
          this.navLinks = data;
        } catch (error) {
          console.error("Failed:", error);
        }
      }
    }
    
    • In component-name.hbs:
    <div {{did-insert this.fetchNavLinks}}>
     {{#each this.navLinks as |link|}}
       <a href={{link.anchor}}>{{link.title}}</a>
     {{/each}}
    </div>
    

    This will call the fetchNavLinks action whenever the component is inserted (in this case, when you visit the Discourse site and the app renders). Whenever navLinks is updated, the content of the component will update because it’s a tracked property.

    If you want to update the links more often than on component render you’ll need to add some more logic to the JS here… checking if the current route (page) meets certain conditions for example.

    1. This component would be added to a plugin outlet by adding it to the outlet in javascripts/discourse/connectors/below-site-header/my-component-connector.hbs:
    <ComponentName />
    
8 Likes

Thank you so much Kris! This is very helpful.

I went for the second approach, using component, since widgets are gradually phasing out. Aside from the little typo in function call, in the .hbs file it should be {{did-insert this. fetchNavLinks}}, everything works!

Great to know that we have did-insert, that’s a big relief! I have now finished the task. :man_bowing:

1 Like

ah glad you noticed, I’ve fixed my post above

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.