I'm creating a dynamic <ul>, by creating a dynamic <li> tags list based on a template.
the <li> template looks something like that:
<script type="text/html" id="itemTemplate">
<li id="{{id}}">
<div class="name" title="{{name}}">{{name}}</div>
<div class="saveAs"></div>
<div class="copy"></div>
</li>
</script>
My goal is to make the saveAs and the copy div clickable and execute a function with the id as a parameter.
Iv'e managed to do that by this function:
function myView() {
self.itemTemplate = null;
self.myArrayOfObjects = null;
self.initItemsUl = () => {
self.itemsUl = self.mainContainer.find('.itemsUl');
self.myArrayOfObjects.forEach(self.initItemLi);
};
self.initItemLi = (item) => {
var viewObj = {
id: item.Id,
name: item.Name
};
var itemLi = $(Mustache.render(self.itemTemplate, viewObj));
self.mainContainer.append(itemLi[0]);
self.setupItemOnClick(itemLi, item.Id);
};
self.setupItemOnClick = (itemLi, id) => {
itemLi.find('.saveAs').on('click', null, () => {
//do_something(id)
});
itemLi.find('.copy').on('click', null, () => {
//do_something(id)
});
};
return {
init: (myArrayOfObjects) => {
self.myArrayOfObjects = myArrayOfObjects;
self.itemTemplate = $('#itemTemplate').html();
Mustache.parse(self.itemTemplate);
self.initItemsUl();
}
};
}
Pay attention that the function setupItemOnClick is being called every time i'm rendering the li template, my question is how to make this function to be called only once?
Use event delegation on the ul, rather than handlers on the individual .saveAs and .copy elements:
$("selector-for-your-ul")
.on("click", ".saveAs", e => {
// Handle click here, `e.currentTarget` is the `.saveAs` that was clicked
})
.on("click", ".copy", e => {
// Handle click here, `e.currentTarget` is the `.copy` that was clicked
});
Re your comment:
...how do i get the id of the parent li out of the e object?
By using $(e.currentTarget).closest("li").attr("id").
Related
I have a scenario where I wanted to load a page on inital load. I found that this would do the trick:
<main id="mainContent">
<iframe id="InitalIframe" src="./Pages/Start/index.html" onload="this.before((this.contentDocument.body||this.contentDocument).children[0]);this.remove()"></iframe>
</main>
I have some links in my header which I attach click listners to:
(function() {
document.querySelectorAll(".link").forEach((item) => {
item.addEventListener("click", (event) => {
event.preventDefault();
const url = event.target.dataset["url"];
getHtmlFile(`./Pages/${url}/`, (data) => {
document.getElementById("mainContent").innerHTML = data;
executeScripts();
});
return false;
});
});
})();
This worked untill I added a few links inside of the Start/Index.html file which gets renderd via the iframe.
I have these two buttons inside of that html.
<button type="button" class="link refbtn" data-Url="One">
One
</button>
<button type="button" class="link refbtn" data-Url="Two">
Two
</button>
Since I attached my listners before the iframe has loaded they never get picked up.
But when I waited for the iframe to load:
document.getElementById("InitalIframe").onload = function() {
document.querySelectorAll(".link").forEach((item) => {
item.addEventListener("click", (event) => {
event.preventDefault();
const url = event.target.dataset["url"];
getHtmlFile(`./Pages/${url}/`, (data) => {
document.getElementById("mainContent").innerHTML = data;
executeScripts();
});
return false;
});
});
};
the click events did not get attached and I got a weird looking result on the page.
Question is how do I accomplish this?
For anyone struggling with the same:
I made my life easier by listening for document click events. when I found that an element with a certain class was clicked I triggered desierd functions:
document.addEventListener(
"click",
function(event) {
event.preventDefault();
const classes = event.target.classList;
if (classes.contains("link")) {
const url = event.target.dataset["url"];
app.fetcHtml(`./Pages/${url}/`, (data) => {
document.getElementById("mainContent").innerHTML = data;
});
}
return false;
},
false
);
You can get to work this with a different approach also. In other words, I had the method closest() in pure Javascript finding me the closest event target that is trigerred when I click inside the container. It will always get me the nearest <a> element which I clicked when I had wandered throught the clickable div/container area.
let base; // the container for the variable content
let dataSet;
base = document.getElementById(base.id); // target your Iframe in this case.
base.addEventListener('click', function (event) {
let selector = '.link'; // any css selector for children
// find the closest parent of the event target that
// matches the selector
let closest = event.target.closest(selector);
if (closest !== undefined && base.contains(closest)) {
dataSet= event.target.dataset["url"];
app.fetcHtml(`./Pages/${url}/`, (data) => {
document.getElementById("mainContent").innerHTML = data;
});
}
});
You can try and see if it works like this also.
Cheers
I am currently trying to sort all my game objects alphabetically by title. I have gotten my onclick to register but it does not execute my JS function below is the HTML and JS snippets. The sortAlpha is a method within the Games class.
*edit 1: Adjusted function to attach a event listener
*edit 2: I have opted to create a variable to store and call the function. My next question is am I not correctly displaying the newly alphabetized contents with my function ? I get no errors and my log shows that clicks are being registered.
<div id="filter">
<div id="buttons">
<button onclick="let the_game = new Games(); the_game.sortAlpha()">Sort Games Alphabetically</button>
</div>
</div>
class Games {
constructor() {
this.games = []
this.adapter = new GamesAdapter()
this.initBindingsAndEventListeners()
this.fetchAndLoadGames()
}
initBindingsAndEventListeners() {
this.newGameForm = document.getElementById('new-game-form')
this.newGameTitle = document.getElementById('new-game-title')
this.newGameDeveloper = document.getElementById('new-game-developer')
this.newGameCover = document.getElementById('new-game-cover')
this.newGameForm.addEventListener('submit', this.createGame.bind(this))
}
createGame(g) {
g.preventDefault();
const titleValue = this.newGameTitle.value;
const developerValue = this.newGameDeveloper.value;
const coverValue = this.newGameCover.value;
this.adapter.createGame(titleValue, developerValue, coverValue)
.then(game => {
const newGame = new Game(game)
this.games.push(newGame)
this.newGameTitle.value = ' '
this.newGameDeveloper.value = ' '
this.newGameCover.value = ' '
newGame.renderGameBlock()
})
}
fetchAndLoadGames() {
this.adapter
.getGames()
.then(games => {
games.forEach(game => this.games.push(new Game(game)))
})
.then(() => {
this.renderGames()
})
}
renderGames() {
this.games.map(game => game.renderGameBlock())
}
sortAlpha() {
console.log('test');
this.games.sort(function(gameA, gameB){
if (gameA.title < gameB.title) {
return -1;
}
if (gameA.title > gameB.title){
return 1;
}
return 0;
});
window.onload = function() {
document.getElementById("filter").onclick = sortAlpha;
}
}
}
The sortAlpha is a method within the Games class.
That's your answer right there. sortAlpha is not a free-function, but your onclick="" attribute references sortAlpha() as though it were a free-function.
(A "free function" is a function that is not a member of a class/type)
You need to move your sortAlpha function to the global scope, or attach it to the button using addEventListener. You cannot use class method functions in onclick="" without fully qualifying them.
How to short this code and which array method i can use here. I want to add class on every click on a single element and rest of elements remove class.
const activeLink = document.querySelectorAll(".nav-item");
activeLink[0].addEventListener("click", () => {
activeLink[0].classList.add("active");
activeLink[1].classList.remove("active");
activeLink[2].classList.remove("active");
activeLink[3].classList.remove("active");
});
activeLink[1].addEventListener("click", () => {
activeLink[1].classList.add("active");
activeLink[0].classList.remove("active");
activeLink[2].classList.remove("active");
activeLink[3].classList.remove("active");
});
activeLink[2].addEventListener("click", () => {
activeLink[2].classList.add("active");
activeLink[0].classList.remove("active");
activeLink[1].classList.remove("active");
activeLink[3].classList.remove("active");
});
activeLink[3].addEventListener("click", () => {
activeLink[3].classList.add("active");
activeLink[0].classList.remove("active");
activeLink[1].classList.remove("active");
activeLink[2].classList.remove("active");
});
Rather then looping through all items per click, you can just keep track of which item is currently active and reset this one.
https://codesandbox.io/s/compassionate-northcutt-50ucp?file=/src/index.js
let activeLink = null;
document.querySelectorAll(".nav-item").forEach(navItem => {
navItem.addEventListener("click", () => {
if (activeLink) {
activeLink.classList.remove("active");
}
navItem.classList.add("active");
activeLink = navItem;
});
});
I've worked with event delegation in the past but for some reason I'm having trouble setting up a single event listener that executes one of three functions depending on the ID of the element clicked.
Here's the code without event delegation:
eventListeners: function() {
document.getElementById("button-1").addEventListener('click',
function() {
shuffler.reset1();
shuffler.displayCards();
});
document.getElementById("button-2").addEventListener('click', function() {
shuffler.reset2();
shuffler.displayCards();
});
document.getElementById("button-3").addEventListener('click',
function() {
shuffler.reset3();
shuffler.displayCards();
});
I've tried using something along the lines of:
document.getElementsByClass("button").addEventListener('click', function
() {
if (event.target.id == "button-1") {
shuffler.reset1();
}
});
Attach the listener to the container that contains all buttons. Then, I'd use an object indexed by id, and check if the id of the element that was clicked exists in the object - if so, run the function:
const fns = {
'button-1': () => {
shuffler.reset1();
shuffler.displayCards();
},
// ...
}
document.querySelector('< something that contains all buttons >').addEventListener('click', ({ target }) => {
const { id } = target;
if (fns[id]) {
fns[id]();
}
});
Note that in this particular case, you can use just one function by checking the last number in the ID:
document.querySelector('< something that contains all buttons >').addEventListener('click', ({ target }) => {
const { id } = target;
if (id.startsWith('button-')) {
const buttonNum = id.match(/\d+/)[0];
shuffler['reset' + buttonNum]();
shuffler.displayCards();
}
});
I m building a menu bar which has current class which is highlighted when user clicks.
how do i make it so when someone clicks on li it removes from previous li and put it on new li which is clicked on ??
here is my code
li.forEach(li => {
li.addEventListener('click', () => {
li.classList.add("selected");
li.style.background = '';
li.style.paddingTop = '';
});
});
You could do something like this
li.forEach(li => {
li.addEventListener('click', () => {
removeClass();
li.classList.add("selected");
li.style.background = '';
li.style.paddingTop = '';
});
});
function removeClass () {
li.forEach(li => {
li.classList.remove("selected");
})
}
how about this
items.forEach(li => {
li.addEventListener('click', () => {
items.forEach(li => li.classList.remove("selected"));
li.classList.add("selected");
li.style.background = '';
li.style.paddingTop = '';
});
});
One simple approach is the following:
li.forEach(li => {
li.addEventListener('click', () => {
// find the parent-node of the current <li> element:
li.parentNode
// use Element.querySelector() to find the current
// <li> element with the 'selected' class:
.querySelector('li.selected')
// use classList.remove() to remove that class-name:
.classList.remove('selected');
// as before:
li.classList.add("selected");
// why is the following necessary? It *seems* to be
// undoing something that shouldn't be necessary:
li.style.background = '';
li.style.paddingTop = '';
});
});
References:
Element.classList.
Element.querySelector().