Mixin functions only work in render() - javascript

For some reason, it appears that mixin functions in my code only work properly in render() function. It could be that I'm not calling them in the right manner outside of the render(), but shouldn't it be exactly the same way?
This way everything works fine (but I can't stick with this since I have to add some extra stuff to click handling, at the same time not altering the mixin):
var Row = React.createClass({
mixins: [someMixin]
},
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.order(this.props.name, this.props.else);
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.noOrder(this.props.name, this.props.else);
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
});
But when I do the obvious and include the mixin functions in another function to handle the click - like this - everything fails and even 'test' is not printed in the console:
var Row = React.createClass({
mixins: [someMixin]
},
handleOrderClick(type) {
console.log('test');
if (type == 'order') {
this.order(this.props.name, this.props.else);
} else if (type == 'no-order') {
this.noOrder(this.props.name, this.props.else);
}
},
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.handleOrderClick('order');
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.handleOrderClick('no-order');
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
});
EDIT:
order and noOrder functions look like this:
order: function (name, else) {
return function (event) {
event.preventDefault();
var term = name + '&&ยค%' + else;
Order.order(name, else, period, function (data) {
if (term === (global || window).MAIN_NAME + '.' + (global || window).MAIN) {
$(window).trigger('Name:update');
}
}.bind(this));
}.bind(this);
},
noOrder: function (name, else) {
return function (event) {
event.preventDefault();
if (!this.state.transferModalOpen) {
this.toggleTransferModal();
}
}.bind(this);
}

In order to use this.setState in handleOrderClick you'll have to use the bind method in your render method. Therefore handleOrderClick will become:
handleOrderClick(type, event) {
this.setState({foo: 'bar'});
if (type == 'order') {
this.order(this.props.name, this.props.else)(event);
} else if (type == 'no-order') {
this.noOrder(this.props.name, this.props.else)(event);
}
},
and your render method becomes:
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.handleOrderClick.bind(this, 'order');
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.handleOrderClick(this, 'no-order');
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
You'll notice that the functions that are returned by this.order and this.noOrder are no longer returned by handleOrderClick, but are instead executed immediately. This should provide the effect you desire.
I've put the code in your example into a jsfiddle and it now seems to be working correctly. I've had to change the prop 'else' to 'alt' because 'else' is a reserved word. I've also just applied the mixin to the class directly for simplicity. I have simpilfied the order and noOrder functions as I don't have access to the Order object and we are only interested in them firing at the correct time. I've also added a second button that you can click to flip the cases so the other button is rendered, causing the component to render again. I've added a label that will display which function had been called when the button was last pressed.
for reference, you can find more information about the bind method here.
Hope this helps ^_^

Related

Avoid numbers incrementing multiple times when calling a function multiple times

