I am making a ticket generator using javascript. I am using firebase to give the users a code that has already been stored on the database. my database is layed out like this:
"qrcodes" : {
"23KU8877" : {
"email" : "person#email.com",
"ticketgenerated" : "true"
},
"288RX9U5" : {
"email" : "person2#email.com",
"ticketgenerated" : "true"
}
}
my script allows me to get the first code in the qrcodes list and then move it to another database where another website processes it. But I was wanting to find a way that will make a function take a new snapshot everytime it is run.
The functions that grab the data from firebase are here:
function generatehtml(){
ticketname = document.getElementById('name').value;
ticketemail = document.getElementById('email').value;
adultnumber = document.getElementById('adults').value;
childnumber = document.getElementById('child').value;
while (functionruncount < inputnumber){
grabfirebasecode();
}
}
function grabfirebasecode(){
ref.limitToFirst(1).once('value', function(snapshot) {
for(key in snapshot.val()){
genvar = snapshot.child(key + "/ticketgenerated" ).val();
var genvarpath = "test/" + key + "/ticketgenerated";
if (genvar === "false"){
snapshot.forEach(function(childSnapshot) {
ref.child(childSnapshot.key).remove();
});
ref2.child(key).set({
email: ticketemail,
ticketgenerated: "true",
});
createticket();
}
}
});
functionruncount ++;
}
so if the code above runs succesully and grabs the first child of the qrcode list (e.g "23KU8877"), it will stay the same no matter how many times the function loops.
I am not sure how to fix this. Any help would be appreciated.
Thanks,
Daniel Martinez
You're attaching a same listener multiple times in a tight loop. All those listeners start at pretty much the same time, and thus all see the same value from the database. To get a next code, you must be sure to only start reading the next code after you've deleted the previous one. A common way to do this is with a recursive function:
function generatehtml(){
ticketname = document.getElementById('name').value;
ticketemail = document.getElementById('email').value;
adultnumber = document.getElementById('adults').value;
childnumber = document.getElementById('child').value;
grabfirebasecode(inputnumber);
}
function grabfirebasecode(inputnumber){
if (inputnumber > 0) {
ref.limitToFirst(1).once('value', function(snapshot) {
for(key in snapshot.val()){
genvar = snapshot.child(key + "/ticketgenerated" ).val();
var genvarpath = "test/" + key + "/ticketgenerated";
if (genvar === "false"){
var promises = [];
snapshot.forEach(function(childSnapshot) {
promises.push(ref.child(childSnapshot.key).remove());
});
promises.push(
ref2.child(key).set({
email: ticketemail,
ticketgenerated: "true",
})
);
Promise.all(promises).then(function() {
grabfirebasecode(inputnumber-1);
});
createticket();
}
}
});
}
}
So this code builds an array of promises, one for each database operation that happen asynchronously. When all those operations completes, it calls itself again with one lover number.
If your createticket() also performs asynchronous operations, you might want to also include it in the promises array so that its work is completed before the next iteration starts.
Related
I'm creating my custom order id with auto-increment generator function for my project. I will state my question here, if you want to know the whole story please read below.
As written in the title, I need a way to reject my set to Firebase and it has to be done in 1 query. Currently, it will write my orderID to Firebase without rejecting it. But I need to reject if there is the same ID in the table.
The short version of my code will be posted here, the whole function will be posted below.
firebase.database().ref('orderCounter/orderIDsChecker/'+orderID).set({
id: orderID,
}, function(error) {
if (error) {
console.log('Order ID fail to generate. Regenerating new ID')
createOrderID(orderCounterRef);
} else {
console.log('Order ID created!')
}
});
}
The story,
I'm creating my own custom order id with auto-increment generator function for my project. The problem is that if multiple users creating order at the same time, it will generate the same id. Yes, I can use transaction() to solve the problem but I have no idea how to use it. Therefore, I have created my own version of the "transaction". With my method, I am able to prevent duplicates id unless 2 or more users create order within 1 second of gap. Or if anyone is kind enough to show me an example of how to write a transaction for my function, I thank you in advance.
The flow of the code is,
Get "currentMonth" and "orderIdCounter" from Firebase -> orderIdCounter +1 and update to Firebase -> start the process of generating order id -> Send the generated id to firebase -> If return success "order ID created", If not "got duplicate id" Re-run the whole process.
Below is the code for my order id generator function.
function createOrderID(orderCounterRef){
var childData = [];
var orderID;
//Get the Current Month and Order ID Counter from Firebase
orderCounterRef.on('value', function(snap) { childData = snapshotToArrayWithoutID(snap); });
var currentMonth = childData[0];
var orderIDCounter = childData[1];
if (orderIDCounter !== undefined){
//Update orderIDCounter on Firebase.
//This is to prevent duplicate orderID when multiple users is creating order at the same time.
var IDCounter = parseInt(orderIDCounter) + 1;
//Set IDCounter to 3 digits
IDCounter = ('00' + IDCounter.toString()).slice(-3);
firebase.database().ref('orderCounter/orderIDCounter').set(IDCounter);
//Handle the process to generate Order ID. Return in YYMMxxx(auto increment) format.
orderID = handleCreateOrderID(currentMonth, (parseInt(orderIDCounter) - 1));
//Check if duplicate ID on firebase
firebase.database().ref('orderCounter/orderIDsChecker/'+orderID).set({
id: orderID,
}, function(error) {
if (error) {
console.log('Order ID fail to generate. Regenerating new ID')
createOrderID(orderCounterRef);
} else {
console.log('Order ID created!')
}
});
}
return orderID;
}
My DB:
You should indeed use a transaction as you have mentioned in your question.
The following should do the trick:
//Declare a function that increment a counter in a transaction
function createOrderID() {
var orderIdRef = firebase.database().ref('orderId');
return orderIdRef.transaction(function(currentId) {
return currentId + 1;
});
}
//Call the asynchronous createOrderID() function
createOrderID().then(function(transactionResult) {
console.log(transactionResult.snapshot.val());
});
If you want to start the counter at a specific value, just create an orderId node in your database and assign a specific value to it, e.g; 1912000.
If you just want to start at 1, you don't need to create a node, it will be automatically created with the first call to the createOrderID() function.
Thank you, #samthecodingman & #Renaud Tarnec for your advice.
I took #samthecodingman's code and change a bit to fit my project. But I use generateOrderID() only to call the result and it works well. But you won't get any value with just the code. I call out another function (connectToFirebase) whenever users enter the page. I am not sure why it works or if this is the right way, but it works for me and that's good enough.
export function generateOrderID(){
var orderId;
var childData = [];
const orderCounterRef = firebase.database().ref('orderCounter/');
//Get the Current Month from Firebase
orderCounterRef.on('value', function(snap) { childData = snapshotToArrayWithoutID(snap); });
//Check ID format YYMMXXX (XXX=auto_increment). Hanlde auto_increment for Year and Month
handleOrderIdFormat(childData[0], orderCounterRef)
//transaction
orderCounterRef.child('orderId').transaction(function(currentId) {
orderId = (currentId||0) +1;
return orderId;
}, function(err) {
if( err ) {
console.log(err)
}
});
return orderId;
}
export function connectToFirebase(){
//Connection Firebase Database
const orderCounterRef = firebase.database().ref('orderCounter/');
orderCounterRef.on('value', function(snap) { });
}
I´m working in a chrome extension that stores a temporary playlist from items in SoundCloud to perform several actions on it later.
So... Iknow Chrome Storage is an object and "can´t" be ordered per se, but I really need that order in any feasible way.
I tried storing objects in an Array and then Storing that Array in Storage after pushing a new element at the end of it and was the perfect workaround until, with 27 objects in it, chrome told me that i had reached memory limit (I´m going to need more elements to store.)
Storing each element as separate objects allows me virtually any amount of them (I think 50mb, wich is enough for sure), but get method throws elements the way it wants (obviously, being an object).
Objects are stored with timestamp keys, but still not working at all.
Is there a "light way" to do so?
Code is not definitive and I´m thinking in appending elements directly to a new window, leaving storage calls for other stuff and move to "lighter" code, but would like first to know if this is somehow possible.
CODE - popup.js (here is where order is not persistent)
function appendTracks(){
chrome.storage.sync.get(null, function (storageObject) {
//TODO check if is song
$.each( storageObject, function( key, trackData ) {
trackContainer(trackData["permalink"]);
});
});
}
function trackContainer(trackPermalink){
console.log(trackPermalink);
var trackWidget;
$.getJSON(
'http://soundcloud.com/oembed' +
'?format=json' +
'&url='+trackPermalink+'&visual=false'
).done(function (embedData) {
trackWidget = embedData.html;
$("#mainPlayList").append(trackWidget);
});
console.log(trackWidget);
return trackWidget;
}
CODE - main.js (Storage Setter with timestamp as key)
function downloadClick(a) {
playListName = "";
var b = this;
$.ajax({
url: a.data.reqUrl
}).done(function(a) {
var time = Date.now();
var timeStamp = Math.round(time/1000);
var key = timeStamp.toString()+"queueSc";
var trackData = {
"trackId" : a["id"],
"trackTitle" : a["title"],
"thumbnail" : a["artwork_url"],
"streamUrl" : a["stream_url"],
"permalink" : a["permalink_url"],
"duration" : a["duration"],
"genre" : a["genre"]};
trackData[key] = "key";
getStorage(null,function(storageObject){
if(!isTrackInList(trackData,storageObject)){
setStorage({
[key]: trackData
});
}else{
console.log("ya esta en la lista");
}
});
})
}
function isTrackInList(trackData, storageObject){
var isInList = false;
$.each( storageObject, function( key, value ) {
if(trackData["trackId"] == value["trackId"]){
isInList = true;
}
});
return isInList;
}
I think is important to say that other than the order issue there is not any problem with it, everything runs fine, although there are things that could be more "ellegant" for sure.
Thanks in advance, hope you can help!
The problem is that you are exceeding the QUOTA_BYTES_PER_ITEM, i.e. the storage limit you are allowed per object. If you use chrome.storage.sync you are limited to 8,192 Bytes. Using chrome.storage.local will allow you to store unlimited size per item.
Note using chrome.storage.local makes your data local to that machine and thus not synced across browsers on different machine.
Thanks to EyuelDK and finally an async call has been needed, I will try to solve this the next.
CODE - popup.js
function appendTracks(){
chrome.storage.local.get("souncloudQueue", function (storageObject) {
var length = storageObject["souncloudQueue"].length;
for (var i=0;i<length;i++){
var trackPermalink = storageObject["souncloudQueue"][i];
console.log(i, trackPermalink);
$("#mainPlayList").append(trackContainer(trackPermalink));
}
});
}
function trackContainer(trackPermalink){
console.log(trackPermalink);
var trackWidget;
$.ajax({
url: 'http://soundcloud.com/oembed' +
'?format=json' +
'&url='+trackPermalink,
dataType: 'json',
async: false,
success: function(data) {
trackWidget = data.html;
}
});
console.log(trackWidget);
return trackWidget;
}
CODE - main.js
function downloadClick(a) {
var trackUrl = a.data.reqUrl;
console.log(trackUrl);
getStorage("souncloudQueue", function(callback){
console.log(callback["souncloudQueue"]);
var tempArray = callback["souncloudQueue"];
tempArray.push(trackUrl);
setStorage({"souncloudQueue": tempArray}, function() {
});
})
}
So,I am trying to use the twitch API:
https://codepen.io/sterg/pen/yJmzrN
If you check my codepen page you'll see that each time I refresh the page the status order changes and I can't figure out why is this happening.
Here is my javascript:
$(document).ready(function(){
var ur="";
var tw=["freecodecamp","nightblue3","imaqtpie","bunnyfufuu","mushisgosu","tsm_dyrus","esl_sc2"];
var j=0;
for(var i=0;i<tw.length;i++){
ur="https://api.twitch.tv/kraken/streams/"+tw[i];
$.getJSON(ur,function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> "+tw[j]+"<p>"+""+"</p></li>");
if(json.stream==null){
$(".stat").append("<li>"+"Offline"+"</li>");
}
else{
$(".stat").append("<li>"+json.stream.game+"</li>");
}
j++;
})
}
});
$.getJSON() works asynchronously. The JSON won't be returned until the results come back. The API can return in different orders than the requests were made, so you have to handle this.
One way to do this is use the promise API, along with $.when() to bundle up all requests as one big promise, which will succeed or fail as one whole block. This also ensures that the response data is returned to your code in the expected order.
Try this:
var channelIds = ['freecodecamp', 'nightblue3', 'imaqtpie', 'bunnyfufuu', 'mushisgosu', 'tsm_dyrus', 'esl_sc2'];
$(function () {
$.when.apply(
$,
$.map(channelIds, function (channelId) {
return $.getJSON(
'https://api.twitch.tv/kraken/streams/' + encodeURIComponent(channelId)
).then(function (res) {
return {
channelId: channelId,
stream: res.stream
}
});
})
).then(function () {
console.log(arguments);
var $playersBody = $('table.players tbody');
$.each(arguments, function (index, data) {
$playersBody.append(
$('<tr>').append([
$('<td>'),
$('<td>').append(
$('<a>')
.text(data.channelId)
.attr('href', 'https://www.twitch.tv/' + encodeURIComponent(data.channelId))
),
$('<td>').text(data.stream ? data.stream.game : 'Offline')
])
)
})
})
});
https://codepen.io/anon/pen/KrOxwo
Here, I'm using $.when.apply() to use $.when with an array, rather than list of parameters. Next, I'm using $.map() to convert the array of channel IDs into an array of promises for each ID. After that, I have a simple helper function with handles the normal response (res), pulls out the relevant stream data, while attaching the channelId for use later on. (Without this, we would have to go back to the original array to get the ID. You can do this, but in my opinion, that isn't the best practice. I'd much prefer to keep the data with the response so that later refactoring is less likely to break something. This is a matter of preference.)
Next, I have a .then() handler which takes all of the data and loops through them. This data is returned as arguments to the function, so I simply use $.each() to iterate over each argument rather than having to name them out.
I made some changes in how I'm handling the HTML as well. You'll note that I'm using $.text() and $.attr() to set the dynamic values. This ensures that your HTML is valid (as you're not really using HTML for the dynamic bit at all). Otherwise, someone might have the username of <script src="somethingEvil.js"></script> and it'd run on your page. This avoids that problem entirely.
It looks like you're appending the "Display Name" in the same order every time you refresh, by using the j counter variable.
However, you're appending the "Status" as each request returns. Since these HTTP requests are asynchronous, the order in which they are appended to the document will vary each time you reload the page.
If you want the statuses to remain in the same order (matching the order of the Display Names), you'll need to store the response data from each API call as they return, and order it yourself before appending it to the body.
At first, I changed the last else condition (the one that prints out the streamed game) as $(".stat").append("<li>"+jtw[j]+": "+json.stream.game+"</li>"); - it was identical in meaning to what you tried to achieve, yet produced the same error.
There's a discrepancy in the list you've created and the data you receive. They are not directly associated.
It is a preferred way to use $(".stat").append("<li>"+json.stream._links.self+": "+json.stream.game+"</li>");, you may even get the name of the user with regex or substr in the worst case.
As long as you don't run separate loops for uploading the columns "DisplayName" and "Status", you might even be able to separate them, in case you do not desire to write them into the same line, as my example does.
Whatever way you're choosing, in the end, the problem is that the "Status" column's order of uploading is not identical to the one you're doing in "Status Name".
This code will not preserve the order, but will preserve which array entry is being processed
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
if (json.stream == null) {
$(".stat").append("<li>" + "Offline" + "</li>");
} else {
$(".stat").append("<li>" + json.stream.game + "</li>");
}
})
}(i));
}
});
This code will preserve the order fully - the layout needs tweaking though
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
var name = $(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
var stat = $(".stat").append("<li></li>")[0].lastElementChild;
console.log(stat);
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
if (json.stream == null) {
$(stat).text("Offline");
} else {
$(stat).text(json.stream.game);
}
}).then(function(e) {
console.log(e);
}, function(e) {
console.error(e);
});
}(i));
}
});
I am trying to write the Pseudocode given here https://dev.twitter.com/docs/misc/cursoring with javascript using node-oauth https://github.com/ciaranj/node-oauth. However I am afraid because of the nature of the callback functions the cursor is never assigned to the next_cursor and the loop just runs forever. Can anyone think a workaround for this?
module.exports.getFriends = function (user ,oa ,cb){
var friendsObject = {};
var cursor = -1 ;
while(cursor != 0){
console.log(cursor);
oa.get(
'https://api.twitter.com/1.1/friends/list.json?cursor=' + cursor + '&skip_status=true&include_user_entities=false'
,user.token //test user token
,user.tokenSecret, //test user secret
function (e, data, res){
if (e) console.error(e);
cursor = JSON.parse(data).next_cursor;
JSON.parse(data).users.forEach(function(user){
var name = user.name;
friendsObject[name + ""] = {twitterHandle : "#" + user.name, profilePic: user.profile_image_url};
});
console.log(friendsObject);
}
);
}
}
Suppose your code is wrapped in a function, I'll call it getFriends, basically it wraps everything inside the loop.
function getFriends(cursor, callback) {
var url = 'https://api.twitter.com/1.1/friends/list.json?cursor=' + cursor + '&skip_status=true&include_user_entities=false'
oa.get(url, user.token, user.tokenSecret, function (e, data, res) {
if (e) console.error(e);
cursor = JSON.parse(data).next_cursor;
JSON.parse(data).users.forEach(function(user){
var name = user.name;
friendsObject[name + ""] = {twitterHandle : "#" + user.name, profilePic: user.profile_image_url};
});
console.log(friendsObject);
callback(cursor);
});
}
In nodejs all io is done asynchronously, so you will loop a lot more than needed, before actually changing cursor, what you need is loop only when you receive a response from the Twitter API, you could do something like this:
function loop(cursor) {
getFriends(cursor, function(cursor) {
if (cursor != 0) loop(cursor);
else return;
});
}
You start it by calling loop(-1), of course this is just one way of doing it.
If you prefer you could use an external library, like async.
I strongly suggest using async for this. It's made for situations like yours and handles concurrency and execution for you. You will simply end up writing something that does the same thing as async, only yours will not be as tested.
geturls(data,function(urls){
var data = {
"data": [
{ "userProfile": userP },
{ "urls": urls }
]
};
res.send(data);
});
function getUrls(data,done){
links = new Array();
for (var i=0; i<data.length; i++){
user = data[i]
Url.find({where:{data.id}}).success(function(url){
links.push({
"url": ur.text,
"date": data.syncedTime
});
if (urls.length == data.length){
done(links);
}
});
}
}
My problem with my code is this:
I'm returning the response through a callback once data collected in my array equals the length of the parent array. This is obviously a very dangerous and not so elegant solution. As, suppose I get a .failure from Url database, then my urls.length won't be equal with data.length. So, I'm a bit confused how to go about this.
Any help?
It will be easy for you, if you use async.js.
I used mapSeries here. It takes 3 parameters.
collection/array
iterator, which will be called for each item in the passed collection/array with 2 arguments. 1. item in collection, 2. callback. After completing the job in iterator, You should call the callback in node style(err first, results follows).
Final callback, which will be called after all the items in the collection mapped.
function getUrls(data,done){
var async = require('async');
async.mapSeries(data, function(user, cb) {//If you want it to be async `async.map`
Url.find({where:{user.id}}).success(function(url){
cb(null, {
"url": url.text,
"date": user.syncedTime
});
});
}, function(err, results) {
//results is an array. Its the same as `links` in your old code.
done(results);
});
}
geturls(data,function(urls){
var data = {
"data": [
{ "userProfile": userP },
{ "urls": urls }
]
};
res.send(data);
});
Use recursion:
function getUrls(data,done) {
var links = new Array();
function doGetUrl(i) {
var user = data[i];
Url.find({where:{data.id}}).
success(function(url){
links.push({
"url": ur.text,
"date": data.syncedTime
});
if (links.length == data.length){
done(links);
} else {
doGetUrl(i + 1); // get next url
}
}).
failure(function(err) {
doGetUrl(i); // on error, try to get current url again
// other error handling code
});
}
doGetUrl(0);
}
I would probably make use of the complete callback, in jQuery terms. Have a counter that records how many records have been processed and update this in complete, as this executes on success or failure. Then when that counter is >= the length of the data array you can exit.
As an aside, I would always do a >= rather than an == for the comparison you are doing there, that way, if for any crazy reason, the count is upped more than it should you still exit.
If alll you want to do is avoif the problem of checking links.length to determine when you are done then I think its just a matter of adding a separate counter that gets incremented even if the urk database fails. If you do that you can continue using your current stype where the async requests are run in parallel.
var nreq = 0;
for (var i=0; i<data.length; i++){
doTheAsyncOperation(function(){
//Run this part in both the success and error cases
nreq = nreq + 1;
if(nreq >= data.length){ done(links) }
})
}
On the other hand, if you want to run one query after the other you will need to rewrite the for to use recursion. This time, you don't need to worry about keeping a separate counter since you know when the final request runs:
function loop(i){
if(i >= data.length){
done(links);
}else{
doTheAsyncOperation(function(){
loop(i+1);
})
}
}
loop(0);
Finally, its good to know how to code this sort of patterns yourself but in the long run I highly recommend using a control flow library to keep things cleaner.