javascript - avoid repetable code - javascript

I have a page with a button that calls a menu modal. The modal contains two more buttons that call two submenus - one for each button. Watch the pen:
https://codepen.io/t411tocreate/pen/yoxJGO
It actually works. But the current problem is that I re-write a repeatable code to call each submenu:
$('.show-submenu-1').on('click', function () {
$('.submenu-1.offcanvas').addClass('offcanvas--active');
})
$('.show-submenu-2').on('click', function () {
$('.submenu-2.offcanvas').addClass('offcanvas--active');
})
This approach seems to be pretty dumb. I need a solution with less repetition, something like forEach function for arrays:
var menus = [
'.show-submenu-1',
'.show-submenu-2'
];
menus.forEach(function(menu){
$(menu).on('click', function () {
$(`${menu}.offcanvas`).addClass('offcanvas--active');
})
});
Of course, this scenario won't work. How can I make my code DRY?

Use markup:
<div class="submenu" data-index="1">
<div class="submenu" data-index="2">
<button class="show-submenu-button" data-submenu-index="1">
<button class="show-submenu-button" data-submenu-index="2">
Then:
$('.show-submenu-button').on('click', function () {
var index = $(this).attr('data-submenu-index');
$('.submenu[data-index="' + index + '"]').addClass('offcanvas--active');
})
There is little value to using classnames that are so specific that they identify every element on the page individually. Classnames should define a class of elements that behave the same way.

Hi I hope I got the question right but you could use data-attributes for something like this. Just set a general class for .show-submenu and mark their connection to the menus with a number in a data-submenu=x attribute. Where x would be the number in .submenu-x.
And then you do something like this:
Notice that i changed .show-submenu-1 to .show-submenu. Make sure every trigger has this class. Also add a data-submenu=x for every submenu you want to use.
$('.show-submenu').on('click', function () {
var number = $(this).attr("data-submenu");
var selector = '.submenu-' + number + '.offcanvas'
$(selector).addClass('offcanvas--active');
})
So the data-submenu is used to pair the trigger and the modal. This way you can stick to an easy to read html code and a short bit of jquery.

Try this:
var menus = [1, 2];
menus.forEach(index => {
$(`.show-submenu-${index}`).on('click', () => {
$(`.submenu-${index}.offcanvas`).addClass('offcanvas--active');
});
});

You can use this as well.
$('.show-submenu-1, .show-submenu-2').on('click', function (event) {
$(event.target).hasClass('show-submenu-1'){
$('.submenu-1.offcanvas').addClass('offcanvas--active');
}else{
$('.submenu-2.offcanvas').addClass('offcanvas--active');
}
})
it would be better to have your show-submenu-1(as showmenu) and submenu-1(as submenu) in same parent element that allows you to use closest() method and make life easy
for eg:
$('.show-submenu').on('click', function (event) {
$(event.target).closest('.submenu').addClass('offcanvas--active');
})

Related

How can i minimize the code in a class toggler that targets nav?

