0

I am doing a project using Nodejs for the backend, vanilla JS compiled with Parcel Bundler for the client side JS and PUG template engine to generate the views.

Note that I also use the FullCalendar v5 plugin. I don't think this is relevant as I feel this situation could happen without it, but still, it is the reason I encounter this problem at the moment.

Let's get straight to the point : I have a "main" parent function called initCalendar(). It initializes the calendar, creates the FullCalendar instance (along with all the calendars methods), sets it up depending on the configs given and renders it on the view. This function is the top level one of events.js, the "main" function as I like to call it.

initCalendar() is exported from events.js using the export keyword : export const initCalendar = () => { … }.

After this, I coded the calendar proprietary functions, which allow me to perform whatever action I want based on the ones done on the calendar. Like eventClick() for example, which executes whenever an event from the calendar is clicked, as its name suggests.

The point is, I created some functions in this eventClick() function (which itself is in initCalendar()), some of which I need to use in index.js. Therefore the need to export them. Also, I can't move these functions outside of initCalendar() scope, as I will loose important variables needed for my functions to run properly, and I would like to avoid using global variables.

My custom functions are nested like so : initCalendar() -> eventClick() -> myFunction() ("main" exported parent function -> intermediate calendar function -> my functions (to be exported)).

In case you're wondering why I have to do it this way, it is to keep the same workflow I have been using so far for all the client side JS of the project, trying to do it "the Parcel way". I have lots of exported functions that are imported in index.js from many different files, but this problem only got here when I included FullCalendar to the mix.

So the solution I found for now is to export my functions directly from eventClick(), using the exports keyword this time : exports.myFunction = myFunction. Doing this, I can then import them in index.js and continue to use the same workflow I used for all the client side JS (remember, compiled with Parcel Bundler).

What do you think about this "technique" ? Isn't it bad practice to export a child function from an already exported parent function ?

It seems quite hacky to me and I don't really like that… But I didn't find any better solution yet. Maybe someone could give me some insight on wether or not it is OK to do so and if not, how to solve the problem another way ? I thought maybe using callback functions, but I can not get it to work this way.

------- EDIT : Some code -------

Here is some code. I tried to cut it to the minimum, because the code of the clickEvent() function is literally hundred of lines long, and the one for FullCalendar is even bigger.

