Creating a leaderboard with Firebase - javascript

I'm trying to build a top 10 leaderboard using the Firebase Realtime DB - I am able to pull the top 10 ordered by score (last 10 due to the way firebase stores in ascending order) however when I attempt to place them in the page they all appear in key order.
If I was a betting man I'd guess it's to do with the for loop I have to create the elements - but I'm not good enough at Javascript to work out where the issue is I've spent the last 3 hours on MDN and W3Schools and I can't for the life of me work it out.
Either that or I need to run a For Each loop on the actual data query? but I feel like I could avoid that as I'm already collecting the score data so I could just arrange that somehow?
I was sort of expecting everything to appear in ascending order - meaning I would have to go back and prepend my JQuery but instead I've managed to accidentally create a new problem for myself.
Any suggestions will be GREATLY appreciated
Here is my current code:
var db = firebase.database()
var ref = db.ref('images')
ref.orderByChild('score').limitToLast(10).on('value', gotData, errData);
function gotData(data) {
var scores = data.val();
var keys = Object.keys(scores);
var currentRow;
for (var i = 0; i < keys.length; i++){
var currentObject = scores[keys[i]];
if(i % 1 == 0 ){
currentRow = document.createElement("div");
$(currentRow).addClass("pure-u-1-5")
$("#content").append(currentRow);
}
var col = document.createElement("div")
$(col).addClass("col-lg-5");
var image = document.createElement("img")
image.src=currentObject.url;
$(image).addClass("contentImage")
var p = document.createElement("P")
$(p).html(currentObject.score)
$(p).addClass("contentScore");
$(col).append(image);
$(col).append(p);
$(currentRow).append(col);
}
}

Use .sort() beforehand, then iterate over each score object to add it to the page:
function gotData(data) {
const scores = data.val();
const keys = Object.keys(scores);
const sortedKeys = keys.sort((keyA, keyB) => scores[keyB].score - scores[keyA].score);
const content = document.querySelector('#content');
sortedKeys.map(sortedKey => scores[sortedKey])
.forEach(scoreObj => {
const row = content.appendChild(document.createElement('div'));
row.classList.add('pure-u-1-5'); // better done in the CSS if possible
const col = row.appendChild(document.createElement('div'));
col.classList.add('col-lg-5');
const img = col.appendChild(document.createElement('img'));
img.src = scoreObj.url;
img.classList.add('contentScore');
col.appendChild(document.createElement('p')).textContent = scoreObj.score;
});
}
For loops have worse abstraction, require manual iteration, and have hoisting problems when you use var - use the array methods instead when you can.

Related

unshift and push inside a loop in Javascript, infinite loop javascript

I'm a beginner student and I'm try to get some information from "user" using the prompt() function, and then throw this information in an array. I need to use the loop FOR and the WHILE to solve this problem.
This is my code:
let allEmployeess = Number(prompt("How many employees in the company?"));
let employee = new Array(allEmployeess);
let contador = 1;
for (let i = 0; i <= employee.length; i++) {
let colaborador = {
name: prompt("Employee name:"),
salary: Number(prompt("What is the salary amount??"))
}
employee.unshift(colaborador);
}
This isn't working and I'm falling into an infinite loop.
I suppose this happens because the unshift(and push) method, return the new lenght of the array, and maybe I'm increasing the array leaving it always bigger than the counter.
What can I do to solve this?
You are looping through an array that you keep adding elements to, hence the infinite loop. Use the number of employees (scalar) in the for loop instead. Also, I created the colaborador object outside of the loop, so it only takes up one place in memory:
let allEmployeess = Number(prompt("How many employees in the company?"));
let colaborador = {};
let employee = [];
for (let i = 0; i < allEmployeess; i++) {
colaborador["name"] = prompt("Employee name:");
colaborador["salary"] = Number(prompt("What is the salary amount??"));
employee.unshift(colaborador);
colaborador = {};
}
console.log(employee);
The were a few minor mistakes in your code which I tried to address with comments:
let allEmployeess = Number(prompt("How many employees in the company?"));
let employee = new Array(allEmployeess);
let contador = {}; // better to have an empty object instead of 1 for readablity
for (let i = 0; i < employee.length; i++) { // when you are iterating an array, it should go from 0 to length -1
let colaborador = {
name: prompt("Employee name:"),
salary: Number(prompt("What is the salary amount??"))
}
employee[i] = colaborador; // instead of removing ith element, just update it with the new inputs
}
As you point out, you’re continuously adding to the length of the array so employee.length keeps increasing, hence the infinite loop.
It looks like what you want in your for loop conditional is i <= allEmployees rather than i <= employees.length as allEmployees is the total count of employees and will not change.

