Render modal from theme client-side plugin API


(Daniel Sousa) #1

Is there any way to render a modal from the theme javascript?

I was looking forward to render a modal similar to the one that shows up when changing avatar.

Example:

<script type="text/discourse-plugin" version="0.8">
  api.onPageChange((url, title) => {
    if (condition) {
     renderModal;
    }
  });
</script>

(Sam Saffron) #2

@Johani can you help out here?


(Joe) #4

What you need is showModal() and you use it like so

First you require show-modal

const showModal = require("discourse/lib/show-modal").default;

And then use

showModal("modalName") // camelCase

Then all you have to do is decide how to trigger it.

One way to do it is to create a widget and render the modal when it’s clicked like so

<script type="text/discourse-plugin" version="0.8.18">
const showModal = require("discourse/lib/show-modal").default;

api.createWidget("modal-button", {
  tagName: "button.btn.btn-primary",

  html() {
    return "Open Modal";
  },

  click() {
    showModal("avatarSelector");
  }
});
</script>

This creates a button. When that button is clicked, the avatar selector modal will be displayed.

You’d still need to insert that widget somewhere and you have a few options here

So, for example this

<script type="text/x-handlebars" data-template-name="/connectors/topic-above-post-stream/modal-button">
{{mount-widget widget="modal-button"}}
</script>  

Would add the button above topic titles like so

While this would open the modal and everything would display nicely, you’d still need to add a few more things to make sure the functionality - like changing the avatar - works as well. I did not include those bits here because this is an example and because I’m not sure I understand what you want to achieve.

Can you please share a bit more about what you’re trying to achieve?


(Loïc Leuilliot) #5

Hey Joe,

I’m interested by this feature too. I’ve been able to build most of the script using your example but I don’t know how to build a specific modal ?

As far as I understand, the showModal thing is loading a template ?

Here is what I did :

<script type="text/discourse-plugin" version="0.8.24">
    const showModal = require("discourse/lib/show-modal").default;
    
    // EvE Online Magic SDE
    api.decorateCooked(function(cooked){
        SDD.LoadMeta()
        .then(function(arg){
            return arg.source.GetTable('invTypes').Load();
        })
        .then(function(arg){
            cooked.find('a[href="#evesde"]').each(function(){
                var typeName = $(this).text();
                
                results = _.filter(arg.table.data, function(entry) {
                    return entry[arg.table.c.typeName] === typeName;
                });
                
                if (results.length > 0) {
                    $(this).attr('data-typeid', results[0][arg.table.c.typeID]);
                    $(this).on('click', function(){
                        console.debug('show modal');
                        showModal('eveSdeModal');
                    });
                }
            });
        });
    });
</script>

(Daniel Sousa) #6

Thanks for sharing this example. In this specific scenario, I went with a jQuery solution that proved to be enough. It inspired me for other changes we wanted to do, though.


(Daniel Sousa) #7

Hey warlof,

As far as I can understand, showModal renders a template from this folder:

And the name you provide is in CamelCase while the file name is separated by dashes, reason being this:

I hope that helps.


(Loïc Leuilliot) #8

Hum, that’s annoying without getting a plugin but using only the frontend modification.

I get that Discourse was using bootstrap as main framework. I’ve been able to use standard modal but I don’t know if it’s the best way to do it (without making a plugin, apparently it is).

<div role="dialog" id="evesde-modal" class="modal d-modal fixed-modal ember-view in" style="display: none; padding-right: 0px;">
    <div class="modal-outer-container" role="document">
        <div class="modal-middle-container">
            <div class="modal-inner-container">
                <div class="modal-header">
                    <div class="modal-close">
                        <a class="close" data-dismiss="modal">
                            <i class="fa fa-times d-icon d-icon-times"></i>
                        </a>
                    </div>
                    <div class="title">
                        <h3 id="evesde-modal-title"></h3>
                    </div>
                </div>
                <div id="modal-alert" style="display: none;"></div>
                <div id="evesde-modal-description" class="avatar-selector modal-body ember-view"></div>
            </div>
        </div>
    </div>
</div>

