Passing a variable to a (globally?) exported class - javascript

I apologize if this question doesn't really make sense - I'm probably lacking a bit of an understanding on how exactly module.export works and scoping between classes, but I will try my best to explain. I currently have a discord bot with a music player that has a MusicPlayer class - this class is exported in this manner:
const ytdl = require('ytdl-core-discord');
const MusicQueue = require('./musicqueue');
const discordUtils = require ('../../../utils/discord-utils');
class MusicPlayer {
constructor() {
this.repeat_playlist = false;
this.queue = new MusicQueue(); // this is the persistent queue for the server
this.dispatcher = null;
this.volume = 1;
this.stopped = true;
this.paused = false;
this.repeat_current_song = false;
this.auto_delete = false;
this.manual_index = false;
this.autodcEnabled = false;
}
....
}
module.exports = new MusicPlayer();
I have various music-related commands that import this music player like so:
const { Command } = require('discord.js-commando');
const discordUtils = require ('../../utils/discord-utils');
let musicplayer = require(`./modules/musicplayer`);
module.exports = class TestCommand extends Command {
constructor(client) {
....
}
...
}
My question is, how would I pass a local variable to the musicplayer constructor since I'm exporting it via module.exports = new MusicPlayer();?
It's my understanding that using module.exports = new MusicPlayer(); would allow me to use the same musicplayer object throughout the entire project (which is what I want); but - and this is where I'm not sure I understand how module.exports works - there's no guarantee when (if at all) the musicplayer will be instantiated.
If you never use a music-related command the musicplayer will never be created because let musicplayer = require('./modules/musicplayer'); will never be run, right?
Either way, now I need to pass something to the musicplayer constructor from within one of the commands, how can I do so in a way that I don't have to repeatedly do something like: let musicplayer = require('./modules/musicplayer')(variable); in every file that requires the music player? Is there a way for me to declare let musicplayer = require('./modules/musicplayer')(variable); one time and have that musicplayer be used throughout the entire codebase?

Let's list out a few requirements:
You want to share a single music player among a bunch of code.
You want the music player to be created upon demand, only when someone actually needs it
You want to initialize the music player with some variable you have in your code
To make this work, you have to centralize the creation of the music player and that has to be coordinated with whoever has the initialization variable that you want to pass to the constructor. There are many ways to do that. I will illustrate two of them.
Let's assume that it's your application initialization code that has this variable that you want to use when creating the music player. So, rather than having everyone load the music player module themselves, we will have them all ask the app for the music player.
// app.js
const MusicPlayer = require(`./modules/musicplayer`); // load module to get constructor
let musicPlayerInitVariable; // your code sets this during startup
const sharedPlayer;
function getMusicPlayer() {
if (!sharedPlayer) {
sharedPlayer = new MusicPlayer(musicPlayerInitVariable);
}
return sharedPlayer;
}
// one of many possible exports from your app
module.exports = { getMusicPlayer };
Then, in any of your application files that want access to the shared music player, then just do this:
const musicPlayer = require('./app.js').getMusicPlayer();
This requires that anyone using the shared musicPlayer knows how to reference app.js.
Another way to do it is to have your app, initialize the music player module itself with some initial state that it can then use when it creates the music player upon demand.
// musicplayer.js
const ytdl = require('ytdl-core-discord');
const MusicQueue = require('./musicqueue');
const discordUtils = require ('../../../utils/discord-utils');
class MusicPlayer {
constructor() {
this.repeat_playlist = false;
this.queue = new MusicQueue(); // this is the persistent queue for the server
this.dispatcher = null;
this.volume = 1;
this.stopped = true;
this.paused = false;
this.repeat_current_song = false;
this.auto_delete = false;
this.manual_index = false;
this.autodcEnabled = false;
}
....
}
// this is the constructor variable
// You can either give it a default value so things will work if the player
// is not configured or if you don't give it a default value, then the code
// will require that configurePlayer() is called first
let musicPlayerInitVariable;
// call this before anyone calls getMusicPlayer()
function configurePlayer(data) {
// save this for later
musicPlayerInitVariable = data;
}
let sharedPlayer;
function getMusicPlayer() {
if (!sharedPlayer) {
if (!musicPlayerInitVariable) {
throw new Error('Music player must be configured with configurePlayer() first')
}
sharedPlayer = new MusicPlayer(musicPlayerInitVariable);
}
return sharedPlayer;
}
module.exports = { getMusicPlayer, configurePlayer };
Then, in your app-startup code somewhere, you do this:
require('./modules/musicplayer').configurePlayer(passMagicVariableHere);
From then on, anyone can just do:
const musicPlayer = require('./modules/musicplayer').getMusicPlayer();

