I want to ensure that when I click on the divs (A, B, C), the link of the button changes and gets the values of the data attributes in the appropriate places. I wrote a small script, but it does not work, and there is still not enough knowledge to understand exactly where I went wrong. Any help would be welcome.
document.getElementById("product").onclick = function() {
document.getElementById("purchase").href =
"/?add-to-cart=" + this.data-product +
"&variation_id=" + this.data-id + "/";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="product__items" id="product">
<div data-id="338" data-product="A" id="uI-1" class="items-uniqueItem">A</div>
<div data-id="339" data-product="B" id="uI-2" class="items-uniqueItem">B</div>
<div data-id="340" data-product="C" id="uI-3" class="items-uniqueItem">C</div>
<div class="product__items---btn">
Button
</div><!-- btn -->
</div>
You have several problems here.
First, I suggest you consult the documentation for HTMLElement.dataset or jQuery's .data().
Also, if you intend on using event delegation, you can't use this to refer to the event source element in a vanilla event listener as it will refer to the delegate.
Since you do have jQuery involved, you might as well use it since it makes this a lot easier (see also vanilla JS version below)
const button = $("#purchase")
$("#product").on("click", ".items-uniqueItem[data-id][data-product]", function() {
// Due to the selector above, `this` is now the clicked `<div>`
// Extract data properties
const { product, id } = $(this).data()
// Construct URL parameters
const params = new URLSearchParams({
"add-to-cart": product,
"variation_id": id
})
// Set the `href`
button.prop("href", `/?${params}/`)
})
/* this is just for visibility */
.items-uniqueItem{cursor:pointer;}#purchase{display:block;text-decoration:none;margin: 1rem;}#purchase:after{content:attr(href);display:block;color:#ccc;margin:.5rem;}
<!-- your HTML, just minified -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script><div class="product__items" id="product"><div data-id="338" data-product="A" id="uI-1" class="items-uniqueItem">A</div><div data-id="339" data-product="B" id="uI-2" class="items-uniqueItem">B</div><div data-id="340" data-product="C" id="uI-3" class="items-uniqueItem">C</div><div class="product__items---btn">Button</div></div>
A vanilla JS version would look something more like this. You can use Element.closest() to locate the delegated event source
const button = document.getElementById("purchase")
document.getElementById("product").addEventListener("click", e => {
// find the required event source element
const el = e.target.closest(".items-uniqueItem[data-id][data-product]")
if (el) {
// Extract data properties
const { product, id } = el.dataset
// Construct URL parameters
const params = new URLSearchParams({
"add-to-cart": product,
"variation_id": id
})
// Set the `href`
button.href = `/?${params}/`
}
})
.items-uniqueItem{cursor:pointer;}#purchase{display:block;text-decoration:none;margin: 1rem;}#purchase:after{content:attr(href);display:block;color:#ccc;margin:.5rem;}
<!-- your HTML, just minified -->
<div class="product__items" id="product"><div data-id="338" data-product="A" id="uI-1" class="items-uniqueItem">A</div><div data-id="339" data-product="B" id="uI-2" class="items-uniqueItem">B</div><div data-id="340" data-product="C" id="uI-3" class="items-uniqueItem">C</div><div class="product__items---btn">Button</div></div>
As you can see, it's not very different to the jQuery version so maybe you might not need jQuery
I've never personally used the element.onlick = function() {...} notation, so I'll be usingelement.addEventListener('click', (e) => ...), but it should work the same way.
What you are doing is selecting the object that has the id "product". But "product" is the parent os the elements you want to select.
If you want to select several elements and do something with them, you can't use the id attribute, since id is unique for html page. So you'll want to use classes for that.
Create a class and add that class to each child (the ones with the data-product).
Select all children with .querySelectorAll(). Here is the doc. This returns a NodeList, but it's similar to an Array.
Iterate thought the List with a .forEach(item => ...) where item represents each element of the list.
Add an Event Listener (or .click, I guess) on each item.
*theList*.forEach( (item) => {
item.addEventListener('click', (event) => {
event.target.href = "/?add-to-cart=" + event.target.dataset.product + "&" + "variation_id=" + event.target.dataset.id + "/";
})
));
To access a dataset in JS you use the .dataset property.
First, grab all the divs that have a given class so that we can use their data.
const items = document.querySelectorAll('.items-uniqueItem');
items.forEach(item => item.addEventListener('click', (e) => console.log(e.target)))
Then inside you click handler you can get the button reference and assign the properties you want to get from it.
Related
I want to build a note taker app with html css and js but when i want add second note there is a problem.
let myNote = "";
let myTitle = "";
let noteInput = document.getElementById("note-input");
let titleInput = document.getElementById("title-input");
let title = document.getElementById("title");
let note = document.getElementById("first-note-p");
let addButton = document.getElementById("addButton");
let removeButton = document.getElementById("remove-button");
let newDiv = document.createElement("div");
let newP = document.createElement("p");
let newH3 = document.createElement("h3");
let newButton = document.createElement("button");
let notePlace = document.getElementById("note-place");
let button = document.getElementsByTagName("button");
let div = document.getElementsByTagName("div");
let paragrapgh = document.getElementsByTagName("p");
let head3 = document.getElementsByTagName("h3");
let tally = 0;
const addNote = () => {
myNote = noteInput.value;
myTitle = titleInput.value;
notePlace.appendChild(newDiv);
div[tally].appendChild(newH3);
div[tally].appendChild(newP);
div[tally].appendChild(newButton);
notePlace = document.getElementById("note-place");
head3[tally].innerText = myTitle;
paragrapgh[tally].innerText = myNote;
button[tally + 1].innerText = "remove";
tally += 1;
};
const removeNote = () => {
title.innerHTML = "";
note.innerHTML = "";
};
addButton.onclick = addNote;
<h1>Take your notes</h1>
<input id="title-input" onfocus="this.value=''" type="text" value="title" />
<input id="note-input" onfocus="this.value=''" type="text" value="note" />
<button id="addButton">add</button>
<div id="note-place"></div>
I use addNote function to add a new note but for second note I encounter to the following error.
Cannot set properties of undefined (setting 'innerText')
at HTMLButtonElement.addNote (notetaker.js:37:26)
Sorry for my bad English.
The main problem with your attempt is that you're selecting the elements before actually creating and appending them to the DOM and that will lead to problems because those elements that were initially selected are no longer there when a new note is added.
The fix is fairly easy, select the elements at the time you create a new note. Actually, I won't just stop here and I will happily invite you to follow along with my answer as we approach your task (of making notes and showing them in the screen) in a better approach that, i think, will be more helpful than just giving a fix.
So, here's what we're going to do, we're firstly go by tackling the task and see what are the main sub-tasks to do in order to have a working demo (with add and remove notes features):
To have a better performance, we'll select and cache the elements that we will use extensively in our task. Mainly, the element div#note-place should be cached because we're going to use many times when we add and remove notes.
The inputs, for the note title and text, the button that adds a note, those elements should be cached as well.
The main thing we will be doing is creating some elements and appending them to div#note-place so we can assign that sub-task to a separate function (that we will create). This function will create an element, add the wanted attributes (text, class etc...) then it returns that created element.
At this stage, our solution has started to take shape. Now, to create a note we will listen for the click event on the add note button and then we will have a listener that will handle the creation of the new note based on the values found on the inputs and then append that to the DOM. We will use addEventListener to attach a click event listener on the add note button (modern JS, no more onclicks!).
Now, for the remove note feature. The initial thinking that comes to mind is that we will listen for click events on the remover buttons and then do the work. This can work, but here's a better solution, Event Delegation, which basically allow us to have 1 listener set on div#note-place element that will call the remove note logic only when a remove button was clicked (see the code below for more info).
So, let's not take more time, the live demo below should allow you to easily understand what's being said:
/** cache the elemnts that we know we will use later on */
const notesContainer = document.getElementById('note-place'),
titleInp = document.getElementById('title-input'),
noteInp = document.getElementById('note-input'),
addNoteBtn = document.getElementById('add-note-btn'),
/** this class will be added to all remove note buttons This will allow us to catch clicks on those buttons using event delegation */
noteRemoverBtnClass = 'note-remover-btn',
/**
* a simple function that create an element, add the requested attribute and return the newly created element.
* tag: the tag name of the element to create (like div, h3 etc...).
* text: the text to show on the element (using textContent attribute).
* attributes: an object that holds "key: value" pairs where the keys are the attributes (like id, type etc...) and the values are the values for each attribute set on that parameter (see usage below).
*/
createElement = (tag, text, attributes) => {
const el = document.createElement(tag);
attributes = attributes || {};
!!text && (el.textContent = text);
for (let attr in attributes)
attributes.hasOwnProperty(attr) && el.setAttribute(attr, attributes[attr]);
return el;
};
/** listen for click events on the add note button */
addNoteBtn.addEventListener('click', () => {
/** create a div that will wrap the new note */
const noteEl = createElement('div');
/**
* create an "h3" for the note title, a "p" for the note text and a "button" that acts as the remove note button
* then loop through them and add them to the note wrapper that we just created
*/
[
createElement('h3', titleInp.value),
createElement('p', noteInp.value),
createElement('button', 'Remove', {
type: 'button',
class: noteRemoverBtnClass
})
].forEach(el => noteEl.appendChild(el));
/** append the entire note element (including the "h3", "p"p and "button" to "div#note-place" */
notesContainer.appendChild(noteEl);
});
/** implement event delegation by listening to click events on "div#note-place" and execute a set of logic (to remove a note) only when the clicked element is actually a remove button (thanks to "noteRemoverBtnClass" that we add to each created remove button) */
notesContainer.addEventListener('click', e => e.target.classList.contains(noteRemoverBtnClass) && e.target.parentNode.remove());
<h1>Take your notes</h1>
<input id="title-input" onfocus="this.value=''" type="text" value="title" />
<input id="note-input" onfocus="this.value=''" type="text" value="note" />
<button id="add-note-btn">add</button>
<div id="note-place"></div>
The above code sample is definitely NOT the only way to get things done, it only aims to be simple while recommending the use of some modern JS technics and logics. There always be more ways to do the task and even some better ways to do it.
My list is being populated with this block of code:
function addToHistory(cityName) {
let searchHistory = JSON.parse(localStorage.getItem("Weather Search History")) || [];
searchHistory.push(cityName);
localStorage.setItem("Weather Search History", JSON.stringify(searchHistory));
};
function updateHistory() {
let searchHistory = JSON.parse(localStorage.getItem("Weather Search History")) || [];
$("#searchHistory").html(searchHistory
.map(searchHistoryList => {
return (`<li><button class="btn btn-link"> ` + searchHistoryList + `</button></li>`);
})
.join(""));
};
and that works great. It pulls from an array in local storage that is created each time the user enters a search term. Then populates the site's sidebar with said list.
However, I'm not sure how to then take the text values of the buttons out so that I may manipulate it.
Currently have:
$('#searchHistory').on('click', function () {
console.log($(???).val());
});
You want .text() or innerText (plain JavaScript). this refers to the current element. You can also use event.target.
$('#searchHistory').on('click', function () {
console.log($(this).text());
});
Try this in your function:
console.log($(this).innerHTML());
"this" refers to the specific element that triggered the click event.
As I have made the table body in my javascript which is shown in the below code. As I am new to programming, can anyone tell how to make data of each table row into querystring then pass to another page like html? Thank in advance
var Ref = firebase.database().ref().child("posts");
Ref.on("child_added", snap => {
var name = snap.child("name").val();
var region = snap.child("region").val();
var form = snap.child("form").val();
var code = snap.child("code").val();
$("#table_body").append("<tr><td><a href='postlist.html?key='>" + code + "</td><td>" + name + "</td><td>" + region + "</td><td>" + form. +"</a></td><td>");
$("#table_body").off("click").on( "click", "tr", function() {
});
I would use an HTML5 data attribute on each row to associate the table row with a record held in a persistent array. The click handler would read the data-attribute, pull the target row from the array in order to construct the URL.
So...
const posts = []
const Ref = firebase.database().ref().child("posts")
Ref.on("child_added", snap => {
posts.push({
'name': snap.child("name").val(),
'region': snap.child("region").val(),
'form': snap.child("form").val(),
'code': snap.child("code").val()
})
})
Then, use that array to generate the table:
posts.forEach((post, i) => {
$("#table_body")
.append(`
<tr data-post-index="${i}">
<td>${post.code}</td>
<td>${post.name}</td>
<td>${post.region}</td>
<td>${post.form}</td>
</tr>
`);
})
I removed an <A> tag that was being used badly. You cannot start an <A> in one table cell and then close it in a different table cell. And, since it seems you want row-clicks to be handled using javascript, that anchor is superfluous.
Finally, let's set up the click handler. It will rely on the fact that each row has an HTML attribute that indicates the index of the full record in the array.
$("#table_body").off("click").on( "click", "tr", function() {
const postIndex = $(this).attr('data-post-index')
const post = posts[postIndex]
window.location = `postlist.html?key=${post.code}` // or whatever
});
Warning: it's been a while since I used jquery. I seem to recall that jquery event handlers receive the clicked element as this. If that's not the case, you might need to do a little more work inside that handler to identify the relevant <TR> from the event.
If that's the case, post a comment and I can dig into it. Or you could do a little research and figure it out yourself.
I am trying to create an extension for VSTS using their extension kit (https://learn.microsoft.com/en-us/vsts/extend/overview?view=vsts).
<script type="text/javascript">
VSS.init();
var items = {}
// Get data service and display
VSS.getService(VSS.ServiceIds.ExtensionData).then((dataService) => {
dataService.getDocuments('MyCollection2').then((docs) => {
// keep a reference to the element instead of searching for it in each loop.
const itemsDiv = document.getElementById('items');
const contents = [];
for (let i = 0; i < docs.length; i++) {
// using template strings here to show you another way of working with strings in es6
var name = docs[i].name
contents.push(
`<div
class="listItem"
onClick="console.log(docs[i])"
onmouseover="this.style.background='#D5DBDB';"
onmouseout="this.style.background='white';">
${docs[i].name}
</div>`
)
}
// finally update the target element one time with your contents.
// The new line character isn't required, can just use '',
// but this might be easier to read for you
itemsDiv.innerHTML = contents.join('');
});
});
</script>
So what my javascript part does is I try to fetch objects from VSTS`s internal data storage (I named it MyCollection2) and display the objects as a list
HTML part
<section>
<nav>
<div class="create_button">+ Create KPI</div>
<div id="items"></div>
</nav>
<article>
<h2>Create KPI</h2>
<br>
<form action="" id="form" onsubmit="sConsole(event)">
KPI Name<br>
<input type="data" id="name">
<br><br>
Actual Value<br>
<input type="data" id="actual">
<br><br>
Potential Value<br>
<input type="data" id="potential">
<br><br>
Goal %<br>
<input type="data" id="goal">
<br><br>
<button type="submit">Create</button><span>Cancel</span>
</form>
</article>
</section>
So all the objects are rendered in the div with the id items.
Everything is fine up to this point.
The problem is the onClick="console.log(docs[i]) part in my javascript part.
My intention was to console.log the document object whenever each item in the list was clicked.
However, this doesn't print the object as I intended.
It just prints externalContentHost10 and I don't know what that is.
What can I do to make this work?
docs is defined in your function; the scope of the onclick attribute (note: should be all lowercase) is not the same. In general, you should avoid inline event handlers as they’re not very flexible or maintainable. You should instead use addEventListener, which means ditching innerHTML and working with proper element nodes. A few other changes I would make are:
Flattening the promises (removing the nesting) by returning them
Using for...of for iteration
Using const (and let, but in this case const is enough) instead of var so that your variables have the right scope
This gives us:
VSS.init();
const items = {};
// Get data service and display
VSS.getService(VSS.ServiceIds.ExtensionData)
// the callback on the next line returns a promise, which the JavaScript engine will follow, so you don't need to nest the next `then`
.then((dataService) => dataService.getDocuments('MyCollection2'))
.then((docs) => {
// keep a reference to the element instead of searching for it in each loop.
const container = document.getElementById('items');
// this loop will remove any existing children
while (container.firstChild !== null) {
container.removeChild(container.firstChild);
}
// `for...of` is a simpler way to iterate over a collection
for (const doc of docs) {
// create a `div` element
const div = document.createElement("div");
// add a text node to it
div.appendChild(document.createTextNode(doc.name));
// add event listeners to change its background
div.addEventListener("mouseover", e => { div.style.background = "#D5DBDB"; });
div.addEventListener("mouseout", e => { div.style.background = "white"; });
// add a `click` listener
div.addEventListener("click", e => { console.log(doc); });
// add the new div to the container
container.appendChild(div);
}
});
If you wanted to use classes instead to manage the styling—which is the recommended method—then you could implement the event listeners using classList:
div.addEventListener("mouseover", e => div.classList.add("hover-class"));
div.addEventListener("mouseout", e => div.classList.remove("hover-class"));
(classList has toggle and replace methods, but they aren’t supported by IE at all, and Edge only seems to support toggle, so whether to use them depends on your minimum supported version.)
But you would probably be better off defining a CSS :hover class rather than doing all this, if styling is all you want to change.
var = cooldynamicelement
How could I store the inner html I grab with jQuery from my div ie. <div class="username"> </div> to store as an accessible variable in jQuery eg. cooldynamicelement so I can grab and use at different areas of my site by just calling ie. $cooldynamicelement and updates with the dynamic .username element value.
1. Store HTML into localStorage
var dynamicElementHTML = localstorage.dynamicElementHTML || $(".username").html() || "";
localstorage["dynamicElementHTML"] = dynamicElementHTML;
To make it available to other pages a way would be to use the power of localstorage
https://developer.mozilla.org/en/docs/Web/API/Window/localStorage
If you're actually interested in the whole element (not only it's inner HTML) than instead of .html() use .prop("outerHTML")
2. Binding using jQuery (essential idea)
If you only want a way to reflect some variable HTML as actual html and make it alive you could do like:
var $myElement = $("<div />", {
class : "userData",
append : $someDynamicElements,
appendTo : $someParentElement,
on : {
contentUpdate : function() {
$(this).html( $someDynamicElements );
}
}
});
than whenever your $someDynamicElements changes you can trigger a contentUpdate
$myElement.trigger("contentUpdate")
3. Binding using jQuery (concept)
Here's the same elements binding concept gone wild:
// Here we will store our elements
var EL = {};
// Create desired HTML elements like this:
var LIST = {
username: $("<b/>", {
html : "UNKNOWN",
click : function() {
alert( $(this).text() );
}
}),
email: $("<a/>", {
html : "test#test.test",
href : "mailto:"+ "test#test.test"
}),
// add more here, you got the idea.
// don't forget that you can assign any JS / jQuery propery to your element.
// You can go insane using .on() and later .trigger()
};
// Our small "program" that replaces data-bind elements
// with dynamic elements from our list
$("[data-bind]").replaceWith(function(i){
var bind = this.dataset.bind;
if(!LIST[bind]) return;
if(!EL.hasOwnProperty(bind)) EL[bind] = [];
var klon = LIST[bind].clone(true)[0];
EL[bind].push(klon);
return klon;
});
// That's it. Now goes your code ///////////////
$(EL.username).css({color:"red"}); // just to test if it works :D
$("[data-target]").on("input", function(){
var target = this.dataset.target;
$(EL[target]).html( this.value );
});
// P.S: Even having thousands of elements inside EL
// say you have "EL.tableRows" you can do fabulously
// quick stuff like i.e: sorting, cause you iterate over a plain JS array.
// After the sorting of EL.tableRows is done and you need a jQuery
// representation simply use $(EL.tableRows).
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h2>Dynamic element Binding in jQuery</h2>
Enter some text and see the update trigger in different places<br>
<input data-target="username"><br>
Welcome <span data-bind="username"></span> !!<br>
You name is <span data-bind="username"></span> Click the red text!<br>
<span data-bind="email"></span>
Well if you want to have the jqueryObject in a variable, just do this:
$(function(){
window.$cooldynamicelement = $("div.username");
})
that way you're able to use $cooldynamicelement in a global context. If is that what you want. This way you're saving a reference to your .username element and thus every time you use it will be updated.
NOTE: If you decide to do this, be careful with polluting your global context.: