Rewrite the code in a loop - javascript

I'm just learning JS and jQuery, so I can not reduce the normal code is shown below:
var menuBtn = '#menu',
classMenuOpen = 'side_menu_open',
aboutBtn = '#about',
classAboutOpen = 'side_about_open',
dateBtn = '#date',
classDateOpen = 'side_date_open',
closeBtn = '.header__menu a, .close';
// Menu Side
$(menuBtn).on('click', function() {
$('html').toggleClass(classMenuOpen);
});
$(closeBtn).not(menuBtn).on('click', function() {
$('html').removeClass(classMenuOpen);
});
// About Side
$(aboutBtn).on('click', function() {
$('html').toggleClass(classAboutOpen);
});
$(closeBtn).not(aboutBtn).on('click', function() {
$('html').removeClass(classAboutOpen);
});
// Date Side
$(dateBtn).on('click', function() {
$('html').toggleClass(classDateOpen);
});
$(closeBtn).not(dateBtn).on('click', function() {
$('html').removeClass(classDateOpen);
});
I would like to write a loop (example below) , but knowledge is not enough. I hope someone can help, thanks in advance ;)
['menu', 'about', 'date'].forEach((selector) => {
$('.' + selector + ' .scrollbar-inner').scrollbar({
onScroll: function(y, x){
$('.' + selector + ' .scrollbar-inner').toggleClass('scroll-shadow', y.scroll >= 5);
}
});
});

maybe something like this:
// wrap in IIFE for scope containment
(function($) {
// Setup button keys
const buttons = ['menu', 'about', 'date'];
// click handler
const createClickHandler = value => {
// set loop variables
let selector = `#${value}`
, className = `side_${value}_open`;
// create event triggers
$(selector).on('click', e => $('html').toggleClass(className));
$('.header__menu a, .close').not(selector).on('click', e => $('html').removeClass(className))
};
// iterate keys and apply handler method
buttons.forEach(createClickHandler);
})(jQuery);

Here is the loop you are looking for!
In the forEach() loop you are looping through the array of strings, component holds the string element (so here 'menu', 'about', etc...). Then inside the loop's body set two variables:
selector is the selector string
classOpen is the class name of an element you have associated with the component
Then you basically write the same code using only those two variables instead of writing the code three times with set strings.
let closeBtn = '.header__menu a, .close'
['menu', 'about', 'date'].forEach(function(component) {
let selector = '#' + component;
let classOpen = '.side_' + component + '_open';
$(selector).on('click', function() {
$('html').toggleClass(classOpen);
});
$(closeBtn).not(selector).on('click', function() {
$('html').removeClass(selector);
});
});

Related

How can I loop variables with Jquery

