I've made a function that generates buttons for every item in a list, and every button when clicked should run a function in the App.vue file.
The problem is that the onclick and the v-on:click methods do not work.
This is the code:
await resetView();
// document.getElementById("projSelLb").style.display = "none";
var projects = await detaStorage.getAllProjects();
var parentDiv = document.getElementById('projectsDetaDiv');
projects.forEach(function(item) {
var div = document.createElement('div');
div.innerHTML = `<button type="button" class="btn btn-primary" style="margin-top: 12px;" #click.native="loadFromDeta(String(item))">${item}</button>`;
parentDiv.appendChild(div);
});
// document.getElementById("projSelLb").style.display = "block";
document.getElementById("spinnerGetProj").style.display = "none";
async function resetView() {
// document.getElementById("projSelLb").style.display = "none";
document.getElementById("spinnerGetProj").style.display = "block";
document.getElementById('projectsDetaDiv').innerHTML = null;
}
function loadFromDeta(name) {
workspaceClear();
var data = detaStorage.getProjectData();
Blockly.Xml.domToWorkspace(data.xml, foo.value.workspace);
}
Oh, if you really want to create the elements manually in Vue, you need to use the render() function. However, while there are practical applications for it, I am pretty sure it would serve you better to write your example in the regular Vue style, where you separate between template and data operations, as it gives you cleaner code that is easier to maintain.
In the template, you can express the exact same DOM operations you did above, using v-if to show and hide the loading spinner and using v-for to create the buttons from the list:
<div v-if="loading" id="spinnerGetProj"/>
<div v-else v-for="item in projects" :key="item">
<button
type="button"
class="btn btn-primary mt-4"
#click="loadFromDeta(item)"
>{ item }</button>
</div>
You didn't say which Vue version you are using, but in Vue 3 composition api, the corresponding script part would look something like this:
const projects = ref([])
const loading = ref(true)
const loadProjects = async() => {
loading.value = true
projects.value = await detaStorage.getAllProjects()
loading.value = false
}
And that's it. Now you just have to call loadProjects(), and everything else falls into place. Hope that helps.
Related
Is it possible to execute a <Script/> every time the props of a react/nextjs component change?
I am converting markdown files to html using marked and, before rendering the html, I would like to have a [copy] button on each <pre> block (those are the code blocks). I have a <script/> that iterates through the <pre> blocks of the DOM document.querySelectorAll("pre") and injects the button needed. If the html changes though at a later stage, then I have found no way to re-run the script to add the copy buttons again.
I have the impression that this is not a very react/nextjs way of doing this, so any hints would be appreciated.
The Script to add the copy buttons. I have added this as the last tag of my <body>:
<Script id="copy-button">
{`
let blocks = document.querySelectorAll("pre");
blocks.forEach((block) => {
if (navigator.clipboard) {
let button = document.createElement("img");
button.src = "/images/ic_copy.svg"
button.title = "Copy"
button.id = "copy"
button.addEventListener("click", copyCode);
block.appendChild(button);
}
});
async function copyCode(event) {
const button = event.srcElement;
const pre = button.parentElement;
let code = pre.querySelector("code");
let text = code.innerText;
await navigator.clipboard.writeText(text);
button.src = "/images/ic_done.svg"
setTimeout(()=> {
button.src = "/images/ic_copy.svg"
},1000)
}
`}
</Script>
the React component. Not much to say here. The content is coming from the backend. Not sure what would be the 'React' way to do this without the script.
export default function Contents({ content }) {
return (
<div className='pl-2 pr-2 m-auto w-full lg:w-2/3 mb-40 overflow-auto break-words'>
<div className="contents" dangerouslySetInnerHTML={{ __html: content }} />
</div>
)
}
You should absolutely not do this and instead incorporate this logic into your react app, but if you must you can leverage custom window events to make logic from your html script tags happen from react.
Here is an example script:
<script>
function addEvent() {
function runLogic() {
console.log("Stuff done from react");
}
window.addEventListener("runscript", runLogic);
}
addEvent();
</script>
And calling it form react like this:
export default function App() {
const handleClick = () => {
window.dispatchEvent(new Event("runscript"));
};
return (
<div className="App" onClick={handleClick}>
<h1>Hello</h1>
</div>
);
}
so I want to make an animation whenever you click a button, and in js I would probably do it something like that:
var x = document.GetElementById("inputBox");
function changeToRegister(){
x.style.justify-content: flex-start;
}
but when I try something like that in react
const changeToRegister = () => {
var x = document.getElementsById("inputs");
x.style.justify-content: flex-start;
}
it just doesn't work, is there anyway to do what I did in the first code section in react? the function will be called after pressing a button
<button type="button" class="toggle-btn" onClick={changeToRegister}>Register</button>
It is not a good practice to var x = document.GetElementById("inputBox"); in react
Instead you can try this
const [justifyContentStart,setJustifyContentStart] = useState(false);
const changeToRegister = () => {
setJustifyContentStart(true)
}
<button
type="button"
class="toggle-btn"
onClick={changeToRegister}
style={ justifyContentStart ? { justifyContent:'flex-start'} : null }
>Register</button>
Use a state variable to decide whether to apply that particular style or not
Initially that state variable is set to false hence style is not applied
Once the user clicks on Button, The state variable will be set to true and the style you want can be applied
I was trying to get data from an API that gives me a list of movies and this is how I implemented the rendering of the results.
const getAllData = async () => {
const movieData = await getMovies()
const movieContainer = movieData.map((movie)=> {
const listItem = `
<li class="movie">
<img src="${movie.Poster}"></img>
<h1>
${movie.Title}
</h1>
<h2>Release Year: ${movie.Year}</h2>
<p>
<button onclick="getDetails('${movie.imdbID}')">
Click for more details
</button>
</p>
</li>
`;
return listItem
}).join('')
document.getElementById("movieList").innerHTML = movieContainer;
}
Here is the getDetails function that the button click calls.
function getDetails(id){
sessionStorage.setItem('movieId', id);
window.location = 'movie.html';
return false;}
This code works as intended. However, what I was having trouble with is that I want to implement this logic with event listeners instead of inline HTML event-handlers. How could I implement this?
Would I need to change anything drastic about my current code?
Create a <li> element instead of an HTML string, then select the button descendant using querySelector and you can use addEventListener:
const getAllData = async () => {
const movieData = await getMovies();
const ul = document.getElementById("movieList");
for (const movie of movieData) {
const li = ul.appendChild(document.createElement('li'));
li.className = 'movie';
li.innerHTML = `
<img src="${movie.Poster}"></img>
<h1>
${movie.Title}
</h1>
<h2>Release Year: ${movie.Year}</h2>
<p>
<button>
Click for more details
</button>
</p>
`;
li.querySelector('button').addEventListener('click', () => {
getDetails(movie.imdbID);
});
}
};
I'd also highly recommend against direct concatenation of external input into an HTML string, like with
<img src="${movie.Poster}"></img>
unless the input is absolutely trustworthy, since that can result in arbitrary code execution, which is a security risk. If you aren't doing it already, I'd suggest either verifying that the interpolated values are well formatted (for example, without anything which would result in HTML markup, like <script> tags), or assign the dynamic properties/attributes after the <li> has been populated with the base template.
I'm rebuilding a todo list app and trying to work with Object Oriented Programming, which is new to me. I've got the task section built, but I am getting stuck on locating the "delete" buttons. When adding a new task, a font awesome icons show to the right. I'm trying to select them, but I am getting an empty nodelist each time the function runs:
Codepen: To Do List
To reproduce:
Add a task, and check the console. You'll see an empty nodelist.
What I've tried:
Right now, I'm trying to simply console.log the element. I'm running console.log(buttons) Each time the addTask() method runs.
Here's the full JS:
const submit = document.querySelector("#commit-task"),
results = document.querySelector("#task-results"),
input = document.querySelector("#input-task"),
buttons = document.querySelectorAll(".fa-times"); // These are what I'm trying to select
class Task {
constructor(task) {
this.taskText = task;
}
addTask() {
const text = input.value;
ui.clearInput();
const taskBody = `<div class="task">
<span>${text}</span>
<span>
<i class="fas fa-check" style="color: green;"></i>
<i class="fas fa-times" style="color: red;"></I> //This is the element I'm trying to select
</span>
</div>`;
results.innerHTML += taskBody;
console.log(buttons); //Here's where the Console.log statement is run
}
}
class UI {
clearInput() {
input.value = "";
input.focus();
}
}
const newTask = new Task();
const ui = new UI();
// Add Event Listeners:
submit.addEventListener("click", () => {
newTask.addTask(); //Here is when addTask() is run.
});
input.addEventListener("keyup", (e) => {
if (e.keyCode === 13) {
newTask.addTask();
}
});
Why does JavaScript think these buttons are not in the DOM? Thanks in advance.
document.querySelectorAll(".fa-times"); gets executed during the first assignment and as there are no icons during the time of initialization, buttons are equal to an empty NodeList.
In order check the current status you need to re run the query.
Just declare buttons as let buttons = document.querySelectorAll(".fa-times");
Then re-run the query and assign it's latest result to your buttons variable before logging it:
buttons = document.querySelectorAll(".fa-times");
console.log(buttons);
Forgive me if this is redundant, I'm having trouble finding questions/answers that are using vanilla JS specifically.
I have a data object for store items I've created that I'm trying to display on the page but I only seem to be getting the first item in the array to appear, which lead me to believe I needed some sort of for loop related to the array length but I tried variations and I seem to be getting the same result (only the first item), or in some cases nothing at all.
I've logged out the HTML and the correct items are there in the console so its working and they are ready to go. I'm missing something.
feature = () => isFeatured.map(item => {
// console.log("imworking");
html = `
<img src="${item.image}" alt="">
<h2>${item.info}</h2>
<h3>${item.price}</h3>
<button>Add to Cart</button>
`
//console.log(html);
document.getElementById('featuredItem').innerHTML = html;
})
I don't think the problem is the HTML because the one item is displaying fine but here it is anyways
<div id="featuredItem"></div>
You're replacing the featuredItem's HTML every time the loop runs. Also, you shouldn't use map since you're not mapping to a new array; use forEach instead. Also, make sure to declare all variables with const (or let), to avoid implicitly creating global variables, which should be avoided:
const feature = () => isFeatured.forEach(item => {
// console.log("imworking");
const html = `
<img src="${item.image}" alt="">
<h2>${item.info}</h2>
<h3>${item.price}</h3>
<button>Add to Cart</button>
`;
//console.log(html);
document.getElementById('featuredItem').innerHTML += html;
});
feature();
But directly inserting variables into HTML markup like that is not all that safe. It would be better to explicitly create and append elements, assigning values to their textContent, like this:
const featuredItem = document.querySelector('#featuredItem');
const feature = () => isFeatured.forEach(({ image, info, price }) => {
featuredItem
.appendChild(document.createElement('img'))
.src = image;
featuredItem
.appendChild(document.createElement('h2'))
.textContent = info;
featuredItem
.appendChild(document.createElement('h3'))
.src = price;
featuredItem
.appendChild(document.createElement('button'))
.textContent = 'Add to Cart';
});
feature();
Hey sorry this is so late, hopefully you figured it out already! I ran out of room in the comment section so I'm throwing my response to #maxineheadroom down here. If I understand your question you want the img htags and buttons wrapped in a div? I would make a new function called creatItem or something.
const createItem = (item) => {
const wrapper = document.createElement('div');
wrapper.classList.add(`item_${item.id}`)
const img = document.createElement('img').src = item.img
const info = document.createElement('h2').textContent = item.info;
const price = document.createElement('h3').textContent = item.price;
const btn = document.createElement('button').textContent = 'Add to cart'
wrapper.appendChild(img);
wrapper.appendChild(info);
wrapper.appendChild(price);
wrapper.appendChild(btn);
return wrapper
}
then in your for loop you can just do
featuredItem.appendChild(createItem(item))
It is because you are setting the innerhtml, through looping the HTML changes as well as the innerhtml.
You should have a container for string HTML generated by loop.
feature = () => isFeatured.map(item => {
let markup = new Array();
html = `
<img src="${item.image}" alt="">
<h2>${item.info}</h2>
<h3>${item.price}</h3>
<button>Add to Cart</button>
`
markup.push(html);
html = markup.join("");
document.getElementById('featuredItem').innerHTML = html;
})
Also it can be than if you use appendchild rather than innerhtml.