At least it is working (almost - clicking somewhere else out of the modal isn’t closing it for some reason that I can’t get).


(Daniel Sousa) #9

For the close I went with a simple jQuery event listener:

$("body").on("click", "#badge-modal-close", function() {
  $(".badge-modal").fadeOut(600);
});

Notice that the selector is body because in my example I needed to add html dynamically and this way I don’t need to rebind/add listeners only after.


(Joe) #10

Creating a new modal involves 4 steps.

1. creating the template for the modal

You can create a new template like so:

<script type="text/x-handlebars" data-template-name="modal/custom-modal">
{{#d-modal-body title="custom_modal_title" class="custom-modal"}}
  Your content goes here!
{{/d-modal-body}}
</script>

A few quick notes about this:

  • The path you set in data-template-name must start with modal/ followed by your template name
  • {{d-modal-body}} is required
  • The title attribute on {{d-modal-body}} is the title that show at the top of the modal
  • it’s a good idea to add unique classes to your modal

2. Adding a title for the modal

If you notice above, the title for {{d-modal-body}} is set to “custom_modal_title” so we need to define what that is and you can do it like so

let currentLocale = I18n.currentLocale();
I18n.translations[currentLocale].js.custom_modal_title = "My custom modal";

3. creating a trigger for the modal

For this I’m going to be reusing the method I described in my previous post. Create a widget and show the modal when the widget is clicked but anything equivalent is fine as well. All you need is to get showModal() to fire somehow depending on what you have to work with.

const showModal = require("discourse/lib/show-modal").default;

api.createWidget("modal-button", {
 tagName: "button.btn.btn-primary",

 html() {
   return "Open Modal";
 },

 click() {
   showModal("customModal"); // name is the same you use for the new modal template in camelCase
 }
});

4. Add the widget to a template somewhere

Again, I’ll go back to my previous example and use

<script type="text/x-handlebars" data-template-name="/connectors/topic-above-post-stream/modal-button">
  {{mount-widget widget="modal-button"}}
</script> 

which would add the widget (button) right under the topic title on topic pages.

Now all you have to do is put all of this together and add it to the header section of your theme / theme component

<script type="text/x-handlebars" data-template-name="modal/custom-modal">
{{#d-modal-body title="custom_modal_title" class="custom-modal"}}
  <h1>Hello World!</h1>
{{/d-modal-body}}
</script>


<script type="text/discourse-plugin" version="0.8.18">
let currentLocale = I18n.currentLocale();
I18n.translations[currentLocale].js.custom_modal_title = "My custom modal";

const showModal = require("discourse/lib/show-modal").default;

api.createWidget("modal-button", {
  tagName: "button.btn.btn-primary",

  html() {
    return "Open Modal";
  },

  click() {
    showModal("customModal");
  }
});
</script>

<script type="text/x-handlebars" data-template-name="/connectors/topic-above-post-stream/modal-button">
{{mount-widget widget="modal-button"}}
</script> 

and you will have created a custom modal. :+1:

I’ve put together a small demo on theme-creator. Just visit any topic page and click on the “open modal” button below the title.


(Loïc Leuilliot) #11

Oh, I figure I had to create files into Discourse to handle the modal templating.

Actually I’m doing this by inserting raw bootstrap modal definition :

$('#evesde-modal-title').text(data.name)
    .prepend(
        $('<img>').attr('src', `//image.eveonline.com/Type/${typeID}_32.png`)
            .attr('alt', data.name)
            .css('margin-right', '10px'));
$('#evesde-modal-description').html(content);
$('#evesde-modal').modal('toggle');

After few tests, I assume the transformation have to be applied this way :
$('#evesde-modal-title').text(data.name) is becoming I18n.translations[currentLocale].js.eve_sde_modal_title = data.name;
$('#evesde-modal').modal('toggle'); is becoming showModal('eveSdeModal');

I don’t know how to update the body dynamically (which have to be raw html) though. I tried to use jquery in order to retrieve the body and update it, but it doesn’t do anything.

Also, I have an image to put before the text of the modal header. I don’t figure how to handle this properly neither.