I'm having trouble understanding why my onclick buttons are not working. My onclick buttons are within a JS function which modifies the HTML.
Here is the function which includes the onclick buttons:
const bookmarkHTML = (bookmark) => {
let html = '';
if (bookmark.url.includes('https://') || bookmark.url.includes('http://')) {
const li = `
<li>
<div class="row card-margin">
<div class="col s12 m12 card-padding">
<div class="card-panel teal card-size">
<img src='${'https://www.google.com/s2/favicons?domain_url=' + bookmark.url}' alt='icon' class='img-size'>
<a class='white-text' href='${bookmark.url}' target='_blank'>${bookmark.website}</a>
<button class='btn-flat waves-light btn-small right' onclick='switchCategory(bookmark)'><i class='material-icons left'>compare_arrows</i>switch</button>
</div>
</div>
</div>
</li>
`;
html += li;
} else {
const li = `
<li>
<div class="row card-margin">
<div class="col s12 m12 card-padding">
<div class="card-panel teal card-size">
<img src='${'https://www.google.com/s2/favicons?domain_url=' + bookmark.url}' alt='icon' class='img-size'>
<a class='white-text' href='${'https://' + bookmark.url}' target='_blank'>${bookmark.website}</a>
<button class='btn-flat waves-light btn-small right' onclick='switchCategory(bookmark)'><i class='material-icons left'>compare_arrows</i>switch</button>
</div>
</div>
</div>
</li>
`;
html += li;
}
console.log('HTML:', html);
return html;
}
Here is the function called in onclick:
switchCategory = (bookmark) => {
if (bookmark.favorite) {
bookmark.favorite = false;
} else {
bookmark.favorite = true;
}
}
If needed, here is the function which calls the bookmarkHTML function:
const setupBookmarks = (data) => {
let favoritesHTML = '';
let bookmarksHTML = '';
if (data.length) {
data.forEach(doc => {
const bookmark = doc.data();
if (bookmark.favorite) {
favoritesHTML += bookmarkHTML(bookmark);
} else {
bookmarksHTML += bookmarkHTML(bookmark);
}
})
}
favoriteList.innerHTML = favoritesHTML;
bookmarkList.innerHTML = bookmarksHTML;
}
When I click on the button with onclick, I get this error:
Uncaught ReferenceError: bookmark is not defined
at HTMLButtonElement.onclick (index.html:1)
Thanks for any help.
It seems that is happening because when you are passing an object inside template literals switchCategory(bookmark) this object is converted to [object Object]. A workaround for this may be converting that object to string switchCategory(${JSON.stringify(bookmark)}) but it won't do the job in your case because you are mutating that object later. I think that adding EventListener may be the way to resolve this. I also found similar problem so u can get more information about that and maybe a better solution.
Related
I want to make my own To Do list using JavaScript and localStorage. After writing the input and pressing the send button, the item will be added and display on the screen. A check button will appear next to the item. After pressing the check button, I want the class to be added. I want add css (opacity, line...). But when I press a check button, the console shows this error: Uncaught TypeError: Cannot read properties of undefined (reading 'add')
at check (app.js:23)
at HTMLElement.onclick (index.html:30)
HTML code:
<body>
<div class="main">
<input id="item" type="text">
<i id="addItem" class="fas fa-plus"></i><br> <br>
</div>
<div class="items">
<div id="addedItems"></div>
</div>
<i onclick='deleteAll()' class="fas fa-trash"></i>
</body>
JS code:
const item = document.getElementById("item");
const add = document.getElementById("addItem");
const addedItems = document.getElementById("addedItems");
add.onclick = function() {
const itemValue = item.value;
const itemValue2 = ' ';
if (itemValue && itemValue2) {
localStorage.setItem(itemValue, itemValue2);
location.reload();
}
};
for (let a = 0; a < localStorage.length; a++) {
const itemValue = localStorage.key(a);
addedItems.innerHTML += `<div class= 'text'> <div>${itemValue}</div> <div>
<i onclick='check("${itemValue}")' class="fas fa-check"></i><i
onclick='deleteItem("${itemValue}")' class="fas fa-trash-alt"></i>
</div></div> <br>`;
}
function check(itemValue) {
itemValue.classList.add("mystyle");
}
function deleteItem(itemValue) {
localStorage.removeItem(itemValue);
location.reload();
}
function deleteAll() {
localStorage.clear();
location.reload();
}
You can try this answer:
<script>
const item = document.getElementById("item");
const add = document.getElementById("addItem");
const addedItems = document.getElementById("addedItems");
add.onclick = function() {
const itemValue = item.value;
const itemValue2 = ' ';
if (itemValue && itemValue2) {
localStorage.setItem(itemValue, itemValue2);
location.reload();
}
};
for (let a = 0; a < localStorage.length; a++) {
const itemValue = localStorage.key(a);
addedItems.innerHTML += `<div class= 'text'> <div>${itemValue}</div> <div>
<i onclick='check("${itemValue}", ${a})' class="fas fa-check" id="${a}"></i><i
onclick='deleteItem("${itemValue}")' class="fas fa-trash-alt"></i>
</div></div> <br>`;
}
function check(itemValue, id) {
const elem = document.getElementById(id);
elem.classList.add("mystyle");
}
function deleteItem(itemValue) {
localStorage.removeItem(itemValue);
location.reload();
}
function deleteAll() {
localStorage.clear();
location.reload();
}
</script>
<div class="main">
<input id="item" type="text">
<i id="addItem" class="fas fa-plus"></i><br> <br>
</div>
<div class="items">
<div id="addedItems"></div>
</div>
<i onclick='deleteAll()' class="fas fa-trash"></i>
Only changes I made is pass id of element for checkbox click along with itemValue element. Value you are passing checkbox value not document element. If you console you will get to know itemValue is just string not DOM element.
Here is fiddle : https://jsfiddle.net/aviboy2006/ortqu1ps/
Sorry for the long post, but I tried explaining things in as much detail as possible.
So as I dive deeper into JavaScript and start learning more and more about AJAX requests and other components, I've stumbled across something that I can't seem to figure out.
So below, I will explain what I'm doing and what I would like to do, and see if someone has some guidance for me.
So here is my Vue.js app:
new Vue({
name: 'o365-edit-modal',
el: '#o365-modal-edit',
data: function() {
return {
list: {},
}
},
created() {
this.fetchApplicationsMenu();
},
methods: {
fetchApplicationsMenu() {
var self = this;
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'GET',
}).then(menu => self.list = menu.data);
},
changed() {
const selected = this.$data.list.selected;
function get_ids(list, field) {
const output = [];
for (let i=0; i < list.length ; ++i)
output.push(list[i][field]);
return output;
}
const result = get_ids(selected, "id");
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'PUT',
data: {
ids: result,
},
}).then((post) => {
return post;
},
(error) => {
console.log(error);
});
},
add(x) {
this.$data.list.selected.push(...this.$data.list.available.splice(x, 1));
this.changed();
},
remove(x) {
this.$data.list.available.push(...this.$data.list.selected.splice(x, 1));
this.changed();
},
},
});
Then here is the HTML portion that I'm using to render the two columns:
<div class="column is-half-desktop is-full-mobile buttons">
<nav class="level is-mobile mb-0">
<div class="level-left">
<div class="level-item is-size-5 has-text-left">Selected</div>
</div>
<div class="level-right">
<div class="level-item">
<i class="fas fa-sort-alpha-up is-clickable"></i>
</div>
</div>
</nav>
<hr class="mt-1 mb-3">
<draggable class="list-group"
v-model="list.selected"
v-bind="dragOptions"
:list="list.selected"
:move="onMove"
#change="changed">
<button class="button is-fullwidth is-flex list-group-item o365_app_handle level is-mobile" v-for="(app, index) in list.selected" :key="app.id">
<div class="level-left">
<span class="icon" aria-hidden="true">
<img :src="app.icon_url" />
</span>
<span>{{app.name}}</span>
</div>
<div class="level-right">
<span class="icon has-text-danger is-clickable" #click="remove(index)">
<i class="fas fa-times"></i>
</span>
</div>
</button>
</draggable>
</div>
<div class="column is-half-desktop is-full-mobile buttons">
<div class="is-size-5 has-text-left">Available</div>
<hr class="mt-1 mb-3">
<draggable class="list-group"
v-model="list.available"
v-bind="dragOptions"
:list="list.available"
:move="onMove">
<button class="button is-fullwidth is-flex list-group-item o365_app_handle level is-mobile" v-for="(app, index) in list.available" :key="app.id">
<div class="level-left">
<span class="icon" aria-hidden="true">
<img :src="app.icon_url" />
</span>
<span>{{app.name}}</span>
</div>
<div class="level-right">
<span class="icon has-text-primary is-clickable" #click="add(index)">
<i class="fas fa-plus"></i>
</span>
</div>
</button>
</draggable>
</div>
That outputs the following items, and all works great. See the video display below of each component working as needed. This all works great! I'm calling the changed() method on add and remove which grabs all the IDs and stores them in the DB via an endpoint.
The Problem:
Now I have the following dropdown menu, which depends on the fh/v1/menus/applications endpoint to pull in all the items as shown below:
As you can see below, when I open the dropdown, it has three apps, when I open the cog wheel and remove one of the apps and it saves it but the dropdown doesn't get automatically updated, I have to refresh the page and then I will see the updates.
Does anyone know how to fetch the new items without a refresh?
Here is the HTML and the JS for the dropdown piece:
HTML: As you can see in there, I have data-source="applications" which pulls in the items inside the init_menu as shown in the JS.
<div class="dropdown-menu" id="dropdown-o365" role="menu">
<div class="dropdown-content">
<div class="container is-fluid px-4 pb-4">
<?php if ($application = Applications::init()): ?>
<div class="columns">
<div class="dropdown-item column is-full has-text-centered is-size-6">
<div class="level is-mobile">
<div class="level-left">
<?= $application->get_name() ?>
</div>
<div class="level-right">
<a class="navbar-item modal-element icon" id="o365-apps-cogwheel" data-target="o365-modal-edit" aria-haspopup="true">
<i class="fa fa-cog"></i>
</a>
</div>
</div>
</div>
</div>
<div class="columns is-multiline" data-source="applications"></div>
<?php else: ?>
<div class="columns">
<div class="column is-full">
No applications present.
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
Then here is the JavaScript. I initilize the method inside DOMContentLoaded using init_menu('applications');:
function init_menu(paths)
{
paths.forEach(path => {
const target = document.querySelector('[data-source=' + path + ']');
if (target) {
wp.api.loadPromise.done(function () {
const Menus = wp.api.models.Post.extend({
url: wpApiSettings.root + 'fh/v1/menus/' + path,
});
const menus = new Menus();
menus.fetch().then(posts => {
// This returns the data object.
const data = posts.data;
let post_list;
// Check if it's an array and see if selected is empty otherwise show available.
if (Array.isArray(data.selected) && data.selected.length !== 0) {
post_list = data.selected;
} else {
post_list = data.available;
}
post_list.forEach(function (post) {
switch(path) {
case 'applications':
target.appendChild(create_apps_dom_tree(post));
break;
default:
console.log('Path route is invalid.');
break;
}
})
})
})
}
});
}
function create_apps_dom_tree(post) {
const {
icon_url,
url,
name,
} = post
const container = document.createElement('div');
container.className = 'column is-one-third is-flex py-0';
const anchor = document.createElement('a');
anchor.href = url;
anchor.className = 'dropdown-item px-2 is-flex is-align-items-center';
const figure = document.createElement('figure');
figure.className = 'image is-32x32 is-flex';
const img = document.createElement('img');
img.src = icon_url;
const span = document.createElement('span');
span.className = 'pl-2';
span.textContent = name;
figure.appendChild(img);
anchor.append(figure, span);
container.appendChild(anchor);
return container;
}
If anyone has some guidance or an answer on how to pull in live data from the database on the fly, that would be amazing.
Basically, I need my data-source: to automatically grab the items when my vue/db request is sent so I don't have to refresh the page.
Inside my Vue app, I have the following method:
fetchApplicationsMenu() {
var self = this;
wp.apiRequest( {
path: 'fh/v1/menus/applications',
method: 'GET',
}).then(menu => self.list = menu.data);
},
which calls a GET request and then stores the data inside the return { list: {} }.
A quick fix might be to just invoke init_menu() from the component's beforeDestroy() hook, called when the dialog closes. You might choose to do it from changed() instead if the dropdown is still accessible with this dialog open.
new Vue({
// option 1:
beforeDestroy() {
init_menu('applications');
},
// option 2:
methods: {
changed() {
init_menu('applications');
}
}
})
Alternative: You already know what the final application list is in changed(), so you could update the dropdown with the new list from that method.
function update_menu(path, post_list) {
const target = document.querySelector('[data-source=' + path + ']');
// remove all existing children
Array.from(target.childNodes).forEach(x => x.remove());
post_list.forEach(post => target.appendChild(create_apps_dom_tree(post)))
}
new Vue({
methods: {
changed() {
update_menu('applications', this.$data.available);
}
}
})
I want to create a list of users. For each row I want to add the avatar, name and a button to open a modal with the user's profile. I am having trouble adding the "onClick"s to each button.
Ive searched and found multiple solutions that would require me to rewrite this whole function. I was wondering if there was a way to solve this and still get to keep the way I am implementing the ul's.
It is worth to mention that I am not using JQuery.
Here is the code:
const userList = document.querySelector('.userListDiv');
const setupUserList = (data, currentUser) => {
if(data.length) {
let html = '';
var list = [];
data.forEach(doc => {
const user = doc.data();
if(user.coachUid == currentUser.uid)
{
list.push(user);
}
});
list.forEach(user => {
const ul = `
<ul class="userListUl pull-right" style="border-left: 0px;">
<li>
<button class="button" id="myBtn"><i class="material-icons" style="font-size:48px;color:rgb(34, 34, 34)">person</i></button>
</li>
</ul>
<ul class="userListUl" style="border-right: 0px;">
<li>
<img src="https://www.w3schools.com/howto/img_avatar.png" class="userListImg" alt="Avatar">
</li>
<li>
<p class="userListP">${user.personal_info[0]} ${user.personal_info[1]}</p>
</li>
</ul>
`;
html += ul
});
userList.innerHTML = html;
}
}
<div class="userListDiv logged-in"></div>
Thanks in advance!
You could just add a snippet at the point you have added your HTML then you wont need to change any of your existing code? See comment/snippet below...
const userList = document.querySelector('.userListDiv');
const setupUserList = (data, currentUser) => {
if (data.length) {
let html = '';
const list = [];
data.forEach(doc => {
const user = doc.data();
if (user.coachUid == currentUser.uid) {
list.push(user);
}
});
list.forEach(user => {
const ul = `
<ul class="userListUl pull-right" style="border-left: 0px;">
<li>
<button class="button"><i class="material-icons" style="font-size:48px;color:rgb(34, 34, 34)">person</i></button>
</li>
</ul>
<ul class="userListUl" style="border-right: 0px;">
<li>
<img src="https://www.w3schools.com/howto/img_avatar.png" class="userListImg" alt="Avatar">
</li>
<li>
<p class="userListP">${user.personal_info[0]} ${user.personal_info[1]}</p>
</li>
</ul>
`;
html += ul
});
userList.innerHTML = html;
// Add event listener to buttons.
const buttons = userList.querySelectorAll('.userListUl .button');
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
alert('Button clicked!');
});
}
}
}
I have a simple HTML form which has an event listener binded to it and when you click on the button inside the form that has a class of 'booking__form__counter--increase' this should increase the input field value by 1. It calls a javascript function named 'increaseCounter()' I declare a variable that points to this value but when i try to use the variable to increment it, it doesn't work. If i use the methods in the variable directly it works? I am missing something simple here but i cannot work out what.
let bookingForm = document.querySelector('.booking__form');
bookingForm.addEventListener('click', function (e) {
let target = e.target;
let inputCounterValue = target.parentElement.firstElementChild.value;
let inputMaxCounterValue = target.parentElement.firstElementChild.dataset.maxCount;
let showCounterValue = target.parentElement.firstElementChild.nextElementSibling.textContent;
if (target.classList.contains('booking__form__counter--increase')) {
increaseCounter();
}
function increaseCounter() {
if (inputCounterValue === inputMaxCounterValue) {
return;
} else {
//does not update
inputCounterValue++;
showCounterValue = inputCounterValue;
//this does update
target.parentElement.firstElementChild.value++;
target.parentElement.firstElementChild.nextElementSibling.textContent = target.parentElement.firstElementChild.value;
}
}
});
<form class="booking__form">
<div class="container">
<div class="booking__form__group">
<div class="booking__form__section booking__form__section--arrival">
<div class="booking__form__control">
<label for="arrival">Arrival Date</label>
<div class="booking__form__counter">
<span class="booking__form__counter--value">0</span>
<div class="booking__form__counter--button booking__form__counter--increase">
<svg class="fal fa-chevron-up"></svg>
</div>
<div class="booking__form__counter--button booking__form__counter--decrease">
<svg class="fal fa-chevron-down"></svg>
</div>
</div>
</div>
</div>
<div class="booking__form__section booking__form__section--duration">
<div class="booking__form__control">
<label for="arrival">Nights</label>
<div class="booking__form__counter">
<input type="hidden" name="duration" value="1" data-max-count="21">
<span class="booking__form__counter--value">1</span>
<div class="booking__form__counter--button booking__form__counter--increase">
<svg class="fal fa-chevron-up"></svg>
</div>
<div class="booking__form__counter--button booking__form__counter--decrease">
<svg class="fal fa-chevron-down"></svg>
</div>
</div>
</div>
</div>
<div class="booking__form__section booking__form__section--adults">
<div class="booking__form__control" id="booking--adults">
<label for="arrival">Adults</label>
<div class="booking__form__counter">
<input type="hidden" name="adults" value="1" data-max-count="8">
<span class="booking__form__counter--value">1</span>
<div class="booking__form__counter--button booking__form__counter--increase">
<svg class="fal fa-chevron-up"></svg>
</div>
<div class="booking__form__counter--button booking__form__counter--decrease">
<svg class="fal fa-chevron-down"></svg>
</div>
</div>
</div>
</div>
<div class="booking__form__section booking__form__section--children">
<div class="booking__form__control" id="booking--children">
<label for="arrival">Children</label>
<div class="booking__form__counter">
<input type="hidden" name="children" value="0" data-max-count="5">
<span class="booking__form__counter--value">0</span>
<div class="booking__form__counter--button booking__form__counter--increase">
<svg class="fal fa-chevron-up"></svg>
</div>
<div class="booking__form__counter--button booking__form__counter--decrease">
<svg class="fal fa-chevron-down"></svg>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
UPDATED Javascript
I have had a play around and added my updated javascript below which now seems to be working ok. I removed the data attributes 'data-max-count' and just added in the 'max' attribute and changed the variable decelerations around.
let bookingForm = document.querySelector('.booking__form');
bookingForm.addEventListener('click', function (e) {
let target = e.target;
let input = target.parentElement.firstElementChild;
let displayValue = target.parentElement.firstElementChild.nextElementSibling;
if (target.classList.contains('booking__form__counter--increase')) {
increaseCounter();
} else if (target.classList.contains('booking__form__counter--decrease')) {
decreaseCounter();
}
function increaseCounter() {
if (input.value === input.max) {
return;
} else {
input.value++;
displayValue.textContent = input.value;
}
}
});
I re-wrote your js and it now works.
You had some issues with your selectors and the way you updated the values.
I associated the max-count with the hidden input you have there and read the data-max-count attribute value. If this is not present then the auto-increment doesn't work because I set the initial value of inputMaxCounterValue equal to 0.
Keep in mind that I only update what the user sees and not the input value.
let bookingForm = document.querySelector('.booking__form');
bookingForm.addEventListener('click', function (e) {
let target = e.target;
let parentElem = target.parentElement;
let inputCounterValue = 0;
let valueContainer = parentElem.querySelector('.booking__form__counter--value');
if (typeof valueContainer.textContent!=="undefined") {
inputCounterValue = parseInt(valueContainer.textContent,10);
}
if (target.classList.contains('booking__form__counter--increase')) {
increaseCounter(valueContainer);
}
function increaseCounter(element) {
let inputMaxCounterValue = 0;
let parentElem = target.parentElement;
if (typeof parentElem.querySelector('input')!=="undefined" && parentElem.querySelector('input')!==null) {
inputMaxCounterValue = parentElem.querySelector('input').getAttribute("data-max-count");
}
if (inputCounterValue === inputMaxCounterValue) {
return;
} else {
//does not update
inputCounterValue++;
showCounterValue = inputCounterValue;
//this does update
element.textContent = inputCounterValue;
}
I'm trying to develop a Load-More button using Javascript calling an API done with PHP.
So far, so good. I can load the new objects in a range i += i + 4 (I load three new comments everytime I press the button).
This is my load-more button:
$(document).ready(function () {
$(".load-more").on('click', function () {
var tab = $(this).data('tab');
var next_page = $(this).data('next-page');
console.log(next_page);
console.log(tab);
$.get($(this).data('url') + '?tab=' + tab + '&page=' + next_page, function (data) {
addNewQuestions($.parseJSON(data));
});
});
});
And for every object loaded I want to print each of these html blocks.
<div class="question-summary narrow">
<div class="col-md-12">
<div class="votes">
<div class="mini-counts"><span title="7 votes">
{if $question['votes_count']}
{$question['votes_count']}
{else}
0
{/if}
</span></div>
<div>votes</div>
</div>
<div {if $question['solved_date']}
class="status answered-accepted"
{else}
class="status answer-selected"
{/if}
title="one of the answers was accepted as the correct answer">
<div class="mini-counts"><span title="1 answer">{$question['answers_count']}</span></div>
<div>answer</div>
</div>
<div class="views">
<div class="mini-counts"><span title="140 views">{$question['views_counter']}</span></div>
<div>views</div>
</div>
<div class="summary">
<h3>
<a href="{questionUrl($question['publicationid'])}" class="question-title" style="font-size: 15px; line-height: 1.4; margin-bottom: .5em;">
{$question['title']}
</a>
</h3>
</div>
<div class = "statistics col-sm-12 text-right" style="padding-top: 8px">
<span>
<i class = "glyphicon glyphicon-time"></i>
<span class="question-updated-at">{$question['creation_date']}</span>
</span>
<span>
<i class = "glyphicon glyphicon-comment"></i>
<span class="question-answers">{$question['answers_count']}</span>
</span>
</div>
</div>
</div>
The problem is that I have several conditions, as {if$question['votes_count']} and I'm struggling because I don't know how get those variables when rendering the html.
Then I found something but I can't figure out how to adapt to my case
On that addNewQuestions I think that a good approach would be:
function addNewQuestions(objects) {
$.each(objects, function (i, object) {
console.log(object);
var lastItem = $('div.active[role="tabpanel"] .question-line:last');
var newLine = lastItem.clone(true);
var newObject = newLine.find('.question-col:eq(' + i + ')');
newObject.find('.question-info-container').attr('data-id', object.publicationid);
newObject.find('.vote-count').html(object.votes);
updateTitleAndLink(newObject.find('.question-title'), object);
lastItem.after(newLine);
});
}
function updateTitleAndLink(questionTitle, object) {
questionTitle.attr('href', questionTitle.data('base-question-url') + object.publicationid);
questionTitle.html(object.title);
}
But nothing happens and I can't figure out why.
Any idea or suggestion?
Kind regards