How to shorten these jQuery functions? - javascript

I have these 3 jQuery functions that basically do the same thing, but they are a tiny bit different (width of window, and class that is removed/toggled)
I need the functionality of all these 3 functions, but want to somehow combine them/shorten them into one function. I've tried to but my code keeps breaking
Can anyone help to shorten them?
Here are the 3 functions
jQuery(document).ready(function($) {
$('.exampleimg').click(function() {
$('.about').hide(600);
if (($(window).width() > 670) && ($(this).hasClass('exampleimgopen'))) {
$(this).removeClass('exampleimgopen');
} else if ($(window).width() > 670) {
$('.exampleimg').removeClass('exampleimgopen');
$(this).addClass('exampleimgopen');
}
});
});
jQuery(document).ready(function($) {
$('.exampleimg').click(function() {
$('.about').hide(600);
if (($(window).width() < 670) && ($(this).hasClass('exampleimgopen2'))) {
$(this).removeClass('exampleimgopen2');
} else if ($(window).width() < 670) {
$('.exampleimg').removeClass('exampleimgopen2');
$(this).addClass('exampleimgopen2');
}
});
});
jQuery(document).ready(function($) {
$('.exampleimg').click(function() {
$('.about').hide(600);
if (($(window).width() < 540) && ($(this).hasClass('exampleimgopen3'))) {
$(this).removeClass('exampleimgopen3');
} else if ($(window).width() < 540) {
$('.exampleimg').removeClass('exampleimgopen3');
$(this).addClass('exampleimgopen3');
}
});
});

I need the functionality of all these 3 functions, but want to somehow combine them/shorten them into one function.
Generally, a good approach when refactoring similar functions is to create a single factory function that will take your variable data as arguments and return a function that has access to that data via its closure.
function myFactory(conditionFunc, windowWidth, cssClass) {
return function() {
$('.about').hide(600);
var windowCondition = conditionFunc($(window).width(), windowWidth);
if (windowCondition && ($(this).hasClass(cssClass))) {
$(this).removeClass(cssClass);
} else if (windowCondition) {
$('.exampleimg').removeClass(cssClass);
$(this).addClass(cssClass);
}
}
}
Then you can call this function 3 times to build your functions:
// helper methods that will perform '<' or '>' on window width
var lessThan = function(a, b) { return a < b; };
var greaterThan = function(a, b) { return a > b; };
var func1 = myFactory(greaterThan, 670, 'exampleimgopen');
var func2 = myFactory(lessThan, 670, 'exampleimgopen2');
var func3 = myFactory(lessThan, 540, 'exampleimgopen3');
Which you can then pass each into their corresponding listeners.
$('.exampleimg').click(func1);
$('.exampleimg').click(func2);
$('.exampleimg').click(func3);
The advantage of doing things this way is that you only write a single function which then creates different versions of your listener callback functions, based on the data you give to it.

I think is maybe closer to what you wanted, although it's unclear what you wanted to have happen when the width was < 540. Might want to use if .. then ... else instead
jQuery(document).ready(function($) {
$('.exampleimg').click(function() {
var width = $(window).width();
var classes = [];
if (width < 540) {
classes.push('exampleimgopen3');
}
if ($(window).width() < 670) {
classes.push('exampleimgopen2');
}
if ($(window).width() >= 670) {
classes.push('exampleimgopen');
}
classes.forEach(function(class) {
$('.exampleimg').removeClass(class);
if (!$(this).hasClass(class)) {
$(this).addClass(class);
}
});
});
});

Related

Functions only apply to the last div using .each function [duplicate]

