Javascript element handling - javascript

I've made a table with many griditems which come from a database. I'm using pure Javscript and AJAX for editing and deleting those griditems.
Code:
var main_table=document.getElementById('main-table'),//get the element handler for the table
griditems=main_table.querySelectorAll('.griditem'),//get an array with all the griditems in the main-table
griditems_count=griditems.length;
var griditem_array = [];
for(var i=0;i<griditems_count;i++){
griditem_array[i] = new create_griditem(griditems, i); //here i create a array for every single element and pass the handler for the griditems and the position on the table to the object
function create_griditem(griditems, griditems_count){
var pos=griditems_count;
var id=griditems[pos].getAttribute('data-id'); //the griditem div's also have the id from the Database
var griditems_buttons=griditems[pos].querySelectorAll('.griditem-buttons');
var del_button = griditems_buttons[0].querySelectorAll('.delete')[0];
del_button.addEventListener('click', function() {
alert('Delete '+id);
}, false);
//there are more things called for creating a delete and edit button for every griditem
}
I'm getting every griditem from the table and create an object array for every single griditem for creating the appropriate buttons for the corresponding item.
my problem is, as you can see on line 13(griditems[pos]), I'm getting the element handle(or DOM handle) through the position in the query selector from the main table.
BUT this grid is a sortable grid, so I can drag and drop the items and the position for the griditem with the corresponding id is somewhere completely different.
So how do I get DOM handler through my id, which I'm passing to the object?
Or can someone give my advice how to code the whole situation in a better way?
Edit:
I've created a fiddle the see the whole thing running: http://jsfiddle.net/MCRte/
As you can see,in the last line main_table.removeChild(griditems[pos]);, I'm getting the DOM handle through the position in the query selector(.griditem) for the main-table. But what if the sorting of those changed before i deleted one... any other would be deleted.
How to i get the DOM handle through the id?

No need to use an auto executing anonymous function at all. Just use the browser event API.
The event handler function receives a single argument called event which is the click event object. The event.target property is the element that was clicked by the user, which in your case would be the button that has the class name ".delete". You can climb the document tree from that element until you reach an element that has the ".griditem" class name, and grab the data-id attribute in order to know which element to delete.
var gridItems = document.querySelectorAll('#main-table .griditem'),
i = 0,
length = gridItems.length;
for(i; i < length; i++) {
gridItems[i]
.querySelector(".griditem-buttons .delete")
.addEventListener("click", deleteGridItem);
}
function deleteGridItem(event) {
event.preventDefault();
var gridItem = event.target;
// Climb the document tree from the .delete button to the .griditem
while (gridItem) {
if (gridItem.classList.contains("griditem")) {
break;
}
else {
gridItem = gridItem.parentNode;
}
}
if (gridItem) {
alert("Delete item: " + gridItem.getAttribute("data-id"));
}
else {
throw new Error("Failed to find griditem");
}
}
I'm assuming at some point the table row will be removed from the document. You must detach event handlers to avoid memory leaks. I've found the best way to do this is by utilizing Event Delegation.
JsFiddle: http://jsfiddle.net/Yj2eg/
var table = document.getElementById("main-table");
table.addEventListener("click", function(event) {
if (event.target.nodeName === "BUTTON") {
event.preventDefault();
console.log("BUTTON");
if (event.target.classList.contains("delete")) {
deleteItem(event.target);
}
else if (event.target.classList.contains("edit")) {
editItem(event.target);
}
}
});
function deleteItem(button) {
var gridItem = getGridItem(button);
if (confirm("Delete item: " + gridItem.getAttribute("data-id") + "?")) {
gridItem.parentNode.removeChild(gridItem);
}
}
function editItem(button) {
var gridItem = getGridItem(button);
alert("Edit item: " + gridItem.getAttribute("data-id"));
}
function getGridItem(button) {
var gridItem = button;
while (gridItem) {
if (gridItem.classList.contains("griditem")) {
break;
}
else {
gridItem = gridItem.parentNode;
}
}
if (!gridItem) {
throw new Error("Could not find griditem");
}
return gridItem;
}

I'm not completely certain what your actual problem is, but if you're trying to set up a click listener for a button within one of your griditems that performs an action based on the griditem's data-id attribute, something like this would do the job:
var main_table = document.getElementById('main-table'); //get the element handler for the table
var griditems = main_table.querySelectorAll('.griditem'); //get an array with all the griditems in the main-table
for (var i = 0; i < griditems.length; i++) {
var griditem = griditems[i];
var id = griditem.getAttribute('data-id');
var del_button = griditem.querySelector('.delete');
del_button.addEventListener('click', function(id) {
alert('Delete ' + id);
}(id), false);
}

Related

how to remove selected list element, instead of removing only top li tag

I'm trying to remove specific li elements, based off of which one has the x button clicked. Currently I'm having an error
"bZMQWNZvyQeA:42 Uncaught TypeError: Failed to execute 'removeChild' on 'Node': parameter 1 is not of type 'Node'."
I am aware that this could mean that the paramater is null, but this dosn't make any sense to me. Chrome dev tools show that the onClick attribute is correctly exectuing removeItem, and passing in the idName as a parameter. How is this not working?
var note = 0;
function saveInfo() {
var idName = "note" + note;
//assign text from input box to var text, and store in local storage
var input = document.getElementById('input').value;
var text = localStorage.setItem(note, input);
var list = document.createElement("li");
var node = document.createTextNode(input);
var removeBtn = document.createElement("button");
list.setAttribute("id", idName);
removeBtn.setAttribute("onClick", `removeItem(${idName})`);
removeBtn.innerHTML = "X";
list.appendChild(node);
list.appendChild(removeBtn);
document.getElementById("output").appendChild(list);
note += 1;
}
function removeItem(name) {
var parent = document.getElementById("output");
var child = document.getElementById(name);
parent.removeChild(child);
}
In my comment, I suggested that you listen to click event bubbling from the removeBtn. In this case, all you need is to remove the onclick attribute assignment logic from your code, and instead give your removeButton an identifiable property, such as a class. Lets give it a class of delete-button:
var removeBtn = document.createElement("button");
removeBtn.classList.add('delete-button');
removeBtn.type = 'button';
removeBtn.innerHTML = 'X';
Then, you can listen to the click event at the level of #output, which is guaranteed to be present at runtime. When the event is fired, you simply check if the event target has the identifiable property, e.g. the remove-button class in our case:
output.addEventListener('click', function(e) {
// GUARD: Do nothing if click event does not originate from delete button
if (!e.target.matches('.remove-button')) {
return;
}
// Delete parent node
e.target.closest('li').remove();
});
If the click event did not originate from the remove button, we simply return and don't do anything else. Otherwise, we know that the button has been clicked, and we can then use Element.closest(), i.e. .closest('li') to retrieve the closest <li> parent node and delete it.
If you absolutely have to support IE11 (which in turn, does not support Element.closest()), you can also use Node.parentNode to access and delete the <li> element, assuming that your remove button is a direct child of the <li> element:
// Delete parent node
e.target.parentNode.remove();
See proof-of-concept below:
var rows = 10;
var output = document.getElementById('output');
for (var i = 0; i < rows; i++) {
var list = document.createElement('li');
var node = document.createTextNode('Testing. Row #' + i);
var removeBtn = document.createElement("button");
removeBtn.classList.add('remove-button');
removeBtn.type = 'button';
removeBtn.innerHTML = 'X';
list.appendChild(node);
list.appendChild(removeBtn);
output.appendChild(list);
}
output.addEventListener('click', function(e) {
// GUARD: Do nothing if click event does not originate from delete button
if (!e.target.matches('.remove-button')) {
return;
}
e.target.closest('li').remove();
});
<ul id="output"></ul>
The issue is that you have missing quotes around the id that you pass to removeItem:
removeBtn.setAttribute("onClick", `removeItem(${idName})`);
This should be:
removeBtn.setAttribute("onClick", `removeItem('${idName}')`);
Better pattern
It is better practice to bind the click handler without relying on string evaluation of code, and without needing to create dynamic id attribute values:
removeBtn.addEventListener("click", () => removeItem(list));
And then the function removeItem should expect the node itself, not the id:
function removeItem(child) {
child.parentNode.removeChild(child);
}
You can remove the following code:
var idName = "note" + note;
list.setAttribute("id", idName);

My jquery function is pushing elements to all my arrays

On my website, users can click on some text to open up a Modal. This Modal allows users to choose a bunch of toppings to add to their Pizza.
Through Javascript, I add each selected topping to an array and change the text display to match their selected toppings. This more or less works, but the problem is for some reason, whenever they add a topping, it is added to ALL arrays, not just the item it's selected for. Can someone help me find why this is happening?
// Open Toppings Modal
$(document).ready(function() {
var count = -1
var tplist = []
$(".order").each(function(){
count += 1
tplist.push([])
var listeners = 0
setModal(count, tplist, listeners)
});
function setModal(count, tplist, listeners) {
$("#openModal" + count).click(function(){
console.log("clicked")
$("#toppingModal" + count).modal()
if (listeners == 0) {
listeners += 1
$("input[type='checkbox']").change(function() {
// TODO: Fix Bug
// Adding to all javascript lists
if (this.checked) {
tplist[count].push($(this).val());
console.log(tplist)
}
else {
ele = $(this).val();
pos = $.inArray(ele, tplist[count])
if ( ~pos ) tplist[count].splice(pos, 1);
}
// Change text to list
if (tplist[count].length > 0) {
$("#openModal" + count).text(tplist[count])
}
else {
$("#openModal" + count).text("Select Toppings")
}
})
}
});
};
});
I am suspecting your $("input[type='checkbox']").change(function() {} is called for every model. Try setting count number somewhere when you click select topping and compare inside $("input[type='checkbox']").change(function() {} to prevent adding of toppings in all arrays

hiding a child layer name link from mapbox and then making it visible when its parent layer is clicked

I have an array of mapLayers coming in from a table in sql. These mapLayers have parent layers which contain one or more child layers. The element a that is being created is the one that holds the name of the layer
I would like to make the layer name to the parent layer visible by default and then only make the child layer names visible when the parent layer is clicked.
The kind of functionality I'm seeking is similar to that of the bootstrap accordion. My index emp[2] != null is what determines if the layer has a parent or not (if its a child).Iif it is a child I would like to hide it then only display when the parent is clicked, but since there is no ID on the "a" tag I am not sure how to set the onClick event. Also if I assing ID to a each time its created it will mean a will always have the same ID and that will cause problems.
$.when(getSecureData("/api/layers/"))
.then(function (retmaplayers) {
for (i = 0; i < retmaplayers.length; i++) {
addLayer(map, L.mapbox.tileLayer(retmaplayers[i][1]), retmaplayers[i][0], i + 1);
$.each(retmaplayers, function (index, emp) {
// pageViewModel.LocationViewModel.parentID(emp.fullname);
if (emp[2] != null)
{
pageViewModel.locationVM.parentID(1)
}
function addLayer(map, layer, name, zIndex) {
var layers = document.getElementById('menu-ui');
layer.setZIndex(zIndex)
layer.addTo(map);
// Create a simple layer switcher that
// toggles layers on and off.
var link = document.createElement('a');
link.href = '#';
if (zIndex == 1) {
link.className = 'active';
// link.class = 'accordion-toggle';
if(pageViewModel.locationVM.parentID()==1)
{
link.style.visibility = "hidden";
}
}
else {
map.removeLayer(layer);
this.className = '';
}
link.innerHTML = name;
if (zIndex != 1) {
link.onclick = function (e) {
e.preventDefault();
e.stopPropagation();
if (map.hasLayer(layer)) {
map.removeLayer(layer);
this.className = '';
} else {
layer.setOpacity(1.0);
map.addLayer(layer);
this.className = 'active';
}
};
}
layers.appendChild(link);
since there is no ID on the "a" tag I am not sure how to set the
onClick event
In general, you don't use elements' ids in Knockout (at least not in your viewmodel code), and in particular, you don't set onClick events. You use the click binding on the clickable element.
You do not seem to be embracing the spirit of Knockout, which is that you manipulate the view elements by doing data operations on your viewmodel. You're creating DOM elements in your viewmodel code instead of in a binding handler.
Your addLayer should just be inserting a row into an observableArray. You should have (probably) a foreach binding for that observableArray, and within it a custom binding handler that turns each element into a map layer.

Event listener fails to attach or remove in some contexts

I've created a script that attaches an event listener to a collection of pictures by default. When the elements are clicked, the listener swaps out for another event that changes the image source and pushes the id of the element to an array, and that reverses if you click on the swapped image (the source changes back and the last element in the array is removed). There is a button to "clear" all of the images by setting the default source and resetting the event listener, but it doesn't fire reliably and sometimes fires with a delay, causing only the last element in a series to be collected.
TL;DR: An event fires very unreliably for no discernible reason, and I'd love to know why this is happening and how I should fix it. The JSFiddle and published version are available below.
I've uploaded the current version here, and you can trip the error by selecting multiple tables, pressing "Cancel", and selecting those buttons again. Normally the error starts on the second or third pass.
I've also got a fiddle.
The layout will be a bit wacky on desktops and laptops since it was designed for phone screens, but you'll be able to see the issue and inspect the code so that shouldn't be a problem.
Code blocks:
Unset all the selected tables:
function tableClear() {
//alert(document.getElementsByClassName('eatPlace')[tableResEnum].src);
//numResTables = document.getElementsByClassName('eatPlace').src.length;
tableArrayLength = tableArray.length - 1;
for (tableResEnum = 0; tableResEnum <= tableArrayLength; tableResEnum += 1) {
tableSrces = tableArray[tableResEnum].src;
//alert(tableSrcTapped);
if (tableSrces === tableSrcTapped) {
tableArray[tableResEnum].removeEventListener('click', tableUntap);
tableArray[tableResEnum].addEventListener('click', tableTap);
tableArray[tableResEnum].src = window.location + 'resources/tableBase.svg';
} /*else if () {
}*/
}
resTableArray.splice(0, resTableArray.length);
}
Set/Unset a particular table:
tableUntap = function () {
$(this).unbind('click', tableUntap);
$(this).bind('click', tableTap);
this.setAttribute('src', 'resources/tableBase.svg');
resTableArray.shift(this);
};
tableTap = function () {
$(this).unbind('click', tableTap);
$(this).bind('click', tableUntap);
this.setAttribute('src', 'resources/tableTapped.svg');
resTableArray.push($(this).attr('id'));
};
Convert the elements within the 'eatPlace' class to an array:
$('.eatPlace').bind('click', tableTap);
tableList = document.getElementsByClassName('eatPlace');
tableArray = Array.prototype.slice.call(tableList);
Table instantiation:
for (tableEnum = 1; tableEnum <= tableNum; tableEnum += 1) {
tableImg = document.createElement('IMG');
tableImg.setAttribute('src', 'resources/tableBase.svg');
tableImg.setAttribute('id', 'table' + tableEnum);
tableImg.setAttribute('class', 'eatPlace');
tableImg.setAttribute('width', '15%');
tableImg.setAttribute('height', '15%');
$('#tableBox').append(tableImg, tableEnum);
if (tableEnum % 4 === 0) {
$('#tableBox').append("\n");
}
if (tableEnum === tableNum) {
$('#tableBox').append("<div id='subbles' class='ajaxButton'>Next</div>");
$('#tableBox').append("<div id='cazzles' class='ajaxButton'>Cancel</div>");
}
}
First mistake is in tapping and untapping tables.
When you push a Table to your array, your pushing its ID.
resTableArray.push($(this).attr('id'));
It will add id's of elements, depending on the order of user clicking the tables.
While untapping its always removing the first table.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
resTableArray.shift(this);
So, when user clicks tables 1, 2, 3. And unclicks 3, the shift will remove table 1.
Lets fix this by removing untapped table
tableUntap = function () {
$(this).unbind('click', tableUntap);
$(this).bind('click', tableTap);
this.setAttribute('src', 'http://imgur.com/a7J8OJ5.png');
var elementID = $(this).attr('id');
var elementIndex = resTableArray.indexOf(elementID);
resTableArray.splice(elementIndex, 1);
};
So you were missing some tables after untapping.
Well lets fix tableClear,
You have a array with tapped tables, but you are searching in main array.
function tableClear() {
tableLen = resTableArray.length;
for (var i = 0; i < tableLen; i++) {
var idString = "#" + resTableArray[i];
var $element = $(idString);
$element.unbind('click', tableUntap);
$element.bind('click', tableTap);
$element.attr("src", 'http://imgur.com/a7J8OJ5.png');
}
resTableArray = [];
}
Im searching only tapped tables, and then just untap them and remove handlers.
fiddle: http://jsfiddle.net/r9ewnxzs/
Your mistake was to wrongly remove at untapping elements.

obj is null, javascript

function init()
{
alert("init()");
/**
* Adds an event listener to onclick event on the start button.
*/
xbEvent.addEventListener(document.getElementById("viewInvitation"), "click", function()
{
new Ajax().sendRequest("31260xml/invitations.xml", null, new PageMaster());
xbEvent.addEventListener(document.getElementById("declinebutton"), "click", function ()
{
declineInvitation();
});
});
ok so what I have here is a event listerner function, the case is when viewInvitation is clicked , the program will fetch my xml file and run page master function where I created my decline button with id="declinebutton", however this does not work, the error message that i get is obj=null or the program could not find id = declinebutton, why is it so? I have created it when I called page master using dom. any help will be appreciated.
function PageMaster()
{
this.contentDiv = document.getElementById("content");
}
/**
* Builds the main part of the web page based on the given XML document object
*
* #param {Object} xmlDoc the given XML document object
*/
var subjectList;
var i;
PageMaster.prototype.doIt = function(xmlDoc)
{
alert("PageMaster()");
alert("Clear page...");
this.contentDiv.innerHTML = "";
if (null != xmlDoc)
{
alert("Build page...");
//create div Post
var divPost = document.createElement("div");
divPost.className = "post";
//create h1 element
var h1Element = document.createElement("h1");
var headingText = document.createTextNode("Invitations");
h1Element.appendChild(headingText);
//insert h1 element into div post
divPost.appendChild(h1Element);
subjectList = xmlDoc.getElementsByTagName("subject");
var groupList = xmlDoc.getElementsByTagName("group");
for (i = 0; i < subjectList.length; i++) //for each subject
{
var divEntry = document.createElement("div");
divEntry.className = "entry";
var subjectNum = subjectList[i].attributes[0].nodeValue;
var subjectName = subjectList[i].attributes[1].nodeValue;
var groupId = groupList[i].attributes[0].nodeValue;
var groupName = groupList[i].attributes[1].nodeValue;
var ownerId = groupList[i].attributes[2].nodeValue;
//set up the invitation table attributes
var table=document.createElement("table");
table.width = 411;
table.border = 3;
table.borderColor = "#990000"
var input=document.createElement("p");
var inputText=document.createTextNode("You are invited to join " + groupName + "(groupId : " + groupId +")");
input.className="style11";
var blank=document.createElement("nbps");
input.appendChild(inputText);
var acceptButton=document.createElement("input");
acceptButton.type="button";
acceptButton.id="acceptbutton";
acceptButton.value="accept";
var declineButton=document.createElement("input");
declineButton.type="button";
declineButton.id="declinebutton";
declineButton.value="decline";
table.appendChild(input);
table.appendChild(acceptButton);
table.appendChild(declineButton);
divEntry.appendChild(table);
var blankSpace = document.createElement("p");
divEntry.appendChild(blankSpace);
divPost.appendChild(divEntry);
}
//insert div post into div content
this.contentDiv.appendChild(divPost);
}
};
/**function getValueOf()
{
return i;
}**/
function declineInvitation()
{
alert("decline");
}
function acceptInvitation()
{
alert("hello");
/**var pos=getValueOf();
alert(subjectList[pos].attributes[0].nodeValue);**/
}
That's my page master function, and I definitely have created the button. but it does not work.
Try calling your function like this:
window.onload=init;
The javascript runs as the page loads. At that point, the element does not yet exist in the DOM tree. You'll need to delay the script until the page has loaded.
The example you gave doesn't create the "Decline" button, as your question suggests it should. If it should, you might want to look at that.
Of course, if the button already exists, please disregard this answer.
You have a listener inside a listener. Is that right?
What about this?:
function init(){
alert("init()");
/** * Adds an event listener to onclick event on the start button. */
xbEvent.addEventListener(document.getElementById("viewInvitation"), "click", function()
{
new Ajax().sendRequest("31260xml/invitations.xml", null, new PageMaster());
}
xbEvent.addEventListener(document.getElementById("declinebutton"), "click", function ()
{
declineInvitation();
});
As far as I understand, you create button with id="declinebutton" for each entry from xml, is that right?
If yes, I'd suggest you to generate different id's for each button (for example, append line index to 'declinebutton', so you have buttons 'declinebutton0', 'declinebutton1' an so on), and assign event listener to buttons separately in the loop.

Categories