Related

How can multiple Imported classes call each others methods?

I have this index.js file:
import GameController from './Controllers/GameController'
import LandController from './Controllers/LandController'
const GAME = new GameController();
const LAND = new LandController();
And in this class, I want to call a function every second using setInterval:
export default class GameController {
tickCount = 0;
tick = setInterval( () => {
this.Tick()
}, 1000);
Tick () {
LAND.updateLand();
this.tickCount++;
}
}
But I get this error:
Uncaught ReferenceError: GAME is not defined
I'm new to webpack, I had this working in vanilla javascript but I'm still learning, so any help would be greatly appreciated! Thank you
EDIT:
Updated GameController class based on bravo's answer, however now I get the error:
Uncaught ReferenceError: LAND is not defined
How can I make it so multiple classes can call each others methods?
You started using modules and classes, so you are moving to OOP (Object Oriented Programming). This means that you should forget about scripting, where you declare globals, everything manipulates everything, and start reasoning in a structured, hierarchical, and isolated fashion (where you have objects responsible for specific tasks).
You started properly by understanding what isolation is. You created an autonomous class to control the land. Then you need a game controller, which controls the land (and anything else, but we will come back to this lather).
Follow the reasoning; Game controls Land, in OOP this would translate in either Game receives Land or Game creates Land. Which of two to choose depends on the specific use case.
Game creates Land
// ./Controllers/GameController.js
import LandController from './Controllers/LandController'
export default class GameController {
tickCount = 0;
land = new LandController();
intercal = setInterval(this.onTick.bind(this), 1000);
onTick () {
this.land.updateLand();
this.tickCount++;
}
}
// index.js
import GameController from './Controllers/GameController'
const GAME = new GameController();
Here you can see how Game creates Land, this is useful but reduces your access to the Land constructor (assuming you do not want to change Game to configure land)
Game receives Land
// ./Controllers/GameController.js
export default class GameController {
tickCount = 0;
land;
constructor(land) {
this.land = land;
}
intercal = setInterval(this.onTick.bind(this), 1000);
onTick () {
this.land.updateLand();
this.tickCount++;
}
}
// index.js
import GameController from './Controllers/GameController'
import LandController from './Controllers/LandController'
const LAND = new LandController();
const GAME = new GameController(LAND);
Here you have access to both constructors and your code is more maintainable. Game is not concerned about Land (what is, what it has in or where it came from) as long as it has a method updateLand() that has to be called at an interval.
More than land
The second solution allows you to implement a more versatile Game. Assume your Land has a method called update() rather than updateLand().
// ./Controllers/GameController.js
export default class GameController {
tickCount = 0;
children;
constructor(children) {
this.children = children;
}
intercal = setInterval(this.onTick.bind(this), 1000);
onTick () {
this.children.forEach(c => c.update());
this.tickCount++;
}
}
And then you can go beyond Land, maybe have a Character or more Land, or anything else.
// index.js
import GameController from './Controllers/GameController'
import CharController from './Controllers/CharController'
import LandController from './Controllers/LandController'
const LAND1 = new LandController();
const LAND2 = new LandController();
const CHAR = new CharController();
const GAME = new GameController([ LAND1, LAND2, CHAR ]);
This is how you can start reasoning to structure your OOP software properly; objects that are concerned about tasks and other objects.
When an object is not concerned about another object it should not have access to it (so no globals)
When an object is responsible for another object it should have direct access (receive them from the constructor or a setter) or directly instantiate them (create them).
If you really want to make the instances global, which I would not recommend because there are normally are better methods then using global variables, you should setup the variables in the window object
window.yourNamespace = {};
window.yourNamespace.game = new GameController();
window.yourNamespace.land = new LandController();
Then you can access both objects everywhere you like with
window.yourNamespace.game
window.yourNamespace.land
Answering your question about a non global solution:
I can only guess what your application will do but it sounds like a game. So GameController should be the main class. From this class you should instantiate all other classes like the LandController(). In that case you can access the LandController of course from the GameController. If you need an object in a class you should give the object as an parameter. E.g.
Class GameController()
{
landController = new LandController();
… here all needed methods for the class …
// Example if you need LandController in an other Class
exampleMethod()
{
Let otherClass = new OtherClass(this.landController);
}
}
You can do it this way:
We may use a constructor for that.
export default class GameController {
tickCount = 0;
tick = null;
constructor() {
this.tick = setInterval(() => {
this.Tick()
}, 1000);
}
Tick () {
LAND.updateLand();
this.tickCount++;
}
}