I have written a quantity selector function to display on a page. The page can open some modals, which need to have another quantity selector within each.
I am calling the function within the main page, and also within the modal (to enable the functionality once the modal is displayed.)
When I adjust the quantity in the modal, close the modal, and adjust the quantity on the main page, the quantity increments/decrements double (or 3 times if I was to call the function 3 times.)
Is there a way to "reset" each of these event listeners/functions, to only adjust for their respective elements?
I've looked into "removeEventListener" but haven't had any joy in implementing this within my code.
Example of my work so far here (you can see what I mean if you click the buttons.)
https://codepen.io/777333/pen/zYoKYRN
const quantitySelector = () => {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
if(qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
});
});
}
};
quantitySelector(); // called within main page
quantitySelector(); // called within modal
The issue at hand is that each time you're calling the function, a new event handler is added on top of the previous ones. The best way to avoid this is through Event Delegation where you add a global event handler only once.
// A global event handler
document.addEventListener(
"click",
function (event) {
// Find the qty-group if clicked on it
const qtyGroup = event.target.closest(".qty-group");
// Stop if the click was elsewhere
if (qtyGroup) {
// Get your elements
const qtyDecrease = qtyGroup.querySelector("[data-quantity-decrease]");
const qtyIncrease = qtyGroup.querySelector("[data-quantity-increase]");
const qtyInput = qtyGroup.querySelector("[data-quantity-input]");
const disableEnableDecrease = () => {
if (qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
// Match your elements against what was clicked on.
if (event.target == qtyDecrease) {
event.preventDefault();
if (qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
}
if (event.target == qtyIncrease) {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
}
}
},
false
);
Instead of listening to individual elements, you can capture all the clicks on the document, and then finding those that click on elements of interest. You can make a second event handler for the keyup event.
You can save the value of qtyInput on mousedown event and then in the increment you add or subtract one from the saved value instead of the current value of the input.
const quantitySelector = () => {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
let savedValue = null;
const saveState = (evebt) => savedValue = Number(qtyInput.value);
qtyDecrease.addEventListener('mousedown', saveState)
qtyIncrease.addEventListener('mousedown', saveState)
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
if(qtyInput.value > 1) {
qtyInput.value = savedValue - 1;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
qtyInput.value = savedValue + 1;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
event.stopPropagation();
});
});
}
};
quantitySelector();
quantitySelector();
There is a method called removeEventListener (MDN) but I suggest you to reshape your code such that you do not add event listener if they are already present.
Put all of your addEventListener just when you create your elements, or in a "document ready" callback if they are instantiated by HTML code. Then, when you open your modal, just update your values.
UPDATING YOUR CODE
// hide/show modal function
function toggleModal() {
let modal = document.getElementById('modal');
modal.style.display = modal.style.display == 'none' ? 'block' : 'none';
}
// your document ready function
function onReady() {
const qtyGroups = document.querySelectorAll('.qty-group');
if(qtyGroups) {
qtyGroups.forEach((qtyGroup) => {
const qtyDecrease = qtyGroup.querySelector('[data-quantity-decrease]');
const qtyIncrease = qtyGroup.querySelector('[data-quantity-increase]');
const qtyInput = qtyGroup.querySelector('[data-quantity-input]');
const disableEnableDecrease = () => {
if(qtyInput.value == 1) {
qtyDecrease.disabled = true;
} else {
qtyDecrease.disabled = false;
}
};
qtyDecrease.addEventListener('click', (event) => {
event.preventDefault();
if(qtyInput.value > 1) {
qtyInput.value--;
}
disableEnableDecrease();
});
qtyIncrease.addEventListener('click', (event) => {
event.preventDefault();
qtyInput.value++;
disableEnableDecrease();
});
qtyInput.addEventListener('keyup', () => {
disableEnableDecrease();
});
});
}
// attach hide/show modal handler
const toggle = document.getElementById('modal_toggle');
toggle.addEventListener('click', toggleModal);
}
onReady();
<div class="qty-group">
<button data-quantity-decrease disabled>-</button>
<input data-quantity-input value="1">
<button data-quantity-increase>+</button>
</div>
<div class="qty-group" id="modal" style="display: none;">
<button data-quantity-decrease disabled>-</button>
<input data-quantity-input value="1">
<button data-quantity-increase>+</button>
</div>
<button id="modal_toggle">Toggle Modal</button>
REFACTORING
It is better in such cases to reason as Components. Components ensure code encapsulation, maintainability, reusage, single responsability and many other usefull principles:
// hide/show modal function
function toggleModal() {
// get the modal
let modal = document.getElementById('modal');
// hide the modal
modal.style.display = modal.style.display == 'none' ? 'block' : 'none';
// reset the input of the modal
modalInputReference.reset();
}
function createQuantityInput(target, initialQuantity=1, min=1, max=10, step=1) {
let quantity = 0;
// assign and check if should be disable, also bind to input value
let assign = (q) => {
quantity = Math.max(Math.min(q, max), min);
decrease.disabled = quantity <= min;
increase.disabled = quantity >= max;
input.value = quantity;
};
// CREATION
// This part is not mandatory, you can also get the elements from
// the target (document.querySelector('button.decrease') or similar)
// and then attach the listener.
// Creation is better: ensure encapsulation and single responsability
// create decrease button
let decrease = document.createElement('button');
decrease.addEventListener('click', () => { assign(quantity - step); });
decrease.innerText = '-';
// create increase button
let increase = document.createElement('button');
increase.addEventListener('click', () => { assign(quantity + step); });
increase.innerText = '+'
// create input field
let input = document.createElement('input');
input.value = quantity
input.addEventListener('change', () => { assign(parseFloat(input.value)); });
// resetting the quantity
assign(initialQuantity);
// appending the new component to its parent
target.appendChild(decrease);
target.appendChild(input);
target.appendChild(increase);
// return a reference to manipulate this component
return {
get quantity() { return quantity; },
set quantity(q) { assign(q); },
assign,
reset: () => assign(initialQuantity)
};
}
// this will be your modal reference
let modalInputReference;
function onReady() {
// inject all qty-group with a "quantityInput" component
document.querySelectorAll('.qty-group').forEach(elem => {
let input = createQuantityInput(elem);
if (elem.id == 'modal') {
// if it is the modal I save it for later use
// this is just an hack for now,
// a full code should split this part into a "modal" component maybe
modalInputReference = input;
}
});
// emualte the modal
let toggle = document.getElementById('modal_toggle')
toggle.addEventListener('click', toggleModal)
}
// this function should be wrapped by a
// $(document).ready(onReady) or any other
// function that ensure that all the DOM is successfully loaded
// and the code is not executed before the browser has generated
// all the elements present in the HTML
onReady();
<div class="qty-group"></div>
<div class="qty-group" id="modal" style="display: none;"></div>
<button id="modal_toggle">Toggle Modal</button>
It is shorter (without comments) and also more maintenable. Don't trust who says it is overengineered, it is just kind of time to learn to reason this way, then is much easier and faster. It is just a time investment to waste less time in the future. Try figure out why React or Angular(JS) have climbed the charts of the best frameworks so fast.

