Function not printing object properties correctly onto the HTML - javascript

Been trying all day to print an array of objects into the "cart" part of the HTML, but for the life of me I can't figure out what's wrong. The click event is working just fine, it either pushes the object into the array if it's not already there, or if it is, then it adds one to the count of "quantity" property. The issue is that, in order for the HTML to reflect these changes I have to refresh the whole page.
My guess is something not woring right with the storage, here's the initial setting of the "cart" part of the storage:
``if(localStorage.getItem("carrito")) {
productosEnCarrito = JSON.parse(localStorage.getItem("carrito"))
initCartHTML(productosEnCarrito)
}else{
productosEnCarrito = []
localStorage.setItem("carrito", productosEnCarrito)
}`
`
Here is everything involed in the printing and said array:
Event listener on the <button> tag:
`let agregarBtn = document.getElementById(`addCart${curso.clase}`)
agregarBtn.addEventListener("click", () => {
addCart(curso)
})`
the addCart() function:
`function addCart (curso) {
let cursoAgregado = productosEnCarrito.find((elem)=> elem.clase == curso.clase)
if(cursoAgregado == undefined) {
productosEnCarrito.push(curso)
localStorage.setItem("carrito", JSON.stringify(productosEnCarrito))
cartHTML(curso)
} else {
cursoAgregado.quantity++
localStorage.setItem("carrito", JSON.stringify(productosEnCarrito))
cartHTML(curso)
}
console.log(productosEnCarrito)
itemsInCart.innerText = productosEnCarrito.length
}`
the primary printing function (this is the bit not working)
`function cartHTML (curso) {
let cursoAgregado = productosEnCarrito.find((elem)=> elem.clase == curso.clase)
let newCurso = document.createElement("ul")
if (cursoAgregado == undefined) {
newCurso.className = "nav nav-tabs d-flex justify-content-between flex-nowrap"
newCurso.innerHTML =
`<li class="nav-item m-2">${curso.id}</li>
<li class="nav-item m-2">${curso.duration}</li>
<li class="nav-item m-2">€${curso.price}</li>
<li class="nav-item m-2">x${curso.quantity}</li>`
cartList.appendChild(newCurso)
} else {
newCurso.innerHTML =
`<li class="nav-item m-2">${curso.id}</li>
<li class="nav-item m-2">${curso.duration}</li>
<li class="nav-item m-2">€${curso.price}</li>
<li class="nav-item m-2">x${curso.quantity}</li>`
}
}`
I also set another function to retreive the storage and printing it when refreshing, this might have something to do with it, idk:
`function initCartHTML (array) {
for (let curso of array) {
let newCurso = document.createElement("ul") // <-- linea modificada
newCurso.className = "nav nav-tabs d-flex justify-content-between flex-nowrap"
newCurso.innerHTML =
`<li class="nav-item m-2">${curso.id}</li>
<li class="nav-item m-2">${curso.duration}</li>
<li class="nav-item m-2">€${curso.price}</li>
<li class="nav-item m-2">x${curso.quantity}</li>`
cartList.appendChild(newCurso)
}
itemsInCart.innerText = productosEnCarrito.length
}`
I'm trying to get the cart part of the html to reflect the changes immediately after it's modified, but it only does so after refreshing.

Related

Navbar TypeError: Cannot read property 'classList' of null