Accessing objects from JavaScript factories and modules

I am writing a Tic Tac Toe app in Javascript, HTML and CSS, where I have to practice placing all my functions inside modules or factories. Currently I am having an issue accessing a factory created object inside other modules.
My complete code can be found here:
https://codepen.io/wulfenn/pen/xxVqGrW
Breakdown of my code:
I have two modules and one factory; gameBoard is a module in charge of anything board related. (reading the board, modifying the board, visual effects, etc)
The game module is in charge of the functionality (starting new rounds, handling player turns, checking for winners, etc)
Lastly, Player is my factory to create new players and has simple functionality, such as retrieving the name, or retrieving the mark assigned to it. (X or O)
The problem I am currently having is when creating a player with const p1 = Player('P1Name', 'X') inside the game module, specifically the soloPlay() function inside it. If I try to access it within said function, I have no problems, but the rest of the code cannot access it.
If I were to create a const p1 = Player('P1Name', 'X') outside of my factory and modules, all the code can access it. But the issue is that I need to be able to create a new player within the soloPlay() function inside the game module.
How can I go about calling soloPlay() and still be able to access P1 and P2 objects along with their functions outside of the code?
Thanks for your help, and I apologize if my code is cluttered or poor, I am still learning.
Relevant code if you don't want to go through all my project:
soloPlay() inside a game module.
// Our second menu for solo play, to set the P1 name, and start the game once done.
const soloPlay = () => {
const enterBtn = document.querySelector('.enter-btn');
enterBtn.addEventListener('click', function () {
const p1 = Player(document.querySelector('.p1').textContent, 'X');
const p2 = Player('AI', 'O');
const soloMenu = document.querySelector('.solo-menu');
soloMenu.style.display = 'none';
const gameMenu = document.querySelector('.menu-bg');
gameMenu.style.display = 'none';
game.setup(); // Sets our grid listeners
game.newRound(); // Starts a new round.
});
}
Player Factory
/* Our player factory in charge of creating new players, and assigning them functions to obtain their names and marks */
const Player = (name, mark) => {
const hasTurn = true;
const getName = () => name;
const getMark = () => mark;
return { getName, getMark, hasTurn };
}
Source: https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Sentencias/const
Declaring a variable const, defines it with a block scope, meaning that nothing outside the function that created it will be able to see it... in the case of your players, you are not changing the Player factory, you are creating an instance of it when you declare
const p1 = Player.... so p1 is created within soloPlay and is not accessible to any other part of the code... if you declare p1 and p2 as global (outside any other scope or inside a scope you choose that makes sense for the code) and not declare them as const your game should work.
//This goes outside 'const gameBoard'
var p1;
var p2;
And not declaring here p1 and p2 as const:
const soloPlay = () => {
const enterBtn = document.querySelector('.enter-btn');
enterBtn.addEventListener('click', function () {
p1 = Player((document.querySelector('#p1').value), 'X');
console.log('Jugador 1: '+p1.getName());
p2 = Player('AI', 'O');
console.log('Jugador 2: '+p2.getName());
const soloMenu = document.querySelector('.solo-menu');
soloMenu.style.display = 'none';
const gameMenu = document.querySelector('.menu-bg');
gameMenu.style.display = 'none';
game.setup(); // Sets our grid listeners
game.newRound(); // Starts a new round.
});
}

Weird error with node js: TypeError X not a constructor

