How to fit these two classes together? - javascript

This is my first attempt at doing OOP and made a simple Pomodoro timer. https://codepen.io/hyrosian/project/editor/XpjOPR
There was an attempt to seperate the countdown timer from the controls. The countdown timer is working but i'm not sure how to get class Counter and class Controls somehow 'fit' together.
const counter = new Counter(DOMnode)
const controls = new Controls()
The plan was to set up the eventlisteners and handlers inside Controls. We get the values e.g. +5mins for changing the session/break length inside of it. But Controls needs access to the state inside Counter.
class Controls extends Time {
constructor() {
super()
this.session_btn = document.querySelectorAll('[data-session]')
this.break_btn = document.querySelectorAll('[data-break]')
}
handleBreak(e) {
console.log(e.target.dataset.break)
}
handleSession(e) {
console.log(e.target.dataset.session)
}
init() {
this.session_btn.forEach(btn => btn.addEventListener('click', this.handleSession))
this.break_btn.forEach(btn => btn.addEventListener('click', this.handleBreak))
}
}
What would be the proper way to fit them together?

Controls should have a reference to the Counter object, but not the other way around.
Controls can access the state by asking for (getTime, getState). If Controls needs to subscribe to some event (timer has run out) then Counter could emit such an event.
The underlying idea would be that Counter is a completely isolated object and just maintains its own state and timers, but Controls is the thing that handles events from the user (and potentially updates the DOM when needed).
This is a pretty common pattern. In this scenario, Counter would often be called the model, and Controls the Controller.
You should first create the Counter, and pass it as a constructor argument to Controls

Related

Can I decouple Classes by adding logic to the Controller in MVC?

I have hopefully explained my problem below by writing a short simplified example with the same conceptual question as my actual larger application. My question is centred around ways in which I can de-couple classes from each other. Whilst I accept no application can be free of coupling completely and modules have to interact with each other, I am trying to establish best ways in which I can keep each class as a separate unit so that if changes are made in one class it won't affect another. I want to keep changes simple.
Below I have outlined an example to the regular problem I face. As an example I have centred this example around a simple game where we can add a player named 'Kevin' everytime we press enter. Then everytime we click a button we add a score of 10 to every players running score and print the updated score of the current player to the screen.
Like most things I have used the MVC pattern to achieve this small example game. The Model has a Player class with data and methods. One of the methods adds 10 to the players score.
I also have a PlayerCircle class in the Model which returns things like the current player as-well as changing which players turn it is.
Lastly in the Model I have a Game Class which imports PlayerCircle. PlayerCircle is then a property of the Game class. Its the only property at the moment but as the game grows, further classes will be added e.g. Dice class
The controller module calls methods from the Model and passes things into view much like how an MVC pattern should work.
This is my PlayerCircle class
export class PlayersCircle {
constructor() {
this.players = [];
this.playersTurn = 0;
}
addPlayer(name) {
const player = new Player(name);
this.players.push(player);
}
changePlayer() {
this.playersTurn++;
}
getPlayer() {
return this.players[this.playersTurn];
}
increaseAllScores() {
this.players.forEach((player) => {
player.add10();
});
}
getAllPlayers() {
return this.players;
}
}
This is my player class
export class Player {
constructor(name) {
this.name = name;
this.score = 0;
}
add10() {
this.score += 10;
}
getScore() {
return this.score;
}
}
This is my game class
import { PlayersCircle } from "./PlayersCircle.js";
export class Game {
constructor() {
this.playersCircle = new PlayersCircle();
}
}
Now I have set up the classes for this very simple game, I now write this very simple function from the controller which adds a player to the game.
import { Game } from "./model/Game.js";
import { viewShowScore } from "./view/viewShowScore.js";
const game = new Game();
const addPlayer = () => {
game.playersCircle.addPlayer("kevin");
};
document.querySelector("body").addEventListener("keypress", addPlayer);
Ok so now I want to add 10 points to all the players in the game. Lets say I have fired the keypress 5 times and have 5 players. This is where I often struggle to establish the best Architectural Plan going forward with an Application.
The two options I normally have are
Call the add10 method on each player from the playerCircle class :-
This will enable me to use my controller just to call methods which change data or get data to the render the state into the view. This keeps my controller short and concise.However it means that the PlayersCircle class is now tightly coupled with the Player Class as one of the PlayerCircle methods uses a method of the Player class ( the add10 method) which means any changes in the Player Class on the add10 method will now break the PlayerCircle class.
My function from the controller to add 10 point to each player looks like this:
const increaseAllScores = () => {
game.playersCircle.increaseAllScores();
const currentPlayer = game.playersCircle.getPlayer();
viewShowScore(currentPlayer.getScore()); // function from view prints 10
};
document.querySelector("button").addEventListener("click", increaseAllScores);
Return all the players to the controller and then from inside the controller module run a loop on the returned array which holds the players-:
This will enable my PlayersCircle class to be completely independent from the Player class and just control the state of who'es turn it is, return all the players at once or separately. It won't have to concern itself with the inner methods of the Player class. The Player class as the game grows will have many methods. If I want to call a method on more than one player at a time then (like the add10 method) then the playersCircle class is very tightly coupled and changes from both classes will almost have to simultaneously occur.
However this would mean that my controller module isn't now just calling methods from the Model and passing results into the view but is now containing loops. Whilst I prefer this way whenever I see examples of MVC the controller is full of simple methods calls from the Model to the view.
The function inside the controller now looks like this:
const increaseAllScores = () => {
const allPlayers = game.playersCircle.getAllPlayers();
allPlayers.forEach((player) => {
player.add10();
});
const currentPlayer = game.playersCircle.getPlayer();
viewShowScore(currentPlayer.getScore()); // function from view prints 10
};
Both Pieces of code give the same result. However for long term code management when the game increases in complexity and may have new features in the future, which one is better than the other and why - option 1 which calls the add10 from the playersCircle targeting every player or option 2 which runs a loop from the controller targeting every player which is returned from the playerCircle class ?

