Need help changing jquery to javascript for an on change function - javascript

I am trying to change some jQuery code to plain JS for an on call function. I keep getting the following error - Failed to execute 'addEventListener' on 'EventTarget': parameter 2 is not of type 'Object'.
Old
$(document).on('change', '.js-variant-radio', onVariantRadioChange);
New
document.querySelector('.js-variant-radio').addEventListener("change", onVariantRadioChange);
Complete code block here
(function(){
//START QUANTITY INPUT VALIDATION FUNCTION
let
onVariantRadioChange = function(event) {
let
$radio = document.querySelector(this),
$form = $radio.closest('form'),
max = $radio.attr('data-inventory-quantity'),
$quantity = $form.querySelector('.js-quantity-field'),
$addToCartButton = $form.querySelector('.add-to-cart-button');
if($addToCartButton.prop('disabled') === true) {
$addToCartButton.prop('disabled', false);
}
$quantity.attr('max', max);
if (parseInt($quantity.value) > max) {
$quantity.val(max).change();
}
};
//END QUANTITY INPUT VALIDATION FUNCTION
})();

Here is your code in vanillaJS. The only thing I didn't do is mimic the change() trigger that was appended
document.addEventListener('DOMContentLoaded', () => {
let
onVariantRadioChange = function(event) {
let
$radio = event.target,
$form = $radio.closest('form'),
max = $radio.dataset['inventory-quantity'],
$quantity = $form.querySelector('.js-quantity-field'),
$addToCartButton = $form.querySelector('.add-to-cart-button');
if ($addToCartButton.getAttribute('disabled')) {
$addToCartButton.removeAttribute('disabled');
}
$quantity.setAttribute('max', max);
if (parseInt($quantity.value) > max) {
$quantity.value = max;
}
};
document.querySelectorAll('.js-variant-radio').foreach(el => el.addEventListener("change", onVariantRadioChange));
})

You're still adding the event listener with jQuery. Try changing it to:
const jsVariantRadio = document.getElementsByClassName('.js-variant-radio')[0];
jsVariantRadio.addEventListener('change', onVariantRadioChange);

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.

JavaScript check file extension for multiple uploads

I have contact form, and Js which validates the size of selected files. Also I want to add extension check. My code so far is
var inputs = $('input[type="file"]')
inputs.on('change', function () {
var size = 0;
inputs.each(function () {
$.each(this.files, function () {
size += this.size;
var names = [];
alert(x);
});
});
if (size > 19000000) {
alert('Ukupna dozvoljena veličina fajlova za upload je 20mb!');
$('.inputDugme').attr('disabled', 'disabled');
} else {
$('.inputDugme').removeAttr('disabled', 'disabled')
}
});
Is there I way to get extension of files, and save it in array. And then check content of array.
You can collect extension names like this:
var size = 0,
etx = [];
inputs.each(function () {
$.each(this.files, function () {
size += this.size;
ext.push(this.name.split('.').pop());
});
});
x[] = this.name; is not valid syntax in Javascript, you should use Array.prototype.push method.
try this.
var ext = filename.split('.')[filename.split('.').length - 1];

I am getting function as undefined while i trigger a bind on input field

I am hearing a input, while input is changed i am getting value. as a initial i am passing 1 as a default. so after that the user change the value i should get the values,
But i am getting error as : undefined is not a function
what is the issue..?
here is my code :
var docLoader = function (params) {
window.container = window.container || $("#tenderContent");
return {
init : function () {
this.container = container.find("#documentscroll");
this.inputPage = container.find("#inputpage");
this.width = this.container.width();
this.height = this.container.height();
this.preload();
this.inputChange();
$(this.inputPage).bind("change paste keyup", this.inputChange);
},
preload : function () {
var that = this;
this.container.load("../common/preloader/index.html",
function(msg){
$('#mask').css({width:that.width,height:that.height});
});
},
//load page
loadPage : function (num) {
this.container.load("Doc/chapter"+num+"/index.html");
},
//input change
inputChange : function (e) {
var inputVal = e != undefined ? e.target.value : 1;
this.loadPage(inputVal); //while page load it works, getting value from input, on change i am getting error.
}
}
}
jQuery(document).ready(function($) {
docLoader().init();
$(window).resize(function(){
docLoader().init();
});
});
In this function (inputChange:) this is reference to current ($(this.inputPage)) element, that why you get error (because in element there is not method loadPage). To fix it, you need bind this (which is reference to object that located in return {}) to function, there are several ways how to do it
$(this.inputPage).bind("change paste keyup", this.inputChange.bind(this));
Or
var _this = this;
$(this.inputPage).bind("change paste keyup", function (e) {
_this.inputChange(e)
});
Or
$(this.inputPage).bind("change paste keyup", $.proxy(this.inputChange, this));
About $.proxy