Function sortAlpha is undefined

I am currently trying to sort all my game objects alphabetically by title. I have gotten my onclick to register but it does not execute my JS function below is the HTML and JS snippets. The sortAlpha is a method within the Games class.
*edit 1: Adjusted function to attach a event listener
*edit 2: I have opted to create a variable to store and call the function. My next question is am I not correctly displaying the newly alphabetized contents with my function ? I get no errors and my log shows that clicks are being registered.
<div id="filter">
<div id="buttons">
<button onclick="let the_game = new Games(); the_game.sortAlpha()">Sort Games Alphabetically</button>
</div>
</div>
class Games {
constructor() {
this.games = []
this.adapter = new GamesAdapter()
this.initBindingsAndEventListeners()
this.fetchAndLoadGames()
}
initBindingsAndEventListeners() {
this.newGameForm = document.getElementById('new-game-form')
this.newGameTitle = document.getElementById('new-game-title')
this.newGameDeveloper = document.getElementById('new-game-developer')
this.newGameCover = document.getElementById('new-game-cover')
this.newGameForm.addEventListener('submit', this.createGame.bind(this))
}
createGame(g) {
g.preventDefault();
const titleValue = this.newGameTitle.value;
const developerValue = this.newGameDeveloper.value;
const coverValue = this.newGameCover.value;
this.adapter.createGame(titleValue, developerValue, coverValue)
.then(game => {
const newGame = new Game(game)
this.games.push(newGame)
this.newGameTitle.value = ' '
this.newGameDeveloper.value = ' '
this.newGameCover.value = ' '
newGame.renderGameBlock()
})
}
fetchAndLoadGames() {
this.adapter
.getGames()
.then(games => {
games.forEach(game => this.games.push(new Game(game)))
})
.then(() => {
this.renderGames()
})
}
renderGames() {
this.games.map(game => game.renderGameBlock())
}
sortAlpha() {
console.log('test');
this.games.sort(function(gameA, gameB){
if (gameA.title < gameB.title) {
return -1;
}
if (gameA.title > gameB.title){
return 1;
}
return 0;
});
window.onload = function() {
document.getElementById("filter").onclick = sortAlpha;
}
}
}
The sortAlpha is a method within the Games class.
That's your answer right there. sortAlpha is not a free-function, but your onclick="" attribute references sortAlpha() as though it were a free-function.
(A "free function" is a function that is not a member of a class/type)
You need to move your sortAlpha function to the global scope, or attach it to the button using addEventListener. You cannot use class method functions in onclick="" without fully qualifying them.

One button to fire toggle functions? Press button -> toggleOn(), press again -> toggleOff(), press again -> toggleOn()

