jQuery to Javascript adding required attribute - javascript

So I have the following jQuery code that I've built out that checks whether a on change event has been triggered on #rtk5 and then either removes or adds the 'required' attribute.
Works perfectly in jQuery:
// Make checkbox textboxes not required unless checked
$(document).ready(function() {
$('#rtk5').change(function() {
if ($('.rtk5ReqField').attr('required')) {
$('.rtk5ReqField').removeAttr('required');
}
else {
$('.rtk5ReqField').attr('required','required');
}
});
});
I would like to convert it to JavaScript with a function call, but I can't seem to figure out how to properly do it.
Error:
TypeError: rtk5req.getAttribute is not a function
Here is my attempt:
var rtk5req = document.getElementsByClassName('rtk5ReqField');
function rtk5Required() {
rtk5req.addEventListener('change', (e) => {
if (rtk5req.getAttribute('required')) {
rtk5req.removeAttribute('required');
} else {
rtk5req.getAttribute('required', 'required');
}
});
}
rtk5req.addEventListener('change', rtk5Required());
document.addEventListener('DOMContentLoaded', rtk5Required);
rtk5Required();
Updated code: Removed the repetitive change call
var rtk5req = document.getElementsByClassName('rtk5ReqField');
function rtk5Required() {
if (rtk5req.getAttribute('required')) {
rtk5req.removeAttribute('required');
} else {
rtk5req.getAttribute('required', 'required');
}
}
rtk5req.addEventListener('change', rtk5Required());
document.addEventListener('DOMContentLoaded', rtk5Required);
rtk5Required();
Updated code #2:
Thanks all for all the hard work, there's one small issue that I'm still experiencing and had to make some tweaking - When I uncheck the checkbox, it doesn't remove the required tag placed on rtk5Declaration from which it did in the jQuery.
var rtk5_selection = document.getElementById('rtk5');
document.addEventListener('DOMContentLoaded', () => {
rtk5_selection.addEventListener('change', () => {
if (rtk5_selection.getAttribute('required')) {
document.getElementById('rtk5Declaration').removeAttribute('required');
} else {
document.getElementById('rtk5Declaration').setAttribute('required', 'required');
}
});
});
Thanks so much all!

Since you only have one element you should be using its ID instead of its class, and avoiding the complication caused by document.getElementsByClassName returning a pseudo-array of elements instead of a single element.
NB: use setAttribute to change an attribute's value, or better yet (as shown in the code below) use the direct boolean property that mirrors the element's attribute.
document.addEventListener('DOMContentLoaded', () => {
const rtk_sel = document.getElementById('rtk5');
const rtk_dec = document.getElementById('rtk5Declaration');
rtk_sel.addEventListener('change', () => {
rtk_dec.required = !rtk_sel.checked;
});
});

Thanks all for the contribution, below is the working version which I have tweaked:
var rtk5_selection = document.getElementById('rtk5');
var rtk5declaration = document.getElementById('rtk5Declaration');
function rtd3Declaration() {
if (!rtk5_selection.checked) {
rtd3declaration.removeAttribute('required');
} else {
rtd3declaration.setAttribute('required', 'required');
}
}
rtk5_selection.addEventListener('change', rtd3Declaration);
document.addEventListener('DOMContentLoaded', rtd3Declaration);
rtd3Declaration();

Related

How do I apply localStorage to select options AND classList.add?