I am trying to implement a navbar to html which has the effect dynamically switch pages instead of changing links via href. The trick I'm using to accomplish this is by adding a classList of is-active to the div elements in the section tag.
Here is an example code of the generated HTML :
navBar.js
const navbarItem = [
{
navitem : "About",
link: 1
},
{
navitem : "Legal",
link: 2
},
{
navitem : "Contact",
link: 3
}
];
window.onload = function navLoad() {
const navbar = document.getElementById('navbar');
navbar.innerHTML =
`
<div class="toggle">
<i class="fa fa-bars menu" aria-hidden="true"></i>
</div>
<ul class="nav-list">
<li class="tab is-active">
<a onclick="renderNav()">Home</a>
</li>
${navbarItem.map(loadNavitems).join(" ")}
</ul>
`
}
function loadNavitems(navItems) {
return `
<li class="tab">
<a data-switcher data-id="${navItems.link}" onclick="renderNav(); navSwap();">${navItems.navitem}</a>
</li>
`
}
function renderNav(){
const pages = document.getElementById('main');
document.getElementById('alphabetButtons').style.display = "block";
pages.innerHTML =
`
<section class="pages">
${navbarItem.map(item => `
<div class="page" data-page="${item.link}">
<h1>${item.navitem}</h1>
</div>
`).join('')}
</section>
`
};
And here is the code which takes care of the page switching:
navSwitcher.js
function navSwap() {
const tab_switchers = document.querySelectorAll('[data-switcher]');
for (let input of tab_switchers) {
const page_id = input.dataset.switcher;
console.log(page_id);
input.addEventListener('click', function () {
if(document.querySelector('.nav-list .tab.is-active')){
document.querySelector('.nav-list .tab.is-active').classList.remove('is-active');
console.log('removed');
}
if(input.parentNode.classList.contains('tab')){
input.parentNode.classList.add('is-active');
}
//SwitchNav(page_id);
});
}
}
function SwitchNav(page_id) {
const currentPage = document.querySelector('.pages .page');
const next_page = document.querySelector(`.pages .page[data-page="${page_id}"]`);
console.log(next_page);
if(document.querySelector('.pages .page.is-active')){
document.querySelector('.pages .page.is-active').classList.remove('is-active');
}
next_page.classList.add('is-active');
}
Update : The issue is that causes the error comes when attempting to place [data-page="${page_id}"] after .page inside the querySelector. When console logging, [data-page="${page_id}"] will return null. This is weird because data-page is named correctly in the renderNav function which holds the div of class page.
Hence my hypothesis now is that the issue comes from [data-page="${page_id}"] in the SwitchNav(page_id) function. My question is why is this occuring if everything is named correctly?
Fixes tried:
Attempted to change the for/of loop to a regular for loop inside the navSwap function.
Inside the navSwap function, const page_id = input.dataset.tab; was changed to const page_id = input.dataset.switcher; which now returns 3 items.
So after some digging around, in the end it was actually const page_id = tab_switcher.dataset.tab; which was returning undefined, hence causing the error to happen. In reality it was returning id so I changed it to tab_switcher.dataset.id;.
Here is the final attempt at the code (i placed everything in one single file):
navBar.js:
const navbarItem = [
{
navitem : "About",
link: 1
},
{
navitem : "Legal",
link: 2
},
{
navitem : "Contact",
link: 3
}
];
window.onload = function navLoad() {
const navbar = document.getElementById('navbar');
navbar.innerHTML =
`
<div class="toggle">
<i class="fa fa-bars menu" aria-hidden="true"></i>
</div>
<ul class="nav-list">
<li class="tab is-active">
<a onclick="renderNav()">Home</a>
</li>
${navbarItem.map(loadNavitems).join(" ")}
</ul>
`
}
function loadNavitems(navItems) {
return `
<li class="tab">
<a data-switcher data-id="${navItems.link}" onclick="renderNav(); navSwap();">${navItems.navitem}</a>
</li>
`
}
function renderNav(){
const pages = document.getElementById('main');
document.getElementById('alphabetButtons').style.display = "block";
pages.innerHTML =
`
<section class="pages">
${navbarItem.map(item => `
<div class="page" data-page="${item.link}">
<h1>${item.navitem}</h1>
</div>
`).join('')}
</section>
`
};
function navSwap() {
const tab_switchers = document.querySelectorAll('[data-switcher]');
for (let i = 0; i < tab_switchers.length; i++) {
const tab_switcher = tab_switchers[i];
const page_id = tab_switcher.dataset.id;
tab_switcher.addEventListener('click', function(){
document.querySelector('.nav-list .tab.is-active').classList.remove('is-active');
tab_switcher.parentNode.classList.add('is-active');
SwitchNav(page_id);
});
}
}
function SwitchNav(page_id) {
const currentPage = document.querySelector('.pages .page');
const next_page = document.querySelector(`.pages .page[data-page="${page_id}"]`);
console.log(next_page);
if(document.querySelector('.pages .page.is-active')){
currentPage.classList.remove('is-active');
}
if(document.querySelector('.pages .page')){
next_page.classList.add('is-active');
}
}
Although the code generates the intended results, I have to click twice on the tag to fire it's onclick event. If anybody knows why this occurs it would be of much help to know in the comments.
Also let me know if this is a viable fix. Thanks again :)

JS innerHTML not displaying