I love clean code but I'm zero in javascript. I'd love to do two things to the super easy code below:
function nav_open() {
var myNav = document.getElementById('nav_anim');
if (myNav.className == 'nav_closed') {
myNav.className = 'nav_open';
} else {
myNav.className = 'nav_closed';
}
}
Use getElementsByTagName to target the nav instead of giving it a useless id (in order to use only <nav> instead of <nav id="nav_anim"> in the body. I tried some combos but none of them works.
Get rid of that ugly myNav name, is it mandatory? I know I can change it, but can I remove it? Is it possible to use something like
nav.className=='nav_closed' or even better className=='nav_closed' instead of myNav.className=='nav_closed'
I would suggest keeping the id on your nav, targetting your DOM elements using ids or classes is something that is commonly done and can speed up lookup. Using getElementsByTagName() adds unnecessary complexity and would need to traverse your entire DOM to find your element, so it isn't very efficient, espeicially if you just have one nav element. If you really want to, you could use querySelector to select the first nav item:
const myNav = document.querySelector("nav");
At the end of the day though, if you want to interact with elements in your JavaScript code, you'll need to explicitly grab them (not counting named access on the global window object as it is recommended not to use this).
To further improve your toggle code, you could perform two toggles using DOMTokenList.toggle(), one to hide your first class and one to add your other. Every time you run both toggles, they will add/remove both classes depending on whether they exist or not:
const myNav = document.getElementById("nav_anim");
myNav.classList.toggle('nav_closed');
myNav.classList.toggle('nav_open');
See example below:
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
btn.classList.toggle("on");
btn.classList.toggle("off");
});
.on {
background-color: lightgreen;
}
.off {
background-color: red;
}
<button id="btn" class="on">Click me</button>
Depending on your code, you may even be able to remove the nav_closed class by targeting your nav element that does not have the nav_open class:
#nav_anim:not(.nav_open) {
/* nav_closed styles */
}
With this setup, you can use just one toggle:
const myNav = document.getElementById("nav_anim");
myNav.classList.toggle('nav_open');
See example below:
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
btn.classList.toggle("on");
});
.on {
background-color: lightgreen;
}
#btn:not(.on) {
background-color: red;
}
<button id="btn" class="on">Click me</button>
You should be able to do this with a conditional (ternary) operator, like so:
var nav = document.getElementsByTagName("nav")[0];
(nav.classList.contains('nav_open')) ? nav.classList.remove('nav_open') : nav.classList.add('nav_open'));
This is like a 1 line if statement with 3 parameters:
condition ? exprIfTrue : exprIfFalse

JavaScript - querySelectorAll and lower/uppercase

I have a problem with querySelectorAll. I use it like this:
Array.prototype.forEach.call(buttons, (btn) => {
this.setItemActive(target);
});
setItemActive(item) {
_.addClass(document.querySelectorAll(`[title="${item}"]`)[0], this.activeClass);
}
And it works, if provided item is written exactly the same as the target value in the said element. But I use text-transform on my buttons and it is causing a problem. I tried adding toLowerCase() to both, but this didn't help. What can I do?
As for me - title is not very comfortable to use in such situations. Think about a kind of identificator.
Proposition:
in your HTML code, make additional attr, kind of :
<a title="WrIght As You Wish" data-title="strictlowercasetitle" >Your info with title</a>
and in your JS :
Array.prototype.forEach.call(buttons, (btn) => {
this.setItemActive(target);
});
setItemActive(item) {
// make item lower case
_.addClass(document.querySelectorAll(`[data-title="${item.toLowerCase()}"]`)[0], this.activeClass);
}
I tried to setItemActive function and wrote this below function.
Hope this will be helpful to you
function _setItem(item){
console.log(item)
var m = document.querySelectorAll('a[title='+item+']')[0];
console.log(m)
}
_setItem("WrIght");
Check this jsFiddle

Making part of an Id name into a variable