Nested web-components and event handling

I'm writing a memory game in javascript. I have made a web-component for the cards, <memory-card> and a web-component to contain the cards and handle the game state <memory-game>. The <memory-card> class contains its image path for when its turned over, the default image to display as the back of the card, its turned state and an onclick function to handle switching between the states and the images.
The <memory-game> class has a setter that receives an array of images to generate <memory-cards> from. What would be the best method to handle updating the game state in the <memory-game> class? Should I attach an additional event listener to the <memory-card> elements there or is there a better way to solve it? I would like the <memory-card> elements to only handle their own functionality as they do now, ie changing images depending on state when clicked.
memory-game.js
class memoryGame extends HTMLElement {
constructor () {
super()
this.root = this.attachShadow({ mode: 'open' })
this.cards = []
this.turnedCards = 0
}
flipCard () {
if (this.turnedCards < 2) {
this.turnedCards++
} else {
this.turnedCards = 0
this.cards.forEach(card => {
card.flipCard(true)
})
}
}
set images (paths) {
paths.forEach(path => {
const card = document.createElement('memory-card')
card.image = path
this.cards.push(card)
})
}
connectedCallback () {
this.cards.forEach(card => {
this.root.append(card)
})
}
}
customElements.define('memory-game', memoryGame)
memory-card.js
class memoryCard extends HTMLElement {
constructor () {
super()
this.root = this.attachShadow({ mode: 'open' })
// set default states
this.turned = false
this.path = 'image/0.png'
this.root.innerHTML = `<img src="${this.path}"/>`
this.img = this.root.querySelector('img')
}
set image (path) {
this.path = path
}
flipCard (turnToBack = false) {
if (this.turned || turnToBack) {
this.turned = false
this.img.setAttribute('src', 'image/0.png')
} else {
this.turned = true
this.img.setAttribute('src', this.path)
}
}
connectedCallback () {
this.addEventListener('click', this.flipCard())
}
}
customElements.define('memory-card', memoryCard)
implementing the custom event after Supersharp's answer
memory-card.js (extract)
connectedCallback () {
this.addEventListener('click', (e) => {
this.flipCard()
const event = new CustomEvent('flippedCard')
this.dispatchEvent(event)
})
}
memory-game.js (extract)
set images (paths) {
paths.forEach(path => {
const card = document.createElement('memory-card')
card.addEventListener('flippedCard', this.flipCard.bind(this))
card.image = path
this.cards.push(card)
})
}
In the <memory-card>:
Create with CustomEvent() and dispatch a custom event with dispatchEvent()
In the <memory-game>:
Listen to your custom event with addEventListener()
Because the cards are nested in the game, the event will bubble naturally to the container.
This way the 2 custom elements will stay loosley coupled.
Supersharps answer is not 100% correct.
click events bubble up the DOM,
but CustomEvents (inside shadowDOM) do not
Why firing a defined event with dispatchEvent doesn't obey the bubbling behavior of events?
So you have to add the bubbles:true yourself:
[yoursender].dispatchEvent(new CustomEvent([youreventName], {
bubbles: true,
detail: [yourdata]
}));
more: https://javascript.info/dispatch-events
note: detail can be a function: How to communicate between Web Components (native UI)?
For an Eventbased programming challenge
this.cards.forEach(card => {
card.flipCard(true)
})
First of all that this.cards is not required, as all cards are available in [...this.children]
!! Remember, in JavaScript Objects are passed by reference, so your this.cards is pointing to the exact same DOM children
You have a dependency here,
the Game needs to know about the .flipCard method in Card.
► Make your Memory Game send ONE Event which is received by EVERY card
hint: every card needs to 'listen' at Game DOM level to receive a bubbling Event
in my code that whole loop is:
game.emit('allCards','close');
Cards are responsible to listen for the correct EventListener
(attached to card.parentNode)
That way it does not matter how many (or What ever) cards there are in your game
The DOM is your data-structure
If your Game no longer cares about how many or what DOM children it has,
and it doesn't do any bookkeeping of elements it already has,
shuffling becomes a piece of cake:
shuffle() {
console.log('► Shuffle DOM children');
let game = this,
cards = [...game.children],//create Array from a NodeList
idx = cards.length;
while (idx--) game.insertBefore(rand(cards), rand(cards));//swap 2 random DOM elements
}
My global rand function, producing a random value from an Array OR a number
rand = x => Array.isArray(x) ? x[rand(x.length)] : 0 | x * Math.random(),
Extra challenge
If you get your Event based programming right,
then creating a Memory Game with three matching cards is another piece of cake
.. or 4 ... or N matching cards
It would be very helpful to see some of your existing code to know what you have tried. But without it you ca do what #Supersharp has proposed, or you can have the <memory-game> class handle all events.
If you go this way then your code for <memory-card> would listen for click events on the entire field. It would check to see if you clicked on a card that is still face down and, if so, tell the card to flip. (Either through setting a property or an attribute, or through calling a function on the <memory-card> element.)
All of the rest of the logic would exist in the <memory-game> class to determine if the two selected cards are the same and assign points, etc.
If you want the cards to handle the click event then you would have that code generate a new CustomEvent to indicate that the card had flipped. Probably including the coordinates of the card within the grid and the type of card that is being flipped.
The <memory-game> class would then listen for the flipped event and act upon that information.
However you do this isn't really a problem. It is just how you want to code it and how tied together you want the code. If you never plan to use this code in any other games, then it does not matter as much.