Anyone know why this is happening?
I have a pretty complex system so to simplify it we have this code:
profile_manager.js
const Profile = require('./profile');
class ProfileManager {
doThing() {
const prf = new Profile("Lemon", "ade");
}
}
const prfManager = new ProfileManager();
module.exports = prfManager;
profile.js
class Profile {
constructor(arg0, arg1) {
//do thing
}
}
module.exports = Profile;
index.js
const prfManager = require('./profile_manager');
prfManager.doThing();
Once .doThing() is called, I get a TypeError saying that "Profile is not a constructor".
HOWEVER... When I change profile_manager.js to the following code below, it works perfectly. No TypeError.
class ProfileManager {
doThing() {
const Profile = require('./profile');
const prf = new Profile("Lemon", "ade");
}
}
const prfManager = new ProfileManager();
module.exports = prfManager;
I even console.logged the prf object and it works the way I want it to. Why does it only work when I move the "const Profile = require('./profile');" within the method but when I put it at the top of the module, it does not want to work.
I found out that profile.js had an instance of profile_manager.js or something like that
This post talks more about it How to deal with cyclic dependencies in Node.js

Imported Classes relationship from one to other

O.K..so I kind of figure it out. but I want to make sure its not possible.
lets say I have 3 files:
index.js main file,player.js player class file,game.js game class file.
the main file (index) is importing all the classes. but, I need to have the option to create Game object in both files
example:
index.js:
var game = require("./game.js");
var player = require("./player.js");
new player("player");
new game("game");
player.js:
module.exports = class Player {
constructor(arg){
console.log(arg);
var asd=new game("ASD");/// <- the problem
}
};
game.js:
module.exports = class Game {
constructor(arg){
console.log(arg);
}
};
how can i call to Game in player class without require game both in the index.js and the player.js
I know this will solve it but is there another way without a duplicate require?
(2 require, one in index.js and one in player.js)
index.js:
var player = require("./player.js");
var game = player.Game;
new player("player");
new game("game");
player.js:
var game = require("./game.js");
module.exports = class Player {
constructor(arg){
console.log(arg);
var asd=new game("ASD");/// <- the problem
}
};
modules.exports.Game = Game;
game.js:
module.exports = class Game {
constructor(arg){
console.log(arg);
}
};

NodeJS required module in module is always undefined

I am pretty much new to backend-code. For now I try learning the architecture of a nodeJS-server.
My Problem for now is pretty simple I believe, but I can't run through -.-
server.js (main):
var io = require("socket.io"),
express = require("express"),
expressHbs = require('express3-handlebars'),
Player = require("./models/Player").Player,
Room = require("./models/Room").Room,
Game = require("./models/Game").Game,
dataSet = require('./data/data.json');
//...
Then I have 3 modules:
Player
Room
Game
Each module is the same structure:
Player.js
// importing another module
var Room = require("./Room").Room;
if (!Player.players) {
Player.players = {};
}
function Player(foo) {
this.foo = foo;
Player.players[foo] = this;
}
Player.getAllPlayersAsObject = function () {
return Player.players;
};
// ...
Player.prototype.removePlayer = function () {
if (Player.players.hasOwnProperty(this.id)) {
delete Player.players[this.id];
}
};
// ...
/**
* node export
* #type {Player}
*/
exports.Player = Player;
Room.js
var Player = require("./Player").Player;
if (!Room.rooms) {
Room.rooms = {};
}
function Room(foo) {
this.foo = foo
}
Room.getAllRoomsAsObject = function () {
return Room.rooms;
};
Room.prototype.toString = function () {
return JSON.stringify(this);
};
/**
* node export
* #type {Room}
*/
exports.Room = Room;
My main problem is, that I can use Room, Player, Game from server.js - constructor, prototype and all other functions normally.
In Player.js I can use the imported Room-module normally, too!
Room.js tells me:
path/path/path/gameserver/models/Room.js:222
var currentPlayer = Player.getPlayer(this.players[player].id);
^
TypeError: Cannot read property 'getPlayer' of undefined
I am going crazy. What am I doing wrong? When I do a console.log (debug) of that variable "Player" at the top of the file, I am always getting undefined.
I walked around on google and here at stackoverflow, not finding any solution for my problem.
Thx and best regards,
Michael
You have a cyclic dependency between your modules.
You start by requiring Player. The first thing it does is require Room. Room then requires Player. At this point, Player has not yet completed its loading. Node prevents an infinite loop occurring by returning an incomplete version of Player. At this point, all bets are off as to what will happen.
You need to structure your modules to avoid this direct loop.
Background information of cyclic dependencies here: https://nodejs.org/api/modules.html#modules_cycles

Categories