Exporting a function from an already exported function - javascript

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

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.

Related

Allowing users to modify imported ES6 module functions in context

I'm getting rather confused as to if something is possible or not.
I create a module that contains the following:
export function logText(){
console.log('some text');
}
export class Example {
constructor(){
logText();
}
}
The intent is for the user to call new Example to start off the module logic.
import { logText, Example } from 'example';
// Do some magic here to modify the functionality of logText
new Example();
Is it possible for the end user to modify logText?
It would make sense for there to be a way for users to do something like this, or they'd have to take the entire module into their own repo just to make small functionality tweaks.
I frequently see repos with lots of functions exported that are useless without the users having to remake almost all the functionality manually, making it rather pointless to do. One good example is this repo whre theuy even call the exported functions their 'API'. In that example these are rather pointless exports and at worse would just cause issues if someone tried to use them in conjunction with the main function. But if you could modify them and have them still run then it would make sense to me.
Given this:
import { logText, Example } from 'example';
Is it possible for the end user to modify logText?
Since you aren't being very specific about what you mean by "modify logText", I'll go through several options:
Can you reassign some other function to the variable logText?
No. You cannot do that. When you use import, it creates a variable that is const and cannot be assigned to. Even if it wasn't const, it's just a local symbol that wouldn't affect the other module's use of its logText() anyway. The import mechanism is designed this way on purpose. A loader of your module is not supposed to be able to replace internal implementation pieces of the module that weren't specifically designed to be replaced.
Can you modify the code inside of the logText function from outside of the module that contains it?
No, you cannot. The code within modules lives inside it's own function scope which gives it privacy. You cannot modify code within that module from outside the module.
Can you replace the logText() function inside the module such that the implementation of Example inside that class will use your logText() function?
No, you cannot do that from outside the module. You would have to actually modify the module's code itself or someone would have to design the Example interface to have a replaceable or modifiable logText() function that the Example object used.
For example, logText() could be made a method on Example and then you could override it with your own implementation which would cause Example's implementation to use your override.
Code in the module that you do not modify:
export class Example {
constructor(){
this.logText();
}
logText() {
console.log('some text');
}
}
Code doing the import:
import { Example } from 'example';
class MyExample extends Example {
constructor() {
super();
}
logText() {
console.log("my own text");
}
}
let o = new MyExample();
Can you create your own version of logText and use it locally?
Sure, you can do that.
function myLogText() {
do your own thing
}
And, you could even NOT import logText so that you could use the symbol name logText() locally if you wanted. But, that won't affect what Example does at all.
Are there ways to design the example module so that that logText() can be easily replaced.
Yes, there are lots of ways to do that. I showed one above that makes logText a method that can be overriden. It could also be passed as an optional argument to the Example constructor.
There could even be an exported object that allowed the caller to replace properties on that object. For example:
export const api = {
logText: function logText(){
console.log('some text');
}
};
export class Example {
constructor(){
api.logText();
}
}
Then, use it like this:
import { api, Example } from 'example';
api.logText = function() {
console.log('my Text');
};
I would generally not recommend this because it sets you up for usage conflicts between multiple users of the same module where each one tries to modify it globally in ways that conflict with each other. The subclassing model (mentioned above) lets each use of the module customize in its own way without conflicting with other usages of the module.
Is it possible for the end user to modify logText?
No, that's not possible, import bindings are immutable, and function objects are basically immutable wrt the code they contain.
It would make sense for there to be a way for users to do something like this, or they'd have to take the entire module into their own repo just to make small functionality tweaks.
Why not make the log function an optional argument in the constructor? Usually when something is variable it becomes a parameter.
export class Example {
constructor(log=logText){
log();
}
}

not able to call child class method twice in parent js file

