JQuery click event firing multiple times - javascript

I know that there's lot here on already on multiple click events being fired off, I think I've read them all but still can't see what's going wrong here.
Hope fully I'm missing something obvious that someone else can pick up easily...
Some background
My code works inside an Enterprise Social Networking platform and creates a BI dashboard for content analysis (about a 1000 lines of the stuff, mostly domain specific, so too much to post in it's entirety).
The part that is causing me grief is the function that builds the dashboard visualisation itself.
Here goes...
function makePage(){
$("#policyCount").text(policyCount);
var docTypes=getGlobalDocTypes(polOwners); //returns a constrained vocab array
var statusTypes=getGlobalStatusTypes(polOwners); //returns a constrained vocab array
$.each(polOwners,function(){ // polOwners is a global array that contains the BI data to be visualised
html=""
var ownerName = this.name.split(":")[1]; // name is a str in format "Owner:HR"
html += "<div id='" + ownerName + "' class='ownerData'>";
html += "<div class='ownerHeading'>" + ownerName + "</div>";
html += this.policies.length + " Policy documents maintained<br />"; // policies is an array of docs managed by owner
divIDReview = "dboard_" + ownerName + "reviewchart";
html += "<div id='" + divIDReview + "' class='dboardelement'></div>";
divIDType = "dboard_" + ownerName + "typechart";
html += "<div id='" + divIDType + "' class='dboardelement'></div>";
divIDStatus = "dboard_" + ownerName + "statuschart";
html += "<div id='" + divIDStatus + "' class='dboardelement'></div>";
html += "<div id='" + ownerName + "ToggleTable' class='toggletable' owner='" + ownerName + "'>";
html += "Click to display all " + ownerName + " documents<br /></div>";
html += "<div id='" + ownerName + "polTable' class='poltable'>";
html += getPolTable(this.policies); // Returns an HTML table of doc metadata
html += "</div>";
html += "</div>";
  $("#owners").append(html); // When this function is called #owners is an empty div
$(".toggletable").mouseover(function(){
$(this).css({'cursor':'pointer','text-decoration':'underline'});
});
$(".toggletable").mouseout(function(){
$(this).css( {'cursor':'default','text-decoration':'none'});
});
$(".toggletable").each(function(i, elem){
$(elem).click(function(){
if ($(this).next(".poltable").css("display")=="none"){
// Currently hidden - so show
if (debug){console.log($(this).attr("id") + " was clicked")}
$(this).html("Click to hide " + $(this).attr('owner') + " documents<br/>");
$(this).next(".poltable").css("display","block");
} else {
if (debug){console.log($(this).attr("id") + " was clicked")}
$(this).html("Click to display all " + $(this).attr('owner') + " documents<br />");
$(this).next(".poltable").css("display","none");
}
});
});
// the next section calls functions that use the Google vis api to draw pie charts
drawPie(300,200, "Review Status", "Status", "Policies", getReviewStatus(this.policies), ["green","orange","red"], divIDReview);
drawPie(300,200, "Document Types", "Type", "Docs", getDocTypes(this.policies, docTypes), [], divIDType);
drawPie(300,200, "Document Status", "Status", "Docs", getStatusTypes(this.policies, statusTypes), [], divIDStatus);
});
}
Hopefully that's enough to illustrate the problem.
You'll see that the code builds a dashboard display for each polOwner consisting of three pie charts and an option to hide or display a table of underlying data.
I started by applying the click event to the .toggletable class. When that fired multiple times I used the method described on another answer here with the .each to attach a unique event to each instance of the class.
So, what happens?
There are currently 9 polOwners and at first glance, the click event only seems to be toggling the display state of every other table. The console log however shows that this is because it is firing 9 times for the first instance, 8 for the second, 7 for the third etc. with the odd numbers leaving the table in the alternate state (when this works the display will change to a .toggle animation).
For info, While I'm a text editor person, I do have a copy of MS Expression Web 4 which is a useful tool for error checking HTML. I've pasted in a copy of the entire generated markup (nearly 4000 lines) and can't see any bad nesting or structure errors.
Any ideas folks?

You've got some nested loops:
// jQuery each on polOwners
$.each(polOwners,function(){
// ... code that appends .toggletable class
// jQuery each on .toggletable class
$(".toggletable").each(function(i, elem){
// code that runs on the toggletable element
});
});
For each polOwner you are adding a div with the toggletable class. Then inside there you are looping through each div with a toggletable class and adding a click event.
This adds 1 click for the first polOwner, 2 for the second, three for the third and so on.
Move the toggletable each outside of the polOwner each and you should be good

Related

How do I prevent loading the same image multiple times when generating elements?

I have a script that generates product "cards" on my website. These product cards contain images of the products. When they initially load, they have tiny-sized placeholder images to indicate that the actual product images are still loading (lazy-loading).
However, when I check the network activity, the placeholder image [blank.gif] is being downloaded and re-downloaded for every instance of it on the page. This impacts the loading of the page, which cuts into the time saved with the lazy-load script.
screenshot of images loading in the network tab of dev console
Currently, the cards' HTML is assembled into a variable then it's appended to its respective place in the body of the page's HTML, one at a time. See Below
How can I make it so that the image only loads from the server once, and shows up in the multitude of places it belongs?
Card Code:
// Simplified for Stack Exchange
// A JSON file contains activeCards which this script pulls data from
var cardImgs = '';
for (let i of activeCards) {
cardImgs = "<img class='soloImg' src='images/blank.gif' data-src='images/" + i.img + "' alt='" + i.imgAlt + "' />";
// Builds card HTML from elements above
card = "<div class='card' id='" + i.id + "'>" +
"<div class='cardImagesCont'>" + cardImgs + "</div>" +
"<h2>" + i.title + "</h2>" +
"<h4 class='itemDesc'>" + i.description + "</h4></div>";
// Adds card to section
$('.cardSection').append(card);
}
Lazy Load Code:
function lazyload() {
for (let i of $('img[data-src]')) {
$(i).attr('src', $(i).attr('data-src'));
}
}
I'm self-taught, so please don't be too cruel if there's something shockingly wrong with this code (but do let me know)

How to pass a value from a Javascript generated button to a controller?

My code generates a table with a button at the end of each row. When the user clicks a button how can I pass a property u.userEmail to the controller via the button? Will the value being sent to the controller be a string?
My (non-working) attempt:
<script>
$(document.body).append("waiting on async table to load<br>");
$(document).ready(function () {
$.getJSON("/Account/LoadClaimsTable", function (crewResponse) {
//returns a List<UserClaims>
$(document.body).append("<table>")
crewResponse.forEach(function (u) {
var s = "";
s+="<tr><td>" + u.userEmail + "</td>";
u.userClaims.forEach(function (k) {
console.log("added claim"+k.value);
s += ("<td>" + k.type + "</td><td>" + k.value + "</td><td>" +
"<input type=\"hidden\" name=\"userEmail\" value=\"`${u.userEmail}`\" />"+
"<input type=\"button\" value=\"Create\" onclick=\"location.href='#Url.Action("EditClaims", "Account")'" />
+"</td>");
});
s += "</tr>";
$(document.body).append(s);
s = "";
});
$(document.body).append("</table>")
});
});
</script>
AccountController.cs contains:
public ActionResult EditClaims(string userEmail)
{
return View("StringView", userEmail);
}
You have to pass it on the url of the action. Not sure if you want to pass u.userEmail, but it could looks like this:
crewResponse.forEach(function (u) {
var s = "<tr><td>" + u.userEmail + "</td>";
u.userClaims.forEach(function (k) {
console.log("added claim"+k.value);
s += ("<td>" + k.type + "</td><td>" + k.value + "</td><td>" +
"<input type=\"hidden\" name=\"userEmail\" value=\"`${u.userEmail}`\" />"+
"<input type=\"button\" value=\"Create\" onclick=\"location.href='#Url.Action("EditClaims", "Account")?userEmail=" + u.userEmail + "'\"/></td>");
});
s += "</tr>";
$(document.body).append(s);
});
There are multiple ways to do it. One is mentioned in the answer above by Felipe. Here is another alternate approach using unobtrusive js
Add the email as html5 data attributes to the button along with another attribute which we will use bind the click behavior.
u.userClaims.forEach(function (k) {
// Add quotes as needed if you want multiline ( i just removed those)
s += "<td>" + k.type + "</td><td>" + k.value + "</td><td>
<input type='button'
clickablebutton data-email='" + u.email + "' value='Create'/></td>";
});
Now, in your document ready, bind a click event handler to those elements (with our custom attribute) and read the data attribute and build the url you need.
$(document).on("click", "input[clickablebutton]", function (e){
var url = '#Url.Action("EditClaims", "Accounts")?useremail=' + $(this).data("email");
window.location.href = url;
});
Some other suggestions
Use the appropriate element. Button is better than input (Consider accessibility)
If it is for navigation, Use an anchor tag instead of a button.
Inline javascript is not great. Let the browser parses your markup without any interruptions and you can add the behavior scripts later (that is the whole point of uobutrisive js approach)
The approach you appear to be taking would be Ajax, response, render a template. With that being said, you may want to rethink your approach.
Step 1.
Build a template
<template id="...">
<button type="button" value="[action]" onclick="[url]">[text]</button>
</template>
Step 2.
Create your request.
axios.get('...').then((response) => {
// Retrieve template.
// Replace bracket with response object model data.
html += template.replace('[action]', response.action);
});
Step 3.
Have the JavaScript render your template.
The above can create a clear concise codebase that is easier to maintain and scale as the scope changes, rather than an individual request performing a change with embedded markup. This approach has worked quite well for me, also I feel it'll make you troubleshooting and definition easier, as the controller is handing an object back to your JavaScript instead of a markup / view data. Which will be a better finite control for the frontend and clear modifications in future.

Need to get qtip to display correct new javascript html table data that is dynamic

I have a similar question in which I didn't have the right data in a fiddle to show. What the other question shows is doing a table row clone, but my data is table append to a div
The jQuery $.each loop shows where I have a dynamically created the title (tooltip)
This is the fiddle: http://jsfiddle.net/bthorn/Lpuf0x7L/1/
$.each(allData, function (index, issues) {
strResult += "<tr><td class='nameField'> <a href='#'>" + issues.LAST_NAME + " " + issues.FIRST_NAME + " " + issues.INITIALS + "</a></td><td>" + issues.OFFICE + "</td><td>" + issues.TITLE + "</td>";
strResult += "<td>" + issues.DEPARTMENT + "</td><td class='alias'>" + issues.ALIAS_NAME + "</td>";
// NEED TO ADD QTIP to the issues.DEPARTMENT title tooltip //////
addTooltips();
/////////
strResult += "</tr>";
});
strResult += "</table>";
$("#divEmpResult").html(strResult);
My old question from a few hours with OP answer should be helpful
dynamic javascript data with qtip is overriding all tooltips with same message
I am trying to call this function but i know that I needs to have additional data from qtip appended to it.
OP was doing a .insertBefore(this) but I am not sure how to do that with my table row
$('button').on('click', function() {
$('<div/>', {
class: 'tips',
text: 'Dynamically inserted.'
}).insertBefore(this);
addTooltips();
In the second code snippet, the addTooltips function was called after the dynamic element(s) were inserted into the DOM via the .insertBefore() method.
In your first code snippet, you are calling the addTooltips function before the elements are actually appended, which is why it isn't working as expected:
$("#divEmpResult").html(strResult);
addTooltips(); // Call the function *after* the elements exist in the DOM
In order to prevent the previous tooltips from being overridden, negate all the elements with data-hasqtip attributes. You can also set the tooltip text based on the title attribute, or some pre-defined defaults like in the example below:
$('.tips:not([data-hasqtip])').each(function() {
$(this).qtip({
content: {
text: $(this).attr('title') || 'This is the message!'
},
hide: {
fixed: false
},
position: {
corner: {
target: 'topLeft',
tooltip: 'bottomRight'
}
}
});
});
To address your last issue where the tooltips only included the first word, you need to enclose the title attribute value in quotes. Previously, your HTML was being rendered like: title=some words here, which resulted in the browser automatically inserting quotes around the first white-space separated word and turning the following words into separate attributes.
Working Example Here
The title attribute value needs to be enclosed in quotes:
strResult += '<td class="tips" title="' + issues.DEPARTMENT + '">' + issues.DEPARTMENT + '</td><td class="alias">' + issues.ALIAS_NAME + '</td>';
To avoid mistakes like this, I would highly suggest using a JS templating engine such as handlebars.

How to get the total of the list ordered, with Javascript

I'm making a code of a online delivery webpage, and I having a hard time trying to figure out how to output the total of the list ordered by the user.
function ListOrder(){
document.getElementById('order').innerHTML += "<div id=\"YourOrders\">" + + document.getElementById('FoodName').value + document.getElementById('quantity').value + document.getElementById('Totality').value + "</div><br>";}
Edited: I want to know how I can get the sum of the total price. So, I placed a parseInt between the document.getElementById('Totality').value . It looks like this now,
function ListOrder(){
document.getElementById('order').innerHTML += "<div id=\"YourOrders\">" + + document.getElementById('FoodName').value + document.getElementById('quantity').value + parseInt(document.getElementById('Totality').value) + "</div><br>";}
Can someone help me make a function or something for that? Javascript only, please. I'm still kinda new at it.
function ListOrder(){
document.getElementById('order').innerHTML +=
"<div id=\"YourOrders\">" +
parseInt(document.getElementById('FoodName').value) +
parseInt(document.getElementById('quantity').value) +
parseInt(document.getElementById('Totality').value) +
"</div><br>";
}
the kernel of your code should look like the following (double + operator deleted, reformatted):
function ListOrder(){
document.getElementById('order').innerHTML +=
"<div id=\"YourOrders\">" + (
document.getElementById('FoodName').value
+ document.getElementById('quantity').value
+ document.getElementById('Totality').value
)
+ "</div><br>"
;
}
You've phrased your question in a way that suggests you wish to output an order list assembled from the content of all (html) elements with certain ids.
this won't work reliably:
Ids should be document unique.
The Js functions you use do not iterate over lists.
instead, proceed along the following lines (which assume that you import jquery, a cross-browser dom-handling and ajax library (which you should use anyway :)):
function ListOrder(){
var e_orders = $("<div id=\"YourOrders\">");
$("#order").append(e_orders);
$(".FoodName").each ( function ( idx_fn, e_fn ) {
$(e_orders).append(
$("<div/>").append(
$(e_fn).val()
+ $(e_fn).nextAll('.quantity').val()
+ $(e_fn).nextAll('.Totality').val()
);
);
$(e_orders).append("<br>");
});
return e_orders;
}
The code template assumes that the source data are elements with value attributes being marked with css classes quantity, Totality and 'FoodName``, that these elements are siblings and unique within a container element for each item incl. quantity information. It should be flexible enough to be tailored to your actual needs and html structure.

Javascript: Subtracting items from shopping cart result in negative value

I've got this shopping cart script that I'm trying to revise. Trouble is, whenever I try to delete more than one item from the cart, I get a negative value. The cart never goes back to zero when all items are deleted. I can add items fine.
Here is the fiddle.
Below is a code snippet of this feature. The full code is in the fiddle as it is easier to explain by showing you a demo of the problem I am having.
function addToCart(id, container_id, corTitle, corPrice, credit_hrs) {
var amount = parseFloat(corPrice);
var hours = parseFloat(credit_hrs);
var remove = "<button type=\"button\" class=\"remove\"></button>";
var selected_product = "<div class=\"item \">"
+ "<div class=\"title\">"
+"<div class=\"remove\"><button type=\"button\" title=\"remove from cart\" class=\"remove-from-cart\" alt=\"Remove Course\" ></button></div>"
+ corTitle
+ " for $" + corPrice
+ "</div>"
+ "<input name=\"containerId\" value=\"" + container_id
+ "\" type=\"hidden\">" + "</div>";
$(selected_product).insertBefore("#subtotals");
register("add", amount, hours);
$(".remove-from-cart").click(function() {
$(this).parents(".item").slideUp("slow");
console.log(this);
register("subtract", amount, hours);
$(toId(id)).removeAttr("disabled").fadeTo("slow", 1);
$(this).parents(".item").remove();
});
}
The problem appears to be that the click handler attached to the remove button is invoked multiple times when a remove button is clicked. The duplicate invocation of register("subtract", amount, hours) causes the total to go negative. How can I fix this?
The problem is that you re-run $(".remove-from-cart").click(...) each time you add an item to the cart, so all existing remove buttons get an extra handler.
Use jQuery to parse to HTML into a jQuery-wrapped DOM structure, and then use that as a context for your .remove-from-cart selector (as demonstrated in this working fiddle). That way, the .remove-from-cart selector will only apply to your newly-added item.
var selected_product = "<div class=\"item \">" + ...;
// jQuery-wrapped DOM structure
var $prod = $(selected_product)
$prod.insertBefore("#subtotals");
register("add", amount, hours);
// use $prod as jQuery context argument,
// so `.remove-from-cart` only looks in this DOM tree
$(".remove-from-cart", $prod).click(function() {
...
});

Categories

Resources