J’ai créé un thème plus élaboré pour user-card-contents, en ajoutant quelques champs personnalisés pour les utilisateurs. J’aimerais rendre la carte collante, avec un bouton de fermeture, de manière similaire au composant « Créer un nouveau sujet ».
Ai-je raison de penser que je devrai remplacer user-card-contents.js pour empêcher l’appel qui ferme l’élément ? Serait-il possible d’intégrer cela dans le thème ?
La réponse courte est oui. Si vos modifications n’affectent que le front-end, elles peuvent être effectuées dans un thème ou un composant.
Pourriez-vous préciser ce que vous entendez par « sticky » ? Si vous voulez dire que vous souhaitez qu’il défile avec le contenu tout en restant à la même position, cela nécessiterait une modification CSS. De plus, la modification est-elle destinée à être incluse à la fois sur ordinateur et sur mobile ?
Pourriez-vous décrire précisément à quel appel vous faites référence ? (par exemple, au clic, au défilement, etc.)
Le code HTML du bouton de fermeture devra être ajouté au modèle Handlebars de la carte utilisateur. La logique pour gérer l’action lors du clic sur le bouton devra être ajoutée au fichier .js du composant.
Un remplacement complet pourrait ne pas être nécessaire ; il existe des points d’extension (hooks) que vous pouvez utiliser pour remplacer des méthodes spécifiques dans une classe. Je peux vous en dire plus si vous décrivez un peu plus ce que vous souhaitez faire.
Ce que je cherche à faire, c’est empêcher le gestionnaire d’événements clickOutsideEventName du mixin card-contents-base.js de fermer la carte sur ordinateur. Je préférerais plutôt obliger les utilisateurs à cliquer sur un bouton pour la fermer. Je devrai probablement adopter une approche différente pour mobile.
J’ai réussi à faire fonctionner ce modèle Handlebars, maintenant je vais me pencher sur le fichier .js
You probably already know most of this since you’ve already worked on your theme but I will try to keep it a little bit more detailed for the broader audience.
The first thing I’d do is to search either locally or on Github. On Github you’d get something like this. In most cases, a search term will have more than one result and you’d either have to be more specific or manually scan the result to find something close to what you want.
This file is a Mixin. Why do I mention this? Because you need to be aware that mixins can be shared in a number of different places. In this case, it’s used for both user cards and group cards. So, the changes you make here will affect both.
If you search in the file, you’ll find that clickOutsideEventName is first defined here
Ember guarantees that, by the time didInsertElement() is called:
The component’s element has been both created and inserted into the DOM.
The component’s element is accessible via the component’s this.element property.
Why do we need this? Because we need a different mousedown handler for user cards and group cards. If we go back a bit, you’ll now notice that the id of the element is used in the clickOutsideEventName
Which like we discussed above is then passed on as a property.
Now, let’s move on to how all of this relates to what you’re doing.
You’re trying to prevent the cards from being closed when the user clicks outside of them. So, let’s try to find out a way to do that. If you recall, we discussed how clickOutsideEventName eventually gets consumed here
In a nutshell this adds a mousedown handler to the HTML element when a card is inserted (on the first page view). We then check the target of the mousedown event. If the target is somewhere in the card, we bail. If it’s outside of the card, we close it by calling this._close()
And this is what you’ll need to call when you add your close button - but we’ll get back to that later.
Now, the goal is to remove this mousedown handler if you want clicks outside the card not to close it. So how do we do this? Well, we’ll need to modify didInsertElement() and here’s how that can be done.
Discourse themes have access to the plugin API, which contains many methods that you can use. There’s a bit more details about the most commonly used ones here
If you remember, the file we’re working with is a Mixin. A Mixin is an Ember class. So, the method we’re going to use is modifyClass
When you use modifyClass you can either add, modify or completely override a class method. We’ll focus on modifying a method since that’s what you want to do.
We want to modify didInsertElement() so we can do something like this
why is that? Well it turns out that the modifyClass method doesn’t currently support modifying Mixins (as far as I’ve tested) I will make a note to figure out why that’s the case and check if we can fix that. For now though, let’s get back to what you want to do.
Well, we can’t modify Mixins, so I guess we’re stuck, yeah? No. Let’s dig a little bit deeper.
If you read carefully, you’ll notice that we first import the Mixin we discussed above here
and then create a new Ember component and pass the Mixin to it.
What does that mean? it means that didInsertElement() for user-card-contents is actually inherited from the Mixin. The same can be said about group-card-contents.
Where does that leave us? Well, there’s good news and bad news. The good news is that if you want to make changes to the user-card-contents without affecting group-card-contents then you can! The bad news is that if you want your changes to apply to both, then you’ll have to duplicate some code.
Let’s go back to modifyClass and try again with user-card-contents. So something like this:
The change is registered and we can see it in the console, but we’re not quite there yet.
So, now that we can modify didInsertElement(), let’s try to get back to what you’re trying to do. If you remember, the mousedown handler is defined in didInsertElement here
You’ll end up with something broken. Why is that? because that’s not a modification of the method. That’s a full override of the core didInsertElement() method for that component. So, none of the code in core is actually applied if you do this.
How do we fix this? Well it turns out that Ember has a thing called this._super(...arguments)
What does that do? it allows you to append or prepend code in addition to what the class method already has. For example, if you do this
api.modifyClass('component:user-card-contents', {
didInsertElement() {
// code you want to add
$("html").off(clickOutsideEventName);
// code from core
this._super(...arguments);
}
});
then the code you want to add will run before anything else here
This is a great way to keep your theme resilient to changes in core since everything in core runs first, then your code runs after that. So, let’s try this again and see what happens.
api.modifyClass('component:user-card-contents', {
didInsertElement() {
// code from core
this._super(...arguments);
// code you want to add
$("html").off(clickOutsideEventName);
}
});
Why is this happening? it’s because of different code context. In the Ember component file clickOutsideEventName is already defined by the time it’s consumed. Your theme is in a different file, so clickOutsideEventName is not defined there.
How do we fix it? Remember this?
clickOutsideEventName is a property of the component, so if you use this.clickOutsideEventName then it should work. Let’s try that.
api.modifyClass('component:user-card-contents', {
didInsertElement() {
// code from core
this._super(...arguments);
// code you want to add
$("html").off(this.clickOutsideEventName);
},
});
And indeed it works
The user cards now open without any errors and clicking anywhere outside doesn’t do anything.
You can do the exact same thing for group cards like so
api.modifyClass('component:group-card-contents', {
didInsertElement() {
// code from core
this._super(...arguments);
// code you want to add
$("html").off(this.clickOutsideEventName);
},
});
The only thing left is to hookup that close button to the _close() method we discussed earlier and there are three steps to this.
add a closeCard action (you can name whatever you want)
add a button to the user card template
call that action when the button is clicked.
I’ll stick with the user cards for simplicity. So, we add this (along with what we already discussed)
And of course you can do something similar for group cards like I mentioned above.
I’ll leave the group card and mobile implementation as exercise for you since you’d essentially use the exact same concepts we discussed above if you want to make changes to those but please let me know if you run into any problems.
Merci beaucoup, cela a fait un excellent tutoriel ! Je vous en suis vraiment reconnaissant. Je n’avais pas réalisé l’existence de l’API des plugins.
Une chose qui n’était pas immédiatement évidente ci-dessus était d’encapsuler les modifications JavaScript du thème dans des balises script et de les placer dans le fichier common/head_tag.html du plugin :
Par simple curiosité, le numéro de version dans la balise a-t-il de l’importance ici ? Et est-il toujours préférable de placer ces éléments dans head_tag.html plutôt que dans header.html, ou cela n’a-t-il pas vraiment d’importance ?