I'm trying to create a dynamic navbar that is different when a user is signed in vs. not signed in.
I have the auth portion working (confirmed with console logs); however, for some reason, the HTML is not getting updated when I use .innerHTML = .... I'm console logging the innerHTML after I update it, and it appears correct in the log but the navbar isn't displaying like it should.
HTML:
...
<ul class="site-menu main-menu js-clone-nav mr-auto d-none d-lg-block">
<li><strong>Home</strong></li>
<li id ="nav-listings">
<strong>Listings</strong>
</li>
<li><strong>Contact</strong></li>
<li class="has-children" id="nav-account">
<strong>Sign-In</strong>
<div id="navDropdown" class="dropdown">
Profile
Sign-Up
</div>
</li>
</ul>
...
js code:
AOS.init({
duration: 800,
easing: 'slide',
once: true
});
jQuery(document).ready(function($) {
"use strict";
$(".loader").delay(1000).fadeOut("slow");
$("#overlayer").delay(1000).fadeOut("slow");
...
firebase.auth().onAuthStateChanged(firebaseUser => {
if (firebaseUser) {
var username = firebaseUser.email;
console.log(`${firebaseUser ? `- User signed in: ${firebaseUser.email} => ${username}` : "No User Signed in"}`);
document.getElementById("nav-listings").className = "has-children";
document.getElementById("nav-listings").innerHTML = "<a href='Search.html' class='nav-link'><strong>Listings</strong></a>" +
"<div id='listDropdown' class='dropdown'>" +
"<a href='AddHouse.html'>Add Listing</a>" +
"<a href='User.html'>Favorites</a>" +
"</div>";
console.log(document.getElementById("nav-listings").innerHTML);
} else {
btnLogin.disabled = false;
}
});
});
EDIT:
It is appearing in the condensed form of the navbar but the main navbar???
See Here for the full code (minus the CSS... which makes the problem hard to see but all the js and HTML are there).
This should work
HTML:
<ul class="site-menu main-menu js-clone-nav mr-auto d-none d-lg-block">
<li><strong>Home</strong></li>
<li id="nav-listings">
<strong>Listings</strong>
</li>
<li><strong>Contact</strong></li>
<li class="has-children" id="nav-account">
<strong>Sign-In</strong>
<div id="navDropdown" class="dropdown">
Profile
Sign-Up
</div>
</li>
</ul>
JavaScript:
firebase.auth().onAuthStateChanged(firebaseUser => {
if (firebaseUser) {
var username = firebaseUser.email;
console.log(`${firebaseUser ? `- User signed in: ${firebaseUser.email} => ${username}` : "No User Signed in"}`); ;
$("#nav-listings").addClass('has-children').html("<a href='Search.html' class='nav-link'><strong>Listings</strong></a><div id='listDropdown' class='dropdown'><a href='AddHouse.html'>Add Listing</a><a href='User.html'>Favorites</a></div>")
console.log($("#nav-listings").html());
} else {
btnLogin.disabled = false;
}
});
When you are setting the innerHTML you are also setting the class for nav-listings to has-children. This removes the existing class of nav-link.
Do you mean to do this? If you want to keep the nav-link class as well then instead of
document.getElementById("nav-listings").className = "has-children";
put a space before the has-children and add the string to the classname. This will then add has-children to the class and wont remove the nav-link.
document.getElementById("nav-listings").className += " has-children";
If you console.log the outerHTML instead of innerHTML you can check whether both nav-link and has-children are there.

how to keep the value of dropdowns while navigating in jsp application?

<li class="dropdown active">
<b>Title</b><span class="caret"></span>
<ul class="dropdown-menu" style="background-color:#b3ffb3;">
<li><b>1</b></li>
<li role="separator" class="divider"></li>
<li><b>2</b></li>
<li role="separator" class="divider"></li>
<li><b>3</b></li>
</ul>
</li>
above is my dropdowns and navigation. when i navigate to other page by clicking 1, 2,3 it resets dropdowns in my form.
i have found the solution for this.
below is the code to maintain session
// Run on page load
window.onload = function() {
// If sessionStorage is storing default values (ex. name), exit the function and do not restore data
if (sessionStorage.getItem('Environment') == "Environment" ) {
return;
}
// If values are not blank, restore them to the fields
var Environment = sessionStorage.getItem('Environment');
if (Environment !== null) $('#Environment').val(Environment);
var DataFilter = sessionStorage.getItem('DataFilter');
if (DataFilter !== null) $('#DataFilter').val(DataFilter);
var InUse= sessionStorage.getItem('InUse');
if (InUse!== null) $('#InUse').val(InUse);
var DataUsage= sessionStorage.getItem('DataUsage');
if (DataUsage!== null) $('#DataUsage').val(DataUsage);
}
// Before refreshing the page, save the form data to sessionStorage
window.onbeforeunload = function() {
sessionStorage.setItem("Environment", $('#Environment').val());
sessionStorage.setItem("DataFilter", $('#DataFilter').val());
sessionStorage.setItem("InUse", $('#InUse').val());
sessionStorage.setItem("DataUsage", $('#DataUsage').val());
}