Using javascript to automaticly load whats in a drop down to a textfield

I have got 3 dropsdowns that functions as choosing a date then when .onchange occurs, it fills the text box with the current date. How would I make it so it automatically does this .onload
This is what I used change the date .onchange
function showDate(){
var combos = [
document.getElementById('daydropdown'),
document.getElementById('monthdropdown'),
document.getElementById('yeardropdown')
];
var values = [], combo, value;
for(var i = 0; i < combos.length; i++){
combo = combos[i];
value = combo.options[combo.selectedIndex].innerHTML;
values.push(value);
}
document.getElementById('date').value = values.join('/');
}
// event handlers:
document.getElementById('daydropdown').onchange =
document.getElementById('monthdropdown').onchange =
document.getElementById('yeardropdown').onchange = showDate;
document.getElementById('daydropdown').onload =
document.getElementById('monthdropdown').onload =
document.getElementById('yeardropdown').onload = showDate;
</script>
<script type="text/javascript">
I have set up a refined solution for you that is working well in JSFiddle, take a look a see if this helps.
http://jsfiddle.net/mWEw9/6/
The basic changes are these:
window.onload = setDateListener;
function setDateListener() {
showDate();
document.getElementById('daydropdown').onchange =
document.getElementById('monthdropdown').onchange =
document.getElementById('yeardropdown').onchange = showDate;
}
Just hook the event handlers to the onload event and call the showDate() there to set the dropdowns on load, too.
window.addEventListener("load",function() {
document.getElementById('daydropdown').onchange =
document.getElementById('monthdropdown').onchange =
document.getElementById('yeardropdown').onchange = showDate;
document.getElementById('daydropdown').onload =
document.getElementById('monthdropdown').onload =
document.getElementById('yeardropdown').onload = showDate;
showDate();
});

NotFoundError: DOM Exception 8 when substituting innerHTML

I'm new to js-development. I have the following code:
<html>
<body>
<div><span id="inline">Click here to start editing</span></div>
<script>
var inline = document.getElementById("inline");
inline.onclick = function() {
if (!inline.editable) {
var text = inline.innerText;
inline.innerHTML = "<input type='text' id='inline-editable'>";
inline.editable = true;
var inline_editable = document.getElementById("inline-editable");
inline_editable.value = text;
inline_editable.onblur = function() {
var value = inline_editable.value;
inline.editable = false;
inline.innerHTML = value;
}
inline_editable.onkeypress = function(event) {
if (event.keyCode == 13) {
inline_editable.onblur();
}
}
}
}
</script>
</body>
</html>
Which shows some text inside span and allows inline editing. When I finish editing within just onblur event it work perfectly fine. But if I want to terminate editing by Enter and use the same hander I get an error NotFoundError: DOM Exception 8 in this line:
inline.innerHTML = value;
Nevertheless everything works as I expect. Can anyone help me to avoid this error?
I assume that is happened because I destroy inline-editable element while event handling is not finished and it wants to invoke onchange maybe. Should I have 2 controls all the time an switch their visibility instead?
Problem here is the onblur is triggered twice, the second time, the element is not there which causes the problem. Kill the events
var inline = document.getElementById("inline");
inline.onclick = function() {
if (!inline.editable) {
var text = inline.innerText;
inline.innerHTML = "<input type='text' id='inline-editable'>";
inline.editable = true;
var inline_editable = document.getElementById("inline-editable");
inline_editable.value = text;
inline_editable.onblur = function() {
this.onblur = function(){};
var value = this.value;
inline.editable = false;
inline.innerHTML = value;
}
inline_editable.onkeypress = function(event) {
if (event.keyCode == 13) {
this.onblur();
}
}
}
}

Categories

Resources