Is it possible to use the same onClick from a button to make it a "toggle"?
I have a button, <button>Toggle</button> and basically toggling it on should execute either toggleOn() or toggleOff().
How do I do this with just onClick property?
Here is a simple "toggler" higher-order function:
function toggler(fn1, fn2) {
var toggled = true;
return function () {
toggled = !toggled;
return (toggled ? fn1 : fn2).apply(this, arguments);
}
}
function logToConsole (e) {
console.log(this, e);
}
function logToAlert (e) {
alert(e);
}
var toggledLog = toggler(logToConsole, logToAlert);
Now running toggledLog with arguments and context will work properly. You can use it in your onClick handler.
You will need to maintain some state as to whether the button is "on" or "off". I would do this by creating a closure over a buttonState variable:
const setText = ( text ) => () => {
document.getElementById('toggled').innerHTML = text;
};
const toggleOff = setText('off');
const toggleOn = setText('on');
const makeClickhandler = () => {
let buttonState = false;
return () => {
buttonState ? toggleOff() : toggleOn();
buttonState = !buttonState;
}
};
document.getElementById('yourbuttonid').addEventListener('click', makeClickhandler());
<button id='yourbuttonid'>Toggle</button>
<span id='toggled'>off</span>
I can imagine different ways to achieve it.
The first to come to my mind would be something like:
var button = document.getElementById('toggle');
var state = document.getElementById('current-state');
button.addEventListener("click", toggleState);
function toggleState() {
if ( button.dataset.state == "on" ) {
button.dataset.state = "off";
state.innerHTML = "I'm off";
} else {
button.dataset.state = "on";
state.innerHTML = "I'm on";
}
}
<button id="toggle" data-state="off">Toggle me!</button>
<div id="current-state">I'm off</div>
A similar but slightly more compact approach to Alex Young's:
function toggleOn() {
console.log( 'on' );
}
function toggleOff() {
console.log( 'off' );
}
var button = document.getElementById( 'button' );
button.addEventListener( 'click', function clickToggle() {
clickToggle.state = !clickToggle.state;
clickToggle.state ? toggleOn() : toggleOff();
} );
<button id="button">Toggle</button>
This uses the fact that you can attach properties to named functions this way. I'm not sure what the exact terminology for this is.
To address Joe Frambach's valid concerns, addressed in the comments, you could change it to:
button.addEventListener( 'click', function clickToggle() {
clickToggle.state = !clickToggle.state;
return ( clickToggle.state ? toggleOn : toggleOff ).apply( this, arguments );
} );

Toggle Event Listeners

