I am currently doing a course on Udemy on JavaScript and i have been stuck on a project for a couple of weeks, I have followed the instruction to the best of my abilities many times but i seem to be missing a vital part that is stopping the project form displaying an object.
The purpose of this project to display cocktail drinks and the ingredients when you submit your favourite cocktail into a form. I have four JavaScript files app.js this is the main section of the JS code, then CocktailAPI.js this holds a class that handles the API queries, then we have UI.js this is for the interface behaviour and lastly the part i have not yet reached is the COcktailDB.js.
The problem i am facing is that i have created the class that handles the API request and the tutor starts by converting it into json and then turn the response into a object which is then logged on the console log to prove that everything is working fine.The problem i am facing is that even though i have copied the tutor the object does not display on my console and i get a error message that reads:
Access to fetch at 'http://www.thecocktaildb.com/api/json/v1/1/search.php?s=vodka' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
I have checked the url many times and even copied and paste the url to eliminate that as the problem, i have solved this problem before but ended up starting the project again because i encountered another problem and though starting again would solve it. However when i reached this point again i didn't not know what i done to solve the problem as i tried so many things.
As there is 2 files that are currently in use at the moment as i am still quite early into the project there is nothing on 2 files so i will only post the 2 js files.If this is not enough please let me know what i need to add.
app.js
//Instanciate the classes
const ui = new UI(),
cocktail = new CocktailAPI();
//Create the event listeners
function eventListeners() {
//Add event listeners when the form is submittted
const searchForm = document.querySelector('#search-form');
if (searchForm) {
searchForm.addEventListener('submit', getCocktails);
}
}
eventListeners();
//Get cocktail function
function getCocktails(e) {
e.preventDefault();
const searchTerm = document.querySelector('#search').value;
//Check something is on the search input
if (searchTerm === '') {
ui.printMessage('Please add something intot the form', 'danger');
} else {
//Query by name of the drink
cocktail.getDrinkByName(searchTerm)
.then(cocktails => {
console.log(cocktails);
})
}
}
Cocktail.js
class UI {
//Display a custom message
printMessage(message, className) {
const div = document.createElement('div');
//Add the HTML
div.innerHTML = `
<div class="alert alert-dismissable alert-${className}">
<button type="button" class="close" data-dismiss="alert">X</button>
${message}
</div>
`;
//Insert befrore
const reference = document.querySelector('.jumbotron h1');
const parentNode = reference.parentElement;
parentNode.insertBefore(div, reference);
//Remove after 3 seconds
setTimeout(() => {
document.querySelector('.alert').remove();
}, 3000);
}
}
cocktailAPI.js
class CocktailAPI {
//Get recipe by name
async getDrinkByName(name) {
//Search by name
const apiResponse = await fetch(`http://www.thecocktaildb.com/api/json/v1/1/search.php?s=${name}`);
//returns json respnse
cocktails = await apiResponse.json();
return {
cocktails
}
}
}
It may become clearer what i am trying to produce when you load up all the files
I understand that this may not be enough information but if you ask me i will be able to explain in more detail, but the code is mean to display the response of the API in the console log with all the properties
I will copy the code so that it can be viewed if any one want the files itself to look into more deeply.
This is happening because you are using http to communicate with the API. If you change http://www.thecocktaildb.com/api/json/v1/1/search.php?s=${name} to https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${name} (notice the https) it should work for you.
class CocktailAPI {
//Get recipe by name
async getDrinkByName(name) {
try {
//Search by name
const apiResponse = await fetch(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${name}`);
//returns json respnse
let cocktails = await apiResponse.json();
return { cocktails };
} catch (err) {
throw err;
}
}
}
let cocktails_api = new CocktailAPI();
cocktails_api
.getDrinkByName("vodka")
.then(vodka => { console.log(vodka) })
.catch(err => { console.log("ERR", err) });
Your request for the JSON data must be served over https:// and not http:// since thecocktaildb.com most probably added an Access-Control-Allow-Origin wildcard that only accept https requests and not http requests in their response header.
Just replace the http in your fetch request with an https like this:
fetch(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${name}`);
Check and run the following code snippet for a practical example of the above:
async function hello(name) {
const apiResponse = await fetch(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${name}`);
let cocktails = await apiResponse.json();
console.log(cocktails);
return cocktails;
}
hello("vodka");
Related
I am trying to learn the way API's work. Here I am trying to get the POST method to work. I am using this code to make the document in the database,
app.post('/add', async (req, res) => {
try {
const data = require('./test.json');
const newItemId = Math.floor(Math.random() * 1000 + 10).toString();
data.id = newItemId;
data.Partnership_Id = newItemId;
//for testing purpose only
let documentDefinition = {
"id": newItemId,
"name": "Angus MacGyver",
"state": "Building stuff"
};
// Open a reference to the database
const dbResponse = await cosmosClient.databases.createIfNotExists({
id: databaseId
});
let database = dbResponse.database;
const { container } = await database.containers.createIfNotExists({id: containerId});
// Add a new item to the container
console.log("** Create item **");
const createResponse = await container.items.create(data);
res.redirect('/');
} catch (error) {
console.log(error);
res.status(500).send("Error with database query: " + error.body);
}
})
Here I am using test.json for the data input. I am making a fake id using newItemId for data.id and data.Partnership_Id.
With this approach, I can make a document in the database and can check on Postman too but there is nothing in the Body tag in Postman.
I am confused on this part, I feel like the data for the new document should be passed through the Postman Body rather than me using newItemId for it.
This might be a silly question to ask but I am trying to get my head around how API works and how to pass data in them.
IDs are almost always auto generated on the backend (or at least should be) when creating a database resource, so what you have seems to be correct. I would recommend using a library like nanoid to generate the ids though, just to remove the potential for errors.
Its is RESTful convention to return created data, so in this case you would return a JSON on the created document, and then redirect etc on the front end (to ensure complete separation of backend front end - so you can say host them separately). Your approach is also fine and works though.
My advice is to think of your backend and frontend as been completely separate, I would have a project for each personally. This was it is more clear how everything links together.
I'm making a Discord bot in JavaScript and implementing a feature where when you ask a coding question it gives you a snippet. I'm using Grepper and returning the url with the search results. For example:
Hello World in JavaScript Search Results. I would like to access the div containing the snippet. Is this possible? And how would I do it?
Here's my code:
if (message.startsWith('programming')) {
// Command = programming
message = message.replace('programming ', ''); // Remove programming from the string
message = encodeURIComponent(message) // Encode the string for a url
msg.channel.send(`https://www.codegrepper.com/search.php?answer_removed=1&q=${message}`); // Place formatted string into url and send it to the discord server
// Here the program should access the element containing the snippet instead of sending the url:
}
I'm new to JavaScript so sorry if this is a stupid question.
As far as I know the API you are using returns HTML/Text data, not JSON, Grepper has a lot more APIs if you just look into them, you can instead use this API that returns JSON data. If you need more information you can check this Unofficial List of Grepper APIs
https://www.codegrepper.com/api/get_answers_1.php?v=2&s=${SearchQuery}&u=98467
How Do I Access the div containing the snippet?
To access the div you might need to use python web scraping to scrape the innerHTML of the div but I think it's easier to use the other API.
Or
You can put /api/ in the url like:
https://www.codegrepper.com/api/search.php?answer_removed=1&q=js%20loop
The easiest way for this is to send a GET request to the underlying API
https://www.codegrepper.com/api/search.php?q=hello%20world%20javascript&search_options=search_titles
This will return the answers in JSON format. Obviously you'd have to adjust the parameters.
How did I find out about this?
Simply look at the network tab of your browser's dev tools while loading the page. You'll see a GET request being sent out to the endpoint, returning mentioned answers as JSON.
The best way is to use the grepper api.
Install node-fetch
npm i node-fetch, You need this package for making requestes to the api.
To import It in the code just type:
const fetch = require('node-fetch');
Write this code
Modify your code like this:
if (message.startsWith('programming')) {
message = message.replace('programming ', '');
message = encodeURIComponent(message)
// Making the request
fetch(`https://www.codegrepper.com/api/search.php?answer_removed=1&q=${message}`)
.then(res => res.json())
.then(response => {
// response is a json object containing all the data You need
// now You need to parse this data
const answers = response.answers; // this is an array of objects
const answers_limit = 3; // set a limit for the answers
// cheking if there is any answer
if(answers.length == 0) {
return msg.channel.send("No answers were found!");
}
// creating the embed
const embed = new Discord.MessageEmbed()
.setTitle("Here the answers to your question!")
.setDescription("")
// parsing
for(let i = 0; i < answers_limit; i++) {
if(answers[i]) {
embed.description += `**${i+1}° answer**:\n\`\`\`js\n${answers[i].answer}\`\`\`\n`;
}
}
console.log(embed)
msg.channel.send(embed);
});
}
I’m running into an issue with generating dynamic pages in Next.js. I’m pulling data from Sanity, and I think I’ve got the code right, but every time I try load the page I get a type error - “the ‘id’ argument must be of type string. Received null”. The file path is ‘/post/[slug].js’, so that’s the variable name I’ve used throughout - I’m not sure where id is coming from, or if it’s something extra I need to pass, or if I’m not passing my slug right!
I’ve asked in the Sanity forums, and it doesn’t seem to be an API issue - I’m able to pull data in other parts of the app with no problem. In the console, it seems like the page compiles successfully, but this error comes up when attempting to load it. I’ve tried it with an empty div in the page component, to make sure it’s nothing in the presentation logic, with no luck.
I’ve put the full error message in a gist, and it looks like something with jest or next-server. For the life of me I can’t figure it out!
import client from '../../client’
const Post = ({ post }) => {
...
}
export default Post
export const getStaticPaths = async () => {
const posts = await client.fetch(`*[_type == "blog"]{ slug }`)
const paths = posts.map(post => ({
params: {
slug: post.slug.current
}
}))
return {
paths,
fallback: false }
}
export const getStaticProps = async ({ params }) => {
const post = await client.fetch(`*[_type == "blog" && slug.current == $slug]{ title, date, link, excerpt, body, slug }[0]`, {slug: params.slug})
return {
props: { post }
}
}
UPDATE: this seems to be an issue with either play.js or the configuration it uses - trying in a desktop environment doesn’t result in the same error.
I think the issue is your file name. the name should only be [slug].js, it should be located in pages/post. It should work perfectly when you access the page via the URL http://localhost:xxx/post/pulled-sanity-slug
So I ran into this strange issue. I have a feed where you can add posts. One of these posts are event posts where you pick a time for when an event happens. I tested this locally and it seems to work fine. It shows the time I had picked. However, when I deploy the app it suddenly displays the wrong time when I post an event. e.g I posted an event that took place at 3:30PM and when it displays the event time is set at 09:30PM.
I am currently in Mexico City and I assume this is 6 hours difference from the UTC. I just don't understand why it is correct when I test it locally and incorrect when the app is deployed. Can someone help me out? I'll post some of my images below to show what the issue is.
(Maybe it can help to mention that I work together on this project with several other people internationally in different timezones)
this is the post when I try it locally: the content is the time I had intented it to be and as you can see it displays the correct time
the database also seems to save the correct time (see highlighted line)
Here is the app when it's deployed. As you can see I intented to post an event set for 3:30PM, but it ended up showing 9:30PM. So locally it displays correctly, but when deployed not
this is the code that handles adding the post to the database. below you'll see the result of the first console.log in this piece of code
const add = async (req, res, next) => {
try {
const postData = req.body;
console.log('req.body', req.body);
// // Id it's event post, convert due_to date to UTC before storing
// if (postData.type === 'event') {
// postData['event.due_to'] = moment.utc(postData['event.due_to']).format();
// }
const post = await Post.create(postData);
if (post._content_mentions.length !== 0) {
// Create Notification for mentions on post content
notifications.newPostMentions(post);
// start the process to send an email to every user mentioned
post._content_mentions.forEach((user, i) => {
sendMail.userMentionedPost(post, user, i);
});
}
// Send Email notification after post creation
switch (post.type) {
case 'task':
await notifications.newTaskAssignment(post);
await sendMail.taskAssigned(post);
break;
case 'event':
await notifications.newEventAssignments(post);
await sendMail.eventAssigned(post);
break;
default:
break;
}
return res.status(200).json({
message: 'New post created!',
post
});
} catch (err) {
return sendErr(res, err);
}
};
console.log(req.body) in the code above
req.body { 'event._assigned_to': '5c34e8b57b82e019b09f569d',
content: '<p>2:30PM</p>',
type: 'event',
_posted_by: '5c34e8b57b82e019b09f569d',
_group: '5c34e8b57b82e019b09f569e',
'event.due_to': '2019-01-09 02:30:00.000' }
part of the angular front end that handles the time settings before sending the new post to the server
// create date object for this event
const date = new Date(this.model_date.year, this.model_date.month -1, this.model_date.day, this.model_time.hour, this.model_time.minute);
const post = {
content: this.post.content,
type: this.post.type,
_posted_by: this.user_data.user_id,
_group: this.group_id,
event: {
due_date: moment(date).format('YYYY-MM-DD'),
due_time: moment(date).format('hh:mm:ss.SSS'),
due_to: moment(date).format('YYYY-MM-DD hh:mm:ss.SSS'),
// problem: assignedUsers will always be empty
_assigned_to: assignedUsers,
_content_mentions: this.content_mentions
},
files: this.filesToUpload
};
Goal: Front-end of application allows users to select files from their local machines, and send the file names to a server. The server then matches those file names to files located on the server. The server will then return a list of all matching files.
Issue: This works great if you a user select less than a few hundred files, otherwise it can cause long response times. I do not want to limit the number of files a user can select, and I don't want to have to worry about the http requests timing out on the front-end.
Sample code so far:
//html on front-end to collect file information
<div>
<input (change)="add_files($event)" type="file" multiple>
</div>
//function called from the front-end, which then calls the profile_service add_files function
//it passes along the $event object
add_files($event){
this.profile_service.add_files($event).subscribe(
data => console.log('request returned'),
err => console.error(err),
() => //update view function
);
}
//The following two functions are in my profile_service which is dependency injected into my componenet
//formats the event object for the eventual query
add_files(event_obj){
let file_arr = [];
let file_obj = event_obj.target.files;
for(let key in file_obj){
if (file_obj.hasOwnProperty(key)){
file_arr.push(file_obj[key]['name'])
}
}
let query_obj = {files:title_arr};
return this.save_files(query_obj)
}
//here is where the actual request to the back-end is made
save_files(query_obj){
let payload = JSON.stringify(query_obj);
let headers = new Headers();
headers.append('Content-Type', 'application/json');
return this.http.post('https://some_url/api/1.0/collection',payload,{headers:headers})
.map((res:Response) => res.json())
}
Possible Solutions:
Process requests in batches. Re-write the code so that the profile-service is only called with 25 files at a time, and upon each response call profile-service again with the next 25 files. If this is the best solution, is there an elegant way to do this with observables? If not, I will use recursive callbacks which should work fine.
Have the endpoint return a generic response immediately like "file matches being uploaded and saved to your profile". Since all the matching files are persisted to a db on the backend, this would work and then I could have the front-end query the db every so often to get the current list of matching files. This seem ugly, but figured I'd throw it out there.
Any other solutions are welcome. Would be great to get a best-practice for handling this type of long-lasting query with angular2/observables in an elegant way.
I would recommend that you break up the number of files that you search for into manageable batches and then process more as results are returned, i.e. solution #1. The following is an untested but I think rather elegant way of accomplishing this:
add_files(event_obj){
let file_arr = [];
let file_obj = event_obj.target.files;
for(let key in file_obj){
if (file_obj.hasOwnProperty(key)){
file_arr.push(file_obj[key]['name'])
}
}
let self = this;
let bufferedFiles = Observable.from(file_arr)
.bufferCount(25); //Nice round number that you could play with
return bufferedFiles
//concatMap will make sure that each of your requests are not executed
//until the previous completes. Then all the data is merged into a single output
.concatMap((arr) => {
let payload = JSON.stringify({files: arr});
let headers = new Headers();
hearders.append('Content-Type', 'application/json');
//Use defer to make sure because http.post is eager
//this makes it only execute after subscription
return Observable.defer(() =>
self.post('https://some_url/api/1.0/collection',payload, {headers:headers})
}, resp => resp.json());
}
concatMap will keep your server from executing more than whatever the size of your buffer is, by preventing new requests until the previous one has returned. You could also use mergeMap if you wanted them all to be executed in parallel, but it seems the server is the resource limitation in this case if I am not mistaken.
I'd suggest to use websocket connections instead because they don't time out.
See also
- https://www.npmjs.com/package/angular2-websocket
- http://mmrath.com/post/websockets-with-angular2-and-spring-boot/
- http://www.html5rocks.com/de/tutorials/websockets/basics/
An alternative approach would be polling, where the client makes repeated requests in a defined interval to get the current processing state from the server.
To send multiple requests and waiting for all of them to complete
getAll(urls:any[]):Observable {
let observables = [];
for(var i = 0; i < items.length; i++) {
observables.push(this.http.get(urls[i]));
}
return Observable.forkJoin(observables);
}
someMethod(server:string) {
let urls = [
'${server}/fileService?somedata=a',
'${server}/fileService?somedata=b',
'${server}/fileService?somedata=c'];
this.getAll(urls).subscribe(
(value) => processValue(val),
(err) => processError(err),
() => onDone());
}