Cannot fetch HTML element added via JavaScript - javascript

As title states, I am having trouble accessing an HTML element that was added with JavaScript. I am trying to fetch a DOM element using document.getElementById('sampleID') after adding an HTML div using JavaScript. However, the HTML source code does not reflect the change made so I can't actually fetch the new element.
The application is very similar to a ToDo list but tracks applications instead of tasks. There is a checkbox included in the div of each application submitted and the POST request still goes through, thus deleting the application from the database, but it is not reflected in the page until being refreshed.
The only related concept I have stumbled upon is the MutationObserver interface for JavaScript which doesn't seem to allow for accessing the added elements.
All suggestions are appreciated!
$('#applicationDelete').submit(function(event) {
event.preventDefault(); // Stops browser from navigating away from page
var names = [];
$(":checkbox").each(function () {
var isChecked = $(this).is(":checked");
if (isChecked) {
names.push($(this).attr("class"));
}
});
var data = { names: names }
console.log('Data: ' + names);
$.ajax({
type: 'POST',
contentType: 'application/json',
url: window.location + 'applications/delete',
data: JSON.stringify(data),
dataType: 'json',
success: function(res) {
if (res.response == 'success') {
var application;
for (i in names) {
application = document.getElementById(names[i]);
application.parentNode.removeChild(application);
}
}
}
});
});
EDIT:
function addApplication(application) {
const container = document.getElementsByClassName('applicationDelete')[0];
const appWrap = document.createElement('div');
appWrap.classList.add('application-wrapper');
// ADDED THIS LINE THANKS TO SUGGESTIONS!
appWrap.id = application.company;
const checkbox = document.createElement('div')
checkbox.classList.add('checkbox');
const check = document.createElement('input');
check.type = 'checkbox';
check.classList.add(application.company);
const app = document.createElement('div')
app.classList.add('application');
checkbox.appendChild(check);
appWrap.appendChild(checkbox);
appWrap.appendChild(app);
container.insertBefore(appWrap, container.childNodes[3]);
app.innerHTML = '\
Company: ' + application.company + '<br>\
Position: ' + application.position + '<br>\
Experience: ' + application.experience + '<br>\
Source: ' + application.source + '<br>';
}
Apologies for spaghetti code! I am sure my use of classes and ID's is far from proper coding convention but you guys still managed to find my mistake (without even seeing the addApplication method). Thank you!

You are pussing class names to the names array with this.
names.push($(this).attr("class"));
And retrieve the elements by id.
application = document.getElementById(names[i]);
You should store id's in the names array (I would choose different name for the array too, it suggests you store names).
names.push($(this).attr("id"));
Or get the elements with one of the following.
document.getElementsByClassName(names[i]); //returns array
document.querySelector('.' + names[i]); //returns dom element
$('.' + names[i]); //returns jQuery object.
Since you use jQuery I suggest to use a jQuery solution.