Trying to push a pair of array values into a another array randomly without duplicates javascript

I'm trying to make an memory card game but this part is hard to figure out. I have an array with a deck of cards and I'm trying to select a specific amount cards in pairs randomly from the deck of cards and push them into the gameBoard array but I intitally keep getting duplicate of the pairs. Basically I trying to shuffle cards. I tried using a conditional statement but i dont know if this right way to go about this but it still doesnt work.
javascript
const deckOfCards = [
'card1',
'card2',
'card3',
'card4',
'card8',
'card9',
'card10',
'card11',
'card12',
'card13',
'card14',
]
let shuffle = deckOfCards[Math.floor(Math.random() * deckOfCards.length)];
let pair = [shuffle, shuffle]
let len = mode
for (let i = pair; i < deckOfCards.length; i++) {
if (gameBoard.indexOf(deckOfCards[i]) === -1) {
gameBoard.push(deckOfCards[pair])
}
}
console.log(gameBoard)
});
let gameBoard = []
just a thought: you can move the selected element to the back of the array and then randomly select stuff from a slice of the prior, unchanged array... just like array.slice(0, {a_number_here}.
something like this I guess (I cannot get your code working so this is not tested yet):
let shuffle = deckOfCards[Math.floor(Math.random() * deckOfCards.length - 5)]
name = deckOfCards.splice(shuffle, 1)
deckOfCards.push(name)
by the way if you can use libraries, just go for underscore.js and it would become very simple. just like this:
b = _.shuffle(deckOfCards).slice(0,5);
console.log(b)
you can also take a look at these repositories too (I haven't yet had the chance to take a look at them though but they kinda have a lot of stars so):
https://github.com/mmenavas/memory-game/blob/master/js/MemoryGame.js
https://github.com/code-sketch/memory-game
you can copy existing one to new one ex : const cloneSheepsES6 = [...sheeps];
then you can apply your condition without any duplications
This should give you an array of unique pair of cards.
const deckOfCards = [
"card1",
"card2",
"card3",
"card4",
"card8",
"card9",
"card10",
"card11",
"card12",
"card13",
"card14",
];
const randomArray = [];
let noOfPairs = 6; // No. of pairs required
while (noOfPairs) {
let randomNumber = Math.floor(Math.random() * deckOfCards.length);
if (randomArray.includes(randomNumber)) {
continue;
}
randomArray.push(randomNumber);
noOfPairs--;
}
randomArray.sort(() => 0.5 - Math.random());
const gameBoard = randomArray.map((item) => [
deckOfCards[item],
deckOfCards[item],
]);
console.log(gameBoard);

Ridiculously slow Apps-Script Loop

I've just switched from excel to Google sheets and I've had to go through a bit of a learning curve with moving on with "Macros" or scripts as they're now called.
Anyway, a short while later I've written a loop to go through everything in column B and if it's less than 50, delete the row.
It works and I'm happy but it's so slow. I have about 16,000 rows and I'll probably end with more. I let it run for about 4 minutes and it didn't even get rid of 1,000 rows. I refuse to believe that a popular programming language is that slow I can still read stuff as it's being deleted 20 rows up.
function grabData(){
let sheet = SpreadsheetApp.getActive().getSheetByName("Keywords");
var rangeData = sheet.getDataRange();
var lastColumn = rangeData.getLastColumn();
var lastRow = rangeData.getLastRow();
let range = sheet.getRange("B2:B16000");
let values=range.getValues();
for (var i = 0, len = values.length; i<len; i++){
if(values[i] <= 50 ){
sheet.deleteRow(i);
i--
len--
};
};
}
I keep seeing somewhere that something's not being reset, but I have no idea what that means.
Is it because the array length starts off at 16,000 and when I delete a row I'm not accounting for it properly?
Since I never use formulas I would do it this way:
function grabData() {
let ss = SpreadsheetApp.getActive();
let sh = ss.getSheetByName("Keywords");
let rg = s.getRange(2, 2, sh.getLastRow() - 1, sh.getLastColumn());
let values = rg.getValues();
let oA = [];
values.forEach((r, i) => {
if (r[0] > 50) {
oA.push(r);
}
});
rg.clearContent();
sh.getRange(2,1,oA.length,oA[0].length).setValues(oA);
}
It's much faster but it will probably mess up your formulas. Which is one of the reasons I never use formulas. Deleting lines is quite slow. Pretty much anything you do with the UI is slow.
Welcome to App Script and the community! App Script is actually very fast if follow the best practice of App Script.
Here is an example for you that will complete what you need in one second (*modify the variable value in config to fit your own application):
function myFunction() {
// config
const filterValue = 50
const targetSheetName = "Sheet1"
const targetColumn = "A"
const startRowNum = "2"
// get data from target sheet
const ss = SpreadsheetApp.getActiveSpreadsheet()
const sheet = ss.getSheetByName(targetSheetName)
const endRowNum = sheet.getLastRow()
const targetRange =`${targetColumn + startRowNum }:${targetColumn + endRowNum}`
const data = sheet.getRange(targetRange).getValues()
// filter data based on filterValue and set filtered result into new ary
const ary = data.filter(row=>row[0]>=filterValue)
//get max row number in the sheet
const maxRowNum = sheet.getMaxRows()
// break if nothing is filtered out
if(ary.length===0){
// remove all row and break
let deleteStartFromRowNum = parseInt(startRowNum,10) - 1
let deleteRowsCount = maxRowNum - deleteStartFromRowNum
sheet.deleteRows(deleteStartFromRowNum, deleteRowsCount)
return
}
// break if all is filtered out
if(ary.length===data.length){
// remove all trailing empty rows
if(endRowNum<maxRowNum){
let deletStartFromRowNum = endRowNum+1
let deleteRowsCount = maxRowNum-endRowNum
sheet.deleteRows(deletStartFromRowNum,deleteRowsCount)
}
return
}
// get lowerbound (the last row of filtered data in ary)
const lowerBound = parseInt(startRowNum,10) + ary.length - 1
// set ary into sheet range according to lowerBound value
sheet.getRange(`${targetColumn + startRowNum}:${targetColumn + lowerBound.toString()}`).setValues(ary)
// delete rest of the rows that are below lower bound
let deleteStartFromRowNum = lowerBound + 1
let deleteRowsCount = maxRowNum - lowerBound
sheet.deleteRows(deleteStartFromRowNum, deleteRowsCount)
return
}
Issue:
In Apps Script, you want to minimize calls to other service, including requests to Spreadsheets (see Minimize calls to other services). Calling other services in a loop will slow down your script considerably.
Because of this, it's much preferrable to filter out the undesired rows from values, remove all existing data in the range via Range.clearContent(), and then use setValues(values) to write the filtered values back to the spreadsheet (see Use batch operations).
Code snippet:
function grabData(){
let sheet = SpreadsheetApp.getActive().getSheetByName("Keywords");
const range = sheet.getRange("B2:B16000");
const values = range.getValues().filter(val => val[0] > 50);
range.clearContent();
sheet.getRange(2,2,values.length).setValues(values);
}
Reference:
Best Practices

Having Trouble Accessing the Value of a Score stored in Local Storage

I'm having issues when trying to access the value of a score that is stored in the localStorage from a variable that is equal to how many questions the user gets right. I thought it would be exactly the same as setting the value but most likely I've done something wrong, and I lack the experience to figure it out..
I Want to display the User's score on the screen's scoreboard where the complete button is. I easily set the score into the localStorage with the setItem(users, score) line, but it seems getItem(score) doesn't work when I want to set displayUser.textContent = getItem(score).
I've tried a lot of different ways, and I always get null. I also noticed every time I submit a new entry to the scoreboard, the key's name keeps the last entries name and stores it on the end.
I'd love to fix this myself, but after making no progress or any leads for 3 hours, I think I might ask for some help. I reused and changed a lot of this code from a class activity in my boot camp so the complete button is just there to remove entries while in development.
Here's all of the relevant JavaScript hopefully
//Variables to Shorten text
var startButton = document.getElementById('startbtn')
var nextButton = document.getElementById('nextbtn')
var finishEarlyButton = document.getElementById('finishEarlyBtn')
var introSection = document.getElementById('intro')
var questionSection = document.getElementById('Question-Section')
var questionElement = document.getElementById('question')
var answerButtons = document.getElementById('Answer-Section')
var scoreboard = document.getElementById('Score-Container')
var userScore = document.getElementById('Score')
var seeScoreBtn = document.getElementById('seeScore')
var restartBtn = document.getElementById('restart')
var finishbtn = document.getElementById('finishbtn')
var userAnswer = ""
var shuffledQuestions, currentQuestionIndex
var score = 0
var userName = document.getElementById('scoreboard-input')
var leaderboard = document.getElementById('leaderboard')
var leaderboardUsers = document.getElementById('leaderboardUsers')
var users = [];
init();
function init() {
var storedUsers = JSON.parse(localStorage.getItem("Users"))
if (storedUsers !== null) {
users = storedUsers;
renderUsers();
}
}
function renderUsers() {
leaderboardUsers.innerHTML = "";
for (var i = 0; i < users.length; i++) {
var user = users[i];
var li = document.createElement("li");
li.textContent = user;
li.setAttribute("data-index", i);
var button = document.createElement("button");
button.textContent = "Complete";
var displayUser = document.createElement("button");
displayUser.textContent = (localStorage.getItem(score));
//displayUser.textContent = "test";
console.log(localStorage.getItem(users.value))
li.appendChild(displayUser);
li.appendChild(button);
leaderboardUsers.appendChild(li);
}
}
function storeUsers() {
//localStorage.setItem("users", JSON.stringify(users));
//localStorage.setItem(JSON.stringify(users), JSON.stringify(score));
localStorage.setItem(users, score);
}
leaderboard.addEventListener("submit", function() {
event.preventDefault();
var userText = userName.value.trim();
var userCorrectAnswers = score.value;
if (userText === "") {
return
}
//users.push(userCorrectAnswers);
users.push(userText);
userName.value = "";
storeUsers()
renderUsers()
console.log
})
leaderboardUsers.addEventListener("click", function(event) {
var element = event.target;
if (element.matches("button") === true) {
var index = element.parentElement.getAttribute("data-index");
users.splice(index, 1);
storeUsers();
renderUsers();
}
})
Let me know if the html or rest of JS is needed!
Well just by looking at the code we can see that you're accessing it via
var storedUsers = JSON.parse(localStorage.getItem("Users"))
and storing it via
localStorage.setItem(users, score);
With the way you're accessing it, you would set it via
localStorage.setItem("Users", JSON.stringify(users));
It is case-sensitive, which is probably why your attempt of using the key users didn't work in your first comment under your storeUsers function.
This is a lot of code to sift through but setting and getting items requires string key-names and stringified values:
localStorage.setItem('users', JSON.stringify(score))
JSON.parse(localStorage.getItem('users'))
This way you should have the same data before and after setting to localStorage.
You are not using localStorage setItem correctly.
localStorage.setItem(users, score);
Both arguments to setItem() must be strings, with the first argument a key, and the second argument the value to store. Your first argument is an array (the data type of your second argument is unclear).
Typical value of a setItem first argument: 'usersScores'.
localStorage.setItem('usersScores', JSON.stringify(score));
Note the use of JSON.stringify() to convert score to a string, because localStorage only stores data in string form.
You are also not using getItem correctly:
localStorage.getItem(score)
getItem must be called with the key used in setItem:
localStorage.getItem('userScores')
And since score was saved as a string, you need to convert it back when you read it from localStorage:
score = JSON.parse(localStorage.getItem('userScores'))
How to use localStorage is explained clearly in MDN web docs Using the Web Storage API.

Why is this JavaScript looping twice in Zapier?

Here is a video that shows what I'm struggling with.
Here is a high level description of the process, followed by the actual JavaScript code I've written.
PROCESS
I built 2 Zaps that each run like this:
STEP 1 - Trigger (Cognito Form, which has repeating sections)
STEP 2 - JavaScript Code (which creates an Array of the form fields for ONE of the repeating sections, and separates them into individual strings using .split)
STEP 3 - Action (creates a ZOHO CRM Task for each string)
The first Zap runs on one of the sections of the form (Visits with Sales), and the second zap runs on a different section of the form (Visits without Sales). Each of these Zaps works fine on their own so I know the code is good, but I want to combine the two Zaps into one by combining the code.
I tried to combine by making five steps:
Trigger - Code1 - Zoho1 - Code2 - Zoho2
but the Zoho2 Tasks were each repeated
I then tried to re-order the five steps:
Trigger - Code1 - Code2 - Zoho1 - Zoho2
but now Zoho1 Tasks AND Zoho2 tasks were duplicated.
Finally I tried to combine ALL the JavaScript code into one:
Tigger - CombinedCode1+2 - Zoho 1 - Zoho2
but only the strings from Arrays in "Code2" are available to me when I go to map them in Zoho1.
CODE:
if (inputData.stringVSAccount == null) {
var listVSAccountArray = [];
var listVSUnitsArray = [];
var listVSPriceArray = [];
var listVSNotesArray = [];
var listVSVisitCallArray = [];
} else {
var listVSAccountArray = inputData.stringVSAccount.split(",");
var listVSUnitsArray = inputData.stringVSUnits.split(",");
var listVSPriceArray = inputData.stringVSPrice.split(",");
var listVSNotesArray = inputData.stringVSNotes.split(",");
var listVSVisitCallArray = inputData.stringVSVisitCall.split(",");
}
var output = [];
var arrayNos = listVSAccountArray.length;
var i = 0;
do {
var thisItemVSAccount = new String(listVSAccountArray[i]);
var thisItemVSUnits = new String(listVSUnitsArray[i]);
var thisItemVSPrice = new String(listVSPriceArray[i]);
var thisItemVSNotes = new String(listVSNotesArray[i]);
var thisItemVSVisitCall = new String(listVSVisitCallArray[i]);
var thisItemObj = {};
thisItemObj.itemVSAccount = thisItemVSAccount;
thisItemObj.itemVSUnits = thisItemVSUnits;
thisItemObj.itemVSPrice = thisItemVSPrice;
thisItemObj.itemVSNotes = thisItemVSNotes;
thisItemObj.itemVSVisitCall = thisItemVSVisitCall;
output.push({ thisItemObj });
i++;
} while (i < arrayNos);
//This is where the second zaps code is pasted in the combined version
if (inputData.stringOVAccount == null) {
var listOVAccountArray = [];
var listOVNotesArray = [];
var listOVVisitCallArray = [];
} else {
var listOVAccountArray = inputData.stringOVAccount.split(",");
var listOVNotesArray = inputData.stringOVNotes.split(",");
var listOVVisitCallArray = inputData.stringOVVisitCall.split(",");
}
var output = [];
var arrayNos = listOVAccountArray.length;
var i = 0;
do {
var thisItemOVAccount = new String(listOVAccountArray[i]);
var thisItemOVNotes = new String(listOVNotesArray[i]);
var thisItemOVVisitCall = new String(listOVVisitCallArray[i]);
var thisItemObj = {};
thisItemObj.itemOVAccount = thisItemOVAccount;
thisItemObj.itemOVNotes = thisItemOVNotes;
thisItemObj.itemOVVisitCall = thisItemOVVisitCall;
output.push({ thisItemObj });
i++;
} while (i < arrayNos);
I just started learning JavaScript this week, and sense that I am missing something obvious, perhaps a set of brackets. Thanks for any assistance
David here, from the Zapier Platform team. You're running into a confusing and largely undocumented feature where items after a code step run for each item returned. This is usually desired behavior - when you return 3 submissions you want to create 3 records.
In your case, it's also running subsequent unrelated actions multiple times, which sounds like it's undesired. In that case, it might be easier to have 2 zaps. Or, if "Zoho2" only ever happens once, put it first and let the branch happen downstream.
Separately, I've got some unsolicited javascript advice (since you mentioned you're a beginner). Check out Array.forEach (docs), which will let you iterate through arrays without having to manage as many variables (your own i every time). Also, try to use let and const over var when possible - it keeps your variables scoped as small as possible so you don't accidentally leak values into other areas.
​Let me know if you've got any other questions!
Just a note - you are declaring the same array variable output in both segments of your code block - the second declaration will be ignored.
Use the .forEach() method to iterate over your arrays, it will significantly cleanup you code. You also don't need to painstakingly construct the objects to be pushed into the output arrays.
This may not fix your issue but the code is far easier on the eye.
var listVSAccountArray = [],
listVSUnitsArray = [],
listVSPriceArray = [],
listVSNotesArray = [],
listVSVisitCallArray = [],
output = [];
if (typeof inputData.stringVSAccount === 'string') {
listVSAccountArray = inputData.stringVSAccount.split(',');
listVSUnitsArray = inputData.stringVSUnits.split(',');
listVSPriceArray = inputData.stringVSPrice.split(',');
listVSNotesArray = inputData.stringVSNotes.split(',');
listVSVisitCallArray = inputData.stringVSVisitCall.split(',');
}
// iterate over the array using forEach()
listVSAccountArray.forEach(function(elem, index){
// elem is listVSAccountArray[index]
output.push({
itemVSAccount: elem,
itemVSUnits: listVSUnitsArray[index],
itemVSPrice: listVSPriceArray[index],
itemVSNotes: listVSNotesArray[index],
itemVSVisitCall: listVSVisitCallArray[index]
})
})
//This is where the second zaps code is pasted in the combined version
var listOVAccountArray = [],
listOVNotesArray = [],
listOVVisitCallArray = [],
output_two = []; // changed the name of the second output array
if (typeof inputData.stringOVAccount === 'string') {
listOVAccountArray = inputData.stringOVAccount.split(',');
listOVNotesArray = inputData.stringOVNotes.split(',');
listOVVisitCallArray = inputData.stringOVVisitCall.split(',');
}
// iterate over the array using forEach()
listOVAccountArray.forEach(function(elem, index){
// elem is listOVAccountArray[index]
output_two.push({
itemOVAccount: elem,
itemOVNotes: listOVNotesArray[index],
itemOVVisitCall: listOVVisitCallArray[index]
});
});

Categories

Resources