I have a bunch of divs with matching ids (#idA_1 and #idB_1, #idA_2 and #idB_2, etc). In jquery I wanted to assign click functions, so that when I click an #idA it will show and hide an #idB.
Basically I want to make this:
$(".idA_x").click(function(){
$("idB_x").toggleClass("hide")
});
X would be a variable to make #idA and #idB match. I could write each individually, but that would take too much code, is there a way to make the number in the id into a variable?
Sure, you can do:
var num = 13;
addButtonListener(num);
function addButtonListener(num){
$("#idA_"+num).click(function(){
$("#idB_"+num).toggleClass("hide")
});
}
Try JQuery solution :
var x = 1;
$(".idA_" + x ).click(function(){
$(".idB_" + x ).toggleClass("hide")
});
Hope this helps.
There are many ways to achieve that, but what you probably want is to create a shared CSS class, e.g. .ids, and bind the event listener to that one:
$('.ids').click(function () {
//...
});
Then you can handle your logic in a cleaner way within the function body.
In order to make it dynamic, and not have to repeat the code for each one of your numbers, I suggest doing as follows:
First, add a class to all the div's you want to be clickable .clickable, and then use the id of the clicked event, replacing A with B in order to select the element you what to toggle the class:
$(".clickable").click(function(){
var id = $(this).attr('id');
$("#" + id.replace('A', 'B')).toggleClass("hide");
});
Or, you can also select all divs and use the contains wildcard:
$("div[id*='idA_']").click(function(){
var id = $(this).attr('id');
$("#" + id.replace('A', 'B')).toggleClass("hide");
});
This solution won't have the need to add a class to all clickable divs.
You can use attribute selector begins with to target the id's you want that have corresponding elements.
https://api.jquery.com/attribute-starts-with-selector/
Then get the value after the understore using split on the id and applying Array.pop() to remove the 1st part of the array.
http://jsfiddle.net/up9h0903/
$("[id^='idA_']").click(function () {
var num = this.id.split("_").pop();
$("#idB_" + num).toggleClass("hide")
});
Using regex would be your other option to strip the number from the id.
http://jsfiddle.net/up9h0903/1/
$("[id^='idA_']").click(function () {
var num = this.id.match(/\d+/g);
$("#idB_" + num).toggleClass("hide")
});

How do you create variables to open up the same type of div?

I am making a website that displays profiles of people. Each person is designated a svg button and when that button is clicked, a pop up displays that persons information.
I have this jquery function:
$('.button1').click(function() {
$('.person1-profile').fadeIn();
});
$('.button1-exit').click(function() {
$('.person1-profile').fadeOut();
});
$('.button2').click(function() {
$('.person2-profile').fadeIn();
});
$('.button2-exit').click(function() {
$('.person2-profile').fadeOut();
});
$('.button3').click(function() {
$('.person3-profile').fadeIn();
});
$('.button3-exit').click(function() {
$('.person3-profile').fadeOut();
});
I'm wondering if it is possible to do this with Javascript so that it significantly shortens the coding, and rather than copy & pasting that code every time for each person, if variables can be made for people/profile and so it would be something like:
$('var person + button').click(function() {
$('var person + profile').fadeIn();
});
$('var button + exit').click(function() {
$('var person + profile').fadeOut();
});
Thank you I really appreciate it! Sorry if it is unclear.
You could use data-attributes for this one:
Define your buttons like that:
<button class="openButton" data-person="3">Open</button>
<button class="closeButton" data-person="3">Close</button>
And your open/close-code like that:
$('.openButton').click(function() {
var personNumber = $(this).attr("data-person");
$('.person'+personNumber+"-profile").fadeIn();
});
$('.closeButton').click(function() {
var personNumber = $(this).attr("data-person");
$('.person'+personNumber+"-profile").fadeOut();
});
In action: http://jsfiddle.net/ndx4fn9n/
I can think of few ways of doing it.
You could read only 7th character of the class name. This limits you to having only 10 fields. Or you could put id on very end like this person-profile1 and read 16th and up character.
You could also set up additional tag to your container. But this will cause your web page to not HTML validate.
<div class="person" personid="1">// content</div>
You can do this in your selector:
var buttons = document.getElementsByTagName(svgButtonSelector);
for (i = 0; i > buttons.length; i++) {
$(".button" + index).click(function() {
$(".person" + index + "-profile").fadeIn();
});
}
This will attach the event to every svg button you've got on your page. You just gotta make sure the scope of selection for the buttons is declared right (I'm using document as an example).

How can I reduce the redundancies in my jQuery code?

The size of my JavaScript file is getting out of hand because I have hundreds of links, and each one has its own jQuery function even though they all peform basically the same task.
Here's a short excerpt:
$("#link1").click(function ()
{
$(".myDiv").hide();
$("#myDiv1").toggle();
});
$("#link2").click(function ()
{
$(".myDiv").hide();
$("#myDiv2").toggle();
});
$("#link3").click(function ()
{
$(".myDiv").hide();
$("#myDiv3").toggle();
});
Would there be a way to abstract some of this logic so that I have only a single function instead of hundreds that do the same thing?
You can add a class to all the links that do the same thing and act with jQuery on that class.
<a href='whatever' id='link_1' class='toggler'>text</a>
<a href='whatever' id='link_2' class='toggler'>text</a>
jQuery code will be:
$(".toggler").click( function(){
// toggle the divs
var number = $(this).attr("id").split('_')[1];
$(".myDiv").hide();
$("#myDiv"+ number).toggle();
});
The general approach that I use is to use the traversal methods to find related elements rather than using absolute selectors. This will allow you to apply the same code to elements that are similarly configured without any complicated dependencies on the format of the ids, etc. Done correctly it's also reasonably robust against minor changes to the mark up.
For example, say I have a series of links, each followed by a div that will be toggled by clicking on that link. The links each have a particular class so they can easily be referenced.
Toggle
<div>
Some content...
</div>
Toggle
<div>
Other content
</div>
I would then find all the links by class, then use the next method to find the associated div and toggle it's visibility. Note that this is a simple example. You may need to use more complicated traversal mechanisms and filter by element type or class, too, depending on your exact mark up.
$('.linkClass').click( function() {
$(this).next().toggle();
});
What about adding the ID of your target into the href of the link?
<a id="link1" href="#myDiv1" class="toggle">Toggle 1</a><br/>
<a id="link2" href="#myDiv2" class="toggle">Toggle 2</a><br/>
<a id="link3" href="#myDiv3" class="toggle">Toggle 3</a><br/>
Then you could write a single function like so:
$(".toggle").click(function(e) {
e.preventDefault();
$(".myDiv").hide();
$($(this).attr('href')).toggle();
});
Or another approach I've used:
$(".toggle").each(function(i) {
$(this).click(function(e) {
e.preventDefault();
$(".myDiv").hide();
$(".myDiv:eq("+i+")").toggle();
});
});
This one is in the same vein as tvanfosson's idea, using some sort of DOM relationship to link the elements, in this case by assuming that the link elements and the div elements are in the same order on the page.
You can just have each click call an external function and pass in a parameter for the number string.
Ex:
$("#link1").click(toggle("1"));
$("#link2").click(toggle("2"));
function toggle(number) {
$(".myDiv").hide();
$("#myDiv"+number).toggle();
}
function makeToggler(number) {
$('#link' + number).click(function() {
$('.myDiv').hide();
$('#myDiv' + number).toggle();
});
}
makeToggler(1);
makeToggler(2);
makeToggler(3);
You can adapt this to meet your naming standards.
Depending on the structure of your divs and links, there are better ways to do it. If you post the structure of your elements, I'll show you one.
I think this is a simple refactoring
you could define a function as such
function doSomethingTo(thisDiv)
{
$(".myDiv").hide();
$(thisDiv).toggle();
}
and then just reuse it where you need it
$("#link1).click(doSomethingTo(thisDiv));
$("#link2).click(doSomethingTo(thisDiv));
Building on Craig's solution:
$("#link1, #link2").click(toggle(this));
function toggle(obj) {
$(".myDiv").hide();
$("#myDiv" + $(obj).attr("id").replace('link','')).toggle();
}
I change the link become like this (i rename the id to just a number)
<a href='#test1' id='1' class='link'> ... </a>
<a href='#test2' id='2' class='link'> ... </a>
<a href='#test3' id='3' class='link'> ... </a>
and then on js:
$(document).ready(function(){
$('.link').click(function(){
$('.myDiv').hide();
var id = $(this).attr('id'); // take the id
$('#myDiv'+id).toggle();
});
});
throw your makeToggle into a loop?
function makeToggler(number) {
$('#link' + number).click(function() {
$('.myDiv').hide();
$('#myDiv' + number).toggle();
});
}
for(i=1;i>=#;i++) {makeToggler(i);}
then you could even have it count your links for you, something link this?:
function countElementsByClass(className){
var count = 0;
var o = document.getElementsByTagName("a").className;
for(var i=0;i<o.length;i+){
if(o[i].className == "accordion/whatever")
count ++;
}
return count;
}
credit: building on SLaCKS solution

Categories