attaching json into data attribute in an array of DOM elements - javascript

I'm making a website with recipes in it and I am loading them from a json file via a Mustache.js template.
My json looks something like this:
{
"recipes":[
{"name": "A", preparationTime: "40min", "servings": "3", "image": "path/to/imageA"},
{"name": "B", preparationTime: "30min", "servings": "2", "image": "path/to/imageB"},
{"name": "C", preparationTime: "20min", "servings": "3", "image": "path/to/imageC"},
{"name": "D", preparationTime: "30min", "servings": "4", "image": "path/to/imageD"}
]
}
my template looks like this:
var recipeTemplate = "" +
"<div class='col-6 recipeUnit'>" +
"<div class='recipeItem row'>" +
"<div class='recipeItem__image col-5'><img src='{{image}}' alt='recipe image'></div>" +
"<div class='recipeItem__description col-7'>" +
"<h3 class='recipeTitle'>{{name}}</h3>" +
"<div class='details'>" +
"<span>{{preparationTime}}</span>" +
"<span>{{servings}}</span>" +
"</div>" +
"<a class='buttonDetails' href='#'>see more</a>" +
"</div>" +
"</div>" +
"</div>";
And my ajax load function looks like this:
$(document).ready(function(){
loadRecipes()
function loadRecipes(){
$.ajax({
type: "GET",
url: "recipes.json",
dataType: "JSON",
cache: false,
success: function(data){
$section.empty();
for(var i = 0; i < data.recipes.length; i++){
var recipe = data.recipes[i];
var html = Mustache.to_html(recipeTemplate, recipe);
$section.append(html);
$button = $(".buttonDetails");
$button.data.recipe = recipe;
};
$button.on("click", function(){
console.log($(this).data.recipe)
return false;
});
});
}
})
I want to be able to store the json per specific recipe into the $button in each recipe displayed on the page. Everything works fine but when I want to console.log the data.recipe property when I click the button I always get the last array item from the json. I have been struggling with this for quite some time now and I don't understand why it's displaying the last item.
Originally I took the idea from telez here:
Best practices for Storing JSON in DOM.
I would appreciate if anyone could explain to me why is this problem happening and how could I fix it.

Because $button = $(".buttonDetails"); matches all the buttons appended to the document up to that point. So basically you iterate over all recipes and set the last receipts data to the all buttons for each recipe. This leaves you with all buttons data set to the last recipe.

The problem is in the line:
$button = $(".buttonDetails");
You get all the buttons and assign a recipe to all of them at once.
To avoid this, you should change your selector so it will search in the current template only.

There's a couple of issues here.
Your reference to $button is being reassign every iteration, so when you click the $button, it points to the last button that was bound.
Because your $button is being reassigned, the data associated with it will also be reassigned.
Please see this fiddle
$(document).ready(function(){
var $section = $('section');
loadRecipes();
function loadRecipes(){
var solution = [];
for(var i = 0; i < data.recipes.length; i++){
var $button;
var recipe = data.recipes[i];
var html = Mustache.to_html(recipeTemplate, recipe);
$section.append(html);
$button = $(".recipeUnit:last .buttonDetails"); // get the last $button every time...
$button.data.recipe = recipe;
// capture each $button and store it in array. By doing so we ensure we don't reasign the button.
solution[i] = $button;
solution[i].data('recipe',recipe);
};
// $button here is going to be the D button, because $button is assigned 4 times.
$button.on("click",function(){
console.log('This is wrong, because the $button variable is repointed on every iteration',$(this).data.recipe);
return false;
});
// here we have really do have 4 different buttons...
solution.map(function($button){
$button.on('click',function(e){
console.log($(this).data().recipe);
});
});
}
})

Related

Storing dynamic id for TD ID with JSON file

Okay, so i am pulling data from a json file and at the same time i am putting them in to a table. I've managed to give every td item a dynamic id. However, that id is only accessible inside the html and when i try to use for a function it is either undefined or it is getting the length of every row but not the id.
<script type='text/javascript'>
var dynID;
$(function() {
var data = [];
$.getJSON( 'f-data.json', function(data) {
$.each(data.data, function(i, f) {
var tblRow = "<tr><th>" + '<td id = "editable'+this.id+'">' + f.Title+ "</td></th>";
dynID = $(this).find("td").attr("id");
tblRow += '<th colspan="2"><button type ="submit" class="edit" onclick="editButton(dynID)"></i></button>';
$(tblRow).appendTo(".screens tbody");
});
});
});
<script type='text/javascript'>
function editButton(id) {
alert(id);
}
JSON :
{ "data":[
{
"Title": "Screen 1",
"id": 1
},{
"Title": "Screen 2",
"id": 2
}
The above produces a button and when the button is pressed the result is undefined. The strange thing is that the id's are generated correctly (e.g. editable1, editable2, etc). I guess am not selecting the id correctly?
You are running into reference issues within the $.each loop. Within the loop, the reference to $(this) is a jQuery object made of the element of data array. It does not have any HTML. I believe you meant to treat/parse tblRow as jQuery object and extract the id from it. Doing this is bad for performance. your current code simply the output of the button is:
<button onclick="editButton(dynID)"></button>
This is because dynID is becoming a part of the string -- there is no concatenating happening here. Hence, the output becomes a reference to a variable named dynID in the global scope window.
I would rewrite that code as below.
$.each(data.data, function(i, f) {
var id = this.id, // use id accross the loop.
tblRow = "<tr><th>" + '<td id = "editable' + id +'">' + f.Name + "</td></th>";
tblRow += '<th colspan="2"><button type ="submit" class="edit" onclick="editButton("' + id + '")"></i></button>';
$(tblRow).appendTo(".screens tbody");
});

Iterating through a json in jquery

Help please, guys.
After an Ajax call, I have a JSON object with two rows (users). I have dynamic id's as I intend to load some content (a form to edit the user details) on the page. My problem is each user row that the FOR loop generates has the same ID. So all of the Ajax generated rows have the same ID, 48 in this case.
Here is the code..
// Get the admin information
var loadAdmin = function() {
$.ajax({
type: 'GET',
id: id,
cache: false,
url: 'scripts/administratorsList.php?id=' + id
}).done(function(data) {
var adminData = JSON.parse(data);
for (var i in adminData) {
var userId = adminData[i].id;
$('#adminList').append('<li class="media"><div class="media-left"><img src="assets/images/placeholder.jpg" class="img-circle" alt=""></div><div class="media-body"><div class="media-heading text-semibold">' + adminData[i].userName + '</div><span class="text-muted">Administrator</span></div><div class="media-right media-middle text-nowrap"><span class="text-muted"><i class="icon-pin-alt text-size-base"></i> ' + adminData[i].userCompany + '</span></div></li>')
// Add the edit form view here
$('#edit' + userId).on('click', function(userId) {
var userId = adminData[i].id;
$('#userConfig').append('Here I will generate the form to edit user ' + userId); // This is where the ID stays the same. I have used .append over .html for debugging purposes. Each row returns an ID of 48
});
}
});
};
Below is the JSON file
[{
"id": "17",
"userName": "Mark Bell",
"userCompany": "Pro Movers",
"userTelephone": "12345678911",
"userEmail": "info#info.uk",
"userPassword": "md5hash",
"userUAC": "6",
"originalUAC": "6",
"userRegistered": "20150826",
"activationKey": "0",
"userLastLoggedIn": "20160302",
"userBranch": "0",
"userAdmin": "0"
}, {
"id": "48",
"userName": "demo",
"userCompany": "Monstermove",
"userTelephone": "12345678912",
"userEmail": "info#info.uk",
"userPassword": "demo",
"userUAC": "6",
"originalUAC": "6",
"userRegistered": "20160305",
"activationKey": "0",
"userLastLoggedIn": "20160305",
"userBranch": "3",
"userAdmin": "3"
}]
Thanks in advance
With Jacub's implementation
for(var i in adminData)
{
var userId = adminData[i].id;
storeValueToRemainSame(userId);
}
function storeValueToRemainSame(userId) {
$('#adminList').append('<li class="media"><div class="media-left"><img src="assets/images/placeholder.jpg" class="img-circle" alt=""></div><div class="media-body"><div class="media-heading text-semibold">' + adminData[i].userName + '</div><span class="text-muted">Administrator</span></div><div class="media-right media-middle text-nowrap"><span class="text-muted"><i class="icon-pin-alt text-size-base"></i> ' + adminData[i].userCompany + '</span></div></li>')
// Add the edit form view here
$('#edit' + userId).on('click', function(userId) {
var userId = adminData[i].id;
$('#userConfig').append('Here I will generate the form to edit user ' + userId); // This is where the ID stays the same. I have used .append over .html for debugging purposes. Each row returns an ID of 48
});
}
The issue here is that you are referencing value outside of the handler which changes, therefore the last value is retained.
for(var i in data) {
var value = i;
$('#someId').on('click', function(){
console.log(value);
});
}
The only written value will be the last one as the reference to it will remain.
Possible solution is for example:
function storeValueAndHandleClickEvent(value){
$('#someId').on('click', function(){
console.log(value);
});
}
for(var i in data) {
storeValueAndHandleClickEvent(i);
}
EDIT: If I use the same code as in the question
for (var i in adminData) {
var userId = adminData[i].id;
storeValueToRemainSame(userId);
}
function storeValueToRemainSame(userId) {
$('#adminList').append('<li class="media"><div class="media-left"><img src="assets/images/placeholder.jpg" class="img-circle" alt=""></div><div class="media-body"><div class="media-heading text-semibold">' + adminData[i].userName + '</div><span class="text-muted">Administrator</span></div><div class="media-right media-middle text-nowrap"><span class="text-muted"><i class="icon-pin-alt text-size-base"></i> ' + adminData[i].userCompany + '</span></div></li>')
// Add the edit form view here
$('#edit' + userId).on('click', function(userId) {
var userId = adminData[i].id;
$('#userConfig').append('Here I will generate the form to edit user ' + userId); // This is where the ID stays the same. I have used .append over .html for debugging purposes. Each row returns an ID of 48
});
}
Try this simple code outside ajax
You will use event delegation an will select the id from the id of the dynamically added dom element
$('ul').on('click','div[id^="edit"]',function() {
var userId = $(this).attr('id').substring(4);
$('#userConfig').append('Here I will generate the form to edit user ' + userId); // This is where the ID stays the same. I have used .append over .html for debugging purposes. Each row returns an ID of 48
});

JQuery Datatable rows not deleting despite on calling clear function

Im trying to remove all the rows in jquery data tables when new data arrives in order to fill with new rows.
But the problem is despite calling clear function it simply add the result rows to previous rows.
How to clear the rows in jquery data tables . Following is the code
var table = $("#editable");
var tp = table.DataTable({
"paging": true,
"createdRow": function (row, data, index) {
$compile(row)($scope);
}
});
//more code here ajax call only from angular post
if (result != null) {
$scope.reviews = result;
$window.toastr["success"]("Loaded Successfully !", "Recent Reviews");
tp.clear().draw();
for (var i = 0; i < $scope.reviews.length; i++) {
var id = $scope.reviews[i].ID;
var checked = $scope.reviews[i].Enabled == "1" ? true : false;
tp.row.add([
$scope.reviews[i].ID,
$scope.reviews[i].AddedOn,
$scope.reviews[i].Company.Name,
$scope.reviews[i].Rate,
$scope.reviews[i].User.Name,
$scope.reviews[i].Description,
"<div class='switch'><div class='onoffswitch'><input ng-model='$scope.reviews[" + i + "].Enabled' ng-click='ChangeReviewPublishStatus(" + $scope.reviews[i].ID + ")' ng-checked='" + checked + "' class='onoffswitch-checkbox' id= 'stat" + id + "' type= 'checkbox'><label class='onoffswitch-label' for='stat" + id + "'><span class='onoffswitch-inner'></span><span class='onoffswitch-switch'></span></label></div></div>"
]).draw();
}
}
I would place that piece of code inside a $timeout. Both dataTables and angular wants to manipulate the DOM - angular wins the battle and by that dataTables never get a chance to finish its business. A $timeout will force the dataTables clear() and data population into the next digest, and ensure it is actually executed.
$timeout(function() {
tp.clear().draw();
for (var i = 0; i < $scope.reviews.length; i++) {
var id = $scope.reviews[i].ID;
var checked = $scope.reviews[i].Enabled == "1" ? true : false;
tp.row.add([
$scope.reviews[i].ID,
$scope.reviews[i].AddedOn,
//etc
]).draw();
}
});
Well, that is at least my theory - cannot replicate your scenario in full - let me know if it makes any difference.

jQuery Mobile load new data only

I'm working on this project for learning purposes. The tasks for now are very simple:
Populate data from DB using $.getJSON.
Check every 'n' seconds for new data and append it to the list.
Notify user about new data changes.
Here is the example of where I got so far: ( JSBin /Don't forget to run js)
All the issues will be visible when running the example.
Here is the JS code that i have:
$(document).bind('pageinit', function(){
var $myList = $( "#myList" );
var newItems = [];
function loadList(){
$.getJSON("http://jsbin.com/vayeni/2.js",function(data){
$.each(data, function( index, value ) {
newItems.push( "<li><a>" + value.airline + "</a></li>" );
if(data>newItems){
alert('New Entry');
data=newItems;
}
});
$myList.append( newItems.join( "" ) );
$myList.listview( "refresh" );
setTimeout(loadList,1000);
});
}
loadList();
});
Thanks for your help !
Your data comparison is not correct.
You are comapring this:
<li><a>JetBlue</a></li>
<li><a>Continental</a></li>
...
to this:
{
"id": "1",
"airline": "JetBlue",
"number": "222",
"people": "3",
"time": "12:20"
},
{
"id": "2",
"airline": "Continental",
"number": "222",
"people": "5",
"time": "23:21"
},
There will be always inequality.
You should use another approach. For example, if the id field from your JSON array is an unique one you can attach it to each item from the unordered list as an id attribute. For example:
newItems.push( "<li id=\"" + value.id + "\"><a>" + value.airline + "</a></li>" );
This way, at each iteration you can check if the incomming JSON item already exists into your list and add it when there is no match. Eg:
if (!$myList.find('#' + value.id).length) {
newItems.push( "<li id=\" + value.id + \"><a>" + value.airline + "</a></li>" );
}
Finally, you can append the newItems contents directly if there are items inside:
if (newItems.length > 0) {
$myList.append( newItems.join( "" ) );
}
Here is the edited snippet: JSBin

Jquery reading input field that was created from JSON loop

I cannot figure out for the life of me why this will not work. I am trying to pull the value of a textfield that was created with a loop from a json file.
In this code, at the very bottom I just do a simple click(function() {alert()} just to see if I can pull a value and its returning undefined. But if I remove '#name' and put in 'input' it captures it, but only for the first of several input fields.
Any help is really appreciated
JSON
{
"Controls": [{
"Button":[{ "Name":"Button", "x": "1","y": "2","width": "3","height": "4","Transition":"" }],
"Image":[{"x": "5","y": "6","width": "7","height": "8"}],
"TextField":[{"x": "9","y": "10","width": "11","height": "12","Rows":""}]
}]
}
The Code(there is soome getJSON stuff above this)
//Slide In Attributes Panel Based on Selected Object
$(document).on('click', '#code li', function () {
var index = $('#code li').index(this);
var selected = $(this).text();
switch (selected) {
case selected:
$('#options').hide();
hidePanels();
$('#temp').remove();
$('#objectAttributes').show("slide", 200);
break;
//If it does work show what variable is being used
default:
alert(selected);
break;
}
//Shows Selected LI Index
$('#codeIndex').text("That was div index #" + index);
//Pull list of Attributes for selected Object
$.getJSON('controls.json', function (data) {
//Build Attributes List
var attributeList = '<div id="temp">';
//Target based on selected object
var target = selected;
attributeList += '<div>' + target + '<div>';
$.each(data.Controls[0][target][0], function (kk, vv) {
attributeList += '<div style="float:right">' + kk + ':' + '<input type="text" id='+ kk + '>' + '</input>' + '</div>';
});
attributeList += '</div></div>';
attributeList += '</div>';
$('#objectAttributes').append(attributeList);
$('#temp').append('<div id="editIndex">'+"Modifying index" + " " +index+'</div>');
$(document).on('click', '#saveAttributes', function () {
var $x = $('#name').val();
alert($x);
})
});
});
Ok, so after a little hacking around with a jsfiddle the answer turned out to be a lot simpler than I first thought. Ever since HTML 4.01 class names and IDs have been case sensitive (reference), which means that your selector $('#name') wasn't matching the JSON Name.
So a simple change, such as in this simplified jsfiddle seems to work as desired. Hopefully this helps!

Categories

Resources