I've been stuck on this for a few days. I've tried different selectors and unwrapping the img in the div if that was the problem but no luck. I've been trying to make an accordion.
I'm trying to add a class of "rotate" to the img with the class of "arrow". So that when the question tag is clicked, the arrow img will also rotate.
const questionTag = document.querySelectorAll('.question')
questionTag.forEach(questionTag => {
questionTag.addEventListener('click', () => {
if (questionTag.classList.contains('open')) {
questionTag.classList.remove('open');
} else {
const questionTagOpen = document.querySelectorAll('.open');
questionTagOpen.forEach((questionTagOpen) => {
questionTagOpen.classList.remove('open');
});
questionTag.classList.add('open');
}
});
});
.question + .answer {
display: none;
overflow: hidden;
transition: all ease 1s;
}
.question.open + .answer {
display: block;
}
.arrow.rotate {
transform: rotate(180deg);
}
<div class="container">
<div class="question">How many team members can I invite?
<img class="arrow" src="./images/icon-arrow-down.svg">
</div>
<div class="answer">You can invite up to 2 additional users on the Free plan. There is no limit on
team members for the Premium plan.</div>
</div>
You're missing [0] in your code.
The arrowTag comes from document.querySelectorAll(), which returns a NodeList, you need to specify the element from that NodeList:
var questionTag = document.querySelectorAll('.question')
questionTag.forEach(questionTag => {
questionTag.addEventListener('click', () => {
if (questionTag.classList.contains('open')) {
questionTag.classList.remove('open');
} else {
const questionTagOpen = document.querySelectorAll('.open');
questionTagOpen.forEach((questionTagOpen) => {
questionTagOpen.classList.remove('open');
});
questionTag.classList.add('open');
}
});
var arrowTag = document.querySelectorAll('img.arrow')
questionTag.addEventListener('click', () => {
arrowTag[0].classList.toggle('rotate'); // missing [0] added here
});
});
The addEventListener function is applied to an event target.
Thus, you cannot apply it to the NodeList, which is stored in your arrowTag:
Related
Hello I am trying to make sections animate on scroll using IntersectionObserver.
To do this i am trying to use javascript
code for css:
.hidden{
opacity: 0;
filter:blur(5px);
transform: translateX(-100%);
transition: all 1s;
}
.show {
opacity: 1;
filter:blur(0px);
transform: translateX(0);
}
code for js
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
console.log(entry)
if (entry.isIntersecting) {
entry.target.classlist.add('show');
} else {
entry.target.classList.remove('show');
}
});
});
const hiddenElements = document.querySelectorAll('.hidden');
hiddenElements.forEach((el) => observer.observe(el));
code for html:
<section class="hidden"> <h1> heading </h1> </section>
after linking all the files together in html, my class hidden sections stay hidden and do not change to show
Error Message:
animater.js:5
Uncaught TypeError: Cannot read properties of undefined (reading 'add')
at animater.js:5:36
at Array.forEach (<anonymous>)
at IntersectionObserver.<anonymous> (animater.js:
2:13)
I want my code to change the sections in html with the class hidden to class show so that they animate on scrolling the page / viewing the section. Currently the code gives me the above specified error and the sections with class hidden stay with their hidden class.
you are getting error on line number 5
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
console.log(entry)
if (entry.isIntersecting) {
- entry.target.classlist.add('show');
+ entry.target.classList.add('show');
} else {
entry.target.classList.remove('show');
}
});
});
const hiddenElements = document.querySelectorAll('.hidden');
hiddenElements.forEach((el) => observer.observe(el));
Transition does not work with height: auto.
So I need to calculate and set the block's dynamic height with JavaScript to make the transition property work.
This is an example of my code:
<div class="accordion__item">
<div class="accordion__icon">
<div class="accordion__content">
</div>
</div>
</div>
const accItems = document.querySelectorAll('.accordion__item');
accItems.forEach((item) => {
const icon = item.querySelector('.accordion__icon');
const content = item.querySelector('.accordion__content');
item.addEventListener("click", () => {
if (item.classList.contains('open')) {
item.classList.remove('open');
icon.classList.remove('open');
content.classList.remove('open');
} else {
const accOpen = document.querySelectorAll('.open');
accOpen.forEach((open) => {
open.classList.remove('open');
});
item.classList.add('open');
icon.classList.add('open');
content.classList.add('open');
}
});
});
How can I do this?
It's not ideal but there is not much we can do with transitioning height.
For a workaround, give your open class a max-height property that is larger than your expect the largest open element to get. From there you can transition the max-height property.
There are also some optimizations you can make to your event listener callback. I don't believe you need to add open to all the elements in the accordion, just the item itself.
Try something like this:
const accItems = document.querySelectorAll('.accordion__item');
accItems.forEach((item) => {
item.addEventListener("click", () => {
const openItems = document.querySelectorAll(".open")
openItems.forEach(open => open.classList.toggle("open"))
item.classList.toggle("open")
}
});
Then in your css:
.open {
max-height: 200px
}
.accordion__item {
max-height: 0;
transition: max-height 200ms ease;
}
I want an overlay to show up when I click a search icon.
I managed to get it working using jQuery. But can't seem to get it working with javascript.
The click event does not seem to be registering and I don't know why.
I've checked all the class names so they match in the same in both the HTML and javascript
Here is the jQuery code that works:
import $ from 'jquery';
class Search {
constructor() {
this.openButton = $('.js-search-trigger');
this.closeButton = $('.search-overlay__close');
this.searchOverlay = $(".search-overlay");
this.events();
}
events() {
this.openButton.on('click', this.openOverlay.bind(this));
this.closeButton.on('click', this.closeOverlay.bind(this));
}
openOverlay() {
this.searchOverlay.addClass("search-overlay--active");
}
closeOverlay() {
this.searchOverlay.removeClass("search-overlay--active");
}
}
export default Search;
Here is the javascript code that does not work:
class Search {
constructor() {
this.openButton = document.querySelector('.js-search-trigger');
this.closeButton = document.querySelector('.search-overlay__close');
this.searchOverlay = document.querySelector('.search-overlay');
this.events();
}
events() {
this.openButton.addEventListener('click', function() {
this.openOverlay.bind(this);
});
this.closeButton.addEventListener('click', function() {
this.closeOverlay.bind(this);
});
}
openOverlay() {
this.searchOverlay.classList.add('search-overlay--active');
}
closeOverlay() {
this.searchOverlay.classList.remove('search-overlay--active');
}
}
export default Search;
No errors were shown in the javascript where the overlay was not showing.
You'll probably want to change your event listeners to use the correct this binding:
this.openButton.addEventListener("click", this.openOverlay.bind(this));
Or use an arrow function to go with your approach - but make sure you actually call the resulting function, as in the above approach the function is passed as a reference and is called. If you removed the additional () from the code below, it would be the same as writing a function out in your code normally - it would be defined, but nothing would happen.
this.openButton.addEventListener("click", () => {
this.openOverlay.bind(this)();
});
jQuery also uses collections of elements rather than single elements, so if you have multiple elements, querySelectorAll and forEach might be in order.
If we are speaking of ecmascript-6 (I see the tag), I would recommend to use arrow function to have this inherited from the above scope, and no bind is needed:
this.openButton.addEventListener('click', () =>
this.openOverlay()
);
The problems with your code are that a) the function creates new scope with its own this; b) bound methods are not being invoked.
Why Search? You're creating an Overlay. Stick with the plan.
No need to bind anything. Use Event.currentTarget if you want to.
No need to handle .open/.close if all you need is a toggle.
And the below should work (as-is) for multiple Overlays. The overlay content is up to you.
class Overlay {
constructor() {
this.toggleButtons = document.querySelectorAll('[data-overlay]');
if (this.toggleButtons.length) this.events();
}
events() {
this.toggleButtons.forEach(el => el.addEventListener('click', this.toggleOverlay));
}
toggleOverlay(ev) {
const btn = ev.currentTarget;
const sel = btn.getAttribute('data-overlay');
const overlay = sel ? document.querySelector(sel) : btn.closest('.overlay');
overlay.classList.toggle('is-active');
}
}
new Overlay();
*{margin:0; box-sizing:border-box;} html,body {height:100%; font:14px/1.4 sans-serif;}
.overlay {
background: rgba(0,0,0,0.8);
color: #fff;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
padding: 5vw;
transition: opacity 0.4s, visibility 0.4s;
visibility: hidden;
opacity: 0;
}
.overlay.is-active {
opacity: 1;
visibility: visible;
}
<button type="button" data-overlay="#search">OPEN #search</button>
<button type="button" data-overlay="#qa">OPEN #qa</button>
<div class="overlay" id="search">
<button type="button" data-overlay>CLOSE</button>
<h2>SEARCH</h2>
<input type="text" placeholder="Search…">
</div>
<div class="overlay" id="qa">
<button type="button" data-overlay>CLOSE</button>
<h2>Q&A</h2>
<ul><li>Lorem ipsum</li></ul>
</div>
The above is still not perfect, still misses a way to "destroy" events and not re-attach duplicate events to already initialised buttons when trying to target dynamically created ones.
Also, the use of Classes for the above task is absolutely misleading and unnecessary.
these code is working except that for the second or subsequent insert, the message 'Order added' is not appearing. Secondly, after I removed everything using pop and click to view current order, the message 'No customer orders at the moment.' is not appearing even though the code executed.
const order = [];
const customer = {
name: '',
totalCups: 0
}
const checkCustomerOrders = () => {
if (order.length === 0) {
$('.Mesg').show();
$('.Mesg').text("No customer orders at the moment.").fadeTo(4000, 0);
}
}
$('#AllOrders').hide();
$('#btnAdd').click(function () {
var item = $('#customerName');
// Data structure Queue
order.unshift(item.val());
// UX
$('.Mesg').text("Order added").fadeTo(4000, 0);
// UI
var orderElement = $('<div class="orderItem"></div>').text(item.val());
$('#AllOrders').append(orderElement);
// Reset textbox
item.val("");
// Optional Design
$('#ViewAllOrders').click();
debugger;
})
$('#ViewAllOrders').click(function () {
checkCustomerOrders();
$('#AllOrders').show();
$('#CurentOrder').hide();
})
$('#ViewCurrentOrder').click(function () {
debugger;
checkCustomerOrders();
$('#AllOrders').hide();
$('#CurentOrder').show();
var top = order[order.length - 1];
console.log(top);
$('#CurentOrder').empty();
// UI
var orderElement = $('<div></div>').text(top);
$('#CurentOrder').append(orderElement);
})
$('#DeliverOrder').click(function () {
debugger
// Remove one element from array. FIFO.
order.pop();
// Element removed.
// Remove the html element as well
$(".orderItem:first").remove();
// UX
$('.Mesg').text("One customer order delivered").fadeTo(4000, 0);
// Optional Design
$('#ViewAllOrders').click();
})
html{
font-size:1em
}
div.Mesg {
height: 20px !important;
}
ul#menu {
display: flex;
list-style-type: none;
justify-content: space-around;
padding: 0;
margin: 0;
}
ul#menu > li {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul id="menu">
<li><a id="ViewAllOrders" href="#">View all orders</a></li>
<li><a id="ViewCurrentOrder" href="#">View current order in process</a></li>
<li><a id="DeliverOrder" href="#">Deliver one order</a></li>
</ul>
<hr />
<div class="Mesg"></div>
<hr />
<div id="AddOrder">
<input id="customerName" type="text" placeholder="Enter customer's name" />
<input id="btnAdd" type="button" value="Add" />
</div>
<div id="AllOrders"></div>
<div id="CurentOrder"></div>
Is it due to fadeTo method?
Change fadeTo() to fadeOut() should fix this
const order = [];
const customer = {
name: '',
totalCups: 0
}
const checkCustomerOrders = () => {
if (order.length === 0) {
$('.Mesg').show();
$('.Mesg').text("No customer orders at the moment.").fadeOut(4000, 0);
}
}
$('#AllOrders').hide();
$('#btnAdd').click(function () {
var item = $('#customerName');
// Data structure Queue
order.unshift(item.val());
// UX
$('.Mesg').text("Order added").fadeOut(4000, 0, function(){
$('.Mesg').text('');
$('.Mesg').show();
});
// UI
var orderElement = $('<div class="orderItem"></div>').text(item.val());
$('#AllOrders').append(orderElement);
// Reset textbox
item.val("");
// Optional Design
$('#ViewAllOrders').click();
debugger;
})
$('#ViewAllOrders').click(function () {
checkCustomerOrders();
$('#AllOrders').show();
$('#CurentOrder').hide();
})
$('#ViewCurrentOrder').click(function () {
debugger;
checkCustomerOrders();
$('#AllOrders').hide();
$('#CurentOrder').show();
var top = order[order.length - 1];
console.log(top);
$('#CurentOrder').empty();
// UI
var orderElement = $('<div></div>').text(top);
$('#CurentOrder').append(orderElement);
})
$('#DeliverOrder').click(function () {
debugger
// Remove one element from array. FIFO.
order.pop();
// Element removed.
// Remove the html element as well
$(".orderItem:first").remove();
// UX
$('.Mesg').text("One customer order delivered").fadeOut(4000, 0);
// Optional Design
$('#ViewAllOrders').click();
})
I am trying to make an paper-card element change colors based on the status of the customers data on Fire base, but for some reason the color only updates on the second click of the customer. Right now I have the paper cards ID set to the firebase data in order to make it change colors. Here's my elements style code:
<style is="custom-style">
:host {
display: block;
}
#cards {
#apply(--layout-vertical);
#apply(--center-justified);
}
.row {
padding: 20px;
margin-left: 10px;
}
paper-card {
padding: 20px;
}
#check {
float: right;
bottom: 15px;
--paper-card
}
#Done {
--paper-card-header: {
background: var(--paper-green-500);
};
--paper-card-content: {
background: var(--paper-green-300);
};
}
#Default {
/*Apply Default Style*/
/*--paper-card-content: {*/
/* background: var(--paper-red-500);*/
/*};*/
}
paper-icon-button.check{
color: var(--paper-green-500);
}
paper-icon-button.check:hover{
background: var(--paper-green-50);
border-radius: 50%;
}
#check::shadow #ripple {
color: green;
opacity: 100%;
}
.iron-selected{
color: green;
}
And here is the template:
<template>
<firebase-collection
location="https://calllistmanager.firebaseio.com/Wilson"
data="{{wilsonData}}"></firebase-collection>
<div id="cards">
<template id="cards" is="dom-repeat" items="{{wilsonData}}" as="customer">
<paper-card id="{{customer.status}}" class="{{customer.status}}" heading="[[customer.__firebaseKey__]]">
<div class="card-content">
<span>Phone: </span><span>[[customer.number]]</span>
<span>Status: </span><span>[[customer.status]]</span>
<paper-icon-button style="color: green" id="check" on-tap="checktap" icon="check">
</paper-icon-button>
</div>
</paper-card>
</template>
</div>
Here is my script:
<script>
(function() {
Polymer({
is: 'list-display',
properties: {
wilsonData: {
type: Object,
observer: '_dataObserver'
}
},
ready: function() {
var listRef = new Firebase("https://calllistmanager.firebaseio.com/Wilson");
},
checktap: function(e){
// e.model.customer.status = "Done";
console.log("Starting Status: " + e.model.customer.status);
ref = new Firebase("https://calllistmanager.firebaseio.com/Wilson")
var stat;
var store = ref.child(e.model.customer.__firebaseKey__);
store.on("value", function(snapshot){
stat = snapshot.child("status").val();
});
if(stat == "Done"){
store.update({
"status": "Default"
});
e.model.customer.status = "Default";
}
else {
store.update({
"status": "Done"
});
e.model.customer.status = "Done";
}
console.log("Ending Status: " + e.model.customer.status);
this.updateStyles()
}
});
})();
at first I thought the problem may be that the function runs updateStyles(); faster than firebase can update but it always works fine on the second click...any suggestions?
I think the problem could be caused by the call to firebase. store.on("value", is not a synchronous function. However, later in your code you assume that you already have a value, that will be set later on whenever the value event fires. You could try adding the rest of your code in the event handler. Like this:
checktap: function(e){
// e.model.customer.status = "Done";
console.log("Starting Status: " + e.model.customer.status);
ref = new Firebase("https://calllistmanager.firebaseio.com/Wilson")
var store = ref.child(e.model.customer.__firebaseKey__);
store.once("value", function(snapshot){
var stat = snapshot.child("status").val();
if(stat == "Done"){
store.update({
"status": "Default"
});
e.model.set("customer.status", "Default");
}
else {
store.update({
"status": "Done"
});
e.model.set("customer.status", "Done");
}
console.log("Ending Status: " + e.model.customer.status);
this.updateStyles();
}.bind(this));
}
Essentially, you wait until the stat variable has been set to do the rest of your tasks. Also note, the bind(this) at the end, which will allow you to update the the styles from the event handler.
Update
There are a couple of more issues. First it's better to uses classes for changing the styles and not IDs. IDs should not change. Then, to bind to the class attribute, use the $ sign. When you update the model, you should use the set API.
Have a look at this plunker. It is a small working example (only works in Chrome) that changes styles when you click the checkmark. It does not use Firebase, however.
Here's how you could to the style with classes.
.Done {
--paper-card-header: {
background: var(--paper-green-500);
};
--paper-card-content: {
background: var(--paper-green-300);
};
}
And in your template:
<paper-card class$="{{customer.status}}" heading="[[customer.__firebaseKey__]]">