I want to translate my HTML text

I wanted to translate texts in my web site I used this code, I know that I'm close to get the right solution.
Somewhere in my HTML code:
<ul class="nav navbar-nav navbar-right">
<li>A PREPOS</li>
<li>SERVICES</li>
<li>PORTFOLIO</li>
<li>PRICING</li>
<li>CONTACT</li>
<li> </li>
<li ><a class="lang">FR</a></li>
<li ><a class="lang">EN</a></li>
</ul>
and this is my JavaScript code :
var dictionary, set_lang;
// Object literal behaving as multi-dictionary
dictionary = {
"fr": {
"menu" : {
"about": "à propos",
"services": "services"
}
},
"en": {
"menu" : {
"about": "about",
"services": "services"
}
}
};
// Function for swapping dictionaries
set_lang = function (dictionary) {
$("[data-translate]").text(function () {
var key = $(this).data("translate");
if (dictionary.hasOwnProperty(key)) {
console.log(dictionary[key]);
return dictionary[key];
}
});
};
// Swap languages when menu changes
$(".lang").click(function() {
var language = $(this).html().toLowerCase();
console.log(language);
if (dictionary.hasOwnProperty(language)) {
set_lang(dictionary[language]);
}
});
// Set initial language to French
set_lang(dictionary.fr);
});
In this part :
<li>A PREPOS</li>
The Text 'A PREPOS' can't be translated, but when I change It to:
<li>A PREPOS</li>
I can see my object 'menu' using console.log
Your dictionary logic is totally off. services i.e: will always be services and there's not good reason to use an additional menu layer. Redundant and repetitive should be avoided.
Rather use properties like:
var dictionary = { // props in alphabetical order ok? promise?
// prop : {fr, en}
about : {fr:"à propos", en:"about"},
services : {fr:"services", en:"servicesssss"}
};
function translate( lan ) {
$("[data-translate]").text(function(){
var data = this.dataset.translate.split("|");
var prop = data[0]; // the dictionary property name
var style = data[1]; // "uppercase", "lowercase", "capitalize"
if(!prop in dictionary) return console.error("No "+ prop +" in dictionary");
var trans = dictionary[prop][lan]; // The translated word
// Do we need to apply styles?
if(style==="capitalize"){
trans = trans.charAt(0).toUpperCase() + trans.slice(1);
} else if(style==="uppercase"){
trans = trans.toUpperCase();
} else if( style==="lowercase"){
trans = trans.toLowerCase();
}
return trans;
});
}
// Swap languages when menu changes
$("[data-lang]").click(function() {
translate( this.dataset.lang );
});
// Set initial language to French
translate("fr");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="nav navbar-nav navbar-right">
<li>A PREPOS</li>
<li>SERVICES</li>
<li> </li>
<li><a data-lang="fr">FR</a></li> <!-- data-* attributes can be helpful -->
<li><a data-lang="en">EN</a></li>
</ul>
Your problem is that your call to dictionary.hasOwnProperty("menu.about") returns false.
You need to traverse the tree of objects so you're getting the property like this:
dictionary["menu"]["about"]
I've put together a simple recursive example on how you can traverse the tree of objects.
// Object literal behaving as multi-dictionary
dictionary = {
"fr": {
"menu": {
"about": "à propos",
"services": "services"
}
},
"en": {
"menu": {
"about": "about",
"services": "services"
}
}
};
// Function for swapping dictionaries
set_lang = function(dictionary) {
$("[data-translate]").text(function() {
var key = $(this).data("translate");
return parseSubObject(dictionary, key);
});
};
function parseSubObject(obj, str) {
var props = str.split(".");
var thisProp = props.shift();
if (obj.hasOwnProperty(thisProp)) {
if (props.length == 0) {
return obj[thisProp];
} else {
return parseSubObject(obj[thisProp], props.join('.'));
}
} else {
return null;
}
}
// Swap languages when menu changes
$(".lang").click(function() {
var language = $(this).text().toLowerCase();
if (dictionary.hasOwnProperty(language)) {
set_lang(dictionary[language]);
}
});
// Set initial language to French
set_lang(dictionary.fr);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul class="nav navbar-nav navbar-right">
<li>A PREPOS</li>
<li>SERVICES</li>
<li>PORTFOLIO</li>
<li>PRICING</li>
<li>CONTACT</li>
<li> </li>
<li><a class="lang">FR</a></li>
<li><a class="lang">EN</a></li>
</ul>
You're assuming that dictionary.fr.menu.about (à propos) is the same as dictionary.fr['menu.about'] (undefined).
Note the data structure of each section of dictionary.fr.menu.about:
dictionary: Object
dictionary.fr: Object
dictionary.fr.menu: Object
dictionary.fr.menu.about: String
In order to get to the String, you need to walk the objects, which you can do with this code:
set_lang = function(dictionary) {
$("[data-translate]").text(function() {
var key = $(this).data("translate").split('.'),
val = dictionary[key.shift()];
while(key.length) {
val = val[key.shift()];
}
return val;
});
};
Snippet:
var dictionary, set_lang;
// Object literal behaving as multi-dictionary
dictionary = {
"fr": {
"menu": {
"about": "à propos",
"services": "services"
}
},
"en": {
"menu": {
"about": "about",
"services": "services"
}
}
};
// Function for swapping dictionaries
set_lang = function(dictionary) {
$("[data-translate]").text(function() {
var key = $(this).data("translate").split('.'),
val = dictionary[key.shift()];
while(key.length) {
val = val[key.shift()];
}
return val;
});
};
// Swap languages when menu changes
$(".lang").click(function() {
var language = $(this).html().toLowerCase();
console.log(language);
if (dictionary.hasOwnProperty(language)) {
set_lang(dictionary[language]);
}
});
// Set initial language to French
set_lang(dictionary.fr);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="nav navbar-nav navbar-right">
<li>A PREPOS</li>
<li>SERVICES</li>
<li>PORTFOLIO</li>
<li>PRICING</li>
<li>CONTACT</li>
<li> </li>
<li><a class="lang">FR</a></li>
<li><a class="lang">EN</a></li>
</ul>