Under the isChecked check, you're pushing the element's class attribute to the names array:
names.push($(this).attr("class"));
This means that your names array consists of classes that can be found in the DOM, but you're later looking for IDs. The simplest fix is to swap document.getElementById(names[i]); for .querySelector() method:
document.querySelector(names[i]);
// or via jQuery:
$(`.${names[i]}`)
☝️ but that's only if you can ensure that your classes are unique (since querySelector() finds the first element that matches the passed query selector).
However, a more elegant solution is to use IDs instead of classes, since the HTML spec demands them to be unique to begin with (which grants you the fail-safety that you haven't written or generated duplicated classes). So given that all of your checkboxes now have unique ID-s:
names.push($(this).attr("id"));
// now you can look for ID-s again!
document.getElementById(names[i]);
// Or a shortcut via jQuery (saving the extra variable):
application.parentNode.removeChild($(`#${names[i]}`));

Related

C3 charts not rendering from jQuery data

So I'm just getting into some simple UI stuff, and I'm stuggling with Javascript. I managed to get a couple of for-loops to create tags and render some charts, works great.
But I then used jquery to fetch a block of JSON data from a Spring Boot REST service.
$.getJSON('api/randomData?chartCount=' + chartsToMake).done( function(data) {
// Extract list of servers from the data...
var hostList = []
$.map(data, function(row) { hostList.push(row.host); });
// Make it a unique list
var uniqueHostList = hostList.unique();
// Iterate over the unique list of servers
uniqueHostList.forEach( function(host) {
var tag = "chart" + host.replace(/\./g, "");
var hostdata = data.filter( (v,i,a) => v.host === host)
console.log("Processing Server:" + host + " with " + hostdata.length + " rows");
// Create DOM element to bind the chart to
document.getElementById("chartBlocks").innerHTML += "<div id=\"" + tag.replaceAll('#','') + "\"></div>";
// Create the Chart here
var chart = c3.generate({
bindto: "#" + tag,
data: {
json: hostdata,
keys: { value: ['lowerband'] }
}
});
});
});
This gets an array of JSON strings back, each object contains one metric for a server, all the HTML tags are inserted, but only the last chart draws up.
I added lots of console.out() stuff to try and debug this, it has the data and everything seems to be working, and the last chart looks fine, but the other 3 above it dont populate.
I've been pulling my hair out trying to work out why, please help!
PS. I created a github project here which is a simple maven/spring boot application.
https://github.com/tfindlay-au/c3demo
There is a working page called "working.html" and "index.html" which doesnt work.
FWIW - it feels like a variable scope thing or maybe a timing thing if I'm trying to generate the chart before the data is avilable or something. Not sure if that helps.
"only the last chart draws up."
document.getElementById("chartBlocks").innerHTML += "<div id=\"" + tag.replaceAll('#','') + "\"></div>";
Because that line (at first I thought it completely replaced the content but then I saw the += ) has bad side-effects for the existing content of chartblocks. Specifically it wipes out the event functionality (edit: and data) which setting up a c3.chart has attached to elements in that chart. When you then set innerHTML in chartblocks again, all that stuff is replaced by the innerHTML string, which is just a literal copy of the structure of the dom elements - wiping out any previously attached event handlers or data properties.
You instead need to append an extra div to chartblocks, which leaves the existing sibling charts in peace, and since c3 uses the d3 library you can do it like this:
d3.select("#chartBlocks").append("div").attr("id", tag.replace('#',''));

How to .append from within an Object?

I'm working within WikiPedia API, (MediaWiki) and have been doing a lot of research on how to get results/push them into an array. Using Angular, I was finally able to get a successful 'forEach' to work for this matter.
My issue now is when I try to '.append' the data (An Object) to HTML, it isn't working for me. To test, I did console.log it and it's reporting the Objects and their existence to the console log, but unsure how to really push it into HTML Formatting.
I have attached a CodePen that is forked at my current state for review. I am new to Angular, so that isn't my key focus - I'm unsure why Angular can parse objects, but it does work.
function wiki() {
$('#results').html('');
result = [];
search = $('#search').val();
$.getJSON(link + search, function(wikis) {
var tempRes = wikis.query.pages;
var page = 'http://en.wikipedia.org/?curid=';
angular.forEach(tempRes, function(v, k) {
result.push({
title: v.title,
body: v.extract,
page: page + v.pageid
})
console.log(result);
$('#results').append(result)
})
})
}
The link + search variables are established early in the code, and work successfully.
Any assistance would be perfect - Been working on this bit for awhile.
Code Pen Link
Edit:
Upon further trial-n-error, my issue was resolved by modifying the Append to reflect:
$('#results').append('<li>' + result[i].title + '<br>' + result[i].body+ '<br>' + result[i].page + '</li>')
I'm thinking that just pulling result, or result[i] wasn't enough to provide proper modification. I appreciate the off-topic replies, and advice.
In order to append HTML to an element you need to build it first. Either construct your HTML tags as a string front the objects and then append, or create the elements and then append:
var html = "";
result.each(function () {
html += buildHtmlFromObject(this);
});
$('#results').append(html);
$('#results').append('<li>' + result[i].title + '<br>' + result[i].body+ '<br>' + result[i].page + '</li>')
Was the cost that resolved my issue. I needed to append the exact calls, other-wise it was trying to access an object within an object (And since the first Object is random, I couldn't forsee it.)

Display neo4j node properties with Sigma.js

I am using Sigma.js with the cypher plugin to visualise my neo4j database. After following the simple example here https://github.com/jacomyal/sigma.js/blob/master/examples/load-neo4j-cypher-query.html , it is working well. I edited the plugin so that the graph labels displayed are the names of my neo4j nodes, however I would also like to show the other node properties when clicking on the label or node.I am quite new to JavaScript so would like to know if this is possible for a beginner like me to do and if it is where is the best place for me to start.
You have to register an event on the click or hover node.
There is an example into sigmajs for event : https://github.com/jacomyal/sigma.js/blob/master/examples/events.html
This is a short code that demonstrate how to make this. Replace the alert method by what you want.
sigma.bind('overNode', function(event) {
alert(event.data.node);
});
If you just want to discover your database, take a look at tank-browser : https://www.npmjs.com/package/tank-browser
Cheers
You have to edit Cypher plugin
First: Define var let's assume we will call it "has" at the beginning of the file.
Second: You should add ul in your html and add class to it called 'popover'
Third: Add to cypherCallback method you should add inside else if (typeof sig === 'object')
sig.graph.read(graph);
sig.bind('clickNode', function(e) {
var clicknode = e.data.node;
// empty the printed list
$('.popover').empty();
has='';
// create the tlis tof prop. from returend Object
for(var keys in clicknode.neo4j_data ){
$('.popover').append(' <li>' + keys + ' = '+ clicknode.neo4j_data[keys] + '</li>');
has+= 'n.' +keys + '="'+ clicknode.neo4j_data[keys] + '"AND ';
}
$('.popover').show();
});
sig.bind('clickStage', function(e) {
$('.popover').hide();
});

ajax conditional var before posting

I have a like button. It shows on each item of an activity feed. I recently added the ability to like comments as well. Need some help adjusting the ajax to work with this. I currently grab the closest .feeditem and get the id, which is formatted to have the ID of the item.
The comments are showing in the same .feeditem div, so clicking like executes the same code, but does not save the right information. Is there a way to make it first check if one of the parent divs id starts with activity-comment- and if it does, have it select different divs, and if its not within that parent id, select the default (below)?
Or if you have a better way to implement this altogether please suggest. Newer to AJAX so this may not be the most effective way.
$likeButton.click(function (event) {
event.preventDefault();
var $itemClicked = $(this)
**//START SELECT**
var liking_user_id = $itemClicked.closest('.feeditem').attr('data-member').match(/\d+/);
var activity_id = $itemClicked.closest('.feeditem').attr('id').match(/\d+/);
**//END SELECT**
var data = {
'action': 'save_like',
'activity_id': activity_id,
'liking_user_id': liking_user_id
}
$.ajax({
type: 'post',
url: ms_user_actions.ajaxurl,
data: data,
success: function (response) {
if (!response.success) {
}
if (response.data.like == true) {
$itemClicked.siblings('.feedBox_Likes').text('You Like This!');
$itemClicked.html('Unlike').attr("like", "unlike");
}
if (response.data.unlike == true) {
$itemClicked.html('Like').attr("unlike", "like");
}
}
});
});
Having to make some assumptions here as there's no HTML in the question. In particular, I'm assuming that you want liking_user_id and activity_id in both cases, and that these two values are read from the relevant container nodes (divs) in the same way.
If those assumptions are valid, then the easiest approach is probably to devise another class name (eg. 'likeContainer') and apply it to the outer (feeditem) container and each of the inner comment containers. This class name will be additional to any existing class names, eg <div class="feeditem likeContainer">
The jQuery would then be as follows :
var liking_user_id = $itemClicked.closest('.likeContainer').attr('data-member').match(/\d+/);
var activity_id = $itemClicked.closest('.likeContainer').attr('id').match(/\d+/);
Thus,
when a comment's 'like' is clicked, .closest() will find an inner, comment container
when a non-comment's 'like' is clicked, .closest() will find an outer, feeditem container.
If the assumptions are not valid, then you might consider building the outer and inner divs differently. This might be the line of least resistance.

replacing or managing json data from ajax request

I've got a page that makes an ajax request and gets data back in json format.
I needed to sort this data before adding it to the DOM, so it is put into an object with the following code
function(data) {
var birthDates = {};
var uids = {};
$.each(data.users, function() {
if (!uids[this.uid]) {
uids[this.uid] = [];
uids[this.uid].push(this);
}
if (!birthDates[this.birthDate])
birthDates[this.birthDate] = [];
birthDates[this.birthDate].push(this);
});
for (var d in birthDates) {
var date = d;
$('div#holdDates').append('<ul class="dayList" id="' + date + '"><li class="date" >' + date + '</li></ul>');
$.each(birthDates[date], function() {
$('ul#' + date).append('<li class="show" id="' + this.uid + '">' + this.name + '</li>');
});
}
$('li.show').click(function() {
var getuid = $(this).attr('id');
$showArr = uids[getuid];
// now I can get the extended data about the user
this all works great when the page is loaded for the first time, however I'm running into two problems, both as a result of making a second ajax request
1) if i make the same ajax request (giving the same variables, so the same data comes back again), then the data gets added to the newly created objects (uids, and birthDates) twice, and I can't figure out how to keep that as unique
2) sometimes (and i haven't been able to debug to figure out why) i don't get any of the extended user data from uids object. (the stuff I do after the li click
Any ideas? Am i doing this efficiently?
I find it strange that you can't empty an object that you've created, but apparently everything I'm reading says that you can't.
Addition
well, after posting this, the next thing I was doing was building a dynamic tag cloud which is dependent on the returned json.
so, now I run into the same problem again. I need to tag-cloud to be new after each request. I really hope there is a way to get rid of 'legacy' data in javascript.
Thanks,
Pete
Everything declared with the var keyword inside of your function(data) will be created anew each time the function is called.
What's the symptom of your problem? Have you actually looked at the value of the uids variable in firebug and seen that items are being duplicated, or do you just see your dates/names getting doubled up on the page?
My suspicion is that this is a result of not clearing the DOM elements that you are calling .append() on before you display your results.
Try adding to the beginning of the function:
$('div#holdDates').empty();
as well as to your date display loop:
$('ul#'+date).empty();
and for good measure, at the beginning of function:
$('li.show').unbind('click');

Categories