Why callback not detecting latest value of class instance variable - javascript
I have a web component which is basically a class:
class NavList extends HTMLElement {
_wrapper;
_observer;
_observerActive = true;
get observerState() {
return this._observerActive;
}
render() {
this._wrapper.innerHTML = "";
const activeList = window.location.hash.slice(1);
const container = htmlToElement(`<nav class="navlist"></nav>`);
for (let list in veritabani) {
container.appendChild(
htmlToElement(
`<a href=#${list} class="nav-entry ${
activeList === list ? "active" : ""
}">${list}</div>`
)
);
}
// prevent observer from changing hash during smooth scrolling
container.addEventListener("click", this.disableObserver);
this._wrapper.appendChild(container);
}
observe() {
let options = {
root: document.querySelector(".check-list"),
rootMargin: "0px",
threshold: 0.4,
};
let observer = new IntersectionObserver(
this.observerCallback.bind(this),
options
);
this._observer = observer;
const targets = document.querySelectorAll("check-list");
console.log("observer target:", targets);
for (let target of targets) {
observer.observe(target);
}
}
observerCallback(entries, observer) {
console.log("observer active?", this.observerState);
entries.forEach((entry) => {
if (entry.isIntersecting && this.observerState) {
const targetListName = entry.target.getAttribute("list");
console.log(entry, targetListName);
window.location.hash = targetListName;
this.render();
}
});
}
disableObserver() {
this._observerActive = false;
console.log("observer disabled", this._observerActive);
function enableObserver() {
this._observerActive = true;
console.log("observer enabled", this._observerActive);
}
const timer = setTimeout(enableObserver, 2000);
}
connectedCallback() {
console.log("hash", window.location.hash);
// wrapper for entire checklist element
const wrapper = this.appendChild(
htmlToElement(
`<span class="navlist-wrapper ${window.location.hash}"></span>`
)
);
this._wrapper = wrapper;
this.render();
setTimeout(() => {
this.observe();
}, 1);
}
// more code below
As you can see, I have an intersection observer and I am trying to disable its callback when an anchor is clicked.
The observer detects elements on the page and changes the URL hash so that the visible element name is highlighted on the navlist, this works fine but interferes with the function of the navlist since clicking on navlist entry should also scroll the page to that element!
My solution is to disable the intersection observer's callback after a navlist entry is clicked using setTimout:
disableObserver() {
this._observerActive = false;
console.log("observer disabled", this._observerActive);
function enableObserver() {
this._observerActive = true;
console.log("observer enabled", this._observerActive);
}
const timer = setTimeout(enableObserver, 2000);
}
The above code sets an instance variable to false after a click on navlist, the variable changes state to false but the observer's callback does not see the change and uses the old state which is true by default.
My Question: Why is this happening? and how can I fix it?
I tried delaying the callback function thinking that it is being activated before the state change, but it did not work.
UPDATE: Here is a link to a live demo of what I am doing
I found a solution though I still do not quite understand whats happening.
The solution is to move the flag _observerActive outside of the class Navlist:
let OBSERVER_STATE = true;
class NavList extends HTMLElement {
_wrapper;
_observer;
render() {
this._wrapper.innerHTML = "";
const activeList = window.location.hash.slice(1);
const container = htmlToElement(`<nav class="navlist"></nav>`);
for (let list in veritabani) {
console.log(`active? ${list}===${activeList}`);
container.appendChild(
htmlToElement(
`<a href=#${list} class="nav-entry ${
activeList === list ? "active" : ""
}">${list}</div>`
)
);
}
// prevent observer from changing hash during smooth scrolling
container.addEventListener("click", this.disableObserver);
const addButton = htmlToElement(
`<button class="nav-add">
<span class="nav-add-content">
<span class="material-symbols-outlined">add_circle</span>
<p>Yeni list</p>
</span>
</button>`
);
addButton.addEventListener("click", this.addList.bind(this));
this._wrapper.appendChild(container);
this._wrapper.appendChild(addButton);
}
disableObserver() {
OBSERVER_STATE = false;
console.log("observer disabled", this.OBSERVER_STATE);
function enableObserver() {
OBSERVER_STATE = true;
console.log("observer enabled", OBSERVER_STATE);
}
const timer = setTimeout(enableObserver, 2000);
}
addList() {
const inputGroup = htmlToElement(`
<div class="input-group">
</div>`);
const input = inputGroup.appendChild(
htmlToElement(`
<input placeholder="Liste Adi Giriniz"></input>`)
);
const button = inputGroup.appendChild(
htmlToElement(`
<button>✔</button>`)
);
button.addEventListener("click", () =>
this.addNewCheckList(input.value)
);
input.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
console.log(input.value);
this.addNewCheckList(input.value);
}
});
const addButton = document.querySelector(".nav-add");
console.log(this._wrapper);
this._wrapper.replaceChild(inputGroup, addButton);
}
addNewCheckList(baslik) {
veritabani[baslik] = {};
const checkListContainer = document.querySelector(".check-list");
const newCheckList = htmlToElement(`
<check-list
baslik="${baslik} Listem"
list="${baslik}"
placeholder="�� A��klamas�..."
></check-list>`);
checkListContainer.appendChild(newCheckList);
this._observer.observe(newCheckList);
this.render();
newCheckList.scrollIntoView();
}
observe() {
let options = {
root: document.querySelector(".check-list"),
rootMargin: "0px",
threshold: 0.4,
};
let observer = new IntersectionObserver(
this.observerCallback.bind(this),
options
);
this._observer = observer;
const targets = document.querySelectorAll("check-list");
console.log("observer target:", targets);
for (let target of targets) {
observer.observe(target);
}
}
observerCallback(entries, observer) {
console.log("observer active?", OBSERVER_STATE);
entries.forEach((entry) => {
if (entry.isIntersecting && OBSERVER_STATE) {
const targetListName = entry.target.getAttribute("list");
window.location.hash = targetListName;
this.render();
}
});
}
connectedCallback() {
console.log("hash", window.location.hash);
// wrapper for entire checklist element
const wrapper = this.appendChild(
htmlToElement(
`<span class="navlist-wrapper ${window.location.hash}"></span>`
)
);
this._wrapper = wrapper;
this.render();
setTimeout(() => {
this.observe();
}, 1);
}
}
If I understand correctly, you want to
Create a nav list that renders a link for each anchor (id) on a page.
When a target scrolls into view, highlight the associated link and update the location hash
When clicking on a link in the Navbar, scroll to the target and update the location hash
You don't have to keep track of the IntersectObserver state and you don't have to disable it. Just use pushState() instead of location.hash to update the hash. https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
index.html
<head>
<style>
/* Makes sure that the first section scrolls up enough to trigger the effect */
section { scroll-margin: 20px }
</style>
</head>
<body>
<div class="sidebar">
<nav-bar></nav-bar>
</div>
<div class="content">
<section id="One">
<header><h1>One</h1></header>
<p> A bunch of text</p>
</section>
<!-- A Bunch of Sections -->
</div>
</body>
component.js
export class NavList extends HTMLElement {
#template = `
<style>
.visible {
background-color: orange;
}
</style>
<menu></menu>
`;
constructor() {
super();
this.shadow = this.attachShadow({mode: "open"});
}
connectedCallback() {
const li = document.createElement('li');
const a = document.createElement('a');
this.tmpl = document.createRange().createContextualFragment(this.#template);
this.menu = this.tmpl.querySelector('menu');
this.anchors = document.querySelectorAll('[id]');
this.observer = new IntersectionObserver( entries => {
const entry = entries.shift();
const id = entry.target.getAttribute('id');
const link = this.menu.querySelector(`a[href="#${id}"]`);
Array.from(this.menu.querySelectorAll('a')).map(a => a.classList.remove('visible'));
link.classList.add('visible');
history.pushState({}, '', `#${id}`);
}, {threshold: 1});
for (let anchor of this.anchors) {
const item = li.cloneNode();
const link = a.cloneNode();
const id = anchor.getAttribute('id');
link.setAttribute('href', `#${id}`);
link.innerText = id;
link.addEventListener('click', evt => this.clicked(evt));
item.append(link);
this.menu.append(item);
this.observer.observe(anchor);
}
this.render();
}
disconnectedCallback() {
this.observer.disconnect();
}
clicked(evt) {
evt.preventDefault();
const target = evt.target.getAttribute('href');
const elem = document.querySelector(target);
elem.scrollIntoView({behavior: "smooth"});
history.pushState({}, '', `#${target}`);
}
render() {
this.shadow.append(this.tmpl);
}
}
customElements.define('nav-list', NavList);
Related
how to call wrap method when block-tune menu button clicked in Editor.js
Now I'm trying to create Nest function by block-tune-plugin in Editor.js. To do so, I wrote below code. render() { const wrapper = Dom.make("div"); const button = document.createElement("button"); button.classList.add(this.api.styles.settingsButton); button.innerHTML = "nest"; button.type = "button"; wrapper.appendChild(button); this.api.listeners.on(button, "click", (event) => { this.distance = this.distance + this.base; this.padding = this.distance + "rem"; button.classList.toggle(this.api.styles.settingsButtonActive); this.wrap(event); }); return wrapper; } wrap(blockContent) { this.wrapper = Dom.make("div"); this.wrapper.style.paddingLeft = this.padding; this.wrapper.append(blockContent); return this.wrapper; } However if I push button that is in block tune menu, the block don't act. What is the problem of this code? I tried with this code. In this wrap(), I set the default postion and try to toggle CSS class by count up variable. But in this way, I could change the classname but I can't get CSS effect render() { this.api.listeners.on(button, "click", (event) => { this.data = this.count; this.count += 1; this.wrap(event); }); return wrapper; } wrap(blockContent) { this.wrapper.classList.toggle(this.CSS.nest[this.data]); if (this.count === 2) { this.wrapper.style.paddingLeft = "10rem"; } this.wrapper.style.paddingLeft = "5rem";
Why isn't my classlist removing when using this constructor function
So i'm trying to remove my classlist of selected items when they are clicked again: const todo = [] const elements = [] const todoList = document.getElementById('todoList') const addItem = document.getElementById('textBox') const submit = document.getElementById('submit') const todoListItem = document.querySelectorAll('.todoListItem') const todoListItemArr = Array.from(todoListItem) const deleteItem = document.getElementById('deleteButton') let selected class Div { constructor(content) { this.content = content } addToScreen(){ const element = document.createElement('div') element.innerHTML = this.content element.classList.add('todoListItem') todoList.appendChild(element) } select(){ const element = document.querySelectorAll('.todoListItem') element.forEach(item => { item.addEventListener('click', () => { item.classList.add('selectedItem') selected = item }) }) } deselect() { const elements = document.querySelectorAll('.selectedItem') elements.forEach(item => { item.addEventListener('click', () => { item.classList.remove('selectedItem') }) }) } } function createItem() { const todoItem = new Div(addItem.value) todoItem.addToScreen() todo.push(todoItem.content) console.log(todo) addItem.value = '' todoItem.select() todoItem.deselect() } addItem.addEventListener('keypress', (e) => { if(e.key == 'Enter') { createItem() } }) submit.addEventListener('click', createItem) also I'm trying to add a delete function, but everytime i try to remove the element in the constructor by adding a delete function, my delete button does nothing, i've removed those features for now.
Mousemove problem with classes. If i'm moving the mouse out content disappears
I have a problem with the menu. Can somebody check? If I'm moving the mouse out content disappears. I think there is no problem with classes. class Menu{ constructor(id){ this.id = id; const linkBlock = document.querySelector(`.navigation__dropdown-block-${this.id}`); const menuLink = document.querySelector(`.navigation__link-${this.id}`); const menuLinks = document.querySelectorAll(`.navigation__link`); const linkBlockA = document.querySelectorAll('.navigation__dropdown-block--active') menuLink.addEventListener('mouseenter', () => { linkBlock.classList.add('navigation__dropdown-block--active'); menuLink.classList.add('navigation__link--active'); }); linkBlock.addEventListener('mouseenter', () => { linkBlock.classList.add('navigation__dropdown-block--active'); menuLink.classList.add('navigation__link--active'); }); menuLink.addEventListener('mouseleave', () => { linkBlock.classList.remove('navigation__dropdown-block--active'); menuLink.classList.remove('navigation__link--active'); }); linkBlock.addEventListener('mouseleave', () => { linkBlock.classList.remove('navigation__dropdown-block--active'); menuLink.classList.remove('navigation__link--active'); }); } } const menuLinks = document.querySelectorAll('.navigation__link'); for(let i = 0; i < menuLinks.length; i++){ new Menu(i + 1); }
Add/Remove classes to body on scroll
I have a function that adds a class current to a menu item when its corresponding section comes into view. How would I go about also adding/removing different classes to body as well, based on currently visible section? Edit: Per suggestion got it working finally with Intersection Observer but still trying to figure out how to add and swap classes to body: function setColorScheme() { const nav = (entries, observer) => { entries.forEach((entry) => { if (entry.isIntersecting && entry.intersectionRatio >= 0.55) { document.querySelector('li.current').classList.remove('current'); var id = entry.target.getAttribute('id'); var newLink = document.querySelector(`[href$='#${id}']`).parentElement.classList.add('current'); //returning error var newClass = $('body.home').classList.add('.' + id); } }); } const options = { threshold: 0.55, rootMargin: '150px 0px' }; const observer = new IntersectionObserver(nav,options); const sections = document.querySelectorAll('section.op-section'); sections.forEach((section) => {observer.observe(section);}); }
Buttons retrieved from local storage not working
So I'm a newb at web dev, and trying to get my head around JavaScript by writing a.. yeah, you guessed it, a to do list. I've been trying to set the items to the local storage, and then retrieve it, it sorta works, however when the list items are retrieved, the buttons do not seem to function, and I can't for the life of me figure out why... Any thoughts? Here's the code: document.addEventListener('DOMContentLoaded', () => { const submitButton = document.querySelector('.submit'); submitButton.type = 'submit'; const inputField = document.querySelector('.createItem'); const toDoUl = document.querySelector('.toDoUl'); const completedUl = document.querySelector('.completedUl'); const form = document.querySelector('#header'); const tdContainer = document.getElementById('tdContainer'); const toDoItems = document.getElementById('toDoItems'); (function loadStorage() { if (localStorage.getItem('todo')) { tdContainer.innerHTML = localStorage.getItem('todo'); } })(); function noChildren() { if (toDoUl.hasChildNodes()) { tdContainer.classList.remove('tdContainer'); } else { tdContainer.className = 'tdContainer'; } } function createLi() { const wrapper = document.getElementById('wrapper'); const doneButton = document.createElement('input'); const checkedLabel = document.createElement('label'); doneButton.type = 'checkbox'; checkedLabel.className = 'done'; checkedLabel.appendChild(doneButton); const listItem = document.createElement('li'); const p = document.createElement('p'); const editButton = document.createElement('button'); const removeButton = document.createElement('button'); toDoUl.appendChild(listItem); p.textContent = inputField.value; inputField.value = ''; editButton.className = 'edit'; removeButton.className = 'remove'; listItem.appendChild(checkedLabel); listItem.appendChild(p); listItem.appendChild(editButton); listItem.appendChild(removeButton); doneButton.style.display = 'none'; editButton.addEventListener('click', () => { listItem.contentEditable = 'true'; }); listItem.addEventListener('blur', () => { listItem.contentEditable = 'false'; }); removeButton.addEventListener('click', e => { const ul = e.target.parentNode.parentNode; /*const li = e.target.parentNode.parentNode;*/ ul.removeChild(e.target.parentNode); noChildren(); }); doneButton.addEventListener('click', e => { if (e.target.parentNode.parentNode.parentNode.className === 'toDoUl') { completedUl.appendChild(e.target.parentNode.parentNode); e.target.parentNode.parentNode.className = 'removeTransition'; noChildren(); localStorage.setItem('todo', tdContainer.innerHTML); } else { toDoUl.appendChild(e.target.parentNode.parentNode); e.target.parentNode.parentNode.className = 'addTransition'; noChildren(); } }); } form.addEventListener('submit', e => { e.preventDefault(); noChildren(); createLi(); localStorage.setItem('todo', tdContainer.innerHTML); }); }); You can see the working version here: http://kozyrev.site/todo/
i'm glad you're writing arrow functions and cool stuff :D but it seems that you are setting event listeners within createLi function, that is dispatched on form's submit event. But, when you loads localStorage, is setting HTML content like this: tdContainer.innerHTML = localStorage.getItem('todo'); event listener is not attached to them, because all of these elements that you created from localStorage, is not create by createLi function :( but you might write something like this: // loads from localStorage (function loadStorage() { if (localStorage.getItem('todo')) { tdContainer.innerHTML = localStorage.getItem('todo'); } })(); // set listeners below var liSelector = '.toDoUl > li' var liElements = document.querySelectorAll(liSelector) Array.prototype.forEach.call(liElements, (liElement) => { var editButton = liElement.querySelector('.edit') console.log(editButton) // you can set listeners here editButton.addEventListener('click', (e) => { e.preventDefault() console.log('yey, event has dispatched, do your magic :)') }) }) UPDATE: example using named function to reuse them: function createLi() { .... const listItem = document.createElement('li'); .... editButton.addEventListener('click', () => { listItem.contentEditable = 'true'; }); could be written like this // this function at top of script const setEditable = (listItem) => { listItem.contentEditable = 'true'; } // you can use setEditable within createLi function createLi() { .... const listItem = document.createElement('li'); .... editButton.addEventListener('click', () => { setEditable(listItem) }); also, after HTML was written from localStorage like this // loads from localStorage (function loadStorage() { if (localStorage.getItem('todo')) { tdContainer.innerHTML = localStorage.getItem('todo'); } })(); // set listeners below var liSelector = '.toDoUl > li' var liElements = document.querySelectorAll(liSelector) Array.prototype.forEach.call(liElements, (listItem) => { var editButton = listItem.querySelector('.edit') // you can set listeners here editButton.addEventListener('click', (e) => { setEditable(listItem) }) }) I didn't tested, but i hope it works, and it's shows you that named function could be reused for setting listeners :)