i have a problem that i need first to get the image links from the Firebase data base then i call a JQuery code that will organize the images in a beautiful way >> But it seems that the Jquery runs before i get the images,
Help Please ..!
JS Function
new Firebase("https://zoominp.firebaseio.com/photos/"+imageID)
.once('value', function(snap)
{
link = snap.child('imageLink').val();
link = 'images/'+link;
var id = "img";
div.innerHTML += "";
});
JQuery
jQuery("#gallery").unitegallery(
{
tiles_type:"nested",
tiles_nested_optimal_tile_width:200
});
Firebase loads (and synchronizes) the data asynchronously. So the jQuery code you have, will indeed execute before the data has come back from the server.
To fix this, move the jQuery code into the Firebase callback:
var ref = new Firebase("https://zoominp.firebaseio.com/photos/"+imageID);
ref.on('value', function(snap) {
link=snap.child('imageLink').val();
link='images/'+link;
var id="img";
div.innerHTML = div.innerHTML +"";
jQuery("#gallery").unitegallery({
tiles_type:"nested",
tiles_nested_optimal_tile_width:200
});
});
I also changed once() to on(). With that tiny change, your HTML will be updated whenever the data in the database changes. Try changing the data and you'll experience the "magic" of Firebase.
Since asynchronous loading is hard to wrap your head around when you first encounter it, I highly recommend that you read the more in-depth answers to these questions:
How do I return the response from an asynchronous call?
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
Handling Asynchronous Calls (Firebase) in functions
Returning value from a jQuery function
i have never worked with a Firebase, but you will need to have your actual resources ready before running the jQuery - you cannot do this in a synchronous way, as when you call your jquery unitedGallery it is called before the .once('value') event triggers.
do you call that new Firebase(.... thing more times in a loop or something? you could do something like keeping information about whether have all the images loaded in an array. something like this: let's assume, your images are stored in an array allOfYourImages. then,
define a global variable like this
var images_loaded=[];
for(var i=0; i<allOfYourImages.length; i++){ images_loaded[i]=false; }
then i assume you somehow iterate over your pictures since you are using imageID. add an incrementing variable var image_number=0; before the iterator and do image_number++ after each image iteration. like
var image_number=0;
...iteratorofyourchoiseihavenoideawhatareyouusing...{
new Firebase("https://zoominp.firebaseio.com/photos/"+imageID).once('value', function(snap){
...DOM stuff previously did ...
images_loaded[image_number]=true;
checkAllImagesLoaded();
});
image_number++;
}
notice the checkAllImagesLoaded() function. this will look whether have all your images already loaded and fire the jQuery gallery thing, like this
checkAllImagesLoaded(){
var all_loaded=true;
for(var i=0; i<allOfYourImages.length; i++){
all_loaded &= images_loaded[i]; //in case any of the items is false, it will set the all_loaded to false
}
if(all_loaded){
..your jQuery.("#gallery").unitegallery stuff..
}
}
Related
This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 9 months ago.
Sorry if this is kind of a "noob" question but I'll ask anyway. :)
I have a HTML page and a JavaScript file where I make two API calls to get train trip information.
You are filling out the city you want to travel from and the first API call returns an ID for that location.
The the second API call is supposed to use this received ID to get you up to six upcoming departures from that location to a preset destination.
The first API call works with the variable cityname being passed from the input field from the HTML page and in the log I get an ID number back. That is shown in the console logs. Unfortunately I'm not allowed to provide a picture (too low reputation :)).
The second API call always fails since the variable trainoriginid always ends up empty (I guess that this is actually a variable scope issue). I have tried to use the same technique on the second API call as in the first call, as you can see in the commented "var url" line, but no matter where I try to declare or set the trainoriginid it ends up empty. How am I to pass the result from the first API call to the second? All info is appreciated.
This line is from the error message:
GEThttps://api.resrobot.se/v2.1/trip?format=json&originId=&destId=740098037&passlist=true&showPassingPoints=true&accessId
If I use the call with the "const url" line everything works so there you can see what the url should look like.
The (not complete) JavaScript code looks like this:
let reqBtn = document.querySelector('.request-btn');
let reqInput = document.querySelector('.request-input');
let respResultDiv = document.querySelector('.response-result');
let trainoriginid = '';
reqBtn.addEventListener('click', event => {
let cityName = reqInput.value;
console.log(cityName);
var citylookup = `https://api.resrobot.se/v2.1/location.name?input=${cityName}&format=json&accessId=[APIKey]`;
axios.get(citylookup).then(searchresponse => {
console.log(searchresponse.data);
let searchdata = searchresponse.data;
trainoriginid = searchdata.stopLocationOrCoordLocation[0].StopLocation.extId;
console.log(trainoriginid);
});
//var url = `https://api.resrobot.se/v2.1/trip?format=json&originId=${trainoriginid}&destId=740098037&passlist=true&showPassingPoints=true&accessId=[APIKey]`;
const url = `https://api.resrobot.se/v2.1/trip?format=json&originId=740000008&destId=740098037&passlist=true&showPassingPoints=true&accessId=[APIKey]`
axios.get(url).then(response => {
console.log(response.data.Trip);
let data = response.data.Trip;
NOTE!
After following the link above and and doing some testing - using callbacks solved my problem. Just for reference the final solution looks like this:
reqBtn.addEventListener('click', event => {
let cityName = reqInput.value;
respResultDiv.textContent = '';
console.log(cityName);
getStationId(cityName, getTrainList)
});
function getStationId(cityNameinput, callback){
var citylookup = `https://api.resrobot.se/v2.1/location.name?input=${cityNameinput}&format=json&accessId=[API-key]`;
var originid;
axios.get(citylookup).then(searchresponse => {
console.log(searchresponse.data);
let searchdata = searchresponse.data;
originid = searchdata.stopLocationOrCoordLocation[0].StopLocation.extId;
console.log(originid);
callback(originid);
});
}
function getTrainList(trainoriginid){
console.log(trainoriginid);
var url = `https://api.resrobot.se/v2.1/trip?format=json&originId=${trainoriginid}&destId=740098037&passlist=true&showPassingPoints=true&accessId=[API-key]`;
axios.get(url).then(response => {
console.log(response.data.Trip);
let data = response.data.Trip;
[Do a bunch of stuff with the result here]
});
}
In your example above, your second lookup call needs to be inside the "then" response method of the first lookup. You are calling both inline at the same time, so the second call will always use the empty "trainoriginid" as it is not set yet. Also, in your example above, I can not see you actually applying "${trainoriginid}" to the second url.
In this case, you do not need to make the second url a "const" as it is a one use deal. It is probably best to use "let" (and apply the "${trainoriginid}").
As a side note, axios is awaitable, so you could use await on the first call (and make the click method async), then you could have both lookups inline like that, but it is probably easier to just move the second call inside the the response of the first.
In my website I'm Showing my database after user has given the database name, Is there any way I can constantly update the web shown databasebase without refreshing the page . I've tried using setInterval but it's not working for some reason .
function c(){
setInterval(beta, 1000);
}
function beta(){
var d = document.getElementById("opopo").value;
var firebaseRefff= firebase.database().ref('LOCATION/'+d);
firebaseRefff.on('child_added', snap=> {
var slot=snap.getKey();
var alloted=snap.child("ALLOTED").val();
var date=snap.child("DATE").val();
var limit=snap.child("LIMIT").val();
var time=snap.child("TIME").val();
$("table tbody").append(""+slot+""+alloted+""+date+""+limit+""+time+"Null");
});
}
You do not need, and should not use, setInterval to trigger the queries. What you have in your beta() function looks pretty good.
firebaseRefff.on('child_added', snap => {}) means "whenever a child is added under this location, trigger the callback function (empty in my example) with the parameter 'snap'". It will also be called once, initially, for each child that is already at that database reference location.
You need to make sure you've called beta() once to setup this trigger.
If you're still having problems, you might want to insert logging to make sure beta() is being called, what the full reference path is, if the callback is ever triggered, and if your jquery string is correct.
If I am here asking it is because we are stuck on something that we do not know how to solve. I must admit, we already searched in StackOverflow and search engines about a solution.. but we didn't manage to implement it / solve the problem.
I am trying to create a JavaScript function that:
detects in my html page all the occurrences of an html tag: <alias>
replaces its content with the result of an Ajax call (sending the
content of the tag to the Ajax.php page) + localStorage management
at the end unwraps it from <alias> tag and leaves the content returned from ajax call
the only problem is that in both cases it skips some iterations.
We have made some researches and it seems that the "problem" is that Ajax is asynchronous, so it does not wait for the response before going on with the process. We even saw that "async: false" is not a good solution.
I leave the part of my script that is interested with some brief descriptions
// includes an icon in the page to display the correct change
function multilingual(msg,i) {
// code
}
// function to make an ajax call or a "cache call" if value is in localStorage for a variable
function sendRequest(o) {
console.log(o.variab+': running sendRequest function');
// check if value for that variable is stored and if stored for more than 1 hour
if(window.localStorage && window.localStorage.getItem(o.variab) && window.localStorage.getItem(o.variab+'_exp') > +new Date - 60*60*1000) {
console.log(o.variab+': value from localStorage');
// replace <alias> content with cached value
var cached = window.localStorage.getItem(o.variab);
elements[o.counter].innerHTML = cached;
// including icon for multilingual post
console.log(o.variab+': calling multilingual function');
multilingual(window.localStorage.getItem(o.variab),o.counter);
} else {
console.log(o.variab+': starting ajax call');
// not stored yet or older than a month
console.log('variable='+o.variab+'&api_key='+o.api_key+'&lang='+o.language);
$.ajax({
type: 'POST',
url: my_ajax_url,
data: 'variable='+o.variab+'&api_key='+o.api_key+'&lang='+o.language,
success: function(msg){
// ajax call, storing new value and expiration + replace <alias> inner html with new value
window.localStorage.setItem(o.variab, msg);
var content = window.localStorage.getItem(o.variab);
window.localStorage.setItem(o.variab+'_exp', +new Date);
console.log(o.variab+': replacement from ajax call');
elements[o.counter].innerHTML = content;
// including icon for multilingual post
console.log(o.variab+': calling multilingual function');
multilingual(msg,o.counter);
},
error: function(msg){
console.warn('an error occured during ajax call');
}
});
}
};
// loop for each <alias> element found
//initial settings
var elements = document.body.getElementsByTagName('alias'),
elem_n = elements.length,
counter = 0;
var i = 0;
for(; i < elem_n;i++) {
var flag = 0;
console.info('var i='+i+' - Now working on '+elements[i].innerHTML);
sendRequest({
variab : elements[i].innerHTML,
api_key : settings.api_key,
language : default_lang,
counter : i
});
$(elements[i]).contents().unwrap().parent();
console.log(elements[i].innerHTML+': wrap removed');
}
I hope that some of you may provide me some valid solutions and/or examples, because we are stuck on this problem :(
From our test, when the value is from cache, the 1st/3rd/5th ... values are replaced correctly
when the value is from ajax the 2nd/4th .. values are replaced
Thanks in advance for your help :)
Your elements array is a live NodeList. When you unwrap things in those <alias> tags, the element disappears from the list. So, you're looking at element 0, and you do the ajax call, and then you get rid of the <alias> tag around the contents. At that instant, element[0] becomes what used to be element[1]. However, your loop increments i, so you skip the new element[0].
There's no reason to use .getElementsByTagName() anyway; you're using jQuery, so use it consistently:
var elements = $("alias");
That'll give you a jQuery object that will (mostly) work like an array, so the rest of your code won't have to change much, if at all.
To solve issues like this in the past, I've done something like the code below, you actually send the target along with the function running the AJAX call, and don't use any global variables because those may change as the for loop runs. Try passing in everything you'll use in the parameters of the function, including the target like I've done:
function loadContent(target, info) {
//ajax call
//on success replace target with new data;
}
$('alias').each(function(){
loadContent($(this), info)
});
I'm trying to write a plugin-like function in jQuery to add elements to a container with AJAX.
It looks like this:
$.fn.cacheload = function(index) {
var $this = $(this);
$.get("cache.php", {{ id: index }).done(function(data) {
// cache.php returns <div class='entry'>Content</div> ...
$(data).insertAfter($this.last());
});
}
and I would like to use it like this:
var entries = $("div.entry"),
id = 28;
entries.cacheload(id);
Think that this would load another "entry"-container and add it to the DOM.
This is works so far. But of course the variable that holds the cached jQuery object (entries) isn't updated. So if there were two divs in the beginning and you would add another with this function it would show in the DOM, but entries would still reference the original two divs only.
I know you can't use the return value of get because the AJAX-call is asynchronous. But is there any way to update the cached object so it contains the elements loaded via AJAX as well?
I know I could do it like this and re-query after inserting:
$.get("cache.php", {{ id: num }).done(function(data) {
$(data).insertAfter($this.last());
entries = $("div.entry");
});
but for this I would have to reference the variable holding the cached objects directly.
Is there any way around this so the function is self-contained?
I tried re-assigning $(this), but got an error. .add() doesn't update the cached object, it creates a new (temporary) object.
Thanks a lot!
// UPDATE:
John S gave a really good answer below. However, I ended up realizing that for me something else would actually work better.
Now the plugin function inserts a blank element (synchronously) and when the AJAX call is complete the attributes of that element are updated. That also ensures that elements are loaded in the correct order. For anyone stumbling over this, here is a JSFiddle: http://jsfiddle.net/JZsLt/2/
As you said yourself, the ajax call is asynchronous. Therefore, your plugin is asynchronous as as well. There's no way for your plugin to add the new elements to the jQuery object until the ajax call returns. Plus, as you discovered, you can't really add to the original jQuery object, you can only create a new jQuery object.
What you can do is have the plugin take a callback function as a second parameter. The callback could be passed a jQuery object that contains the original elements plus the newly inserted ones.
$.fn.cacheload = function(index, callback) {
var $this = this;
$.get('cache.php', { id: index }).done(function(html) {
var $elements = $(html);
$this.last().after($elements);
if (callback) {
callback.call($this, $this.add($elements));
}
});
return $this;
};
Then you could call:
entries.cacheload(id, function($newEntries) { doSomething($newEntries); } );
Of course, you could do this:
entries.cacheload(id, function($newEntries) { entries = $newEntries; } );
But entries will not be changed until the ajax call returns, so I don't see much value in it.
BTW: this inside a plugin refers to a jQuery object, so there's no need to call $(this).
Currently I'm loading data asynchronously via data.js as provided by the Grid app template. The problem exists where groupedItems.js (the "Hub" page) calls _initializeLayout in the ready handler before the Data in the global WinJS namespace is set due to the asynchronous nature of the StorageFile class.
In data.js:
fileNames.forEach(function (val, index, arr) {
var uri = new Windows.Foundation.Uri('ms-appx:///data/' + val + '.geojson');
Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri).then(function (file) {
Windows.Storage.FileIO.readTextAsync(file).then(function (contents) {
// ... read, parse, and organize the data ...
// Put the data into the global namespace
WinJS.Namespace.define("Data", {
items: groupedItems,
groups: groupedItems.groups,
getItemReference: getItemReference,
getItemsFromGroup: getItemsFromGroup,
resolveGroupReference: resolveGroupReference,
resolveItemReference: resolveItemReference
});
});
});
}
In groupedItems.js:
// ...
// This function updates the ListView with new layouts
_initializeLayout: function (listView, viewState) {
/// <param name="listView" value="WinJS.UI.ListView.prototype" />
if (viewState === appViewState.snapped) {
listView.itemDataSource = Data.groups.dataSource;
listView.groupDataSource = null;
listView.layout = new ui.ListLayout();
} else {
listView.itemDataSource = Data.items.dataSource;
listView.groupDataSource = Data.groups.dataSource;
listView.layout = new ui.GridLayout({ groupHeaderPosition: "top" });
}
},
// ....
Seeing as I cannot move this code out of this file into the done() function of the Promise in data.js, how do I make the application wait until Data is initialized in the WinJS namespace prior to initializing the layout?
You have two asynchronous operations in progress (loading of the data and loading of the page) and one action (initializing the grid) that needs to happen only after both asynchronous operations are complete (page is loaded, data is available). There are a lot of approaches to solve this depending upon what architectural approach you want to take.
The brute force method is that you create a new function that checks to see if both the document is ready and the data is loaded and, if so, it calls _initializeLayout(). You then call that function in both places (where the doc is loaded and when the data is available) and it will execute only when both conditions are satisfied. It appears that you can tell if the data is loaded by checking for the existence of the global Data item and the its relevant properties.
There are more involved solutions that are architecturally a little cleaner. For example, in your doc ready handler, you can check to see if the data is available yet. If it is, you just initialize the layout. If, not you install a notification so that when the data is available, your callback will get called and you can then initialize the layout. If the data loading code doesn't currently have a notification scheme, then you create one that can be used by any client who wants to be called when the data has been loaded. This has the advantage over the first method in that the data loading code doesn't have to know anything about the grid. The grid does have to know about the data - which makes sense because the grid requires the data.
There are surely ways to use the promise/done system to do this too though I'm not personally familiar enough with it to suggest a good way to do it using that.