How to make a generic toggle function in JQuery? - javascript

I have a sidemenu in HTML like this:
_Layout.cshtml
<div class="col nav-left h-100">
<p>Menu</p>
</div>
In my Site.js I have made a generic JQuery function for the toggle:
var eventHandler = {
addToggle: function addToggle(btnElem, openEvent, closeEvent) {
const isClosedClass = 'this-is-closed';
btnElem.click(function () {
if ($(this).hasClass(isClosedClass)) {
openEvent();
} else {
closeEvent();
}
});
}
};
In my _Layout.cshtml I call this function like this:
<script>
eventHandler.addToggle($(".btn-toggle-something"),
function () {
// Slide down
},
function () {
// Slide up
});
</script>
Now I getting stuck how to write the logic for the actual toggle on a div.
How can I toggle my side menu with a generic function?
Sources I looked:
https://api.jquery.com/toggle/
How to toggle (hide / show) sidebar div using jQuery

.slideUp() and .slideDown() in combination with .toggleClass() will work - please see below:
var eventHandler = {
addToggle: function addToggle(btnElem, openEvent, closeEvent) {
const isClosedClass = 'this-is-closed';
btnElem.click(function () {
if ($(this).hasClass(isClosedClass)) {
openEvent();
} else {
closeEvent();
}
// Toggle `isClosedClass` for next time this is clicked.
btnElem.toggleClass(isClosedClass);
});
}
};
// Bind slideDown/slideUp.
var leftNav = $('.nav-left');
eventHandler.addToggle($('.btn-toggle-something'),
function () {
leftNav.slideDown();
},
function () {
leftNav.slideUp();
}
);
body {
margin: 0;
padding: 0;
}
p {
margin: 0;
}
.nav-left {
background-color: lightskyblue;
width: 200px;
height: 400px;
}
.btn-toggle-something {
cursor: pointer;
position: fixed;
top: 0;
left: 250px;
height: 50px;
width: 50px;
background-color: salmon;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="col nav-left h-100">
<p>Menu</p>
</div>
<div class="btn-toggle-something">Toggle</div>

Related

Using getBoundingClientRect() for multiple elements with the same class?

I'm trying to add sticky navigation to my website that will change as it scrolls over different sections. When scrolling over a section with the class .dark, it should change the logo and text colour to white.. otherwise black.
The javascript I've been using is below but this only seems to apply to the first element with the class .dark, how can I adapt this to target all elements with the same class?
window.addEventListener('scroll', function () {
var section = document.querySelector('.dark').getBoundingClientRect(),
logo = document.querySelector('#logo-container').getBoundingClientRect();
if (section.top <= logo.top + logo.height && section.top + section.height > logo.top) {
document.getElementById('logo-container').classList.add('white-logo');
document.getElementById('navholder').style.color = "#fff";
} else {
document.getElementById('logo-container').classList.remove('white-logo');
document.getElementById('navholder').style.color = "#111";
}
});
I apologise if this is an obvious question, I'm not too knowledgeable when it comes to javascript! I've tried to look for a solution to this but have not had much success.. any help would be massively appreciated.
If you break this out into several functions, it makes life easier. You can check if the logo is is any of the sections, and then set its class accordingly:
const setLogoBlackStatus = status => {
if (status) {
document.getElementById('logo-container').classList.add('black-logo');
document.getElementById('logo-container').classList.remove('white-logo');
} else {
document.getElementById('logo-container').classList.add('white-logo');
document.getElementById('logo-container').classList.remove('black-logo');
}
}
const logoIsInSection = logo => sectionRect => sectionRect.top <= logo.top + logo.height &&
sectionRect.top + sectionRect.height > logo.top
window.addEventListener('scroll', function() {
var sectionRects = [...document.querySelectorAll('.dark')]
.map(el => el.getBoundingClientRect());
var logo = document.querySelector('#logo-container').getBoundingClientRect();
var logoInAnySections = sectionRects
.some(logoIsInSection(logo))
setLogoBlackStatus(!logoInAnySections);
});
img {
width: 50px;
position: fixed;
top: 20vw;
left: 20vw;
z-index: 1;
}
.white-logo {
filter: invert(90%);
}
.section {
width: 100%;
height: 300px;
}
.dark {
background-color: rgba(20, 20, 30);
}
.white {
background-color: white;
}
<img src="https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/cc.svg" id="logo-container"/>
<div class="section white"></div>
<div class="section dark"></div>
<div class="section white"></div>
<div class="section dark"></div>

How to run the Callback function inside of the prototype in JavaScript?

According to this question and mdn.doc articles, I'm giving a Callback function inside of aprototype for managing the next code line after it's done.
But even if I create the Callback, the browser keeps ignoring it and running the next code line no matter the Callback is completed or not.
This is the code:
'use strict';
(function() {
function Box($el, $frame) {
// Reassign the Values
this.$el = $el;
this.$frame = $frame;
// Event Register Zone
this.$el.addEventListener('touchstart', (e) => this.start(e));
this.$el.addEventListener('touchmove', (e) => this.move(e));
this.$el.addEventListener('touchend', (e) => this.end(e));
}
Box.prototype = {
start: function(e) {
console.log('touchstart has been detected');
},
move: function(e) {
console.log('touchmove has been detected');
},
end: function(e) {
console.log('touchend has been detected');
this.getanAction(this.moveTop);
},
getanAction: function(callback) {
let bound = callback.bind(this);
bound();
this.$frame[1].classList.add('leftMover');
// Expectation: move the purple box first, and move the orange box next
},
moveTop: function() {
this.$frame[0].classList.add('topMover');
}
}
/***************************************************************/
// Declare & Assign the Original Values
let _elem = document.getElementById('box');
let _frame = _elem.querySelectorAll('.contents');
const proBox = new Box(_elem, _frame);
}());
* {
margin: 0;
padding: 0;
}
#box {
width: auto;
height: 800px;
border: 4px dotted black;
}
.contents {
position: absolute;
width: 200px;
height: 200px;
float: left;
top: 0;
left: 0;
transition: 800ms cubic-bezier(0.455, 0.03, 0.515, 0.955);
}
.purple { background-color: purple; }
.orange { background-color: orange; }
.topMover { top: 600px; }
.leftMover { left: 600px; }
<div id="box">
<div class="contents purple">
</div>
<div class="contents orange">
</div>
</div>
My expectation is the .orange box moves after the .purple box moves done.
Did I miss or do something wrong from the code?
The problem is they are being called one after the other with no delay as JavaScript won't wait for the CSS transition to finish before moving to the next line.
I've fixed waiting for the first transition has finished before calling the bound callback. This way the purple box will move, wait for the transition to finish, then the orange box will move.
'use strict';
(function() {
function Box($el, $frame) {
// Reassign the Values
this.$el = $el;
this.$frame = $frame;
// Event Register Zone
this.$el.addEventListener('touchstart', (e) => this.start(e));
this.$el.addEventListener('touchmove', (e) => this.move(e));
// Added mouse up so it works on desktop
this.$el.addEventListener('mouseup', (e) => this.end(e));
this.$el.addEventListener('touchend', (e) => this.end(e));
}
Box.prototype = {
start: function(e) {
console.log('touchstart has been detected');
},
move: function(e) {
console.log('touchmove has been detected');
},
end: function(e) {
console.log('touchend has been detected');
this.getanAction(this.moveTop);
},
getanAction: function(callback) {
let bound = callback.bind(this);
// Listen for css transition end
this.$frame[0].addEventListener('transitionend', function() {
// Call callback to move orange box
bound()
});
// Move the purple box now
this.$frame[0].classList.add('topMover1')
},
moveTop: function() {
this.$frame[1].classList.add('topMover2');
}
}
/***************************************************************/
// Declare & Assign the Original Values
let _elem = document.getElementById('box');
let _frame = _elem.querySelectorAll('.contents');
const proBox = new Box(_elem, _frame);
}());
* {
margin: 0;
padding: 0;
}
#box {
width: auto;
height: 800px;
border: 4px dotted black;
}
.contents {
position: absolute;
width: 200px;
height: 200px;
float: left;
top: 0;
left: 0;
transition: 800ms cubic-bezier(0.455, 0.03, 0.515, 0.955);
}
.purple { background-color: purple; }
.orange { background-color: orange; }
.topMover1 { top: 600px; }
.topMover2 { left: 600px; }
<div id="box">
<div class="contents purple">
</div>
<div class="contents orange">
</div>
</div>