This question already has answers here:
Event binding on dynamically created elements?
(23 answers)
Closed 2 years ago.
I've seen similar cases but I can't seem to figure it how on my case.
So I got 3 different divs all having the same class of "display-boxes". What I'm trying to do is to apply my functions for all 3 divs. At the moment it's only applying the function for the last div.
$(".display-boxes").each(function() {
var boxList = $(this).context;
function resizeTeasersTablet(teaserNumber, teaserCollection) {
if (teaserNumber == 1) {
teaserCollection[0].style.width = '73.834%';
teaserCollection[0].style.margin = '0 auto 40px';
for (let i = 1; i < teaserNumber; i++) {
teaserCollection[i].style.width = '25%';
}
} else if (teaserNumber == 2) {
for (let i = 0; i < teaserNumber; i++) {
teaserCollection[i].style.width = '50%';
}
} else if (teaserNumber == 4) {
for (let i = 0; i < teaserNumber; i++) {
teaserCollection[i].style.width = '50%';
}
} else if (teaserNumber == 5) {
for (let i = 0; i < teaserNumber; i++) {
teaserCollection[i].style.width = '50%';
}
} else if (teaserNumber == 3) {
for (let i = 0; i < teaserNumber; i++) {
teaserCollection[i].style.width = '33.33%';
}
}
}
function resizeTeasersMobile(teaserNumber, teaserCollection) {
for (let i = 0; i < teaserNumber; i++) {
teaserCollection[i].style.width = '100%';
}
}
document.addEventListener('DOMContentLoaded', function() {
if (window.innerWidth > 2024) {
displayTeasers(boxList.childElementCount, boxList.children);
} else if (window.innerWidth > 641) {
resizeTeasersTablet(boxList.childElementCount, boxList.children);
} else {
resizeTeasersMobile(boxList.childElementCount, boxList.children);
}
});
window.onresize = function() {
if (window.innerWidth > 2024) {
displayTeasers(boxList.childElementCount, boxList.children);
} else if (window.innerWidth > 641) {
resizeTeasersTablet(boxList.childElementCount, boxList.children);
} else {
resizeTeasersMobile(boxList.childElementCount, boxList.children);
}
};
});
<div id="section2" class="padding-margin-border column">
<div class="lobby-images-wrapper small-block-grid-1 display-boxes ongoingPromo">
<div class="display-box">
<div class="wrapper-lobby-image-item">
<span class="image-wrapper">
<a href="##Field.Teaser_Link##">##MarkComponentField(FieldPath+".Teaser_Image")##
<img src="##Field.Teaser_Image##" data-original="##Field.Teaser_Image##"/>
</a>
</span>
</div>
</div>
</div>
</div>
This is one of the 3 divs that I have since it won't let me post more code.
There are two problems here:
You've set window.onresize 3 times in a loop. Only the last assignment has an effect. You overwrite the previous two handlers. This is why your changes are only applied to your last div.
Document:DOMContentLoaded event is raised when the HTML document is fully loaded and parsed, which means either:
Your JavaScript code is run when the document is not loaded, and so your jQuery selector will not find your divs (it seems that it is not your case). This case happens when you load the code directly in a script at the beginning of the document's body.
Your JavaScript code is run after the document is loaded, so you'll find all your elements, but the event is already fired and your handler is never called. This case happens when you put the code inside an onload handler.
Your code is run after the divs are created but before the document is fully loaded. This case happens if you run the code before the end tag of </body> for example. This is the only case that works as expected. You better not put your code at such a risk! Your code should be robust and reliable.
The Fix
Here is how you can fix your issues (please pay close attention to my comments):
// Define your utility functions at the root level
function resizeTeasersTablet(teaserCollection) {
// No need to pass the collection size. It has a `length` property.
let width = undefined;
switch (teaserCollection.length) { // `swith`/`case` is simpler than those `if`s
case 1:
teaserCollection[0].style.width = '73.834%';
teaserCollection[0].style.margin = '0 auto 40px';
// The for loop is not needed. The length is 1!
break;
case 2:
case 4:
case 5:
width = '50%';
break;
case 3:
width = '33.33%';
break;
}
if (width)
for (let t of teaserCollection) // `for..of` is simpler
t.style.width = width;
}
function resizeTeasersMobile(teaserCollection) {
for (let t of teaserCollection)
t.style.width = '100%';
}
// The function name is clear
function resizeBasedOnWindowWidth(boxList) {
if (window.innerWidth > 2024)
displayTeasers(boxList.children);
else if (window.innerWidth > 641)
resizeTeasersTablet(boxList.children);
else
resizeTeasersMobile(boxList.children);
}
// The function name is clear
function resizeAllBoxListsBasedOnWindowWidth() {
$(".display-boxes").each(function () {
resizeBasedOnWindowWidth(this);
});
}
window.onresize = function () {
this.resizeAllBoxListsBasedOnWindowWidth(); // Just call the defined function.
}
$(function () { // See here: https://api.jquery.com/ready/
resizeAllBoxListsBasedOnWindowWidth(); // Do not repeat the code. Just call the function.
})
The principles I used in this code, are VERY important. I advise you to read the code multiple times :)
Good luck

How to offset top bar height dynamically when scrolling down

I am trying to offset an announcement bar when scrolling down, taking into consideration that the height of the bar is more important on smaller devices.
Here's an example of what I want to achieve : https://8kflexwarm.com/
So I ended up with this piece of code, which is working, but I feel like it is not optimized, not clean code. I figure there must be a way to offset $('.announcement-bar') instead of doing it manually with window size.
Also, why is this code not working when I refresh the screen and I'm not on top of the page ?
Is there a way to improve this code without using a library ?
if($(window).width() >= 686){
var a = $(".site-header").offset().top;
function scrollListener(){
if($(document).scrollTop() > a)
{
$('.site-header').css({"margin-top":"-40px"});
$('.site-header').css({"transition":"0.4s"});
} else {
$('.site-header').css({"margin-top":"0px"});
$('.site-header').css({"transition":"0.4s"});
}
};
$(document).scroll(scrollListener);
scrollListener();
} else if($(window).width() >= 370) {
var a = $(".site-header").offset().top;
function scrollListener(){
if($(document).scrollTop() > a)
{
$('.site-header').css({"margin-top":"-65px"});
$('.site-header').css({"transition":"0.4s"});
} else {
$('.site-header').css({"margin-top":"0px"});
$('.site-header').css({"transition":"0.4s"});
}
};
$(document).scroll(scrollListener);
scrollListener();
} else {
var a = $(".site-header").offset().top;
function scrollListener(){
if($(document).scrollTop() > a)
{
$('.site-header').css({"margin-top":"-90px"});
$('.site-header').css({"transition":"0.4s"});
} else {
$('.site-header').css({"margin-top":"0px"});
$('.site-header').css({"transition":"0.4s"});
}
};
$(document).scroll(scrollListener);
scrollListener();
};
Please provide a codePen, it makes it easier to help you with your question.
I came up with this untested piece of javascript:
var myApp = (function(app) {
const $elements = {
siteHeader = null,
}
function setPosition() {
const scrollTop = $(document).scrollTop()
const offsetTop = $elements.siteHeader.offset().top
if(scrollTop > offsetTop){
$elements.siteHeader.css({'margin-top':`${$elements.siteHeader.height() * -1}px`})
} else {
$elements.siteHeader.css({'margin-top':'0px'})
}
}
function initialize() {
// Wait for all elements to be created
$(function() {
$elements.siteHeader = $('.site-header')
setPosition()
$(document).scroll(setPosition)
$(document).resize(setPosition)
})
}
initialize()
return app;
})(myApp || {})

how to attach click function to multiple divs without ID

I have a fade in function im trying to understand better. It works fine when I set up the
My question is if I have 8 links that already have the separate ID and class names how can I attach this function to each clickable link?
Is there a function to getElementbyClass or something and then just add the class to all my links?
here is my javascript:
var done = true,
fading_div = document.getElementById('fading_div'),
fade_in_button = document.getElementById('fade_in'),
fade_out_button = document.getElementById('fade_out');
function function_opacity(opacity_value) {
fading_div.style.opacity = opacity_value / 100;
fading_div.style.filter = 'alpha(opacity=' + opacity_value + ')';
}
function function_fade_out(opacity_value) {
function_opacity(opacity_value);
if (opacity_value == 1) {
fading_div.style.display = 'none';
done = true;
}
}
function function_fade_in(opacity_value) {
function_opacity(opacity_value);
if (opacity_value == 1) {
fading_div.style.display = 'block';
}
if (opacity_value == 100) {
done = true;
}
}
// fade in button
fade_in_button.onclick = function () {
if (done && fading_div.style.opacity !== '1') {
done = false;
for (var i = 1; i <= 100; i++) {
setTimeout((function (x) {
return function () {
function_fade_in(x)
};
})(i), i * 10);
}
}
};
// fade out button
fade_out_button.onclick = function () {
if (done && fading_div.style.opacity !== '0') {
done = false;
for (var i = 1; i <= 100; i++) {
setTimeout((function (x) {
return function () {
function_fade_out(x)
};
})(100 - i), i * 10);
}
}
};
Correcting the answer from BLiu1:
var fadeDivs = document.getElementsByClassName('fade');
for (var i=0, i<fadeDivs.length, i++){
// do stuff to all fade-divs by accessing them with "fadeDivs[i].something"
}
Have you considered using a javascript library like jQuery to manage this. They have some extensive, very easy to use "selectors" that allow you to easily get access to elements in the DOM and animate them with things like "fade ins" and "slides", etc. If you need more animations there are tons of plugins available for this. It also helps to deal with browser compatibility challenges too.
If you want to rely on pure JavaScript, you can use the document.getElementsByClassName() function defined here, but that function is only defined in IE9 and above as well as Safari, Chrome, FF, and Opera.
As said in the comments, there is a getElementsByClassName() method. Here is how you would use it.
for(var i=0; i<document.getElementsByClassName("fade").length; i++ ){
/*apply fade in function*/
}
I'm not sure whether getElementsByClassName() can detect one class name at a time. You might need regex for that.