Javascript Video loadmetadata event not being triggered on React-Redux Application

I have a React-Redux application that reads some metadata from videos.
However the code added to the loadmetadata event is never triggered.
As a workaround I have added a timer to wait 1 second before, which is a pretty bad solution and doesn't work every time.
Another thing is that I couldn't find an elegant way to integrate the video element into Redux code without having to manipulate the DOM.
The code looks like this:
videoPlayerElement = document.getElementById(`videoplayer-${videoId}`);
videoPlayerElement.addEventListener('loadedmetadata', function(e) {
const duration = videoPlayerElement.duration;
...
})
The code inside the listener is never executed.
I also have tried different ways to assign the loadmetadata event, i.e: assigning directly to videoPlayerElement.onloadmetadata still not working.
I thought it might be because of the scope of the object, so I changed it to a global just for testing... didn't help.
Any other idea about what might be causing?
If I run a simple example, like this one it works fine.
In react you should use synthetic events where possible. Example for your use case:
class MediaPlayer extends Component {
handleMetadata = event => {
const duration = event.currentTarget.duration;
// ...
}
render() {
const {src} = this.props;
return(
<video src={src} onLoadedMetadata={this.handleMetadata} />
);
}
}

Global (window) variable is behaving strangely in Emberjs

I am using Ember-cli in my web app. I have a countdown component to show a countdown timer on UI. Here is my component code.
export default Ember.Component.extend({
end_time: 0,
start_time: 0,
some_id: 0,
timer: 0, // Show this in UI - {{timer}} Seconds
init: function() {
this._super();
let end_time = this.get("end_time"), // 1479476467693
start_time = this.get("start_time"), // 1479476381491
some_id = this.get("some_id");
let wait_time = Math.floor((end_time - start_time)/1000);
this.set('timer', wait_time);
let timerName = "timer_" + some_id;
let _self = this;
window.initTimer.someTimer[timerName] = setInterval(function() {
_self.set('timer', wait_time);
if(wait_time <= 0) {
clearInterval(window.initTimer.someTimer[timerName]);
}
wait_time --;
}, 1000);
}
});
This component works fine, if I add this to a single route.
Now, I have added this component to both parent route and child (/:ID) route, since I need to show the component on both templates. In the child (/:ID) template, I have a button to clear the timer. So I have added this code for that button action.
buttonAction: function(some_id) {
let timerName = "timer_" + some_id;
clearInterval(window.initTimer.someTimer[timerName]);
}
Strangely, when the buttonAction is called, the timer on the child template alone is cleared. The timer on parent template keeps running. But both the timer are assigned to a single global variable (window.initTimer.someTimer) and should be cleared when I run clearInterval().
Is there any solution for clearing the timer on both parent route and child route on click of a button, which resides on child template? Couldn't figure out what magic Ember is playing with global variables!!
Ember is doing no magic here, but your code is much to complicated!
The interesting question is from where some_id comes. If its not the same for both then with which one are you calling buttonAction?
Assume you have the ids one and two. Then you have the two intervals at window.initTimer.someTimer.timer_one and window.initTimer.someTimer.timer_two. Now if you clear window.initTimer.someTimer.timer_one why should window.initTimer.someTimer.timer_two be cleared as well? Well, its not, and thats why your code is not working.
Assume you only have one id, lets call is theOnlyOne for both timers.
Then the init hook of the second component will reassign window.initTimer.someTimer.timer_theOnlyOne, and so only this second component can be resetted when you call buttonAction. Then thats why your code is not working.
So what should you do now? First, you really should stop using the global object! There are so much better ways to do this in the ember ecosystem.
For your timers you should check out ember-concurrency.
If you want to use a global state you should use a service. However I don't recommend this for your problem, because it's against the DDAU principle. However to tell you whats the right way to do what you want to do we need to understand why you have this timers, how they are related and why you want to cancel them both with one click. Probably you would have some timer state outside of the components and pass it down to the components. Maybe a util can be helpful. But this really depends on your use-case.