Javascript: can't change background on div click or change marginLeft on element

I tried to test changing backgroundColor and marginLeft on this simple example: https://jsfiddle.net/ntqLo6v0/2/
and couldn't make it work.
var collapsed = 0;
$('[data-toggle=collapse-button]').click(function() {
if (collapsed == 0) {
close();
} else {
open();
}
});
function close() {
document.getElementById("button").style.backgroundColor = "blue";
(document.getElementsByClassName("content")[0]).style.marginLeft = "20px";
collapsed = 1;
}
function open() {
document.getElementById("button").style.backgroundColor = "red";
(document.getElementsByClassName("content")[0]).style.marginLeft = "120px";
collapsed = 0;
}
.content {
background-color: green;
width: 200px;
height: 100px;
}
#button {
background-color: red;
width: 100px;
height: 50px;
margin: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="button" data-toggle="collapse-button">
button
</div>
<div class="content">
some content here
</div>
There is just a little issue: $('[data-toggle=collapse-button]').
You are using jQuery but do not define it. That's why you get a Uncaught ReferenceError: $ is not defined in the console.
Here is your updated fiddle where I added jQuery (in the resources left) in order to make your example running.

Fill parent div with the image being hovered over

I am still fairly new to JS, and I am trying to replace the HTML of a div with a picture that is being moused over, and when the mouse leaves I want it to return to it's normal state. I thought that I did everything right but my code doesn't seem to be working. I've looked through stack overflow and I see a lot of jQuery solutions to my 'problem,' but I would like an answer in pure JavaScript (I'm trying to "maser" this first), along with an explanation so I can understand why the answer IS the answer. Thanks.
I'll try to explain myself (my code). I grabbed reference to the image holder, and I grabbed reference to the the images. I thought I made a function that looped through the array of images and added an event listener to whichever image ( image[i] ) was being moused over. Then, I added an event listener that is supposed to return the image holder to it's default state by inserting the original HTML. I just don't understand how to fix this.
var holder = document.getElementById('holder');
var images = document.getElementsByTagName('img');
var popImage = function () {
for (i = 0; i < images.length; i++) {
images[i].addEventListener('mouseover', = function () {
holder.innerHTML = images[i];
});
images[i].addEventListener('mouseout', function () {
holder.innerHTML =
'<div class='col-md-3 img-fluid' id='img1'><img src='photo1.jpg'></div>
<div class='col-md-3 img-fluid' id='img2'><img src='photo2.jpg'></div>
<div class='col-md-3 img-fluid' id='img3'><img src='photo3.2.jpg'></div>
<div class='col-md-3 img-fluid' id='img4'><img src='photo4.jpg'></div>'
});
};
};
popImage();
You said you are new to JS and just learning which is great but an important part of learning JS is learning when not to use it. As #Yoda said if this was for production you really should use CSS instead of JS.
Here is one way you could accomplish this with pure CSS
<style>
.img {
width: 100px;
height: 100px;
background: #bada55;
border: 2px solid #333;
float: left;
}
.holder:hover > .img {
opacity: 0;
}
.holder:hover > .img:hover {
opacity: 1;
}
</style>
<div class="holder">
<!-- Using div.img for simplicity, these whould be your <img/> tags -->
<div class="img">1</div>
<div class="img">2</div>
<div class="img">3</div>
<div class="img">4</div>
</div>
For the purpose of learning, here's how you'd do it in JS:
var holder = document.getElementById('holder');
var images = document.querySelectorAll('.img');
var filter = false;
function popImage () {
// Use for (var i = 0 . . .
// Instead of for (i = 0 . . .
// Because without var, i will be stored in the global scope
for (var i = 0; i < images.length; i++) {
(function (_i) {
images[_i].addEventListener('mouseover', function () {
holder.innerHTML = '';
// We can't set innerHTML to images[_i]
// because it's a DomNode not a string
holder.appendChild(images[_i]);
});
})(i);
}
holder.addEventListener('mouseout', function (e) {
if (e.target !== holder)
return;
holder.innerHTML = '';
// Again, use var j = 0 . . .
for (var j = 0; j < images.length; j++) {
holder.appendChild(images[j]);
}
});
}
popImage();
.img {
width: 100px;
height: 100px;
background: #bada55;
border: 2px solid #333;
display: inline-block;
}
#holder {
position: relative;
width: 100%;// So doesn't collape and trigger mouseout
height: 100px;
background: red;
padding: 20px 0;
}
<div id="holder">
<!-- Again, these would be your image tags -->
<div class="img">1</div>
<div class="img">2</div>
<div class="img">3</div>
<div class="img">4</div>
</div>
I had 10 mins before leaving work so I had a crack at this to see how I would do it and give you some ideas.
Here is my implementation (https://jsfiddle.net/hg7s1pyh/)
I guess the main thing here is that I've broken it down into lots of smaller parts, this makes solving problems far easier, each method is concerned with doing one thing only.
You will also note the use of classes to show and hide content rather than removing it entirely, this takes lots of the arduous work out of this feature.
function attachEvents() {
var images = getImages();
images.forEach(function(image) {
attachMouseOverEvent(image);
attachMouseLeaveEvent(image);
});
}
function attachMouseOverEvent(element) {
element.addEventListener('mouseover', function(e) {
var clonedImage = e.target.cloneNode();
addImageToPreview(clonedImage);
});
}
function attachMouseLeaveEvent(element) {
element.addEventListener('mouseleave', function(e) {
removeImageFromPreview();
});
}
function getImages() {
return document.querySelectorAll('.js-image');
}
function getImagePreviewElement() {
return document.querySelector('.js-image-box');
}
function addImageToPreview(imageElement) {
var previewElement = getImagePreviewElement();
previewElement.classList.add('previewing');
previewElement.appendChild(imageElement);
}
function removeImageFromPreview() {
var previewElement = getImagePreviewElement();
previewElement.classList.remove('previewing');
var image = previewElement.querySelector('.js-image');
image.remove();
}
attachEvents();
.image-box {
position: relative;
min-height: 400px;
width: 400px;
border: 1px solid #000;
text-align: center;
}
.image-box .placeholder {
position: absolute;
top: 50%;
text-align: center;
transform: translateY(-50%);
width: 100%;
}
.image-box.previewing .placeholder {
display: none;
}
.image-box .image {
position: absolute;
top: 50%;
text-align: center;
transform: translate(-50%, -50%);
height: 100%;
width: 100%;
}
.images {
margin-top: 10px;
}
<div class="js-image-box image-box">
<div class="placeholder">
Placeholder
</div>
</div>
<div class="images">
<div class="col-md-3 img-fluid"><img class="js-image image" src="http://placehold.it/350x150"></div>
<div class="col-md-3 img-fluid"><img class="js-image image" src="http://placehold.it/150x150"></div>
<div class="col-md-3 img-fluid"><img class="js-image image" src="http://placehold.it/400x400"></div>
<div class="col-md-3 img-fluid"><img class="js-image image" src="http://placehold.it/350x150"></div>
</div>

Need add to my jQuery function infinite loop

He needs to do a loop by acting in this way:
$(window).load(function(){
$('.slogan').delay('2000').fadeOut('300', function(){
$('.slogan2').fadeIn('slow').removeAttr('display');
});
});
div .slogan for 3 seconds and div .slogan2 for 3 seconds and 3 seconds after the return to the div .slogan - and so on to infinity.
Can someone please add something to my code?
The following will overlap the fades
function fade(delay, speed) {
$('.slogan1').delay(delay).fadeToggle(speed);
$('.slogan2').delay(delay).fadeToggle(speed, function() {
fade(delay, speed)
});
}
fade(2000, 1000);
.slogan {
width: 200px;
height: 200px;
position: absolute;
}
.slogan {
background: blue;
}
.slogan2 {
background: green;
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="slogan slogan1"></div>
<div class="slogan slogan2"></div>
The following will play the fade one after the other
function fade(delay, speed) {
var slogan1 = $('.slogan1'),
slogan2 = $('.slogan2');
slogan1.delay(delay).fadeOut(speed, function() {
slogan2.fadeIn(speed, function() {
slogan2.delay(delay).fadeOut(speed, function() {
slogan1.fadeIn(speed, function() {
fade(delay, speed)
});
})
});
});
}
fade(2000, 1000);
.slogan {
width: 200px;
height: 200px;
}
.slogan {
background: blue;
}
.slogan2 {
background: green;
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="slogan slogan1"></div>
<div class="slogan slogan2"></div>
Move the animation to a separate function and call it when it's finished, the long version would be:
function animateSlogan() {
$('.slogan')
.delay('3000')
.fadeOut('slow', function() {
// when fadeout complete, start next fadeIn
$('.slogan2')
.fadeIn('slow')
.delay('3000')
.fadeOut('slow', function() {
// when fadeout complete, fadein the original as it's not at the start
$('.slogan').fadeIn('slow', function() {
// when fadein complete, start again
animateSlogan();
});
})
});
}
$(function() {
animateSlogan();
});
you can do it in the following way:
HTML:
<div class="slogan">slogan</div>
<div class="slogan2" style="display: none;">slogan2</div>
JavaScript:
$(document).ready(function(){
doInfiniteLoop(3000, 1000);
});
function doInfiniteLoop(delayTime, fadeTime){
$('.slogan').delay(delayTime).fadeOut(fadeTime,function(){
$('.slogan2').fadeIn(fadeTime,function(){
$('.slogan2').delay(delayTime).fadeOut(fadeTime,function(){
$('.slogan').fadeIn(fadeTime, function(){
doInfiniteLoop(delayTime, fadeTime);
});
});
});
});
}
DEMO

Categories