i am creating a multiple dynamic tooltips using tippyjs library on a page that fetches content using fetch api.
how do i access the data attribute on each of the selector while the initialisation of the tooltip.
here is what i have
Code
<span class="order-tooltip" data-orderid="123456">Order ID 123456</span>
<span class="order-tooltip" data-orderid="454515">Order ID 454515</span>
<span class="order-tooltip" data-orderid="487848">Order ID 487848</span>
<span class="order-tooltip" data-orderid="154214">Order ID 154214</span>
<div id="tooltipTemplate" style="display: none;">
Loading data...
</div>
<script>
const template = document.querySelector('#tooltipTemplate')
const initialText = template.textContent
const tip = tippy('.order-tooltip', {
animation: 'shift-toward',
arrow: true,
html: '#tooltipTemplate',
onShow() {
// `this` inside callbacks refers to the popper element
const content = this.querySelector('.tippy-content')
if (tip.loading || content.innerHTML !== initialText) return
tip.loading = true
console.log($('.order-tooltip').data('orderid')) // This is not working
var orderid = $(this).data('orderid');
var url = "/fetch_position_tooltip?" + $.param({orderid: orderid})
fetch(url).then(resp => resp.json()).then (responseJSON =>{
content.innerHTML = responseJSON
tip.loading = false
}).catch(e => {
console.log(e)
content.innerHTML = 'Loading failed'
tip.loading = false
})
},
onHidden() {
const content = this.querySelector('.tippy-content')
content.innerHTML = initialText
},
// prevent tooltip from displaying over button
popperOptions: {
modifiers: {
preventOverflow: {
enabled: false
},
hide: {
enabled: false
}
}
}
})
</script>
i need to access the data attribute for each of the span element when instantiating the toolitip .
How could i do this?
Contacted the maintainer of the library
Any one looking for this can use.
this._reference
Related
I'm trying to add a bootstrap card inside a div called [itemscontainer] using javascript
by document.getElementById("itemscontainer").innerHTML so i want the cards to be inserted inside the itemscontainer only one time like this :-
but the problem is the items cards keeps reapet them salves more than one time like:-
what i want is to clear the itemscontainer first before adding the cards and this is what i have tried so that the items will be only one cards for each item
// clear function
function clear(){
document.getElementById("ssst").innerHTML = ""
}
// listener append all items to the inventory
window.addEventListener('message', (event) => {
let data = event.data
if(data.action == 'insertItem') {
let name = data.items.name
let count = data.items.count
let icon = data.items.icon
if(document.getElementById("ssst").innerHTML == ""){
clear()
}else{
document.getElementById("ssst").innerHTML +=
"<div class='card holder'>"+
'<div class="card-body">'+
'<img src="icons\\'+icon+'" style="position:absolute;left:15%;width:40px; height:36px;" alt="">'+
'<h4 id="counter">'+count+'</h4>'+
'</div>'+
'<span class="itemname">'+name+'</span>'+
'</div>";'
}
}
})
The real solution is to figure out why you are getting the items more than once. With the information you provided that is impossible for me to answer. So the only thing we can recommend is how to prevent items from being added more than once.
If your messaging system returns duplicates you can determine if you have seen it. If you do, replace it. Otherwise add it.
window.addEventListener('message', (event) => {
const data = event.data;
console.log(data)
if (data.action == 'insertItem') {
let name = data.items.name
let count = data.items.count
let icon = data.items.icon
const html = `
<div class='card holder' data-name="${name}">
<div class="card-body">
<img src="icons/${icon}" style="position:absolute;left:15%;width:40px; height:36px;" alt="${icon}">
<h4 id="counter">${count}</h4>
</div>
<span class="itemname">${name}</span>
</div>`;
const elemExists = document.querySelector(`[data-name="${name}"]`);
if (elemExists) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
elemExists.replaceWith(doc.body);
} else {
document.getElementById("ssst").insertAdjacentHTML("beforeend", html);
}
}
});
window.postMessage({
action: 'insertItem',
items: {
name: 'foo',
count: 1,
icon: 'foo'
}
});
window.postMessage({
action: 'insertItem',
items: {
name: 'bar',
count: 40,
icon: 'barrrrrr'
}
});
window.postMessage({
action: 'insertItem',
items: {
name: 'foo',
count: 1000,
icon: 'foo'
}
});
<div id="ssst"></div>
Why are you using the if statement, what are you checking for?
remove the if statement, I can't see the reason for it to be used here.
clear()
and the rest of your code.
I am stuck on this problem. I am coding a task platform app. Whenever I try to save, the task clones itself. After each "Save Changes," there are more and more clones. I have rewritten the code so many times. But still, I am not successful. Please help me to find the error.
$("#taskSave").click(() => {
const task = {
id: Date.now(),
imageUrl: $("#imageInput").val(),
title: $("#titleInput").val(),
description: $("#descriptionInput").val(),
type: $("#typeInput").val(),
};
$("#overlay").hide();
todos.push(task);
saveStorage(todos);
// reset input values
$("#imageInput").val("");
$("#titleInput").val("");
$("#descriptionInput").val("");
$("#typeInput").val("");
});
function saveStorage(todos) {
localStorage.setItem("todos", JSON.stringify(todos));
display(todos);
};
function display(todos) {
$("#taskBoard").innerHTML = "";
// .html("");
todos.forEach(item => {
let c = document.createElement("div");
c.setAttribute("class", "card");
c.setAttribute('id', item.id);
c.innerHTML = `
<div class="cardTop">
<div class="binContainer">
<div class="binImage"></div>
</div>
</div>
<img src="${item.imageUrl}" alt="task image">
<h2>${item.title}<h2>
<p>${item.description}</p>
<div class="cardType">${item.type}</div>
`;
$("#taskBoard").append(c);
// end
});
};
I've created a minimal working example, and the problem is in the cleanup of the HTML. You cannot use innerHTML on the JQuery object, or you use its html function or you need to retrieve the javascript object with $("#taskBoard")[0].
// You can use:
$("#taskBoard").html("");
// or
// document.getElementById("taskBoard").innerHTML = "";
// or
// $("#taskBoard")[0].innerHTML = "";
// But not:
// $("#taskBoard").innerHTML = "";
The working example here on JSFiddle (on SO dont work localStorage)
let todos = [];
$("#taskSave").click(() => {
const task = {
id: Date.now()
};
todos.push(task);
saveStorage(todos);
});
function saveStorage(todos) {
localStorage.setItem("todos", JSON.stringify(todos));
display(todos);
console.log(todos);
};
function display(todos) {
$("#taskBoard").html("");
// or
// document.getElementById("taskBoard").innerHTML = "";
// or
// $("#taskBoard")[0].innerHTML = "";
// But not
// $("#taskBoard").innerHTML = "";
todos.forEach(item => {
let c = document.createElement("div");
c.innerHTML = `
<p>${item.id}</p>
`;
$("#taskBoard").append(c);
});
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="taskSave">
SAVE
</button>
<div id="taskBoard">
</div>
For a bit of background information: this app is supposed to load a map api and create a workout form linked to a location selected on the map by clicking. Once the map is clicked, a form is loaded to fill out info about that workout and then it's saved to the #workout variable.
Problem: I'm trying to save the #workout variable to local storage to then load all the workouts from storage whenever the page is reloaded.
I'm trying to run the _getLocalStorage() function in the constructor to load items from the local storage when the page loads, but I keep getting this TypeError code:
script.js:239 Uncaught TypeError: this[#workout].forEach is not a function
at App._getLocalStorage (script.js:239)
at new App (script.js:21)
Code:
class App {
#map;
#mapEvent;
#workout = [];
constructor() {
this._getPosition();
this._getLocalStorage();
form.addEventListener('submit', this._newWorkout.bind(this));
inputType.addEventListener('change', this._toggleElevationField);
containerWorkouts.addEventListener('click', this._panToWorkout.bind(this));
}
_panToWorkout(e) {
// find the workout
const workoutEl = e.target.closest('.workout');
if (!workoutEl) return;
const workout = this.#workout.find(
work => work.id === workoutEl.dataset.id
);
// pan to workout object with that id number
this.#map.setView(workout.coords, 13, {
animate: true,
pan: {
duration: 1,
easeLinearity: 0.8,
},
});
}
_getPosition() {
// -> check if this nagivator.geolocation object exits, then loads the map.
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
this._loadMap.bind(this),
function () {
alert("Can't get your position");
}
);
}
}
_loadMap(position) {
const { latitude, longitude } = position.coords;
// -> creating a coordinate variable because the below L.map().setView function expects an array for the coordinates.
// -> adding the map loading script from the imported library after getting coordinates
this.#map = L.map('map').setView([latitude, longitude], 13);
L.tileLayer('https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png').addTo(
this.#map
);
// -> SETUP MAP CLICK LISTENER && OPEN FORM
this.#map.on('click', this._showForm.bind(this));
}
_showForm(event) {
this.#mapEvent = event;
form.classList.remove('hidden');
inputDistance.focus();
}
_hideForm() {
// -> Clear form values when submit
inputDistance.value =
inputCadence.value =
inputDuration.value =
inputElevation.value =
'';
// -> removes the form from view to disable the slide transition while it's being removed.
form.style.display = 'none';
form.classList.add('hidden');
setTimeout(() => (form.style.display = 'grid'), 1000);
}
_toggleElevationField() {
inputElevation.closest('.form__row').classList.toggle('form__row--hidden');
inputCadence.closest('.form__row').classList.toggle('form__row--hidden');
}
_newWorkout(e) {
// -> prevent default submit function which is to refresh the page
e.preventDefault();
const validInput = (...inputs) =>
inputs.every(entry => Number.isFinite(entry));
const allPositive = (...inputs) => inputs.every(inp => inp > 0);
// -> Get data from form
const type = inputType.value;
const distance = +inputDistance.value;
const duration = +inputDuration.value;
const { lat, lng } = this.#mapEvent.latlng;
let workout;
// -> if running, create running object
if (type === 'running') {
// -> check if data is valid
const cadence = +inputCadence.value;
if (
!validInput(distance, duration, cadence) ||
!allPositive(distance, duration, cadence)
) {
return alert('Inputs have to be a positive number.');
}
workout = new Running([lat, lng], distance, duration, cadence);
}
// -> if cycling, create cycling object
if (type === 'cycling') {
const elevation = +inputElevation.value;
// -> check if data is valid
if (
!validInput(distance, duration, elevation) ||
!allPositive(distance, duration)
)
return alert('Inputs have to be a positive number.');
workout = new Cycling([lat, lng], distance, duration, elevation);
}
// -> adds workout to workout array
this.#workout.push(workout);
// -> render the workout
this._renderWorkoutMarker(workout);
// -> Render workout on list
this._renderWorkout(workout);
// -> hide the form
this._hideForm();
// -> save workouts to storage
this._setLocalStorage();
}
_renderWorkoutMarker(workout) {
// -> DISPLAY MAP MARKER ON SUBMIT
L.marker(workout.coords)
.addTo(this.#map)
.bindPopup(
L.popup({
minWidth: 250,
maxWidth: 100,
autoClose: false,
closeOnClick: false,
className: `${workout.type}-popup`,
})
)
.setPopupContent(
`${workout.type === 'cycling' ? 'π΄ββοΈ' : 'πββοΈ'} ${workout.description}`
)
.openPopup();
}
_renderWorkout(workout) {
let html = `
<li class="workout workout--${workout.type}" data-id="${workout.id}">
<h2 class="workout__title">${workout.description}</h2>
<div class="workout__details">
<span class="workout__icon">${
workout.type === 'cycling' ? 'π΄ββοΈ' : 'πββοΈ'
}</span>
<span class="workout__value">${workout.distance}</span>
<span class="workout__unit">km</span>
</div>
<div class="workout__details">
<span class="workout__icon">β±</span>
<span class="workout__value">${workout.duration}</span>
<span class="workout__unit">min</span>
</div>
`;
if (workout.type === 'running') {
html += `
<div class="workout__details">
<span class="workout__icon">β‘οΈ</span>
<span class="workout__value">${workout.pace.toFixed(1)}</span>
<span class="workout__unit">min/km</span>
</div>
<div class="workout__details">
<span class="workout__icon">π¦ΆπΌ</span>
<span class="workout__value">${workout.cadence.toFixed(1)}</span>
<span class="workout__unit">spm</span>
</div>
</li>
`;
}
if (workout.type === 'cycling') {
html += `
<div class="workout__details">
<span class="workout__icon">β‘οΈ</span>
<span class="workout__value">${workout.speed.toFixed(1)}</span>
<span class="workout__unit">km/h</span>
</div>
<div class="workout__details">
<span class="workout__icon">β°</span>
<span class="workout__value">${workout.elevation.toFixed(1)}</span>
<span class="workout__unit">m</span>
</div>
</li>
`;
}
form.insertAdjacentHTML('afterend', html);
}
_setLocalStorage() {
localStorage.setItem('workouts', JSON.stringify(this.#workout));
}
_getLocalStorage() {
const data = JSON.parse(localStorage.getItem('workouts'));
if (!data) return;
this.#workout = data;
console.log(typeof this.#workout);
this.#workout.forEach(work => {
this._renderWorkout(work);
});
}
}
It seems like the result of const data = JSON.parse(localStorage.getItem('workouts')); is not Array.
So you can check it as below
console.log(JSON.parse(localStorage.getItem('workouts')));
Issue:
I want to return the button Element within my document that matches the specified selector, in this case ".comment-body__interaction--delete" but keep getting a return of null every time I console.log the variable that contains the return element.
Background Info
The HTML element I'm Trying to target has been inserted into the document via innerHTML.
All my scripts are at the bottom of the index.html page
I'm using querySelector at the bottom of the js document.
I know my class name is correct because I can style it via CSS.
my code
// LOCATION VARIABLES ***
const conversation = document.querySelector('.conversation-container-posted');
const form = document.querySelector('form');
console.log(form);
// Array THAT HOLDS ALL MY COMMENT OBJECTS
let objectsArray;
// VARIABLE THAT HOLDS MY HTML TEMPLATE
const template = (singleCommentObj) => {
return `
<article class="comment-container">
<figure class="comment-container__picture">
<img class="comment-container__picture-img" src="${singleCommentObj.image}" alt="profile picture" />
</figure>
<div class="comment-body">
<h3 class="comment-body__name">${singleCommentObj.name}</h3>
<div class="comment-body__date">${singleCommentObj.date}</div>
<article class="comment-body__comment"><p>${singleCommentObj.comment}</p></article>
<div class="comment-body__interaction">
<div class="comment-body__interaction--likes">Likes</div>
<button id="${singleCommentObj.id}" class="comment-body__interaction--delete">Delete</button>
</div>
</div>
</article>
<hr class="comment-container__divider"/>
`;
};
const displayComment = (object) => {
let staticComments = object
.sort((a, b) => b.timestamp - a.timestamp)
.map((values) => {
values.image = 'https://loremflickr.com/48/48';
values.date = moment.unix(values.timestamp / 1000).fromNow();
return template(values);
})
.join('');
conversation.innerHTML = staticComments;
};
// Gets AN ARRAY OF OBJECTS FROM THE api AND ASSIGNS IT TO objectsArray
// CALLS displayComment WITH objectsArray AS A PARAMETER TO INSERT ITS CONTENT INTO THE DOM
axios
.get('https://project-1-api.herokuapp.com/comments?api_key=7d8d085e-486e-42dc-b836-58009cbfa68f')
.then((response) => {
objectsArray = response.data;
displayComment(objectsArray);
})
.catch((error) => {
console.log(error);
});
form.addEventListener('submit', (e) => {
e.preventDefault();
let fluidObject = new FormData(e.target);
fluidObject = Object.fromEntries(fluidObject);
axios
.post('https://project-1-api.herokuapp.com/comments?api_key=7d8d085e-486e-42dc-b836-58009cbfa68f&content-type=application/json', {
name: fluidObject.name,
comment: fluidObject.comment,
})
.then((response) => {
objectsArray.push(response.data);
displayComment(objectsArray);
})
.catch((error) => {
console.log(error);
});
});
// DELETE
const a = document.querySelector('.comment-body__interaction--delete');
console.log(a);
This console.log(a) returns NULL
The code that creates the said element, displayComment is in an asynchronous actions callback.
You have to wait for the action to complete before you try to access the element.
In other words const a = document.querySelector('.comment-body__interaction--delete'); executes before your request was successful and the elements were created.
I'm trying to change the HTML received from a database to respond to custom onClick handlers. Specifically, the HTML I pull has divs called yui-navsets which contain yui_nav page selectors and yui_content page contents. I want to click an li in yui_nav, set that li's class to "selected", set the existing content to display:none, and set the new content to style="".
To do this, I have created a function updateTabs which inputs the index of the chosen yui and the new page number, set that li's class to "selected", set the existing content to display:none, and set the new content to style="". This function works: I tried running updateTabs(2, 3) in componentDidUpdate, and it worked fine, changing the content as requested. I want to assign updateTabs to each of the lis, and I attempt to do so in my componentDidMount after my axios request.
However, I keep getting the error: TypeError: this.updateTabs is not a function. Please help?
Page.js:
import React, { Component } from 'react';
import axios from 'axios';
class Page extends Component {
constructor(props) {
super(props);
this.state = {
innerHTML: "",
pageTags: [],
};
console.log(this.props.url);
}
componentDidMount() {
console.log(this.props.url);
axios
.get(
this.props.db_address + "pages?url=" + this.props.url,
{headers: {"Access-Control-Allow-Origin": "*"}}
)
.then(response => {
this.setState({
innerHTML: response.data[0].html,
pageTags: response.data[1]
});
console.log(response);
// Check for yui boxes, evade the null scenario
var yui_sets = document.getElementsByClassName('yui-navset');
if (yui_sets !== null) {
let yui_set, yui_nav, yui_content;
// Iterate through the navs of each set to find the active tabs
for (var yui_set_count = 0; yui_set_count < yui_sets.length; yui_set_count ++) {
yui_set = yui_sets[yui_set_count];
yui_nav = yui_set.getElementsByClassName('yui-nav')[0].children;
yui_content = yui_set.getElementsByClassName('yui-content')[0].children;
let tab_count;
// Give each nav and tab and appropriate ID for testing purposes
for (tab_count = 0; tab_count < yui_nav.length; tab_count ++) {
yui_nav[tab_count].onclick = function() { this.updateTabs(yui_set_count); }
yui_nav[tab_count].id = "nav-"+ yui_set_count.toString() + "-" + tab_count.toString()
yui_content[tab_count].id = "content-"+ yui_set_count.toString() + "-" + tab_count.toString()
}
}
}
})
.catch(error => {
this.setState({ innerHTML: "ERROR 404: Page not found." })
console.log(error);
});
}
updateTabs(yui_index, tab_index){
// Get all yuis
var yui_sets = document.getElementsByClassName('yui-navset');
let yui_set, yui_nav, yui_content
yui_set = yui_sets[yui_index];
yui_nav = yui_set.getElementsByClassName('yui-nav')[0].children;
yui_content = yui_set.getElementsByClassName('yui-content')[0].children;
// Identify the current active tab
var current_tab_found = false;
var old_index = -1;
while (current_tab_found == false) {
old_index += 1;
if (yui_nav[old_index].className === "selected") {
current_tab_found = true;
}
}
// Identify the new and old navs and contents
var yui_nav_old = yui_nav[old_index]
var yui_nav_new = yui_nav[tab_index]
var yui_content_old = yui_content[old_index]
var yui_content_new = yui_content[tab_index]
// Give the new and old navs and contents their appropriate attributes
yui_nav_old.className = "";
yui_nav_new.className = "selected";
yui_content_old.style = "display:none";
yui_content_new.style = "";
}
render() {
return (
<div className="Page">
<div className="Page-html col-12" dangerouslySetInnerHTML={{__html:this.state.innerHTML}} />
<div className="Page-footer">
<div className="d-flex flex-wrap btn btn-secondary justify-content-around">
{this.state.pageTags.map(function(pageTag){return(
<div className="pd-2" key={pageTag.id}>
{pageTag.name}
</div>
)})}
</div>
<div className="d-flex justify-content-center" >
<div className="p-2">Discuss</div>
<div className="p-2">Rate</div>
<div className="p-2">Edit</div>
</div>
<div className="d-flex justify-content-around App">
<div className="p-2">
Unless otherwise stated, the content
of this page is licensed under <br />
<a href="http://creativecommons.org/licenses/by-sa/3.0/"
target="_blank" rel="noopener noreferrer">
Creative Commons Attribution-ShareAlike 3.0 License
</a>
</div>
</div>
</div>
</div>
)
}
}
export default Page
Instead of function with function keyword use arrow functions and it will be solved as follows
You have
yui_nav[tab_count].onclick = function() { this.updateTabs(yui_set_count); }
But use
yui_nav[tab_count].onclick = () => { this.updateTabs(yui_set_count); }
Use this in componentDidMount method
You have to bind the updateTabs method in the constructor:
constructor(props) {
super(props);
...
this.updateTabs = this.updateTabs.bind(this);
}
You should use arrow functions in order to call this method with the correct contetxt:
yui_nav[tab_count].onclick = () => { this.updateTabs(yui_set_count); }