Preventing Circular Dependencies with Exported Singleton Class

I have a question regarding a scenario I keep running into building HTML5 games resulting in difficult to manage circular dependencies.
I understand completely why the circular dependency is occuring and where it is occurring. However, I can't seem to figure out a convenient way to get around it, so I assume my logic / approach is fundamentally flawed.
Here's a little bit of context.
I have a game that has a single point of entry (compiled with Webpack) called Game.js. I have a basic event manager that allows for two functions on(key, callback) and fire(key, parameters).
The event manager simply creates an object, sets the supplied key of on as a property with an array value populated with any callback functions registered to that key. When the fire method is called that property is retrieved and all of the fuctions defined in it's array value are invoked.
What I'm trying to do
I want to be able to instance the event manager on Game.js and export an instance of Game that other classes can import and subsequently register callbacks to the Game instances event manager.
class Game {
constructor() {
this.events = new EventManager();
window.addEventListener('resize', this.resize.bind(this));
}
resize(event) {
if(window.innerWidth < window.innerHeight) {
this.events.fire('orientation-change', 'vertical');
} else {
this.events.fire('orientation-change', 'horizontal');
}
}
}
export default new Game();
Then for example a Button class may need to respond to an orientation change event fired by the Game. Please note the above is simply an example of a circumstance in which the event manager may fire an event, but this condition could be anything.
import Game from '../core/Game';
class Button {
constructor() {
Game.events.on('orientation-change', this.reorient.bind(this));
}
reorient() {
// ...
}
}
export default Button;
The above class is a UI component called Button that needs to know when the orientation-change event is fired, again please note this event could be anything.
What's the problem?
Nothing looks particularly wrong with the above, however, because Game.js is the entry point, at some point an instance of Button is created whether it be directly in Game.js or through another class which is subsequently instanced via Game.js which of course causes a circular dependency because even if not directly, Game imports Button and Button imports Game.
What I've tried
There are two main solutions that I have found that work (to some degree). The first being simply waiting for the export to be available using an interval check of the value of Game in the constructor of Button, like this:
import Game from '../core/Game';
class Button {
constructor() {
let check = setInterval(() => {
if(Game !== undefined) {
Game.events.on('orientation-change', this.reorient.bind(this));
clearInterval(check);
}
}, 100);
}
reorient() {
// ...
}
}
export default Button;
This will typically resolve in a single iteration.
The second solution being to use dependency injection and pass reference of Game to Button when it's instanced, which again works great, but the prospect of having to repeatedly do this per class seems unintuitive. The interval check works fine too, but seems hacky.
I'm feel like I'm completely missing something and that the solution isn't a difficult as I'm making it.
Thanks for any help regarding this.

Categories

Resources