Note: I'm using vanilla JS only.
My select options trigger a classList.add like so
CSS:
body{font-family:arial}
body.uppercase {text-transform:uppercase}
body.comicsans {font-family:comic sans ms}
HTML:
<select class="option" id="test" name="test" onchange="selectFonts(this)">
<option value="arial">Arial</option>
<option value="uppercase">Uppercase</option>
<option value="comicsans">Comic sans (dyslexic friendly)</option>
Script (#1):
function selectFonts(element) {
const a = element.options[element.selectedIndex].value;
if(a == "arial") {
[].map.call(document.querySelectorAll('body'), function(el) {
el.classList.remove("uppercase","comicsans"); el.classList.add('arial');});
}
if(a == "uppercase") {
[].map.call(document.querySelectorAll('body'), function(el) {
el.classList.remove("comicsans","arial");el.classList.add("uppercase"); });
}
if(a == "comicsans") {
[].map.call(document.querySelectorAll('body'), function(el) {
el.classList.remove("uppercase","arial");el.classList.add('comicsans'); });
}
}
All works fine. Now I've been trying to add localStorage so the options and added class remain on refresh by adding the following script.
Script (#2)
document.getElementById("test").onchange = function() {
localStorage['test'] = document.getElementById("test").value;}
window.onload= function() {
if(localStorage['test'])
document.getElementById("test").value = localStorage['test']; }.
Script #2 works, the selected option remains the same on refresh, however it disables Script #1 - the classes are not added.
To clarify, both scripts work when used individually, just not together. #2 cancels out #1.
Is there a way to have them both work together?
Here's a working jsfiddle with Script #1 (adding classes)
and a jsbin of Script #2 (localstorage)
Change event is not firing when you are setting the value for select. The value in the select is getting updated but the styles are not applies.
Instead of having two functions you can set the selected value to localstorage in Script(#1) and split the selectFonts function into to one for getting the selected value and another for setting the styles. Hope below code helps.
function selectFonts(element) {
const a = element.options[element.selectedIndex].value;
setStyles(a);
localStorage['test'] = a;
}
function setStyles(a) {
if(a == "arial") {
[].map.call(document.querySelectorAll('body'), function(el) {
el.classList.remove("uppercase","comicsans"); el.classList.add('arial');});
}
if(a == "uppercase") {
[].map.call(document.querySelectorAll('body'), function(el) {
el.classList.remove("comicsans","arial");el.classList.add("uppercase"); });
}
if(a == "comicsans") {
[].map.call(document.querySelectorAll('body'), function(el) {
el.classList.remove("uppercase","arial");el.classList.add('comicsans'); });
}
}
window.onload= function() {
if(localStorage['test'])
document.getElementById("test").value = localStorage['test'];
setStyles(localStorage['test'])
};
The problem is very simple. Every HTML element (and its corresponding DOM element) can have only one of each property, including handler properties, like onchange.
So your second script essentially overwrites the select element's onchange, and your first script stops working.
That's where .addEventListener() comes in. That's a method that appends event handlers - without overwriting the others:
//Await the DOM to be loaded - just like window.onload, but triggers faster
document.addEventListener('DOMContentLoaded', ()=>{
//Note that it's change, not onchange:
document.getElementById("test").addEventListener('change', selectFonts)
})
Redesign selectFonts to make it work in this case:
//I've simplified you code a bit...
function selectFonts(){
const element = document.getElementById("test")
const selected = element.options[element.selectedIndex].value;
//We do the same things for each value - so, let's simplify:
//Remove all classes
for(const font of ['arial', 'uppercase', 'comicsans'])
document.body.classList.remove(font)
//Add the active class
document.body.classList.add(selected)
}
And your second code:
document.addEventListener('DOMContentLoaded', ()=>{
document.getElementById("test").addEventListener('change', function() {
const element = document.getElementById("test")
const selected = element.options[element.selectedIndex].value;
window.localStorage.setItem('test', selected)
})
const selected = window.localStorage.getItem('test')
if(selected){
for(const elem of document.getElementById("test").options){
if(selected === elem.value){
elem.selected = true
break
}
}
document.body.classList.add(selected)
}
})
See it live on codepen.io

How to convert this jQuery code to plain JavaScript?

I'm trying to get the focus method (touch, mouse or keyboard) on links setting a data-attribute.
$(function() {
var i,
r = document.getElementsByTagName("a")[0];
if ("ontouchstart" in window) {
document.addEventListener("touchstart", function(event) {
i = "touch";
}, true);
}
else {
document.addEventListener("mousedown", function(event) {
i = "mouse";
}, true);
}
document.addEventListener("keydown", function(event) {
i = "keyboard";
}, true);
})
The problem is that I only get results writing the last part in jQuery:
$("a").focus(function() {
$(this).attr("data-focus-method", i)
})
$("a").blur(function() {
$(this).removeAttr("data-focus-method")
})
And I want to write the whole code in plain JavaScript. I've tried the way below:
r.addEventListener("focus", function() {
r.setAttribute("data-focus-method", i);
});
r.addEventListener("blur", function() {
r.removeAttribute("data-focus-method");
});
But it doesn't work.
Can somebody help me please?
I suggest to use querySelectorAll method and use forEach to iterate the nodelist
document.querySelectorAll("a").forEach((link) => {
link.addEventListener("focus", function() {
this.setAttribute("data-focus-method", i);
});
link.addEventListener("blur", function() {
this.removeAttribute("data-focus-method");
});
});
I'm not sure why you're trying to override the i on method whenever a key is pressed on the keyboard. However, I'm assuming that's the desired effect since you don't mention it in your question.
That said, here's something to get you closer to your goal of a vanilla JS version of the code.
The answer uses the spread operator to convert the nodeList you get from getElementsByTagName to an array and then forEach() to loop through the array items. For each item, we add two event listeners. one for focus, and one for blur.
When focused, we add the attribute. When blurred we remove the attribute. I opted for set attribute and remove attribute, but you can also use dataset if you want.
To determine what i (the method) is, I used let and a ternary operator.
I'm still not sure why you want to override the method when a key is pressed on the keyboard so I left that. I can improve that if you let me know what the desired effect is.
let i = ("ontouchstart" in window) ? "touch" : "mouse";
document.addEventListener("keydown", () => {
i = "keyboard"
})
const links = [...document.getElementsByTagName("a")];
links.forEach(link => {
link.addEventListener("focus", () => {
link.setAttribute('data-focus-method', i);
});
link.addEventListener("blur", () => {
link.removeAttribute('data-focus-method');
})
})
My link 1
My link 2
My link 3
I got the solution adding:
var w;
for (w = 0; w < r.length; w++) {
r[w].addEventListener("focus", function() {
this.setAttribute("data-focus-method", i);
});
r[w].addEventListener("blur", function() {
this.removeAttribute("data-focus-method");
});
}
By the way, thanks for everyone who helped me!

JQuery call popup

I am an Apprentice and never worked with Javascript.
My Javascript function calls a popup. This works on the first button but doesn't work on all the following and since the application is constantly adding buttons(same class) I cannot hardcode. I guess ther will be a solution with JQuery...
("open") and ("openPopupUseExisting") are the two buttons.
<script type="text/javascript">
window.onload = function () {
document.getElementById('blackout').addEventListener('click', function () {
document.getElementById('popup').className = "";
document.getElementById('blackout').className = "";
document.getElementById('popupUseExisting').className = "";
}, false);
document.getElementsByClassName("open")[0].addEventListener('click', function () {
document.getElementById('popup').className = 'visable';
document.getElementById('blackout').className = 'visable';
}, false);
document.getElementsByClassName("openPopupUseExisting")[0].addEventListener('click', function () {
document.getElementById('popupUseExisting').className = 'visable';
document.getElementById('blackout').className = 'visable';
}, false);
document.getElementsByClassName("close")[0].addEventListener('click', function () {
document.getElementById('popup').className = "";
document.getElementById('blackout').className = "";
document.getElementById('popupUseExisting').className = "";
}, false);
};
</script>
document.getElementsByClassName("close")[0]
See that 0?
getElementsByClassName returns an array-like object. You are getting the first item off it.
Loop over it with a for loop.
Friend, you don't need to add an entire library just in order to bind dynamically added elements.
You can bind the document for click event, and then check if the clicked element is the one you want. It prevent dynamically added elements to be unbound, since it aims for the entire document.
document.addEventListener('click', function (e) {
if (e.target.classList.contains('blackout')) {
// your fancy magic with .blackout
} else if (e.target.classList.contains('open')) {
// your fancy magic with .open
}
}, false);
If you really want to use jQuery, as you ordered, it's quite simple, use the on method
$('.open').on('click', function(){
// your fancy magic with .open
});

Multiple click handlers for a single element

I've written a few events to handle opening and closing of a snap js drawer. This code below works, but I feel it could be written more efficiently. Any suggestions?
function openMobileMenu() {
event.preventDefault();
snapper.open('left');
$('#btn-menu').off('click', openMobileMenu);
$('#btn-menu').on('click', closeMobileMenu);
}
function closeMobileMenu() {
event.preventDefault();
snapper.close('left');
$('#btn-menu').on('click', openMobileMenu);
$('#btn-menu').off('click', closeMobileMenu);
}
$('#btn-menu').on('click', openMobileMenu);
Make your code modular and your concepts explicit.
You can start by creating a MobileMenu object which encapsulates the logic.
Note: The following code was not tested.
var MobileMenu = {
_snapper: null,
_$button: null,
_direction: 'left',
init: function (button, snapper, direction) {
this._$button = $(button);
this._snapper = snapper;
if (direction) this._direction = direction;
this._toggleSnapperVisibilityWhenButtonClicked();
},
_toggleSnapperVisibilityWhenbuttonClicked: function () {
this._$button.click($.proxy(this.toggle, this));
},
toggle: function () {
var snapperClosed = this._snapper.state().state == 'closed',
operation = snapperClosed? 'open' : 'closed';
this._snapper[operation](this._direction);
}
};
Then in your page you can just do the following to initialize your feature:
var mobileMenu = Object.create(MobileMenu).init('#btn-menu', snapper);
Modularizing your code will make it more maintainable and understandable in the long run, but also allow you to unit test it. You also gain a lot more flexibily because of the exposed API of your component which allows other code to interact with it.
E.g. you can now toggle the menu visibility with mobileMenu.toggle().
Use a variable to keep track of the state:
var menu_open = false;
$("#btn-menu").on('click', function(event) {
event.preventDefault();
if (menu_open) {
snapper.close('left');
} else {
snapper.open('left');
}
menu_open = !menu_open; // toggle variable
});
snap has a .state() method, which returns an object stuffed with properties, one of which is .state.
I think you want :
$('#btn-menu').on('click', function() {
if(snapper.state().state == "closed") {
snapper.open('left');
} else {
snapper.close('left');
}
});
Or, in one line :
$('#btn-menu').on('click', function() {
snapper[['close','open'][+(snapper.state().state == 'closed')]]('left');
});
Also, check How do I make a toggle button? in the documentation.

JQuery prototype not working when traversing

I am using the following (http://jsfiddle.net/mkmurray/drv5w/27/) code to allow me to override the .show() function of a DIV.
<script>
(function ($) {
var _oldShow = $.fn.show;
$.fn.show = function (/*speed, easing, callback*/) {
var argsArray = Array.prototype.slice.call(arguments),
duration = argsArray[0],
easing,
callback,
callbackArgIndex;
// jQuery recursively calls show sometimes; we shouldn't
// handle such situations. Pass it to original show method.
if (!this.selector) {
_oldShow.apply(this, argsArray);
return this;
}
if (argsArray.length === 2) {
if ($.isFunction(argsArray[1])) {
callback = argsArray[1];
callbackArgIndex = 1;
} else {
easing = argsArray[1];
}
} else if (argsArray.length === 3) {
easing = argsArray[1];
callback = argsArray[2];
callbackArgIndex = 2;
}
return $(this).each(function () {
var obj = $(this),
oldCallback = callback,
newCallback = function () {
if ($.isFunction(oldCallback)) {
oldCallback.apply(obj);
}
obj.trigger('afterShow');
};
if (callback) {
argsArray[callbackArgIndex] = newCallback;
} else {
argsArray.push(newCallback);
}
obj.trigger('beforeShow');
_oldShow.apply(obj, argsArray);
});
};
})(jQuery);
</script>
I have the following HTML code
<div id="divBeforeHiddenDiv">
foo
</div>
<div id="hiddenDiv" style="display:none">
bar
</div>
And then:
<script>
$('#hiddendiv').bind("beforeShow", function () {
alert("show event successfully overridden");
});
</script>
It works great when I call $('#hiddenDiv').show() but not if I call $('#divBeforeHiddenDiv').next().show() the hidden div containing 'bar' shows but the alert is not displayed.
So why?
UPDATE
This appears to be a jQuery issue as per Bergi's comment. If I use this JSFiddle on jQuery 1.7.1 it works but using jQuery 1.10.1 or any higher version it does not: JSFiddle. Is there a better solution than simply downgrading?
You need to bind the events to the proper elements.
From the example you've given, and what I've interpreted, this piece of code
$('#beforeShow').bind("beforeShow", function () {
alert("show event successfully overridden");
});
Should be
$('#hiddenDiv').bind("beforeShow", function () {
alert("show event successfully overridden");
});
As you want the events to be bound to the hidden div. (or as described in the question, the div right after "#divBeforeHiddenDiv"
You also should change this piece
$('divBeforeHiddenDiv').next().show()
to this
$('#divBeforeHiddenDiv').next().show()
divBeforeHiddenDiv is an ID and in the first code snippet there is no id in the jQuery object.
JSFiddle

Categories

Resources