Here I attach the screenshots of my console log results. Both of them are Objects. But they are not the same. What could be the problem? One is showing
(5) [{..},{..},{..},{..},{..}]
Another one just shows [].
let tmp_array = [];
this.database.ref('/users/').once('value', (snapshot) => {
snapshot.forEach( (childSnapshot) => {
var key = (childSnapshot.val() && childSnapshot.key) || 'Anonymous';
var name = (childSnapshot.val() && childSnapshot.val().name) || 'Anonymous';
var email = (childSnapshot.val() && childSnapshot.val().email) || 'Anonymous';
tmp_array.push({ key: key, email: email, name: name });
});
this.setState({ data: tmp_array });
this.getImageList(tmp_array);
console.log(tmp_array);
});
let tmp_array2 = [];
lodash.forEach(tmp_array, (value, key) => {
this.storage.ref().child(value.key + '.jpg').getDownloadURL().then(function (url) {
tmp_array2.push({ id: value.key, image: url });
});
});
this.setState({ image: tmp_array2 });
console.log(tmp_array2);
Welcome to programming with web APIs. You have a typical async problem here. I'll explain what's happening in your code, then give you first steps to a solution, point out some further considerations, and finally provide links with more information for you to read up on this incredibly common (but initially equally confusing) topic.
The problem: the download URLs are loaded asynchronously
Your code calls getDownloadURL, which happens asynchronously. This means that your other code continues while the URL is being loaded. Then when the URL is loaded, your callback is called with the URL. This means that any code that needs the URL, must be inside the callback.
This is easiest to see if you simply the code and add some log statements:
console.log("Before starting getDownloadURL");
this.storage.ref().child(value.key + '.jpg').getDownloadURL().then(function (url) {
console.log("Got download URL");
});
console.log("After starting getDownloadURL");
When you run this code it prints:
Before starting getDownloadURL
After starting getDownloadURL
Got download URL
That is probably not the order you expected, but it completely explains why the array is empty when you print it: the URLs haven't been loaded yet, so they haven't been added to the array yet.
The solution: put all code that needs the download URL inside the callback
To solve this problem all code that needs the download URL from the database must be inside the callback (or be called from there). So a first step would be:
let tmp_array2 = [];
lodash.forEach(tmp_array, (value, key) => {
this.storage.ref().child(value.key + '.jpg').getDownloadURL().then(function (url) {
tmp_array2.push({ id: value.key, image: url });
this.setState({ image: tmp_array2 });
console.log(tmp_array2);
});
});
When you run this, you will see that it logs the array as often as there are child nodes/images. Each time it gets a download URL (remember: this happens asynchronously) it adds it to the array, and then tells React that the state has changed (to it can update the UI).
Some additional considerations and problems
There are more pitfalls in my last code snippet still:
Calling setState() repeatedly may result in a UI that is only partially updated, or in flicker. In that case, consider checking if you've gotten all download URLs before calling setState with something like: if (tmp_array2.length === tmp_array.length) this.setState({ image: tmp_array2 });.
The download URL calls happen asynchronously, and may (I think, it depends on the backend implementation of that API) complete in a different order than in which you call them. In other words: the download URL for the second image may be returned before the download URL for the first image. If this is a concern for your application, be sure to either replicate the other information for the image in tmp_array2 (I think you already do this), or consider storing the download URLs in a way that allows you to associate them back with the items in tmp_array.
More info
As I said at the start: this is a very common problem for developers who are new to dealing with asynchronous APIs to run into. So there are probably hundreds more relevant questions/links if you search for them. Here are some of my favorites:
Doug's post on Firebase's asynchronous APIs
Handling Asynchronous Calls (Firebase) in functions
Best way to retrieve Firebase data and return it, or an alternative way
Why Does Firebase Lose Reference outside the once() Function?
JavaScript - Firebase value to global variable
Firebase use query result outside function
A [] is an empty array. The other [{..},{..},{..},{..},{..}] is an array containing 5 objects. Since you didn't specify which temp array is representing which logged array, I cannot help you determine why one array is empty.
Related
I have taken some key points from this site already when it comes to transferring data to and from local storage. However, it does not seem to work properly. In short, I am trying to load data in the form of an array containing objects, to and from the local storage. I am aware that it is saved as a string within local storage and have then used JSON formatting to solve that problem, it is also where I think the problem might be when there is objects within objects. However, in its current state, it does not seem to work. Any ideas?
var students = [{name: "Petrina", age: "20"}];
function saveList(){
localStorage.setItem('somekey', JSON.stringify(students));
console.log("Saved to Local");
}
function loadList(){
students = JSON.parse(localStorage.getItem('somekey'));
}
The code gives no errors, I am using the functions in relation to loading the window.
window.onload = () => { loadList() }
You've added that you are calling from onload, and,
in your code you are loading into students.
add to the beginning of your code:
var students;.
Note: Some Objects need special handling.
For example: Date:
var date = new Date();
localStorage.date = JSON.stringify(date);
date = new Date(JSON.parse(localStorage.date));
Answer before additional information:
key is a method of localStorage.
Don't use it.
Use students instead, for example.
Storage.key()
(This is assuming that you call the functions)
var students = [{name: "Petrina", age: "20"}];
function saveToLocalStorage(key, value){
localStorage.setItem(key, JSON.stringify(value));
console.log("Saved to LocalStorage");
}
function loadFromLocalStorage(key){
console.log("Loaded from LocalStorage");
return JSON.parse(localStorage.getItem(key));
}
console.log("students: "+students);
saveToLocalStorage("stu", students);
var st2=loadFromLocalStorage("stu");
console.log("st2: "+st2);
cannot be run in a snippet: sandboxed, no access to localStorage - cross origin.
It got solved! - I am unsure what the problem was. I cleared all the calls for all functions and debugged the save and load function 1 at a time whilst watching the local storage data. The problem to begin with was that it simply did not save the data that got updated during runtime, hence it just kept loading values that always were the same. #iAmOren's idea by creating a function that returns a value might have done it, however I am unsure why. Thanks for the responses & Support!
Are you missing a call to your saveList function?
Calling saveList before calling loadList works as you expect.
Please refer: https://codesandbox.io/s/strange-brown-3mmeq?file=/index.html
I want to return an item with a specific ID and perform an update operation on it. My query gets the right result, but it won't let me update it.
I tried following this tutorial: https://www.youtube.com/watch?v=mYyPbfpoZeM
And read the documentation. Both didn't help. Other threads to the same topic are, well, different.
I have a database of objects which have a unique ID stored as integer.
I have an HTML form to get an ID as user input and the query below to retrieve the according object.
I tried this. The query worked, the update didn't.
db.collection('objects').where('ID','==', ID ).get().then((snapshot) => {
snapshot.docs.forEach( doc => {
console.log('debug');
console.log(doc.data().ID);
})
});
I am still new to firebase and js, so please forgive me if my code is uterly wrong.
I am currently stuck with this:
db.collection('objects').where('ID','==', ID ).get().then((doc) => {
console.table(doc);
});
Which is still not working.
For the second snippet an I currently get an unlegible table in which I can't really find the object I was looking for.
How do I update a single value in a single document?
EDIT: I forgot my implementation attempts of the update function.
I tried doc.update({value:0}) inside the for loop in snippet one which yielded doc.update is not a function. Similarly for doc.data().update(...).
In the second snippet I mainly tried to see what I got returned and ran variations of the above mentioned uodate function. With no success.
I managed to get it to work by studying the part of the firestore documentation that goes more into detail about the actual functions. Somehow it was hard for me to find this.
db.collection("users").where("name", "==", somename).limit(1).get().then(query => {
console.log(query);
const thing = query.docs[0];
console.log(thing.data());
let tmp = thing.data();
tmp.current_game_play = tmp.current_game_play + 1;
console.log(tmp);
thing.ref.update(tmp);
});
So I use where to get a Query object, use get to get a a querySnapshot inside the then promise resolve, use docs[0] to retrieve the first (and only) documentSnapshot and finally ref to get a reference that makes the whole thing updatable later.
try this:
var objectRef= db.collection("objects").doc(ID);
objectRef.update({
value: 0
}).then(function() {
console.log("Document successfully updated!");
}).catch(function(error) {
// The document probably doesn't exist.
console.error("Error updating document: ", error);
});
I'm having an issue with successive API calls (using JQuery's AJAX) to two different APIs in order to build objects with certain attributes. Here's the summary of my app and what I'm trying to do:
The user enters in the name of an actor or director, and the app is meant to return a total of five movies, each of which has certain attributes like title, overview, year, budget, revenue, and a link to a YouTube preview. I'm using The Movie Database API, plus the YouTube API for the YouTube link.
Here's the order of how things currently to work, with all of this happening in the action creator of the Redux app:
Actor name gets sent to the TMDB API -- returns ActorID number
ActorID number gets sent to the TMDB API -- returns 20 movies with: title, overview, year, poster link, and MovieID number
For EACH movie in that list, the MovieID number gets sent to the API -- returns more attributes: budget, revenue, and IMDB-ID (to use in a link later)
Also for EACH movie in step 2, the title gets sent to the YouTube API -- returns a link to the preview.
Once all of this information is assembled for each movie, I want to return the first five movies and dispatch them as the action payload to the Redux store.
I'm using some promises, and I've tried everything I could think of in terms of rearranging the flow of functions, but I can't get all the information I need with one click of the submit button. The funny thing is, it works with TWO clicks of the submit button, I think because by then all the async AJAX calls are finally done. But after the first click, I have an empty array where the movie objects should be.
Here's some code that should summarize what things look like:
var personId
var movies = []
function actorByRating(UserInput) {
Step 1: get actor ID number:
function searchActors() {
return $.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/search/person?query=${UserInput}&api_key=<key>`
}).done(function(response){
personId = response.results[0].id
})
}
Step 2: Use Actor ID to get list of movies, start assigning them attributes:
function getMovies() {
$.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/discover/movie?with_cast=${personId}&vote_count.gte=20&sort_by=vote_average.asc&budget.desc&api_key=<key>&include_image_language=en`
}).done(function(response) {
response.results.forEach((m) => {
var movie = {}
movie.title = m.title
movie.year = m.release_date.split("-")[0]
movie.movieId = m.id
movie.overview = m.overview
movie.poster = "http://image.tmdb.org/t/p/w500" + m.poster_path
getMovieInfo(movie) //step 3
getYouTube(movie) //step 4
saveMovie(movie)
})
})
}
function saveMovie(movie){
movies.push(movie)
}
Step 3 function, takes in a movie object as an argument:
function getMovieInfo(m){
return $.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/movie/${m.movieId}?&api_key=<key>&append_to_results=imdb_id`
}).done(function(response) {
m.revenue = response.revenue
m.budget = response.budget
m.imdbId = response.imdb_id
})
}
Step 4 function, to get Youtube link. Also takes a movie object:
function getYouTube(movie){
$.ajax({
method: "GET",
url: `https://www.googleapis.com/youtube/v3/search?part=snippet&q=${movie.title.split(" ").join("+")}+trailer&key=<key>`
}).done(function(yt){
movie.youtubeLink = `http://www.youtube.com/embed/${yt.items[0].id.videoId}`
})
}
After this, the filtering functions work fine, when they have an array of movies to work with. The problem is, I think, all these successive API calls keep firing before the previous ones are done, and the latter ones need info from the earlier ones to search with. Thus, when I click submit the first time, the final movies array is empty, so the dispatched payload is an empty array. THEN the movie objects get filled in, so when you click submit again, the movies are already there to work with, and the rest of the app works fine.
I've tried everything I can think of to slow the process down, chain promises together (which doesn't work because Step 2 has to run for several movies, i.e. the return values of each function keep changing, so I can't ".then" them), reorganizing the information that comes in...but I can't get it to give me movie objects with all the attributes I need by the time the filtering functions actually run to create the proper payload.
Any help or suggestions would be greatly appreciated!
(Note: the "key" stuff above is just placeholder text)
UPDATE:
I changed the code to basically the following:
searchActors()
.then(function(response){
const actorId = response.data.results[0].id
return actorId
})
.then((personID) => {
return getMoviesFromPersonID(personID)
})
.then(function(response) {
const movieList = []
response.data.results.forEach((m) => {
var movie = {}
movie.title = m.title
movie.year = m.release_date.split("-")[0]
movie.movieId = m.id
movie.overview = m.overview
movie.poster = "http://image.tmdb.org/t/p/w500" + m.poster_path
movieList.push(movie)
// saveMovie(movie)
})
return Promise.all(movieList)
})
.then((movieList) => {
const deepMovieList = []
movieList.forEach((movie) => {
getMovieInfo(movie)
.then(function(response) {
movie.revenue = response.data.revenue
movie.budget = response.data.budget
movie.imdbId = response.data.imdb_id
deepMovieList.push(movie)
})
})
return Promise.all(deepMovieList)
})
.then((deepMovieList) => {
const finalMovies = []
deepMovieList.forEach((movie) => {
finalMovies.push(getYouTube(movie))
})
return Promise.all(finalMovies)
})
Everything works fine right up until the first mention of "deepMovieList". I can't seem to figure out how to have that particular step to work properly, as it essentially involves making 20 API calls with each movie in the movieList. I can't figure out how to 1) get the info back from the API, 2) assign the attributes to the movie object that is passed in to getMovieInfo, and then 3) push that movie object (with the new attributes) to an array that I can use Promise.all on, all without interrupting the promise chain.
Either it moves on to the next "then" function too early (while deepMovieList is still an empty array), or, with other random stuff I've tried, the array ends up being undefined.
How can I have the next "then" function wait until 20 API calls have been made and each movie object has its updated attributes? This will also run into the same problem in the next step, for the YouTube link.
Thanks!
TL;DR: use fetch and promises instead of jQuery, group promises using Promise.all.
The Longer Version
Ok, I'm not going to repeat all your code here. I'm going to abbreviate some stuff to keep it simple.
Basically, you have a bunch of tasks to perform. I'm going to pretend each of them is a function that returns a promise which is resolved with the data you want.
searchActor() - returns a promise resolved with some ID number
getMoviesFromActorID(actorId) - returns a promise that resolves with an array of movie IDs
getMovie(movieId) - returns a promise that resolves with the details for the given movie ID
getYoutube(movie) - returns a promise that resolves with the Youtube embed code.
Given this basic setup (and I admit I'm leaving out a lot of stuff), the code looks like this:
// search for an actor
searchActor('Brad Pitt')
// then get the movie IDs for that actor
.then((actorId) => getMoviesFromActorID(actorId))
// then iterate over the list of movie IDs & build an array of
// promises. Use Promise.all to create a new Promise which is
// resolved when all are resolved
.then((movieIdList) => {
const promiseList = [];
movieIdList.forEach((id) => promiseList.push(getMovie(id)));
return Promise.all(promiseList);
})
// then get Youtube links for each of the movies
.then((movieDetailsList) => {
const youtubeList = [];
movieDetailsList.forEach((movie) => youtubeList.push(getYoutube(movie)));
return Promise.all(youtubeList);
})
// then do something with all the information you've collected
.then((finalResults) => {
// do something interesting...
});
The key to this is Promise.all (documentation here), which will take an array of Promises (or any other iterable object containing promises) and create a new Promise which will resolve when all of the original promises have resolved. By using Promise.all, you can create a step in your promise chain which can include a variable number of parallel actions which must complete before the next step.
You could do something like this will jQuery and callbacks, but it would be pretty darn ugly. One of the great benefits of promises is the ability to lay out a series of steps like above.
I am currently working on an Office.js add in for Word and I am trying to insert an image from a given Url. I was reviewing the Office.js documentation which is located at :
InlinePicture class (JavaScript API for Word)
I see that they may have a built in functionality of getting the base64 representation from a img url by "getBase64ImageSrc()". The documentation on the dev office website is either misleading or incorrect.
Looking to see if anyone has built a word-addin that inserts an image from a url using "getBase64ImageSrc()"? Or am I looking in the wrong direction.
Need to elaborate more on Mike's answer, to avoid confusion.
Staffer901: you are talking about 2 different subjects on this post.
Inserting Images to the document. which i think is your bottom line question: how to insert an image with an image URL. The options that Michael mentioned, which are basically to insert classic HTML for an image, will work but i would NOT recommend you to use any of them. The reason why is because really what you are doing is storing a reference to the image that has a connection to the internet dependency, which means any user consuming that document must be connected to see the image.
What i DO recommend you to do for image insertion (permanent insertion :) ) is to use the range.insertInlinePictureFromBase64 method. You need to have an additional step to encode the image in the URL to a base64 string, which is what the methods accepts as input parameter and here is a good discussion on how to achieve this.. Check out a sample below showing inserting an InlinePicture on the first paragraph of the document, assumes you have the base64. Note that you can get the current insertion point and insert the pic there if needed as well. insertInlinePictureFromBase64 is a method of any objects that inherits from range, like body, paragraph, content control etc.
here is a sample:
// Run a batch operation against the Word object model.
Word.run(function (context) {
// Create a proxy object for the paragraphs collection.
var paragraphs = context.document.body.paragraphs;
// Queue a commmand to load the style property for all of the paragraphs.
context.load(paragraphs);
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
return context.sync().then(function () {
// Queue a command to get the first paragraph.
var paragraph = paragraphs.items[0];
var b64encodedImg = "iVBORw0KGgoAAAANSUhEUgAAAB4AAAANCAIAAAAxEEnAAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACFSURBVDhPtY1BEoQwDMP6/0+XgIMTBAeYoTqso9Rkx1zG+tNj1H94jgGzeNSjteO5vtQQuG2seO0av8LzGbe3anzRoJ4ybm/VeKEerAEbAUpW4aWQCmrGFWykRzGBCnYy2ha3oAIq2MloW9yCCqhgJ6NtcQsqoIKdjLbFLaiACnYyf2fODbrjZcXfr2F4AAAAAElFTkSuQmCC";
// Queue a command to insert a base64 encoded image at the beginning of the first paragraph.
paragraph.insertInlinePictureFromBase64(b64encodedImg, Word.InsertLocation.start);
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
return context.sync().then(function () {
console.log('Added an image to the first paragraph.');
});
});
})
.catch(function (error) {
console.log('Error: ' + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
Finally note that the setSelectedDataAsync method that Michaels mentioned, was recently updated to support image insertion, you also need to supply the base64 of the image but the benefit is that you get backwards compatibility (it will work with 2013 clients as well) here is a code sample of this:
// assumes a valid base64 is provided as the first parameter.
Office.context.document.setSelectedDataAsync(mybase64, { coercionType: 'image' }, function (result) {
if (result.status == 'succeeded')
app.showNotification("Image inserted");
else
app.showNotification("Error:" + result.error.message + " : " + error.name)
})
Consuming images from the document. This is about getting the base64 from existing images in the document. We have a body. inlinePictures collection you can use to get all the images in the document, and you use the getBase64 method to get the base64 encoded representation of the binary. I would like to know why this is confusing in the documentation, can you please elaborate on that?
I hope this is useful.
thanks and happy coding!
-Juan.
To insert an image from URL in Word, use either the Range.insertHtml method or the Document.setSelectedDataAsync method, depending on your specific scenario and goals.
It looks like there's an error in the documentation for the other method you linked to - I'll make sure that gets corrected, but I don't believe it's the API you're looking for.
So I'm trying to go through one Firebase database to find entries in the database matching a criteria. Therefore I'm using the deferred object of jQuery to handle the database calls.
Once I get a return value from this first database I want to get the user info from a second database for each of those values in the first db. Then the results are added to a JSON array
so its:
<search for value, find one>
<<<search other db for oher info>>>
<continue search for outer value>
But this only returns one value - although everything else is running fine (and the console logs all the info correct).
Here's the code:
function find(searchLocation, profileID) {
var requestUserData = {
data: []
};
var def = $.Deferred();
//This will be executed as long as there are elements in the database that match the criteria and that haven't been loaded yet (so it's a simple loop)
Ref.orderByChild("location").equalTo(searchLocation).on("child_added", function(snapshot) {
def.ressolve(snapshot.val().ID);
});
return def.promise();
};
I hope you guys have any ideas on what to do or how I could solve this. Thanks in advance!
Edit: upon further testing I discovered that this problem already exists in the outer loop - so only the first value is being returned. I think this is related to the posission of the resolve() method but I didn't find a posibility on how to change this behaviour.
Firebase is a real-time database. The events stream as changes occur at the server. You're attempting to take this real-time model and force it into CRUD strategy and do a GET operation on the data. A better solution would be to simply update the values in real-time as they are modified.
See AngularFire, ReactFire, or BackboneFire for an example of how you can do this with your favorite bindings framework.
To directly answer the question, if you want to retrieve a static snapshot of the data, you want to use once() callback with a value event, not a real-time stream from child_added:
Ref.orderByChild("location").equalTo(searchLocation).once("value", function(snapshot) {
def.resolve(snapshot.val());
});