I created a video application using chime js SDK with the help of the documentation https://aws.github.io/amazon-chime-sdk-js/index.html
const indexMap = {};
const acquireVideoElement = tileId => {
for (let i = 0; i < 16; i += 1) {
if (indexMap[i] === tileId) {
return videoElements[i];
}
}
for (let i = 0; i < 16; i += 1) {
if (!indexMap.hasOwnProperty(i)) {
indexMap[i] = tileId;
return videoElements[i];
}
}
throw new Error('no video element is available');
};
const releaseVideoElement = tileId => {
for (let i = 0; i < 16; i += 1) {
if (indexMap[i] === tileId) {
delete indexMap[i];
return;
}
}
};
const observer = {
videoTileDidUpdate: tileState => {
if (!tileState.boundAttendeeId || tileState.localTile || tileState.isContent) {
return;
}
meetingSession.audioVideo.bindVideoElement(tileState.tileId, acquireVideoElement(tileState.tileId));
},
videoTileWasRemoved: tileId => {
releaseVideoElement(tileId);
}
};
meetingSession.audioVideo.addObserver(observer);
const audioMix = document.getElementById('meeting-audio');
meetingSession.audioVideo.bindAudioElement(audioMix);
meetingSession.audioVideo.start();
meetingSession.audioVideo.startLocalVideoTile();
This is working good and I can see all the attendees who is joined in the meeting. But I need to show my video also in a tag. Is it possible?
In your videoTileDidUpdate, for your video tile to show, where are you binding the local tile, I see that if tileState.localTile is true you are returning and the bindVideoElement is not getting called hence your localTile is not showing up. Can you please remove the localTile check and see if that works as the initial step.
videoTileDidUpdate: tileState => {
// Ignore a tile without attendee ID, a local tile (your video), and a content share.
const { allUsers } = this.props;
if (!tileState.boundAttendeeId || tileState.isContent) {
return;
}
if( tileState.localTile ) {
if( Obj[tileState.boundExternalUserId]['tileId'] === tileState.tileId) {
return;
}
}
this.meetingSession.current.audioVideo.bindVideoElement( tileState.tileId,document.getElementById(tileState.boundExternalUserId) );
}
Related
I`m trying to remove event listener from a tile that has been filled correctly on a sudoku game. But it is not working. After i fill the correct number on slot, i wish i wouldnt be able to click it again, but happens as the GIF show, it is erasing the grayed slots, meaning that removeEventListener is not working correctly. Any Help?
function qsa(selector) {
return document.querySelectorAll(selector);
}
function removeListenTiles() {
let tiles = qsa("tile");
for (let i = 0; i < tiles.length; i++) {
if (tiles[i].innerHTML.indexOf("table") == -1) {
if (tiles[i].textContent != "") {
tiles[i].removeEventListener("click", () => handleTile(tile[i), true);
}
}
}
}
function handleTile(tile) {
if (!disableSelect) {
if (tile.classList.contains("selected")) {
removeAllGrayedSelected();
updateMove();
removeSelected(tile);
selectedTile = null;
selectedNum = null;
} else {
removeAllGrayedSelected();
if (tile.innerHTML.indexOf("table") != -1) {
for (let j = 0; j < 81; j++) {
if (qsa(".tile")[j] !== tile) removeSelected(qsa(".tile")[j]);
}
tile.classList.add("selected");
selectedTile = tile;
updateSurround(tile);
updateMove();
}
}
}
}
function addListenTile(tile) {
tile.addEventListener("click", () => handleTile(tile), true);
}
Tried the currying method,and added tile.id to handlers array. Solved the problem.
const handlers = [];
const addListenTile = (tile) => {
tile.addEventListener("click", handlers[tile.id] = handleTile(tile), true);
};
const removeListenTile = (tile) => {
tile.removeEventListener("click", handlers[tile,id], true);
}
const handleTile = function (tile) {
return function onhandlerTile(event) {
console.log("OK1")
if (!disableSelect) {
if (tile.classList.contains("selected")) {
console.log("OK2");
removeAllGrayedSelected();
updateMove();
removeSelected(tile);
selectedTile = null;
selectedNum = null;
} else {
console.log("OK3");
removeAllGrayedSelected();
if (tile.innerHTML.indexOf("table") != -1) {
for (let j = 0; j < 81; j++) {
if (qsa(".tile")[j] !== tile) removeSelected(qsa(".tile")[j]);
}
tile.classList.add("selected");
selectedTile = tile;
updateSurround(tile);
updateMove();
}
}
}
};
};
I am developing a "Battleship" game with two grids made up of divs and am currently attempting to add a click event listener to all of the divs.
The issue that I am having is that the event listener is being repeatedly triggered (until every single div has been clicked) when I refresh my page and I can't understand why...
Here's the event listener in question:
let aiGridCells = document.querySelectorAll(".ai-grid__game-cell");
aiGridCells.forEach(cell => {
cell.addEventListener("click", humanPlayer.humanAttack(cell.getAttribute('data-ai'),aiPlayer))
});
Where humanPlayer is an object that has been generated by a factory function:
const humanPlayer = playerFactory('human');
import gameboardFactory from './gameboardFactory';
const playerFactory = (name) => {
const playerBoard = gameboardFactory();
const humanAttack = (cell, player) => { // Where player is opponent
if (player.playerBoard.gameBoard[cell].id !== 'miss') {
player.playerBoard.receiveAttack(cell);
};
};
const aiAttack = (player) => { // Where player is opponent
const availableCells = [];
for (let i = 0; i < player.playerBoard.gameBoard.length; i++) {
if (player.playerBoard.gameBoard[i].id === null) {
availableCells.push(i);
};
};
const attackCell = Math.floor(Math.random() * availableCells.length);
player.playerBoard.receiveAttack(attackCell);
};
return {
name,
playerBoard,
humanAttack,
aiAttack
}
};
export default playerFactory;
and gameboardFactory is:
import shipFactory from './shipFactory';
const gameboardFactory = () => {
const gameBoard = [];
const shipYard = [];
const init = () => {
for (let i = 0; i<100; i++) {
gameBoard.push({id: null})
};
};
const checkValidCoordinates = (direction, start, end) => {
if (direction === 'horizontal') {
if ((start <= 9) && (end <= 9)) {
return true;
} else {
let newStart = (start/10).toString(10);
let newEnd = (end/10).toString(10);
if ((newStart.charAt(0)) === (newEnd.charAt(0))) {
return true;
};
};
} else {
if ((start <= 9) && (end <= 9)) {
return false
} else if (start <= 9) {
let newStart = start.toString(10);
let newEnd = end.toString(10);
if ((newStart.charAt(0)) === (newEnd.charAt(1))) {
return true;
};
} else {
let newStart = start.toString(10);
let newEnd = end.toString(10);
if ((newStart.charAt(1)) === (newEnd.charAt(1))) {
return true;
};
};
};
return false
};
const checkIfShipPresent = (direction, start, end) => {
if (direction === 'horizontal') {
for (let i = start; i <= end; i++) {
if (gameBoard[i].id !== null) {
return true;
}
};
return false;
} else {
for (let i = start; i <= end; i += 10) {
if (gameBoard[i].id !== null) {
return true;
}
};
return false;
};
};
const placeShip = (id, direction, start, end) => {
if (!checkValidCoordinates(direction, start, end)) {
return;
};
if (checkIfShipPresent(direction, start, end)) {
return;
};
const newShip = [];
if (direction === 'horizontal') {
for (let i = start; i <= end; i++) {
gameBoard[i].id = id;
newShip.push(i);
};
} else {
for (let i = start; i <= end; i += 10) {
gameBoard[i].id = id;
newShip.push(i);
};
};
shipYard.push(shipFactory(id, newShip));
};
const receiveAttack = (cell) => {
console.log(cell)
if (gameBoard[cell].id !== null) {
const attackedShip = shipYard.filter((ship) => {
return ship.id === gameBoard[cell].id;
})[0];
if (!attackedShip.hits.includes(cell)) {
attackedShip.hits.push(cell);
};
} else {
gameBoard[cell].id = 'miss';
};
};
const checkIfAllShipsSunk = () => {
let allShipsSunk = true;
shipYard.forEach((ship) => {
if (ship.isSunk() === false) {
allShipsSunk = false;
};
});
return allShipsSunk;
};
if (gameBoard.length === 0) {
init();
};
return {
gameBoard,
placeShip,
receiveAttack,
shipYard,
checkIfAllShipsSunk
};
};
export default gameboardFactory;
I'm completely lost to what the issue could be and have tried countless things to rectify it. Any suggestions would be hugely appreciated.
Thank you!
You trying to add actual function call as listener here:
let aiGridCells = document.querySelectorAll(".ai-grid__game-cell");
aiGridCells.forEach(cell => {
cell.addEventListener("click", humanPlayer.humanAttack(cell.getAttribute('data-ai'),aiPlayer))
});
So on your event listener initialization you actually call your function instead of passing it as a listener.
You can pass it like this instead:
aiGridCells.forEach(cell => {
cell.addEventListener("click", () => humanPlayer.humanAttack(cell.getAttribute('data-ai'),aiPlayer))
});
I have multiple selectiontags on different picture slides but same page. Each slide has a set of selectiontags and I want users to only choose 1 selectiontag. I have written the code to do this but I wonder if there is another way.
So, basically I want:
Slide1 w. selectiontags1: Choose 1 selectiontag (out of 4)
Slide2 w.selectiontags2: Choose 1 selectiontag
Slide3 w. selectiontags3: Choose 1 selectiontag
Slide4 w. selectiontags4: Choose 1 selectiontag
This is my code so far.
var prevSelectedValue = null;
var prevSelectedValue2 = null;
var prevSelectedValue3 = null;
var prevSelectedValue4 = null;
$w.onReady(function () {
//TODO: write your page related code here...
let tags = $w('#selectionTags1');
if (tags.value.length === 1) {
prevSelectedValue = tags.value[0];
} else if (tags.value.length > 1) {
tags.value = [];
}
let tags2 = $w('#selectionTags2');
if (tags2.value.length === 1) {
prevSelectedValue2 = tags2.value[0];
} else if (tags2.value.length > 1) {
tags2.value = [];
}
let tags3 = $w('#selectionTags3');
if (tags3.value.length === 1) {
prevSelectedValue3 = tags3.value[0];
} else if (tags3.value.length > 1) {
tags3.value = [];
}
let tags4 = $w('#selectionTags4');
if (tags4.value.length === 1) {
prevSelectedValue4 = tags4.value[0];
} else if (tags4.value.length > 1) {
tags4.value = [];
}
});
export function selectionTags1_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue);
prevSelectedValue = event.target.value[0];
}
}
export function selectionTags2_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue2];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue2);
prevSelectedValue2 = event.target.value[0];
}
}
export function selectionTags3_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue3];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue3);
prevSelectedValue3 = event.target.value[0];
}
}
export function selectionTags4_change(event) {
//Add your code for this event here:
if (!event.target.value || event.target.value.length === 0) {
event.target.value = [prevSelectedValue4];
} else {
event.target.value = event.target.value.filter(x => x !== prevSelectedValue4);
prevSelectedValue4 = event.target.value[0];
}
}
Just a couple notes to help clean up the code:
It is better to use event.target.value[event.target.value.length-1] instead of .filter(). While it doesn't make a huge performance difference since our array is so small, we know that the most recently selected item is the last in the array, so using length-1 is more efficient code.
It is better to manipulate the selector and not the event, so I would recommend replacing event.target.value = event.target.value.filter... with $w('#selectionTags2') = event.target.value[..... This will also make your code more readable as you know which element is being updated.
If you are not preloading anything into the $w('#selectionTags') value array, I am not sure the code in your onReady function is necessary (unless I am missing a step where you do populate that selection value array).
For example, if I have CSS that included within document:
div {
color: red;
}
div.test {
border: 1px solid blue;
}
and html tag within document body:
<div id='demo'>
<div class='test'>123</div>
<div>456</div>
</div>
I want to convert everything within #demo as a string containing tag with all styles it was using, such as:
var parent = $('#demo');
var result = convertExternalInternalStylesToInline(parent);
// the desired result:
result = '<div style="color: red; border: 1px solid blue;">123</div>' +
'<div style="color: red">456</div>'
What I need is the content of convertExternalInternalStylesToInline function, that automatically fetch all descendant elements, apply the calculated/used styles then add css styles to those elements, then return it all as html string.
Can it be done using only client side javascript? If yes, how?
(I need to know how to get all calculated/used styles for a tag)
minimal example:
function convertExternalInternalStylesToInline(parent) {
var parent = $(parent).clone(); // clone it before modify, is this deep clone?
parent.find('*').each(function(idx,el){ // fetch all children
var el = $(el); // convert to jquery object
// get all applied styles on this element
// el.? // --> don't know how
// apply css for each styles
el.css( css_prop, css_style );
});
// return as string, maybe:
return parent.html();
}
EDIT: Thanks to Winchestro, I looked up the window.getMatchedCSSRules function, which is actually only in Webkit, and there is a discussion that it should be deprecated.
What you should be using is actually the window.getComputedStyle() Read the docs at MDN.
Another very useful resource you may look into is the CSSUtilities set of libraries.
What you need are two separate things, a library to parse out your CSS Object Modal (CSSOM), and apply the relevant CSS to your DOM elements.
I know of a good nodejs library which does this called Juice.
There are is a library I found which would probably work on the front-end, called inlineresources, which has a browserified build
On second thoughts, I think you may be able to use Juice along with browserify for this... But you'll have to evaluate that possibility manually...
assuming you have the HTML in an array (lines: string[]):
I'm not sure why I spent so much time on this
let styleLines = this.getLinesInsideStyleTag(lines)
let htmlLinesSeparatedByTag = this.getLinesNotInsideStyleTag(lines)
let mapOfCSSrules = this.getMapOfCSSRuleNamesAndPropertiesFromCSSLines(styleLines)
let linesWithInlineRulesApplied = this.applyInlineStylesToLines(htmlLinesSeparatedByTag, mapOfCSSrules)
let finalString = ""
for(let v = 0; v < linesWithInlineRulesApplied.length; v++ ) {
finalString = finalString + linesWithInlineRulesApplied[v]
}
console.log(finalString)
getLinesInsideStyleTag(lines: any[]) {
let styleLines: any[] = []
let foundStylesStartTag = false
let foundStylesEndTag = false
for(let i = 0; i < lines.length; i++ ) {
if(lines[i].indexOf("<style ") != -1) {
foundStylesStartTag=true
} else if(lines[i].indexOf("</style>") != -1) {
foundStylesEndTag = true
}
if(foundStylesStartTag == true && foundStylesEndTag == false) {
if(lines[i].indexOf("<style") == -1 && lines[i].indexOf("</style") == -1) {
styleLines.push(lines[i])
}
}
}
return styleLines
}
getLinesNotInsideStyleTag(lines: any[]) {
let foundStylesStartTag = false
let foundStylesEndTag = false
let linesToKeep: any[] = []
for(let i = 0; i < lines.length; i++ ) {
if(lines[i].indexOf("<style ") != -1) {
foundStylesStartTag=true
} else if(lines[i].indexOf("</style>") != -1) {
foundStylesEndTag = true
}
if(foundStylesStartTag == false && foundStylesEndTag == false) {
linesToKeep.push(lines[i])
} else if(foundStylesStartTag == true && foundStylesEndTag == true) {
if(lines[i].indexOf("<style") == -1 && lines[i].indexOf("</style") == -1) {
linesToKeep.push(lines[i])
}
}
}
let actualLinesToKeep: any[] = []
for(let i = 0; i < linesToKeep.length; i++ ){
let thisLineSplitOnOpeningTag = linesToKeep[i].split("<")
let pushFullLine = false
let modifiedLine = ""
for(let y = 0; y < thisLineSplitOnOpeningTag.length; y++) {
if(thisLineSplitOnOpeningTag[0] !== "") {
pushFullLine = true
} else {
if(thisLineSplitOnOpeningTag.length > 2) {
//then the line contains nested tags (oof)
if(thisLineSplitOnOpeningTag[y].length > 0) {
if( y != thisLineSplitOnOpeningTag.length - 1) {
modifiedLine = modifiedLine + "<" + thisLineSplitOnOpeningTag[y]+"%*#"
} else {
modifiedLine = modifiedLine + "<" + thisLineSplitOnOpeningTag[y]
}
}
} else {
pushFullLine = true
}
}
}
if(pushFullLine == true) {
// console.log("3pushing full line because it doesn't have nested tags: "+linesToKeep[i])
actualLinesToKeep.push(linesToKeep[i])
} else {
actualLinesToKeep.push(modifiedLine)
}
}
// console.log("actualLinesToKeep: ")
// console.log(actualLinesToKeep)
return actualLinesToKeep
}
//e.g. you pass it
// myRule {
// color: blue;
// text-align: left;
// }
// you get back: a dictionary / map where "myRule" is the key, and "color: blue;" and "text-align: left;" are the values for that key.
getMapOfCSSRuleNamesAndPropertiesFromCSSLines(styleLines: any[]) {
// console.log("styleLines: ")
// console.log(styleLines)
//rule, properties
let CSSrules: Map<string,any[]> = new Map();
let rulesSplitOnClosingBracket = styleLines.toString().split("}")
for(let i = 0; i < rulesSplitOnClosingBracket.length; i++) {
let indexOfOpeningBracket = rulesSplitOnClosingBracket[i].indexOf("{")
let ruleName = rulesSplitOnClosingBracket[i].substring(0,indexOfOpeningBracket).trim()
ruleName = this.replaceAll(ruleName,",","").toLowerCase()
if(ruleName[0] === ".") {
ruleName = ruleName.substring(1)
}
//replace dots with a space
ruleName = ruleName.replace(/\./g,' ')
let propertiesOfThisRule = rulesSplitOnClosingBracket[i].substring(indexOfOpeningBracket+1).split(",")
let propertiesToKeep: any[] = []
for(let j = 0; j < propertiesOfThisRule.length; j++) {
propertiesOfThisRule[j] = propertiesOfThisRule[j].trim()
if(propertiesOfThisRule[j] !== undefined && propertiesOfThisRule[j].length > 0) {
propertiesToKeep.push(propertiesOfThisRule[j])
}
}
if(ruleName !== undefined && ruleName.length > 0 && propertiesToKeep !== undefined && propertiesToKeep.length > 0) {
CSSrules.set(ruleName, propertiesToKeep)
}
}
return CSSrules
}
applyInlineStylesToLines(htmlLinesSeparatedByTag: any[], mapOfCSSrules: Map<string,any[]>) {
let linesWithInlineRulesApplied: any[] = []
let ruleNames = Array.from(mapOfCSSrules.keys())
for(let r = 0; r < htmlLinesSeparatedByTag.length; r++) {
let lineSplitOnContinuationCharacter = htmlLinesSeparatedByTag[r].split("%*#")
let partsOfLineThatContainClosingTags: any[] = []
let partsOfLineThatDoNotContainClosingTags: any[] = []
for(let d = 0; d < lineSplitOnContinuationCharacter.length; d++) {
if(lineSplitOnContinuationCharacter[d].indexOf("</") != -1) {
partsOfLineThatContainClosingTags.push({orderNumber: d, line: lineSplitOnContinuationCharacter[d]})
} else if(lineSplitOnContinuationCharacter[d].indexOf("</") == -1) {
partsOfLineThatDoNotContainClosingTags.push({orderNumber: d, line: lineSplitOnContinuationCharacter[d]})
}
}
let orderNumbers1: any[number] = partsOfLineThatDoNotContainClosingTags.map(val => val.orderNumber)
let orderNumbers2: any[number] = partsOfLineThatContainClosingTags.map(val => val.orderNumber)
let maxOrderNumberFor1 = Math.max.apply(Math,orderNumbers1)
let maxOrderNumberFor2 = Math.max.apply(Math,orderNumbers2)
let maxOrderNumber: number;
if(maxOrderNumberFor1 > maxOrderNumberFor2) {
maxOrderNumber = maxOrderNumberFor1
} else {
maxOrderNumber = maxOrderNumberFor2
}
let thisActualLineWithStylesApplied = ""
for(let u = 0; u < maxOrderNumber+1; u++) {
let partOfLineWithoutClosingTag = partsOfLineThatDoNotContainClosingTags.filter(val => val.orderNumber == u)[0]?.line
let partOfLineWithClosingTag = partsOfLineThatContainClosingTags.filter(val => val.orderNumber == u)[0]?.line
if ( partOfLineWithoutClosingTag !== undefined ) {
let idxOfFirstSpace = partOfLineWithoutClosingTag.indexOf(" ")
for(let s = 0; s < ruleNames.length; s++) {
let applyThisRuleToThisLine = true
let textToCheckFor: any[] = ruleNames[s].split(" ")
for(let t = 0; t < textToCheckFor.length; t++) {
if(partOfLineWithoutClosingTag.indexOf(textToCheckFor[t]) == -1) {
applyThisRuleToThisLine = false
}
}
if(applyThisRuleToThisLine) {
let lineAfterApplyingStyle = partOfLineWithoutClosingTag.substring(0, idxOfFirstSpace) +" style=\""
for(let u = 0; u < mapOfCSSrules.get(ruleNames[s]).length; u++) {
let thisPropertyToApply = mapOfCSSrules.get(ruleNames[s])[u]
lineAfterApplyingStyle=lineAfterApplyingStyle+thisPropertyToApply
}
lineAfterApplyingStyle = lineAfterApplyingStyle +"\""
partOfLineWithoutClosingTag = lineAfterApplyingStyle + partOfLineWithoutClosingTag
let lastIndexOfLessThan = partOfLineWithoutClosingTag.lastIndexOf("<")
let lastIndexOfGreaterThan = partOfLineWithoutClosingTag.lastIndexOf(">")
partOfLineWithoutClosingTag = partOfLineWithoutClosingTag.substring(0,lastIndexOfLessThan) + partOfLineWithoutClosingTag.substring(lastIndexOfGreaterThan)
}
}
thisActualLineWithStylesApplied = thisActualLineWithStylesApplied + partOfLineWithoutClosingTag
}
if(partOfLineWithClosingTag !== undefined) {
thisActualLineWithStylesApplied = thisActualLineWithStylesApplied + partOfLineWithClosingTag
}
}
linesWithInlineRulesApplied.push(thisActualLineWithStylesApplied)
}
return linesWithInlineRulesApplied
}
Edit: Thanks to Kumar I realized my suggested method is nonstandard, and
getComputedStyle( element );
should be used instead, which is a bit more difficult to use and filter but has the advantage of giving you only the final rules that actually apply to the element after the CSS was evaluated (which also includes default, not explicitly declared styles, making it a bit trickier to use in this case).
getMatchedCSSRules( element );
just use this vanilla javascript function. It does exactly what you want. I'd explain it if there were anything to explain that isn't implied by the name of the function.
Check out the api --> https://api.icndb.com/jokes/random/10
Everytime when the user clicks on a specific joke, it will be added to the favorite list.
To keep the code concise I will only show the function itself:
(function() {
"use strict";
const getJokesButton = document.getElementById('getData');
getJokesButton.addEventListener('click', getData);
loadLocalStorage();
function loadLocalStorage() {
let storage = JSON.parse(localStorage.getItem('favoList')) || [];
let listOfFavorites = document.getElementById("favorites");
let emptyArray = '';
if(storage.length > 0) {
for(let i = 0; i < storage.length; i++) {
let idNumberJoke = storage[i].id;
emptyArray +=
`<li><input type="checkbox" id='${idNumberJoke}'/> User title: ${storage[i].joke}</li>`;
listOfFavorites.innerHTML = emptyArray;
}
} else {
return false;
}
}
// fetch data from api
function getData() {
let listOfJokes = document.getElementById("list-of-jokes");
fetch('https://api.icndb.com/jokes/random/10')
.then(function(res) {
return res.json();
}).then(function(data) {
// variable is undefined because it is not initialized. Therefore at some empty single quotes
let result = '';
console.log(data.value);
data.value.forEach((joke) => {
result +=
`<li><input type="checkbox" class='inputCheckbox' id='${joke.id}'/> User title : ${joke.joke}</li>`;
listOfJokes.innerHTML = result;
});
bindCheckbox();
}).catch(function(err) {
console.log(err);
});
}
function clickedButton() {
getJokesButton.setAttribute('disabled', 'disabled');
getJokesButton.classList.add('opacity');
}
function bindCheckbox() {
let inputCheckbox = document.querySelectorAll('input[type=checkbox]');
let elems = document.getElementById('list-of-jokes').childNodes;
let favoriteList = document.getElementById('favorites');
let fav = JSON.parse(localStorage.getItem('favoList'))|| [];
if(elems.length > 0) {
inputCheckbox.forEach(function(element, index) {
inputCheckbox[index].addEventListener('change', function() {
let joke = this;
if(joke.checked && joke.parentNode.parentNode.id === 'list-of-jokes') {
joke.checked = false;
favoriteList.appendChild(joke.parentNode);
addFavorite(joke.id, joke.parentNode.innerText, fav);
}
if(joke.checked && joke.parentNode.parentNode.id === 'favorites') {
joke.checked = false;
removeFavorite(joke, index);
}
});
});
}
clickedButton();
}
function removeFavorite(favorite, index) {
let favoriteCheckBox = favorite;
let i = index;
// convert iterable object to an array, otherwise splice method would give an error.
let favoriteListItem = Array.from(favoriteCheckBox.parentNode);
favoriteListItem.splice(i, 1);
document.getElementById('list-of-jokes').appendChild(favorite.parentNode);
localStorage.setItem('favoList', JSON.stringify(favoriteListItem));
}
// store favorites in localStorage
function addFavorite(jokeId, jokeText, fav) {
let norrisJoke = {
id: jokeId,
joke: jokeText
};
let favorites = fav;
for (let i = 0; i < favorites.length; i++) {
if(favorites[i].id !== norrisJoke.id) {
favorites.push(norrisJoke);
}
}
// favorites[i].id !== norrisJoke.id
// always get the object before the push method and pass it into stringify
localStorage.setItem('favoList', JSON.stringify(favorites));
}
// function which will randomly add one joke to favorite list every 5 seconds
// function need a button which allows you to turn on and off this auto add function
})();
<div class="inner-body">
<button id="getData">GET Jokes</button>
<div class='inner-block'>
<h2>Chuck Norris Jokes</h2>
<ul class='unordered-list' id="list-of-jokes">
</ul>
</div>
<div class='inner-block'>
<h2>Favorites</h2>
<ul class='unordered-list' id="favorites">
</ul>
</div>
</div>
The keys and values would not be pushed into localStorage, the only thing I see is an empty [] in localStorage. The norrisJoke object literal will be dynamically changed. So how could I make this function works?
Too complex, but click on the link below and scroll down to the bottom:
https://codepen.io/chichichi/pen/Gyzzvb
You are trying to run through an empty list here
for (let i = 0; i < favorites.length; i++) {
if(favorites[i].id !== norrisJoke.id) {
favorites.push(norrisJoke);
}
}
This means that nothing will ever be pushed. You can reduce your list to an array of id, then check if the joke exists in the list.
const favIds = favorites.reduce((sum, element) => {
return sum.concat(element.id);
},
[]);
Now you can check if the joke doesn't exists in favorites
if(!favIds.includes(jokeId)){
favorites.push(norrisJoke);
}
The problem is the for loop, the first time it's executed favorites will be an empty array so it's length will be 0, so it will never enter the loop
Something like this should work:
favorites = favorites.filter(joke => joke.id !== norrisJoke.id).concat(norrisJoke);
let favorites = JSON.parse(localStorage.getItem('favoList'))|| {};
favorites[norrisJoke.id] =norrisJoke.joke
Why don't you use a map in place of an array?
Also as #fl9 points out your for loop will never start off! because favorites.length is 0 to begin with
But I want to check duplicates before the joke will be pushed into favorite list
By definition a hash will not allow duplicate entries, so no need to worry about duplications
Run localStorage.getItem('favoList') in the console of this fiddle :
(function() {
"use strict";
const getJokesButton = document.getElementById('getData');
getJokesButton.addEventListener('click', getData);
loadLocalStorage();
function loadLocalStorage() {
let storage = JSON.parse(localStorage.getItem('favoList')) || [];
let listOfFavorites = document.getElementById("favorites");
let emptyArray = '';
if(storage.length > 0) {
for(let i = 0; i < storage.length; i++) {
let idNumberJoke = storage[i].id;
emptyArray +=
`<li><input type="checkbox" id='${idNumberJoke}'/> User title: ${storage[i].joke}</li>`;
listOfFavorites.innerHTML = emptyArray;
}
} else {
return false;
}
}
// fetch data from api
function getData() {
let listOfJokes = document.getElementById("list-of-jokes");
fetch('https://api.icndb.com/jokes/random/10')
.then(function(res) {
return res.json();
}).then(function(data) {
// variable is undefined because it is not initialized. Therefore at some empty single quotes
let result = '';
console.log(data.value);
data.value.forEach((joke) => {
result +=
`<li><input type="checkbox" class='inputCheckbox' id='${joke.id}'/> User title : ${joke.joke}</li>`;
listOfJokes.innerHTML = result;
});
bindCheckbox();
}).catch(function(err) {
console.log(err);
});
}
function clickedButton() {
getJokesButton.setAttribute('disabled', 'disabled');
getJokesButton.classList.add('opacity');
}
function bindCheckbox() {
let inputCheckbox = document.querySelectorAll('input[type=checkbox]');
let elems = document.getElementById('list-of-jokes').childNodes;
let favoriteList = document.getElementById('favorites');
let fav = JSON.parse(localStorage.getItem('favoList'))|| [];
if(elems.length > 0) {
inputCheckbox.forEach(function(element, index) {
inputCheckbox[index].addEventListener('change', function() {
let joke = this;
if(joke.checked && joke.parentNode.parentNode.id === 'list-of-jokes') {
joke.checked = false;
favoriteList.appendChild(joke.parentNode);
addFavorite(joke.id, joke.parentNode.innerText, fav);
}
if(joke.checked && joke.parentNode.parentNode.id === 'favorites') {
joke.checked = false;
removeFavorite(joke, index);
}
});
});
}
clickedButton();
}
function removeFavorite(favorite, index) {
let favoriteCheckBox = favorite;
let i = index;
// convert iterable object to an array, otherwise splice method would give an error.
let favoriteListItem = Array.from(favoriteCheckBox.parentNode);
favoriteListItem.splice(i, 1);
document.getElementById('list-of-jokes').appendChild(favorite.parentNode);
localStorage.setItem('favoList', JSON.stringify(favoriteListItem));
}
// store favorites in localStorage
function addFavorite(jokeId, jokeText, fav) {
let norrisJoke = {
id: jokeId,
joke: jokeText
};
let favorites = fav;
for (let i = 0; i < favorites.length; i++) {
if(favorites[i].id !== norrisJoke.id) {
favorites.push(norrisJoke);
}
}
// favorites[i].id !== norrisJoke.id
// always get the object before the push method and pass it into stringify
localStorage.setItem('favoList', JSON.stringify(favorites));
}
// function which will randomly add one joke to favorite list every 5 seconds
// function need a button which allows you to turn on and off this auto add function
})();