Syntax or logical error in jquery/javascript

I have a variable number of promotional items (panels) that are in a sliding belt, that should be set to the width of a panel (300px) multiplied with the amount of panels.
It alerts the correct beltsize. With fixed numbers the slider works too. I suspect the error to be in the if/else if part. I am not even sure this is valid Javascript Syntax.
Any hint is appreciated.
$(window).ready(function(){
var whichpanel = 1;
var panels = $(".panel").length;
var beltsize = panels*300;
$('.belt').css({'width':beltsize});
});
$(window).ready(function promoslider(){
if (panels>whichpanel){
$('.belt').delay(7000).animate({'left':'-=300'}, 500);
whichpanel += 1;
}
else if (panels=whichpanel){
$('.belt').delay(7000).animate({'left':'0'}, 500);
whichpanel = 1;
}
setTimeout(promoslider, 0);
});
promoslider;
UPDATE! Here is the code that works for me now (http://jsfiddle.net/zr5Nd/10/):
$(window).ready(function () {
var whichpanel = 1;
var panels = $(".panel").length;
var beltsize = panels * 300;
$('.belt').css({
'width': beltsize
});
function movingdiv() {
if (panels > whichpanel) {
//alert('Panels:' + panels + '/whichpanel:' + whichpanel);
$('.belt').delay(1000).animate({
'margin-left': '-=300px'
}, 500);
whichpanel += 1;
} else if (panels == whichpanel) {
//alert('Panels:' + panels + '/whichpanel:' + whichpanel);
$('.belt').delay(1000).animate({
'margin-left': '0'
}, 500*panels);
whichpanel = 1;
} else {
alert('3');
}
setTimeout(movingdiv, 0);
}
setTimeout(movingdiv, 0);
});
You need to use the equality/identity operators (==/===) instead of the assignment operator = in your else if statement, e.g.
else if (panels == whichpanel){
$('.belt').delay(7000).animate({'left':'0'}, 500);
whichpanel = 1;
}
Also, I believe promoslider; is supposed to be promoslider();.
You should put the call of promoslider into $(window).ready as well, as the function does not yet exist otherwise. Plus you have to do the call this way promoslider(), promoslider alone will not result in a call.
And of course you have to use the equals-operator == instead of the assign-operator = in your if else-statement.
use panels === whichpanel instead of panels=whichpanel
variables whichpanel, panels have lost their scope in the if/else if part.

Clear a non-global timeout launched in jQuery plug-in

I try to set a timeout on an element, fired with a jQuery plugin. This timeout is set again in the function depending on conditions. But, I want to clear this element's timeout before set another (if I relaunch the plug-in), or clear this manually.
<div id="aaa" style="top: 0; width: 100px; height: 100px; background-color: #ff0000;"></div>
Here's my code (now on http://jsfiddle.net/Ppvf9/)
$(function() {
$('#aaa').myPlugin(0);
});
(function($) {
$.fn.myPlugin = function(loops) {
loops = loops === undefined ? 0 : loops;
this.each(function() {
var el = $(this),
loop = loops,
i = 0;
if (loops === false) {
clearTimeout(el.timer);
return;
}
var animate = function() {
var hPos = 0;
hPos = (i * 10) + 'px';
el.css('margin-top', hPos);
if (i < 25) {
i++;
} else {
if (loops === 0) {
i = 0;
} else {
loop--;
if (loop === 0) {
return;
} else {
i = 0;
}
}
}
el.timer = window.setTimeout(function () {
animate();
}, 1000/25);
};
clearTimeout(el.timer);
//$('<img/>').load(function() {
// there's more here but it's not very important
animate();
//});
});
return this;
};
})(jQuery);
If I make $('#element').myPlugin();, it's launched. If I make it a second time, there's two timeout on it (see it by doing $('#aaa').myPlugin(0);
in console). And I want to be able to clear this with $('#element').myPlugin(false);.
What am I doing wrong?
EDIT :
SOLVED by setting two var to access this and $(this) here : http://jsfiddle.net/Ppvf9/2/
try saving the timeout handle as a property of the element. Or maintain a static lookup table that maps elements to their timeout handles.
Something like this:
el.timer = window.setTimeout(...);
I assume you need one timer per element. Not a single timer for all elements.

Categories