I am trying to make a function that would allow me to toggle eventListener of an element.
In the example below, I have three buttons: main, on and off. When I click on the on button, the main button becomes functional. After I click off button, the main button should not work anymore (but now it still does).
Now I can achieve a desired behavior by clicking on button for the second time, but I guess it's a bad coincidence and it's not supposed to work that way.
Maybe I should add that I would like to work this out without using jQuery or similar and it needs to be a function, because I am going to use it for a lot of buttons.
(I suspect something with scope causes the problem (clickHandler when calling the function to activate the button is not the same as the clickHandler when calling the function to disable the button), but I can't think of a way to test it.)
// buttons definitions, not important
var mainButton = document.querySelector("#mainButton");
var onButton = document.querySelector("#onButton");
var offButton = document.querySelector("#offButton");
// main function
var toggleButtons = function(toggleVal, button, element) {
var activateButton, clickHandler, disableButton;
// callback function for listener bellow
clickHandler = function() {
document.querySelector(element).classList.toggle("yellow");
};
activateButton = function() {
button.addEventListener("click", clickHandler);
};
disableButton = function() {
button.removeEventListener("click", clickHandler);
};
// when first argument is 1, make the button functional, otherwise disable its functionality
if (toggleVal === 1) {
activateButton();
} else {
disableButton();
}
};
// when onButton is clicked, call main function with arguments
// this works
onButton.addEventListener("click", function() {
toggleButtons(1, mainButton, "body");
});
// this fails to disable the button
offButton.addEventListener("click", function() {
toggleButtons(0, mainButton);
});
.yellow {
background-color: yellow;
}
<button type="button" id="mainButton">mainButton
</button>
<button type="button" id="onButton">onButton
</button>
<button type="button" id="offButton">offButton
</button>
<p>mainButton: toggles background color on click
</p>
<p>onButton: turns on mainButtons's functionality</p>
<p>offButton: supposed to turn off mainButton's functionality</p>
var mainButton = document.querySelector("#mainButton");
var onButton = document.querySelector("#onButton");
var offButon = document.querySelector("#offButton");
var element; // declare the element here and change it from toggleButtons when needed.
function clickHandler() {
document.querySelector(element).classList.toggle('yellow');
}
function activateButton(button) { // You missed this part
button.addEventListener('click', clickHandler);
}
function disableButton(button) { // You missed this part
button.removeEventListener('click', clickHandler);
}
function toggleButtons(value, button) {
if (value === 1) {
activateButton(button); // You missed this part
} else {
disableButton(button); // You missed this part
}
};
onButton.addEventListener('click', function() {
element = 'body'; // you can change it to some other element
toggleButtons(1, mainButton);
});
offButton.addEventListener('click', function() {
element = 'body'; // you can change it to some other element
toggleButtons(0, mainButton);
});
Below code helps to toggle between two functions from an eventListener:
var playmusic=false;
function playSound() {
const audio = document.querySelector(`audio[data-key="${event.keyCode}"]`)
audio.currentTime = 0
audio.play()
playmusic=true;
}
function stopSound() {
const audio = document.querySelector(`audio[data-key="${event.keyCode}"]`)
audio.pause()
playmusic=false;
}
window.addEventListener('keydown',
function(){playmusic?stopSound():playSound()} )

Custom image toggle button in ReactJS

I have this ReactJS code to show a custom image button that toggles between 2 different images for ON and OFF state. Is there a simpler way to do this? I was hoping CSS might be less lines of code, but wasn't able to find a simple example.
The code below passes state up from <MyIconButton> to <MyPartyCatButton> then to <MyHomeView>. My app will have 4 of these custom buttons on the home screen, which is why I factored out <MyIconButton>.
btw - this is for a mobile App and I read (and noticed this myself) it's really slow using checkboxes on mobile browsers; that's why I chose to try this without using checkboxes.
ReactJS code
var MyIconButton = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
console.log("INSIDE: MyIconButton handleSubmit");
// Change button's state ON/OFF,
// then sends state up the food chain via
// this.props.updateFilter( b_buttonOn ).
var b_buttonOn = false;
if (this.props.pressed === true) {
b_buttonOn = false;
}
else {
b_buttonOn = true;
}
// updateFilter is a 'pointer' to a method in the calling React component.
this.props.updateFilter( b_buttonOn );
},
render: function() {
// Show On or Off image.
// ** I could use ? : inside the JSX/HTML but prefer long form to make it explicitly obvious.
var buttonImg = "";
if (this.props.pressed === true) {
buttonImg = this.props.onpic;
}
else {
buttonImg = this.props.offpic;
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<input type="image" src={buttonImg}></input>
</form>
</div>
);
}
});
// <MyPartyCatButton> Doesn't have it's own state,
// passes state of <MyIconButton>
// straight through to <MyHomeView>.
var MyPartyCatButton = React.createClass({
render: function() {
return (
<MyIconButton pressed={this.props.pressed} updateFilter={this.props.updateFilter} onpic="static/images/icon1.jpeg" offpic="static/images/off-icon.jpg"/>
);
}
});
//
// Main App view
var MyHomeView = React.createClass({
getInitialState: function() {
// This is where I'll eventually get data from the server.
return {
b_MyPartyCat: true
};
},
updatePartyCategory: function(value) {
// Eventually will write value to the server.
this.setState( {b_MyPartyCat: value} );
console.log("INSIDE: MyHomeView() updatePartyCategory() " + this.state.b_MyPartyCat );
},
render: function() {
return (
<div>
<MyPartyCatButton pressed={this.state.b_MyPartyCat} updateFilter={this.updatePartyCategory}/>
</div>
// Eventually will have 3 other categories i.e. Books, Skateboards, Trees !
);
}
});
if you update the coponent 'pressed' prop dynamically (like you did), simply
var MyIconButton= React.createClass({
render: function(){
var pic= this.props.pressed? this.props.onpic : this.props.offpic
return <img
src={pic}
onClick={this.props.tuggleSelection} //updateFilter is wierd name
/>
}
})
(EDIT: this way, on MyPartyCatButton component, you can pass function to handle 'tuggleSelection' event. event function argument is an event object, but you have the button state allready in the wrapper state (the old one, so you should invert it). your code will be something like that:
render: function(){
return <MyIconButton pressed={this.state.PartyCatPressed} tuggleSelection={this.updatePartyCategory} />
}
updatePartyCategory: function(e){
this.setState(
{PartyCatPressed: !this.state.PartyCatPressed} //this invert PartyCatPressed value
);
console.log("INSIDE: MyHomeView() updatePartyCategory() " + this.state.b_MyPartyCat )
}
)
but if you don't, use prop for defult value:
var MyIconButton= React.createClass({
getInitialState: function(){
return {pressed: this.props.defultPressed}
},
handleClick: function(){
this.setState({pressed: !this.state.pressed})
},
render: function(){
var pic= this.state.pressed? this.props.onpic : this.props.offpic
return <img
src={pic}
onClick={this.handleClick}
/>
}
})

Categories

Resources