How to handle nested menu in knockout viewmodel

I am using knockoutjs in my project. I have a scenario where I have to create a nested menu in my viewmodel, which I did like this:
self.menu = [
{
name: 'Services',
sub: [{ name: 'Service-A' }, { name: 'Service-B' }]
},
// etc
];
self.chosenMenu = ko.observable();
self.goToMenu = function (main, sub) {
var selectedMenu = {
main: main,
sub: sub
};
self.chosenMenu(selectedMenu);
};
My View:
<ul class="nav navbar-nav menuitems col-md-8" data-bind="foreach: menu">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span data-bind="text: name"></span></a>
<ul class="dropdown-menu" role="menu" data-bind="foreach: sub">
<li>
<a href="javascript:void(0);" data-bind="text: name,
click: function() { $root.goToMenu($parent, $data); }">
</a>
</li>
</ul>
</li>
</ul>
However, I feel that this approach of creating nested menu is not good, because suppose if I want to go on any menu item programmatically then with his approach it is not possible?
Can anybody please suggest me the good approach to handle this kind of scenario?
Make sure each menu item has a unique identifier. For the example data you've given name will suffice, but you may have to add a fullPath property to menu item view models.
Your goToMenu function can now take just one parameter: uniqueMenuIdentifier, and recurse through all menu items to find the correct one, like this:
function findMenuItem(menuList, uniqueMenuIdentifier) {
for (var i = 0; i < menuList.length; i++) {
if (menuList[i].name === uniqueMenuIdentifier) {
return menuList[i];
}
if (!!menuList[i].sub) {
var subItem = findMenuItem(menuList[i].sub, uniqueMenuIdentifier);
if (!!subItem) {
return subItem;
}
}
}
return null;
}
self.goToMenu = function (menuItem) {
var uniqueMenuIdentifier = menuItem.name;
var item = findMenuItem(self.menu, uniqueMenuIdentifier);
self.chosenMenu(item);
}
This allows for a much simpler binding in the anchor tag:
<a href="javascript:void(0);" data-bind="text: name, click: $root.goToMenu">
See this fiddle for a demo.
From this you can also guess that it's even possible to set the chosenMenu directly:
// No longer needed:
//function findMenuItem(menuList, uniqueMenuIdentifier) { }
self.goToMenu = function (menuItem) {
self.chosenMenu(menuItem);
}
See this fiddle for a demo.
I ran into a similar scenario yesterday. You can have a look at my solution on
Is there any knockout plugin for showing a nested context menu?. The main point is that I've used the template binding to be able to construct a hierarchical menu of any depth.

Categories