I have a category dropdown(in parent js) whose subcategory fills on parent page load as well on dropdown change . subcategory will fill from child js method .I have to create child js instance twice . on page load and on dropdown down change.
I dont want to create object in document.ready or as global variable
where should i create child class object exactly so that it can be used all over ?
problem is that jquery not letting me call
$.getScript('../Reports/assets/js/BookingReports.js'
twice as it send error that child class name(BookingReports) identifier as already created .
class ReportsInterface extends ReportBase {
constructor() {
super();
this.fillSubCategory;
}
init() {
this.Categories();
}
Categories() {
//fill category
this.FillSubCategory();
}
FillSubCategory() {
if(!this.fillSubCategory) {
$.getScript(
'../Reports/assets/js/BookingReports.js',
function() {
this.fillSubCategory=new FillSubCategory("1");
obj.GetSubCategory();
}
)
}
}
}
$(document).ready(function() {
$("#ddlcategory").on('change', function() {
(new ReportsInterface()).showReportBooking();
})
})
i also tried to save object in parent class property but .cannot use it as object later on. how can I call child class method twice without creating any global variable ?
If you are using ES6, I would recommend not using JQuery to import separate files but rather using the ES6 import/export syntax.
I imagine the issue is that since $.getScript makes an http request to redownload the script file, it is actually running the script file twice (one for each download); in the second download, it will run into the naming conflict. ES6 import/exports would solve this issue for you, preventing BookingReport from being redefined.
You should be aware of a couple of things however:
(1) Using your JQuery setup, you get the benefit of lazy loading. To get the same in ES6, you'd have to use the slightly more complicated dynamic imports (see that same link above) -- for this app, however, it doesn't really look like you'd need that.
(2) You might want to familiarize yourself with a bundler like Webpack as this will do ahead-of-time importing and leave you with a single file to download rather than having to ping-pong back and forth from the server as you try to download all of the modularized files.

Require module that uses a singleton array

I want to create a really basic CRUD (sort-of) example app, to see how things work.
I want to store items (items of a shopping-list) in an array, using functions defined in my listService.js such as addItem(item), getAllItems() and so on.
My problems come when using the same module (listService.js) in different files, because it creates the array, in which it stores the data, multiple times, and I want it to be like a static "global" (but not a global variable) array.
listService.js looks like this:
const items = [];
function addItem (item) {
items.push(item);
}
function getItems () {
return items;
}
module.exports = {addItem, getItems};
and I want to use it in mainWindowScript.js and addWindowScript.js, in addWindowScript.js to add the elements I want to add to the array, and in mainWindowScript.js to get the elements and put them in a table. (I will implement later on Observer pattern to deal with adding in table when needed)
addWindowScript.js looks something like this:
const electron = require('electron');
const {ipcRenderer} = electron;
const service = require('../../service/listService.js');
const form = document.querySelector('form');
form.addEventListener('submit', submitForm);
function submitForm(e) {
e.preventDefault();
const item = document.querySelector("#item").value;
service.addItem(item);
console.log(service.getItems());
// This prints well all the items I add
// ...
}
and mainWindowScript.js like this:
const electron = require('electron');
const service = require('../../service/listService.js');
const buttonShowAll = document.querySelector("#showAllBtn")
buttonShowAll.addEventListener("click", () => {
console.log(service.getItems());
// This just shows an empty array, after I add the items in the add window
});
In Java or C#, or C++ or whatever I would just create a Class for each of those and in main I'd create an instance of the Service and pass a reference of it to the windows. How can I do something similar here ?
When I first wrote the example (from a youtube video) I handled this by
sending messages through the ipcRenderer to the main module, and then sending it forward to the other window, but I don't want to deal with this every time there's a signal from one window to another.
ipcRenderer.send('item:add', item);
and in main
ipcMain.on('item:add', (event, item) => {
mainWindow.webContents.send('item:add', item);
})
So, to sum up, I want to do something like : require the module, use the function wherever the place and have only one instance of the object.
require the module, use the function wherever the place and have only one instance of the object.
TL:DR - no, that isn't possible.
Long version: Nature of Electron is multi process, code you runs in main process (node.js side) and renderer (chromium browser) is runnning in different process. So even you require same module file, object created memory in each process is different. There is no way to share object between process except synchrnonize objects via ipc communication. There are couple of handful synchronization logic modules out there, or you could write your module do those job like
module.js
if (//main process)
// setup object
//listen changes from renderer, update object, broadcast to renderer again
else (//rendere process)
//send changes to main
//listen changes from main
but either cases you can't get away from ipc.

Using shared module (between separate modules)

So I have a module I have created that does a kind of "state" routing for me. I made my own little version to get my exact intended effect, and it seems to be working great until I plug it into separate modules to test.
I inject it into the 2 separate modules, define the information in the .config of each module I need to use it, then call it in a controller to use my change state kind of effect.
It had been going pretty good until I plugged it into separate modules, and now what seems to be happening is the module I have created to handle all of this is creating separate instances for each module. Let me show you what I mean:
Here is an example of one of the modules using it for testing -
angular.
module('urlTesting2', [ 'urlTesting'])
.config(function($moduleObjectProvider) {
var callback = function(name, obj) {
console.log(name, obj);
}
$moduleObjectProvider.$get().set("module2", callback)
.addState("calender", ["day", "week", "month"]);
}).controller("testControl2", function($scope, checkUrl) {
$scope.addSecond = function() {
checkUrl.goState("module2", "calender", ["yes", "no", "maybe"]);
}
});
So it's injected, and in the config I call the provider and set a new modules with states. In the controller I just call goState. This works great when its just by itself. The issue is when I add a separate module in doing the same. I have a fiddle here showing the problem -
https://jsfiddle.net/7hn3ovgz/1/
So - I like to test this in my own browser window but fiddle seems to be the easiest way to share this. It will not change the actual url in the browser but it will still log all the effects.
Basically what I think is happening is when I click to change state in a module, it fires it twice and looks for the state in the other module too (which isn't there). My desired effect was that ALL modules setting a config would be all in one place. So when you do the .set - it just adds the object into a variable called currentModules in the provider. It seems like the configs are setting separate instances (like a closure) of this, instead of pushing all the config set() into one big object for reference.
Apologies if this is unclear, hopefully the fiddle will show clearly enough, and thank you for taking the time to read.
Seems like the issue is the injector for the provider, every time it is called it creates a new instance of that function, so all you should have to do is switch
function $moduleObjectProvider() {
var currentModules = {};
to
var currentModules = {};
function $moduleObjectProvider() {
or restructure the provider not to be an injected function if possible

Circular dependencies in JavaScript OOP

// Main class
function App() {
this.task = new Task(this); // pass the instance of this class to Task so
// it has access to doSomething
}
App.prototype.doSomething = function () {
alert("I do something that Task() needs to be able to do!");
};
function Task(app) {
// This class needs access to App()'s doSomething method
this.appInstance = app;
this.appInstance.doSomething(); // Great, now Task can call the method
}
var app = new App();
The aim of the code above is to give Task access to one of App's methods called doSomething. The code is the current way I'd go about it and I'm posting this to see if it's the best way...
To give Task access I simply pass the whole instance of App, is this efficient or is there a better way to go about it? Is the code above general practice in going about doing something like this?
Yes, what you have is fine. It is a circular dependency, however because of JavaScript's dynamic nature there aren't really any issues.
Another way you could reference App from Task would be a Singleton pattern or something similar, but that would probably be harder to test.
jsFiddle Demo
Generally bind would be used in this scenario assuming that the Task "class" didn't also setup other facilities which were not shown here.
Bind allows for the context to be provided for a function. This could be done in app's constructor. At which point only a function task would be required to call "someMethod".
function task(){
return this["someMethod"]();
}
function App(){
task.bind(this)();
}
App.prototype.someMethod = function(){
alert("Task needed access to this");
};
var a = new App();
However, if task must be a "class", and have other responsibilities then the prototype function could be shared.
function Task(){}
function App(){}
App.prototype.someMethod = Task.prototype.someMethod = function(){
alert("Task needed access to this");
};
var a = new App();
a.task();//->"Task needed access to this"
var t = new Task();
t.someMethod();//->"Task needed access to this"
Your app instances and task instances are tightly bound. App instances have tasks and this can be fine.
A design of loosely coupled objects is more flexible and easier to extend but more complicated to initially create. One such pattern is using a mediator/publish subscriber and have app raise an event/publish message any other object function can listen to this and take action on the event.
For example: your app creates an Ajax instance and when that instance is done it raises some event (fetchedData for example). A listener could be DomDependent.updateView function but later you may want to add/remove/change the order of tasks to do after data is fetched. This can all be configured in a app.init function or per procedure in a controller that kicks of certain procedures (like log in, search, ...).
Instead of creating a whole bunch of specific functions in Ajax (fetchUserPrefs, login, search, ...) you can create one general function and have the controller add listeners or pass the next event when fetchData is complete to run the correct next function.
Here is some pseudo code:
var app = {
init:function(){
mediator.add("updateLogin",domDependent.updateView);
mediator.add("updateLogin",app.loadUserPrefs);
mediator.add("failLogin",domDependent.updateView);
},
login: function(){
mediator.trigger("loadingSometing",{type:"login"});
ajax.fetch({
onComplete:"updateLogin",//what listens to updateLogin you decided in init
onFail:"failLogin",
loginDetails:domDependent.getLogin(),
url:settings.loginUrl,
type:"post"
});
}
}
var ajax = {
fetch:function(data){
data = data || {};
//simple check for onComplete, it's mandatory
var complete = data.onComplete || app.raiseError("ajax.fetch needs onComplete");
//other code to validate data and making ajax request
onSuccess:function(resp){
//mutate data object as the mediator will pass it to
// whatever other function is called next
// you don't hard code domDependent.updateView and
// app.loadUserPrefs because fetch can be used generally and
// success may have to do completely different things after its done
// and you want to define procedures in init, not all over your code
data.response=resp;
//trigger event to do whatever needs to be done next
mediator.trigger(complete,data);
}
}
}
As you can see it gets complicated and maybe doesn't look like code you're used to but it's highly configurable.
I may have misunderstood the advantages of the mediator pattern to loose couple and if so please comment. I use it to:
Make methods more general instead of copying a lot of logic only
because what to do after it's done is different. In fetch the ajax
object just fetches, this would be the same for login or getting
user preferences, the only thing different is what function to call
next/on error when it's done.
A procedure like login involves multiple functions in multiple
objects if this function chain hard code what to do next once a
particular function is done your procedure of login is defined all
over your code. When defining it in init/config you can easily change the
order or add/remove functions in the chain.

Categories