events.js : As you can see, the eventClick() function first opens a Modal which contains all the event info (that I didn't write because not relevant) and one button to delete the clicked event.

This is this button that should have his listener set from index.js calling the "exported child function" removeEvent() on a click event, to delete the associated event from DB and calendar.

There is other functions in the same style in there but this one should be enough to see what I'm talking about.

// events.js

// … All the es6 imports : { Calendar } - { Modal } - axios; etc …


// As you can see, if I try to export the removeEvent() function from here, 
// it would work as exporting goes but I won't have the Modal instance of
// `eventInfoModal` used in `.then()`. Same thing with `const calendar`,
// because they would not be in the scope of `initCalendar()`.
// Therefore I won't be able to call the functions I do on them in `.then()` 


export const initCalendar = () => {
    const calendarEl = document.getElementById('calendar');
    const calendar = new Calendar(calendarEl, {

        // … Ton of code to config FullCalendar, import the events, other calendar functions etc…
        
        eventClick: function(eventInfo) {
            const eventId = eventInfo.event.id;
            const infoModalContainer = document.getElementById('event-info-modal');
        
            // Modal created from an imported class     
            const eventInfoModal = new Modal(
                infoModalContainer,
                this.el
            );
                
            eventInfoModal.init();

            // … Lots of code to populate the modal with the event data, buttons etc …
        
            // So the point here is to call this function from index.js (code of index.js below)
            function removeEvent() {
                if (confirm('Êtes-vous sûr de vouloir supprimer ce RDV ?')) {
                    deleteEvent(eventId)
                      .then(() => {
                        eventInfoModal.close();
                        calendar.refetchEvents();
                      });
                }
            }

            // The "exported child function" I was talking about
            exports.removeEvent = removeEvent;

           // Then other functions defined and exported the same way, to be used just like removeEvent() in index.js
    
    });

    calendar.render();
    
    // Function called from removeEvent() in eventClick() above, just doing an axios DELETE request, no need to write it
    async function deleteEvent(eventId) {
       
    }
};

index.js : Here I import all the exported functions from the other files (only showing the one we are talking about obviously) and try to group and set my listeners together by view or "by category", listeners that will then call the corresponding functions imported from the other files, to execute the needed actions.

// index.js

// … All the es6 imports, including :
import { removeEvent } from './events';

const userEventsPage = document.getElementById('user-events');

if (userEventsPage) {
    const deleteEventBtn = document.getElementById('delete-event');

    userEventsPage.addEventListener('click', evt => {
        if (evt.target === deleteEventBtn) {
            removeEvent();
        }
    });
}

Thank you very much

Tom687
  • 182
  • 2
  • 14
  • I would suggest you to add more code than just words. It's easier to find the problem quickly that way. Along with that, can you add enough code to show the reason for this: _"I can't move these functions outside of initCalendar() scope, as I will loose important variables needed for my functions to run properly"_ – maazadeeb Mar 01 '21 at 01:03
  • @maazadeeb I wanted to do so but there is literally a ton of code… I'll try to cut it to the minimum and update the post. – Tom687 Mar 01 '21 at 01:16
  • @maazadeeb There you go, I added some relevant code – Tom687 Mar 01 '21 at 03:53
  • 1
    Why can't you add a click handler to the `delete-event` button when you create the modal? From the look of the code shared, your `Modal` should have like a `onRemoveButton` property, that should be attached to the `removeEvent` function that you're writing right now. I can't see why you need to export it. – maazadeeb Mar 01 '21 at 05:15

1 Answers1

0

Posting my comment as an answer, as I believe that's the right way to solve this.

You should add a click handler to the delete-event button when you create the modal.

Also, from the look of the code shared, your Modal should have like an onRemoveButtonClicked property, that should be assigned to the removeEvent function that you're writing right now. I can't see why you need to export it.

maazadeeb
  • 5,922
  • 2
  • 27
  • 40
  • Thank you for your answer. However, I already tried using a click handler directly on the `delete-event` button but because of event propagation it doesn't work as expected. If I use `event.stopImmediatePropagation()` it works but I feel this is not good. Isn't it ? Also, my Modal class is generic and used in a lot of places. This `delete-event` button is only in "this" modal (the one populated from the `evenClick()` function from `events.js`), so I feel like adding a property to the class that will be used only in one function from the dozens it is used is not that good. What do you think ? – Tom687 Mar 08 '21 at 00:32
  • Also, I try to code this "the Parcel way", one part of it being, if I understood correctly, to separate the listeners (in `index.js`) from the functions (in any other files). I already successfully wrote all the code in the `eventClick()` function, including listeners and such, but then I loose this "code separation" that I find useful and clean (you have an overview of all the listeners in one place - `index.js` - and you can find the function one listener is calling easily). Or maybe I didn't understood the "Parcel way" of coding I am talking about ? – Tom687 Mar 08 '21 at 00:43
  • Also, by coding everything directly in `eventClick()` (listeners and such), I have lots of event propagation problems. Meaning I need to use `evt.stopImmediatePropagation()` or do a lot of `if (evt.target === someEl) return;` to prevent it. As said before, I don't like using `stopPropagation()` and with using all the `if (evt.target === …) return` the code becomes bigger than needed and ugly. I think those event propagations problems come from FullCalendar, because it uses listeners all over the place so my click events get to them too. I don't have this problem on files not using FullCalendar – Tom687 Mar 08 '21 at 00:48
  • @Tom687 What is this "Parcel" way? Why is a bundler dictating how you write your business logic? I can't get into details not mentioned in your question. But it's very common for a modal to have a `onButtonClick` or whatever is required. If it's not passed, maybe you don't show that button. There are a lot of generic Modals out there, I suggest you give them a look. – maazadeeb Mar 08 '21 at 01:12
  • Actually I am not sure of this "Parcel way", but it seems like a pattern I saw from projects using this bundler. Either way, I like the fact of separating listeners from functions. About the modal, I don't really understand how I would use that `onButtonClick` property ? Is it like `if (modal.buttonClick) { callFunction() }` ? Also, what should it be if the modal contains 3 buttons (this one actually has 3) ? By the way, all the modals I create with this class have different kinds of buttons. Thank you very much – Tom687 Mar 08 '21 at 01:41
  • I would recommend you to look up how to implement generic modals then. An example is here: https://react-bootstrap.github.io/components/modal/ . "Separating listeners from functions" would work well if your listeners were independent from the functions. What happens when somebody calls `removeEvent` without calling `initCalendar`? What happens if `removeEvent` is called multiple times? Anyway, I don't seem to be able to communicate my point. Sorry about that. I will leave the answer here for history of what was discussed. Hope you find a solution. – maazadeeb Mar 08 '21 at 06:59
  • Actually, you should not be able to call `removeEvent()` if `initCalendar()` was not called first, as `initCalendar()` is called whenever a "calendar page" loads, `removeEvent()` being used only on those pages (the listener targets those pages). By the way, I don't use React on this project, but I guess your point still stands and the example you gave too. If you don't mind, I have a last question, would you be able to tell me, if I still use this "export a child function from a parent one" concept, if it is OK to do so or should not be done ? Thank you very much, I kindly appreciate your help – Tom687 Mar 08 '21 at 07:26
  • I have never seen any code that exports code from inside a function. I don't know how it seems to be working as well. I just came from an implementation stand point, saying specific code should not be exported. Seems like a recipe for weird bugs. – maazadeeb Mar 08 '21 at 07:38