I'm getting JSON data from the API, like this
data = {
q: 'sugar',
from: 0,
to: 10,
params: {
sane: [],
q: [ 'sugar' ]
},
more: true,
count: 1000,
hits: [{
recipe: {
uri: 'http://www.edamam.com/ontologies/edamam.owl#recipe_a46ae0ad4f8320fa6285b25fc7b36432',
label: 'Bread Pudding with Apple and Brown Sugared Bacon',
image: 'https://www.edamam.com/web-img/1ae/1ae111af3737d42e9d743445f5605373.JPG '
},
recipe: {
uri: 'http://www.edamam.com/ontologies/edamam.owl#recipe_a36b51f50f102bf5da228af55d2ce040',
label: 'Vanilla sugar',
image: 'https://www.edamam.com/web-img/4fd/4fdd2336043aa81dd05a4b6a08934402.jpg',
}
}]
}
And I try to bind the recipe to divs. For example, there is a div with the id columns,
Here is that piece of codes.
var list = data.hits;
function Builddivs(list) {
var array = new Array(0);
var count = list.length;
for (var i = 0; i < 10; i++) {
var p = list[i];
title = p.recipe.label;
imgurl = p.recipe.image;
href = p.recipe.url;
array.push("<div class='pin'><a href='" + href + "'><img src='" + imgurl + "'/></a><p>" + title + "</p> </div>");
}
var html = array.join("");
$("#columns").html(html);
}
The problem is to generate that html takes like several seconds, so is there a better way to do that? like bind the data directly to existing dynamic number of divs? Thanks!
Instead of generating a lot of HTML at once, it would be more efficient to edit existing HTML.
Example jsfiddle solution to this question
Editing HTML with jQuery
You can replace or add text or HTML to a div:
$(selector).html('Try <span class="red">this!</span>');
$(selector).text('Just add some text');
$(selector).append('Some HTML');
Adding src to an image or adding href to a link:
$(selector).attr('src','http://example.com/someimage.jpg');
$(selector).attr('href','http://example.com');
Instead of using a for loop, you could use javascript Array.forEach or jquery $.each
Generating HTML for the first time
On every run (every time the list changes) you can check if the HTML element to be edited exists. If not, then you can generate the appropriate HTML. On the first run of the code, the HTML would then be generated for every element from the list.
See this question to see how to check if elements exist:
How do you check if a selector matches something in jQuery?
Example
Here is a working jsfiddle with an example of how HTML can be edited using $().attr and $().html: https://jsfiddle.net/fyux250p/1/
var hitsContent =""
(list || []).forEach(function(data,index){
hitsContent += "<div class='pin'><a href='" + data.url + "'><img src='" + data.url + "'/></a><p>" + data.label + "</p> </div>";
})
$("#columns").html(hitsContent);
Related
I've retrieved data (6 users) from an API using AJAX, and I want to display the names and avatars of each user using only JavaScript. The problem is that when I when I run the code, it displays 6 copies of the same user and I don't know why.
I've already played around with $.each and other methods, but they don't seem to work.
var items = JSON.parse(xhr.responseText)
userData = items.data;
for (var i = 0; i < userData.length; i++){
$("body").append("<div class = 'container'> " + "<div class = 'user'> " + "<div class = 'profile_picture'>" + "<img>" + "</img>" + "</div>" + "<div class = 'name'>" + "<h1>"+ "</h1>" + "</div>" + "</div>" + "</div>")
imgEl = $("img")
h1El = $("h1")
$(imgEl).attr("src", userData[i].avatar)
$(h1El).text(userData[i].first_name + " " + userData[i].last_name)
}
I expected that userData[i] would loop through the array, but it just shows the last element.
Your issue is due to your global $('img') and $('h1') selectors updating all the images and h1s on the page to be the last one. You need to update only the last one appended.
Another approach to solving this issue though would be to separate your html from your javascript, to more closely follow the separation of concerns practice. Using an html template, you can reuse it and inject the data that you want for each entry fairly easily.
var userData = [
{
avatar: 'https://www.fakeurl.com/photo1.png',
first_name: 'Jake',
last_name: 'Blane'
},{
avatar: 'https://www.fakeurl.com/photo2.png',
first_name: 'Jill',
last_name: 'White'
}
];
var userTemplate = $('#userTemplate').html();
$(document.body).append(
userData.map( user => {
return userTemplate
.replace( '%USER_AVATAR%', user.avatar )
.replace( '%USER_LABEL%', `${user.first_name} ${user.last_name}` )
} )
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<template id="userTemplate">
<div class = 'container'>
<div class = 'user'>
<div class = 'profile_picture'>
<img src = '%USER_AVATAR%' >
</div>
<div class = 'name'>
<h1>%USER_LABEL%</h1>
</div>
</div>
</div>
</template>
You can do it also without setting up a template, like shown below:
var userData = [
{avatar: 'https://via.placeholder.com/100/ffb000',first_name:'Jake',last_name:'Blane'},
{avatar: 'https://via.placeholder.com/100/da0800',first_name:'Jill',last_name:'White'}];
$('body').append(userData.map(u=>
"<div class = 'container'><div class='user'><div class='profile_picture'><img src='"
+u.avatar+"' /></div><div class = 'name'><h1>"
+u.first_name+' '+u.last_name+"</h1></div></div></div>").join("\n"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I am a big fan of jquery and have been using it for a long time but in this case you can achieve the same thing very easily without by replacing
$('body').append( <html_code ...> )
with:
document.querySelector('body').innerHTML += <htm_code ...>
The template method, as shown by #Taplar in this thread is to be preferred, when things get a bit more complex, as they will be easier to maintain then.
So, I am building some sort of e-commerce website and I needed to import a bunch of products from a mysql database, using nodejs and ajax.
I've been able to get that so far, aswell as creating certain buttons below each product that will lead to a /product page where additional information will be displayed of that exact product.
Since those buttons were not dynamic, I had the need to add an attribute, which will contain the products ID, which I will then use to send a POST request to the server so I can get the information about that specific product.
And although I was able to add the attribute to the buttons themselves, I have no idea how to get their value.
This is how I added my attributes to the buttons.
$(function () {
$.ajax({
type: 'POST',
url: '/getPacotes',
success: function (data) {
for (var i = 0; i < data.length; i++) {
var idPacote = data[i].idPacote;
var nomePacote = data[i].Nome_Pacote;
var precoPacote = data[i].Preco_Pacote;
var fornecedorPacote = data[i].Fornecedor_Pacote;
var foto = "https://webitcloud.net/PW/1617/RMR/Views/images/Pacotes/" + data[i].Foto;
var pacoteSet1 = "<div class='col-md-4 pacotes'>";
var pacoteSet2 = "<img src=" + foto + " alt='Mountain View' style='width:150px;height:150px;'><br><br><input id=btnPac" + i + "' type='button' name='comprar' value='Comprar Pacote'>";
var pacoteSet3 = "</div>";
$("#pacotes").append(pacoteSet1 + "<h1>" + nomePacote + "</h1>" + "<h2>" + fornecedorPacote + "</h2>" + "<h3>" + precoPacote + "euros/mes </h3>" + pacoteSet2 + pacoteSet3);
$(document).find("#btnPac" + i).attr({
"idPacote": idPacote
});
}
}
});
});
And this is how I was trying to get their attributes
$("button").each(function () {
console.log(this.idPacote);
$.post("http://localhost:3000/pacote?id=" + this.idPacote);
});
But it doesn't seem to work
idPacote returns undefined and I have no idea why, because I simply give the buttons an "id" and replace the idPacote with the "id" itself, it will return the buttons ID
Sorry if the question sounds dumb. I am not very experienced in these matters.
When you are using jQuery I would suggest you to use the HTML5 data attribute in order to set a specified attribute to a HTML element:
<button data-id="1"></button>
You can set and access it using jQuery:
$('button').data('id', value); //set data-id
$('button').data('id'); //Read data-id
Furthermore change
<input id=btnPac" + i + "' type='button' name='comprar' value='Comprar Pacote'>
to
<button name='comprar'>Comprar Pacote</button>
in order to get elements from $('button')
I am wondering what is the correct way to make new elements with jQuery. For example, say that I have a table, into where I want to append some rows according to a list of objects that I have, so the way I do it currently is like so:
var list =
[
{
name: 'Banana',
cost: 100
},
{
name: 'Apple',
cost: 200
},
{
name: 'Orange',
cost: 300
}
];
var appendThis = '';
for(var key in list)
{
var product = list[key];
appendThis += '<tr>';
appendThis += '<td>' + product.name + '</td>';
appendThis += '<td>' + product.cost + '</td>';
appendThis += '</tr>';
}
$('#producttable').empty().append(appendThis);
Fiddle: https://jsfiddle.net/ahvonenj/ywjbrrak/
But I have heard that concatenating string and appending the rows like that is a good way to do it and instead I should create tr and td elements, into which I append the data from the list and then append those elements in to the HTML table.
So if that is the seemingly correct way to do it, how exactly is it done? I have only ever created div elements programmatically with $('</div>'), but I am not sure how to create a tr like that and on top of that append the programmatically created td in to the programmatically created tr.
Does it work like this perhaps:
var $row = $('</tr>').append($('<\td>').append('TD CONTENT'));
You can create a new element using the syntax $("<element/>"):
$('#producttable').empty();
for(var key in list)
{
var product = list[key];
var $tr = $("<tr/>");
$tr.append($("<td/>").text(product.name));
$tr.append($("<td/>").text(product.cost));
$('#producttable').append($tr);
}
instead of $('</tr>').append($('<\td>').append('TD CONTENT'));
you should try: $('<tr>').append($('<td>').append('TD CONTENT'));
you don't need to close tags. besides that.. \t will result in the tab character rather than your desired <td>
you should checkout this question: jQuery document.createElement equivalent?
In class, our assignment was to create a grocery list of fruit and their prices by placing objects into an array. Now that I did that part, i'm supposed to Extend the shopping cart program from the last lab. Set up a basic HTML page
Append the items and their prices from the shopping list to the page."
This is where I got lost. We can only use vanilla JavaScript. ** I was able to get the webpage to display the total button, and it works(it calculated the items), but where is my list of groceries and their prices?!
my code:
var fruita = {
name: 'apple',
price: 5
};
var fruitb = {
name: 'pear',
price:3
};
var fruitc = {
name: 'orange',
price: 4
};
var grocery = [];
grocery.push(fruita, fruitb, fruitc);
var total = (fruita.price + fruitb.price +fruitc.price);
for (i=0; i<grocery.length; i++){
console.log(grocery[i].name);
console.log(grocery[i].price);
}
var total = (fruita.price + fruitb.price +fruitc.price);
console.log("total price= " + total);
function calcTotal() {
document.getElementById("total").innerHTML = total;
}
function displayList() {
document.write(grocery).innerHTML = grocery;
In my html:
<head>
<script src="monday2assn2.js"></script>
</head>
<body>
<h1> Shopping List </h1>
<p>Click "Total" to add up your shopping list.</p>
<button onclick="calcTotal()">Total</button>
<p id="total"> </p>
One good way to start is to create a global function that standardizes your method for adding elements to the page. Here are functions that can be used to add an element to a parent element:
function addElement(type,content,parent) {
var newElement = document.createElement(type);
var newContent = document.createTextNode(content);
newElement.appendChild(newContent);
get(parent,'id');
current.insertBefore(newElement,current.childNodes[0]);
}
function get(reference, type) {
switch(type) {
case 'id':
current = document.getElementById(reference);
break;
case 'class':
current = document.getElementsByClassName(reference);
break;
case 'tag':
current = document.getElementsByTagName(reference);
break;
}
}
I copied and pasted this from my own files, as I use these frequently. The get function is used to select elements. The addElement function is used to create a new element. The "type" parameter specifies the tag - so p, div, span, etc. "Content" and "parent" are pretty straightforward. The "parent" parameter is represented by the id of the parent. Once done, you can do something as so:
for(var i=0;i<grocery.length;i++) {
var concat = 'Item: ' + grocery[i].name + ' Price: ' + grocery[i].price;
addElement('p',concat,'body'); //assign the body tag an id of "body"
}
An alternate method is to simply set the innerHTML of the body to add everything to it.
get('body','id');
var joined = current.innerHTML; //all elements in a string
for(var i=0;i<grocery.length;i++) {
var concat = 'Item: ' + grocery[i].name + ' Price: ' + grocery[i].price;
joined += concat;
}
current.innerHTML = joined;
Of course, it would help if you'd specify more specifically how exactly you want your data to be formatted in the HTML. But don't fear Vanilla Javascript - I find it to be much easier than using libraries for things like this, and people often become too reliant on libraries that they forget the roots of JS.
I'm working on a bbcode example, but i cannot seem to get it to work.
the regex matches all the [img] tags and make it all look wierd. I'm trying to have the option to click on the image and get it full size and when I do, everything becomes a link (when i have more than once img-tag).
Here's my text:
[img size="small" clickable="no"]img1.jpg[/img]
[img size="large" clickable="yes"]img2.jpg[/img]
Here's my source code:
var bbArray = [/\n/g,
/\[img size="(.*?)" clickable="yes"\](.*?)\[\/img\]/g,
/\[img size="(.*?)" clickable="no"\](.*?)\[\/img\]/g];
var bbReplace = ['<br>',
'<img src="'+path+'img/$1_$2?'+ new Date().getTime() +'" alt="$2">',
'<img src="'+path+'img/$1_$2?'+ new Date().getTime() +'" alt="$2">'];
The operation:
for (var i = 0; i < content_text_bb.length; i++) {
content_text_bb = content_text_bb.replace(bbArray[i], bbReplace[i]);
}
the result:
<img src="localhost/img/small" clickable="no" ]img1.jpg[="" img]
[img size="large_img2.jpg?1423317485160" alt="img2.jpg">;
I'm not that familiar with regex and I really need someone to look at it, I'm lost.
Something that may be of interest to you, Extendible BBCode Parser. An example of use.
var bbcArr = [
'[img size="small" clickable="no"]img1.jpg[/img]',
'[img size="large" clickable="yes"]img2.jpg[/img]'
];
XBBCODE.addTags({
"img": {
openTag: function(params, content) {
params = (params.match(/(\S+?=".*?")/g) || [])
.reduce(function(opts, item) {
var pair = item.match(/(\S+?)="(.*?)"/);
opts[pair[1]] = pair[2];
return opts;
}, {});
var html = '<img src="http://localhost/img/';
if (params.clickable === 'yes') {
html = '<a href="http://localhost/img/' + content +
'" alt="' + content + '">' + html;
}
if (params.size === 'small' || params.size === 'large') {
html += params.size + '/';
}
html += content + '" />';
if (params.clickable === 'yes') {
html += '</a>';
}
return html;
},
closeTag: function(params, content) {
return '';
},
displayContent: false
}
});
bbcArr.forEach(function(item) {
var result = XBBCODE.process({
text: item,
removeMisalignedTags: false,
addInLineBreaks: false
});
this.appendChild(document.createTextNode(result.html + '\n'));
}, document.getElementById('out'));
<script src="https://rawgithub.com/patorjk/Extendible-BBCode-Parser/master/xbbcode.js"></script>
<pre id="out"></pre>
First thing first, your loop should be:
for (var i = 0; i < bbArray.length; i++) {
(not content_text_bb.length)
Secondly, the issue you have is with this size="(.*?). This says: match any content non-greedily till I find the first "thing-that-follow" (in this case the thing-that-follows is the first occurrence of " clickable="yes"
If you look at your input text, the search for [img size="{ANYTHING}" clickable="yes"] means that {ANYTHING} is: small" clickable="no"]img1.jpg[/img][img size="large and you can see how that returns your results, and breaks everything.
So, it should firstly be noted that regexps are not the best tool for language processing (plenty of posts on SO and the internet at large on the topic). In this particular case, you can fix your problem by being very specific about what you want matched.
Do NOT match "anything". If you want to match a size attribute, look for digits only. If you want to match any property value, look for "{ANYTHING_NOT_DOUBLE_QUOTES}". So, with that said, if you change bbArray to the code below, it should work in the particular example you have given us:
var bbArray = [/\n/g,
/\[img size="([^"]*)" clickable="yes"\](.*?)\[\/img\]/g,
/\[img size="([^"]*)" clickable="no"\](.*?)\[\/img\]/g];
Just to be clear: while this should work on your current input, it is by no mean robust bbcode processing. It will only match [img] bbcode tags that have exactly one size attribute and one clickable attribute, in that order!! Most free-to-type bbcode out-there will have much broader variations, and this code obviously won't work on them.