I'm new with Javascript and Jquery and I'm facing a small problem.
I'm trying to make sure that if a given link exists, hovering over this link will bring up a popup with the fadeToggle().
So I wrote this code that works:
if ($('.link-1')) {
$('.link-1').mouseover(function () {
$('.popup-1').fadeToggle();
})
.mouseout(function () {
$('.popup-1').fadeToggle();
})
}
But, instead of repeating it ten times, I wanted to write a loop, like this:
var number = 0;
while (number < 10) {
var popup = '.popup-' + number;
var link = '.link-' + number;
if ($(link)) {
$(link).mouseover(function () {
$(popup).fadeToggle();
})
.mouseout(function () {
$(popup).fadeToggle();
})
}
number++;
}
But it does not work. Could you help me please ?
I thank you in advance !
Based on your comments, I'd recommend this approach.
Add a data attribute to each link that corresponds with the popup you want to fire. This will look something like this:
<a href='#' class='link-1' data-popup='popup-1'> Link </a>
Then add a hover event to ALL links, that performs an action if it has the data type:
//hover event on all links(assumes anchor tags)
$('a').mouseover(function () {
if ($(this).attr('data-popup')) {
let popup = '.' + $(this).attr('data-popup');
$(`${popup}`).fadeToggle();
}})
.mouseout(function () {
if ($(this).attr('data-popup')) {
let popup = '.' + $(this).attr('data-popup');
$(`${popup}`).fadeToggle();
}})
You could also make this a single line function using .hover instead of .mouseover and .mouseout if it fits your use case
**refactoring process is added here:
//start with the original function
$('a').hover(function () {
if ($(this).attr('data-popup')) {
let popup = '.' + $(this).attr('data-popup');
$(`${popup}`).fadeToggle();
}})
//consolidate the enter and exit events using .hover()
$('a').hover(function () {
if ($(this).attr('data-popup')) {
let popup = '.' + $(this).attr('data-popup');
$(`${popup}`).fadeToggle();
}})
//remove the if statement, because the function firing without a pop up won't result in any effect
$('a').hover(function () {
let popup = '.' + $(this).attr('data-popup');
$(`${popup}`).fadeToggle();
})
//substitute the variable directly into the jquery tag
$('a').hover(function () {
$(`'.${$(this).attr('data-popup')}`).fadeToggle();
})
// use an ES6 arrow function to make this a one line function
$('a').hover(() => $(`.${$(this).attr('data-popup')}`).fadeToggle())
//as is, this function won't work, because the arrow function binds the "this" keyword differently.
//Event handlers have an optional parameter that is an event JSON object, so we pass that into the function.
//Because it is a parameter, and is used as a variable we can call event "e" for short
//target is a property of the JSON object 'event' that indicates what specific element is triggering the event
// You can console log "e" to see what other values are baked into the event
$('a').hover((e) => $(`.${$(e.target).attr('data-popup')}`).fadeToggle())
//lastly, because we are using an anonymous arrow function with only one parameter, we can omit the parenthesis around the paremeter
$('a').hover(e => $(`.${$(e.target).attr('data-popup')}`).fadeToggle())
The end result is the one liner below!
$('a').hover(e => $(`.${$(e.target).attr('data-popup')}`).fadeToggle())
Additional info on data attributes can be found here:
https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
welcome to the web community :-)
My jQuery skills are a bit rusty, but I recall, that there is an Attribute Contains Selector, which you could combine with .each() like so:
$('[class*="link-"]').each(function (index, link) {
$('[class="popup-"' + index + '"]').each(function (_, popup) {
$(link)
.mouseover(function () {
$(popup).fadeToggle();
})
.mouseout(function () {
$(popup).fadeToggle();
})
}
}
The second index is not interesting, that's why I named the argument „_”.
Let me know, whether it still works
If your objects are in order from link-1 to link-10, you can try this method
Loop object that has class "link-[number]" using each function
save number using index + 1
give action to object that have been hovered
so the code will be like this:
$('[class*="link-"]').each(function (index) {
var number = index + 1; //index start from 0, so it need to add + 1
$(this)
.mouseover(function () {
$('[class="popup-' + number+ '"]').fadeToggle();
})
.mouseout(function () {
$('[class="popup-' + number+ '"]').fadeToggle();
})
});
But if your object are not in order from link-1 to link-10, I recommend to use custom data attribute in your HTML code.
Example:
<a class="link-1" data-number="1">test 1</a>
<div class="popup-1" style="display:none">
test 1 popup
</div>
Then change number variable to this code:
var number = $(this).attr("data-number");
It will more save.
Hope it helps.

Javascript loop not creating jquery controls correctly

I would like to replace:
var linuxControls = ["CreateLinuxUser", "EditLinuxUser", "DeleteLinuxUser", "Export"];
$("#dlgCreateLinuxUser").dialog({autoOpen: false});
$("#btnCreateLinuxUser").click(function () {
$("#dlgCreateLinuxUser").dialog("open");
});
$("#dlgEditLinuxUser").dialog({
autoOpen: false
});
$("#btnEditLinuxUser").click(function () {
$("#dlgEditLinuxUser").dialog("open");
});
$("#dlgDeleteLinuxUser").dialog({autoOpen: false});
$("#btnDeleteLinuxUser").click(function () {
$("#dlgDeleteLinuxUser").dialog("open");
});
$("#dlgExport").dialog({autoOpen: false});
$("#btnExport").click(function () {
$("#dlgExport").dialog("open");
});
with:
for (i = 0; i < linuxControls.length; i++) {
var strDlg = "#dlg" + linuxControls[i];
var strBtn = "#btn" + linuxControls[i];
$(strDlg).dialog({autoOpen: false});
$(strBtn).click(function () {
$(strDlg).dialog("open");
});
}
However it is only creating the last control "Export." As looping constructs and string building goes, it all looks fine. Is there something weird with jquery that is preventing this?
Use a closure loop so that i won't be changed at runtime, can do that with jQuery each.
$.each(linuxControls, function(i) {
var strDlg = "#dlg" + linuxControls[i];
var strBtn = "#btn" + linuxControls[i];
$(strDlg).dialog({autoOpen: false});
$(strBtn).click(function () {
$(strDlg).dialog("open");
});
});
Since you're jQuery already why not try something similar to this:
$('.linux-control').click(function() {
$('#' + $(this).data('dialog')).dialog("open");
});
<button type="button" id="btnCreateLinuxUser" class="linux-control" data-dialog="dlgCreateLinuxUser">Create User</button>
Edit
The above snipped binds .click() to all .linux-control classes. It then looks for the data-dialog attribute and creates a jQuery selector to open your dialog. In the instance above the button contains dlgCreateLinuxUser in the data-dialog attribute of the element. No loops needed since the .click() function is bound only to element that fires it but listens for all elements with the class .linux-control.

Problems to get functions argument outside the scope

I want to get functions argument outside the scope, but have some kinds of problems to do that.
I have tried to get the value by this code: var text = $.commentString.text();
Live Demo
JQuery:
function addComment(commentString) {
var container = $('#divComments');
var inputs = container.find('label');
var id = inputs.length + 1;
var div = $('<div />', {
class: 'CommentStyle'
});
$('<label />', {
id: 'comment' + id,
text: commentString
}).appendTo(div);
var $edit = $('<p />', {
class: 'edit',
text: 'Edit'
}).addClass('edit').appendTo(div);
div.appendTo(container);
}
$('#divComments').on('click','.edit',function () {
var text = $.commentString.text();
});
In JavaScript it's not possible to get access to a variable outside of the scope which it was declared in. You could declare the variable outside of your function, but it will be overwritten every time the function runs - it doesn't look like this is the behaviour you're after.
What you can do in this example is use jQuery to traverse the DOM and find the text which you require.
$('#divComments').on('click','.edit',function () {
var text = $(this).parents(".CommentStyle").find("label").text();
});
then just declare a variable outside the function:
var someCommentString;
function addComment(commentString) {
someCommentString = commentString;
...
or u can try other way to get value but not sure its correct or not
$('#divComments').on('click','.edit',function () {
$(this).prev().text()
});

jQuery switching between more than two classes

I've already posted a question about jQuery toggle method here
But the problem is that even with the migrate plugin it does not work.
I want to write a script that will switch between five classes (0 -> 1 -> 2 -> 3 -> 4 -> 5).
Here is the part of the JS code I use:
$('div.priority#priority'+id).on('click', function() {
$(this).removeClass('priority').addClass('priority-low');
});
$('div.priority-low#priority'+id).on('click' ,function() {
$(this).removeClass('priority-low').addClass('priority-medium');
});
$('div.priority-medium#priority'+id).on('click', function() {
$(this).removeClass('priority-medium').addClass('priority-normal');
});
$('div.priority-normal#priority'+id).on('click', function() {
$(this).removeClass('priority-normal').addClass('priority-high');
});
$('div.priority-high'+id).on('click', function() {
$(this).removeClass('priority-high').addClass('priority-emergency');
});
$('div.priority-emergency'+id).on('click', function() {
$(this).removeClass('priority-emergency').addClass('priority-low');
});
This is not the first version of the code - I already tried some other things, like:
$('div.priority#priority'+id).toggle(function() {
$(this).attr('class', 'priority-low');
}, function() {
$(this).attr('class', 'priority-medium');
}, function() {
...)
But this time it only toggles between the first one and the last one elements.
This is where my project is: strasbourgmeetings.org/todo
The thing is that your code will hook your handlers to the elements with those classes when your code runs. The same handlers remain attached when you change the classes on the elements.
You can use a single handler and then check which class the element has when the click occurs:
$('div#priority'+id).on('click', function() {
var $this = $(this);
if ($this.hasClass('priority')) {
$this.removeClass('priority').addClass('priority-low');
}
else if (this.hasClass('priority-low')) {
$this.removeClass('priority-low').addClass('priority-medium');
}
else /* ...and so on... */
});
You can also do it with a map:
var nextPriorities = {
"priority": "priority-low",
"priority-low": "priority-medium",
//...and so on...
"priority-emergency": "priority"
};
$('div#priority'+id).on('click', function() {
var $this = $(this),
match = /\bpriority(?:-\w+)?\b/.exec(this.className),
current = match && match[0],
next = nextPriorities[current];
if (current) {
$this.removeClass(current).addClass(next || 'priority');
}
});
[edit: working demo]
Assuming you have 'priority' as the default class already on the element at the initialization phase, this will cycle through the others:
$('div#priority' + id)
.data('classes.cycle', [
'priority',
'priority-low',
'priority-medium',
'priority-normal',
'priority-high',
'priority-emergency'
])
.data('classes.current', 0)
.on('click', function () {
var $this = $(this),
cycle = $this.data('classes.cycle'),
current = $this.data('classes.current');
$this
.removeClass(cycle[current % cycle.length])
.data('classes.current', ++current)
.addClass(cycle[current % cycle.length]);
});
I have tried myself to do this with the sole help of toggleClass() and didn't succeeded.
Try my method that declares an array with your five classes and toggles dynamically through
them.Do adapt to your own names.
//variable for the classes array
var classes=["one","two","three","four","five"];
//add a counter data to your divs to have a counter for the array
$('div#priority').data("counter",0);
$(document).on('click','div#priority',function(){
var $this=$(this);
//the current counter that is stored
var count=$this.data("counter");
//remove the previous class if is there
if(($this).hasClass(classes[count-1])){
$(this).removeClass(classes[count-1]));
}
//check if we've reached the end of the array so to restart from the first class.
//Note:remove the comment on return statement if you want to see the default class applied.
if(count===classes.length){
$this.data("counter",0);
//return;//with return the next line is out of reach so class[0] wont be added
}
$(this).addClass(classes[count++]);
//udpate the counter data
$this.data("counter",count);
});
//If you use toggleClass() instead of addClass() you will toggle off your other classes.Hope is a good answer.

Call Javascript Function with argument and use argument as a variable name

I've the following snip of a code:
var about = "about.html";
function loadPage(target){
$("#dashboard").load(target);
}
$(".nav li").click(function(){
loadPage($(this).attr("class"));
});
So when I click on a button like <li class="about">, target is = about.
But in that way, $("#dashboard").load(target); doesn't load the variable about which is the html-file which I want to load.
So how is it possible to call the variable in this way?
You seem to miss the .html part. Try with
$("#dashboard").load(target+'.html');
But, supposing you have only one class on your li element, you'd better use this.className rather than $(this).attr("class").
EDIT :
if you want to use your about variable, you may do this :
$("#dashboard").load(window[target]);
But it would thus be cleaner to have a map :
var pages = {
'about': 'about.html',
'home': 'welcome.jsp'
}
function loadPage(target){
$("#dashboard").load(pages[target]);
}
$(".nav li").click(function(){
loadPage(this.className);
});
A stupid answer : create a <a> tag, and set its href attribute to the correct value.
Otherwise :
A standard way to store key: values pairs in javascript is to use a plain object :
var urls = {};
urls['about'] = 'mysuperduperurlforabout.html';
function loadPage(target) {
var url = urls[target];
//maybe check if url is defined ?
$('#dashboard').load(url);
}
$(".nav li").click(function(){
loadPage($(this).attr("class") + ".html");
});
or
$("#dashboard").load(target+".html");
You can call the variables like this (if that's what you asked):
var test = 'we are here';
var x = 'test';
console.log(window[x]);
It's similar to the $$ in PHP. The output will be:
we are here in the console window.
You could put the "about" as an object or array reference similar to:
var pageReferences = [];
pageReferences["about"] = "about.html";
var otherReference = {
"about": "about.html"
};
function loadPage(target) {
alert(pageReferences[target]);
alert(otherReference[target]);
$("#dashboard").load(target);
}
$(".nav li").click(function () {
loadPage($(this).attr("class"));
});
Both of these alerts will alert "about.html" referencing the appropriate objects.
EDIT: IF you wished to populate the object based on markup you could do:
var otherReference = {};
$(document).ready(function () {
$('.nav').find('li').each(function () {
var me = $(this).attr('class');
otherReference[me] = me + ".html";
});
});
You could even store the extension in an additional attribute:
var otherReference = {};
$(document).ready(function () {
$('.nav').find('li').each(function () {
var me = $(this).attr('class');
otherReference[me] = me + "." + $(this).attr("extension");
});
});
Better would be to simply put the page reference in a data element:
<li class="myli" data-pagetoload="about.html">Howdy</li>
$(".nav li").click(function () {
loadPage($(this).data("pagetoload"));
});

Categories