I'm developing an asynchronous database searching tool. It currently works with firefox and chrome, but has one enormous hiccup with internet explorer (version 8).
Students can enter their prospective MCAT and GPA scores and then jquery sorts out the schools where they place in the top 25% or the middle 50%. Basically, it's a neurotic premed student's dream (or nightmare).
The jquery cycles through the JSON data, displaying each item that matches the criteria in a <li> item. Again, it works great in ff and chrome, but in internet explorer it refuses to display the list items. It does, however, display the proper count of items, which means that the json data is going through properly.
After searching through stackoverflow I saw some commentary (colorful, often!) on how IE refuses to allow placing elements in tables and some other innerhtml elements using jquery.
I'm wondering if this is the problem, and though I found a similar problem on this question I can't quite figure out how to adapt it for my project (I'm new to javascript).
Any help would be wonderful. The code can be found below.
-samuel
$.getJSON("schoolgrabber.php?jsoncallback=?", function(data){
//loop through the items in the array
for(var x=0; x < data.length; x++){
if( MCAT >= data[x].TopPercentileMCAT && data[x].TopPercentileMCAT!=''){
var li = $("<li>").appendTo("#schoollist");
var school= data[x].School;
//add the actual information into the li using a chain event
//the event begins by appending an <a> tag, with the name of the school inside (text(data[x].School)
//then it adds a unique id, which is the iteration number the counter is on above (x)
//then it adds the url, and adds the school variable, which allows the schools to be clicked over to the results page
//then it puts all of that inside li, as declared above (slightly backwards, but hey)
$("<a>").text(data[x].School).attr("id",x).attr("href", "results.php?school=" + school).appendTo(li);
$("#schoollist li").addClass("school");
var quantity = $(".school").length;
$('#schoolquantity').empty();
$('#schoolquantity').append(quantity);
}}
});
Instead of using jQuery and chaining to build up your DOM, try instead to build out a string of the HTML you want rendered and add that completed string just the one time.
Not only might it fix your bug, but you'll also get better performance. What I'm doing is building up the list's full HTML and calculating the quantity. Then, when I'm completely finished building the HTML, I add it to the DOM. IMO the way I have below is also more readable. Note that I haven't tested this, but you should get the idea:
$.getJSON("schoolgrabber.php?jsoncallback=?", function(data){
//loop through the items in the array
var html = [];
var parentElement = $("#schoollist");
var quantity = 0;
for(var x=0; x < data.length; x++){
if( MCAT >= data[x].TopPercentileMCAT && data[x].TopPercentileMCAT!=''){
var school= data[x].School;
html.push("<li class=\"school\">");
html.push(" <a id=\"" + x + "\" href=\"results.php?school=" + school "\">");
html.push( data[x].School);
html.push(" </a>");
html.push("</li>");
quantity++;
}
}
parentElement.html(html.join(""));
$('#schoolquantity').html(quantity);
});
Remember that everytime you alter the DOM the browser has to do a bunch of stuff to update the webpage. This is costly. You want to avoid adding/grabbing from the DOM as much as possible and instead "batch" your alterations (As a side note, if you want to alter the DOM alot without "batching", take a look at the jquery detach method).
Good luck, hope this works for you!
Related
I am generating QR codes for [id] on an invoice.
Right now I have a javascript at each place where there is going to be a qr code to run, and generate the code.
<script type="text/javascript">
function makeCode (data,width) {
var qrcode = new QRCode(document.getElementById(data), {
width : width,
height : width
});
var length = (data).length;
if (length===10) {qrcode.makeCode("10" + data);}
else {qrcode.makeCode(data);}
}
</script>
<div id="[id]"></div><script>makeCode("[id]","50")</script>
The reason I have to use [id] as the "id" of the div is that it's the only piece of dynamic content I get for the item.
The issue is that when I have more than one item with the same [id] the javascript is of course stacking up all the QR codes into the first <div> with that id.
Is there a way to have the javascript know that it was run from the third DIV (as an example) and then put the code into the third DIV, instead of the first.
I know that you're not supposed to have more than one div with the same ID element. That's part of the issue, I am trying to make them unique but I dont know how.
I have added a jfiddle so you can see the issue a little more clearly.
https://jsfiddle.net/nmteaco/x57o9pko/3/
As mentioned, few things in your current website logic needs to adjust to more dynamic.
First, should have one table which represent shopping cart and within that cart, you can have multiple rows of items for QRcode generation. And each same id can have a indexing flag 1..2..3..
I have made a demo of how your issue can be fixed with change of your html structure.
Demo
Let me know what you think.
I dont know how this worked...
However adding a class to the products of "product" and posting the following code at the end of the document worked.
$.each($(".product"), function(index, value){
var num = index + 1;
$(value).attr("id","qrcode"+ num);
});
It makes each id unique after it runs. However somehow the qr javascript still knows which div to put the correct image into.
Change your html to this:
<div data-content="qr-content" data-value="Your qr content"></div>
<script>
var elements = document.queryselectorall('[data-content="qr-content"]');
for (var i=0; i<elements.length; i++)
makeCode(elements[i].getAttribute('data-value'), 50);
}
</script>
For my school's website they have a dropdown list of courses that you're currently enrolled in on the home page. However, if you see it, it is cluttered full of letters that many people might not want to see.
I already know how I'm going to do this. I'm going to use jQuery to select each list item:
var links = $(".d2l-datalist li .d2l-course-selector-item .d2l-left .vui-link");
This returns an array of <a> elements in text form.
Using links.text("Boo!"); I can set the text of all of them to "Boo!", but I want to change each one individually, using a for/in loop to itterate through each <a> and change the text depending on what the value of the href is.
However, whenever I do this, since the items in the array are strings, I cannot do anything to them with jQuery.
Any help with this is appreciated :)
Here's my code so far (running from a $.getScript() from a JS bookmarklet):
var links = $(".d2l-datalist li .d2l-course-selector-item .d2l-left .vui-link");
//links.text("Boo!");
var count = 1;
for (var link in links) {
link.text("Boo #" + count);
count += 1;
}
Relevant markup: http://hastebin.com/ulijefiqaz.scala
You can use the jQuery .each iterator function.
var links = $(".d2l-datalist li .d2l-course-selector-item .d2l-left .vui-link");
var count = 1;
links.each(function() {
$(this).text("Boo #" + count++);
});
I have a script that gives me the following error: 'TypeError: clickables[ic] is undefined' when I'm checking it with Firebug/in browser consoles. I'm a javascript beginner, who is trying to learn how to do things in vanilla javascript, and so I'm looking specifically for a solution that is just that.
The question: How do I get rid of/silence the undefined TypeError?
What the script should be doing:
I'm using this to reveal hidden elements, whose display attribute is set to none. The script should be getting all the instances of a particular class in a document, .item-reveal, joining that with a unique ID that each item having that class is given, to form a new class to search for via getElementsByClassName. The items with the .item-reveal class are items that are clicked on, the item that is unhidden/revealed has the .ID-reveal-item class (the unique ID of the clickable element followed by the .item-reveal class name reversed, for a simple convention). The ID isn't used for stying at all, it's merely to create a unique class based on a naming convention that can be applied to any pair of elements: one that is clicked on, one that is unhidden/hidden via creating/changing a style for the display attribute.
What the script does:
Currently, the script actually reveals the items onclick, and hides them again on subsequent clicks, and it works with multiple items. So, it kind of, basically, works. I just can't figure out the 'TypeError: clickables[ic] is undefined' issue and how to get rid of it. I get it in several browsers when using developer tools.
The script is an attempt at a self-executing anonymous function sort of thing, so I know the convention is a bit different, but I'm wanting to stick with it so I can apply it to other uses down the road. The article that inspired it is found here:
http://esbueno.noahstokes.com/post/77292606977/self-executing-anonymous-functions-or-how-to-write
EXAMPLE:
HTML
<!-- Item to be clicked, with unique ID -->
<h3 class="item-reveal" id="plan-1">Click for special pricing!</h3>
<p>An introductory paragraph...</p>
<!-- Hidden item to be revealed, will always have a unique class -->
<p class="plan-1-reveal-item">Special, this month only: $49.99</p>
<h3 class="item-reveal" id="plan-b">Click for special pricing!</h3>
<p>An introductory paragraph...</p>
<p class="plan-b-reveal-item">Special, this month only: $29.99</p>
CSS
/* Init - hide/unhide onclicks */
.item-reveal {cursor:pointer;}
[class$="-reveal-item"] {display:none;}
/* Halt - hide/unhide onclicks */
javascript:
var clickables = document.querySelectorAll('.item-reveal');
var clickCount = clickables.length;
(function () {
var Reveal = {
swapThis: function () {
for (var ic = 0; ic <= clickCount; ic += 1) {
// Next line seems to create the error message.
clickables[ic].onclick = function (unhideHide) {
var hidden = this.id;
var findClass = hidden += '-reveal-item';
var revealSwap = document.getElementsByClassName(findClass);
for (rn = 0; rn < revealSwap.length; rn++) {
revealSwap[rn].style.display = (revealSwap[rn].style.display == 'block') ? 'none' : 'block';
}
}
}
}
}
Reveal.swapThis();
}) ();
The script is linked via a SCRIPT tag, just prior to the closing BODY tag. I have tried it with both Async and Defer attributes, with and without other scripts in an HTML document, and the result is the same. I tried adding an event handler to ensure it wasn't something with the DOM loading still ongoing, but I'm not sure how to really test for that to see if it was actually doing anything. Unit testing is something that I'm just starting to attempt familiarizing myself with.
I'm trying to knock the dust off skills after several years in a completely unrelated industry, so the last year has been all about catching up on web development technologies, learning responsive design and HTML5 data stuff, and trying to learn javascript. I've searched, read, and bought several ebooks/books, and this is one of the few times I've run into something I just can't figure out. I imagine it's probably something simple and obvious to someone with formal programming/scripting knowledge, but I was an eBusiness major and networking, marketing, server/systems support, cabling, HTML/CSS, etc., are where I'm comfortable. Any help is greatly appreciated, but keep in mind that I'm trying to implement this in an environment/project that will have no jQuery, by choice. Thanks!
You are going off the end of the list with this:
for (var ic = 0; ic <= clickCount; ic += 1)
Change it to this:
for (var ic = 0; ic < clickCount; ic += 1)
clickCount is the length of the list so since it's 0 based indexing, clickables[clickCount - 1] is the last element in the list. You were trying to access clickables[clickCount] which does not exist.
So I am checking the the scrollHeight of some dynamically generated content to determine whether or not to include a more button in the content. The user can click the more button to expand the content and see everything inside. However using the same function every time sometimes it says the scrollHeight of the elements is 0 which needless to say breaks the function for those elements. I am beyond boggled as to why this function would work 90% of the time but sometimes say the elements don't have a scrollHeight when they clearly do (they take up space on the page).
Here is the function:
function hide_show_expand_button(length){
var current_div = '';
var current_span = '';
//console.log(length);
for(var i = 0; i < length; i++){
if(document.getElementById("message_span"+i)){
current_div = document.getElementById("items_content"+i);
current_span = document.getElementById("message_span"+i);
console.log(current_div.scrollHeight, "height of the div")
if(current_span.scrollHeight > 128 && current_div.scrollHeight < 170){
console.log("inside of the check for the conversation screen");
current_div.innerHTML +="<a href=\"#\" id='expand_colapse_button"+i+"' class=\"expand_colapse_button_conversation\" style=\"color:blue;\" onclick='expand_colapse(\""+ i + "\")'>More...</a>";
} else if(current_span.scrollHeight > 128 && document.getElementById("items_content"+i).scrollHeight > 200 ){
current_div.innerHTML+="<a href=\"#\" id='expand_colapse_button"+i+"' class=\"expand_colapse_button_attatchment\" style=\"color:blue;\" onclick='expand_colapse(\""+ i + "\")'>More...</a>";
} else if(current_span.scrollHeight > 128 && current_div.scrollHeight < 200){
current_div.innerHTML +="<a href=\"#\" id='expand_colapse_button"+i+"' class=\"expand_colapse_button\" style=\"color:blue;\" onclick='expand_colapse(\""+ i + "\")'>More...</a>";
}
}
}
}
As you can see I am checking the scrollHeight on the span and on the div because I have a couple of different types of cells that are being generated and I have to place the more button in different places depending on the type of cell. This function only gets called at the end of the function that generates the content being checked and I have tried it with a setTimeout() call on the miniscule chance that it was going through this function before they were fully created (I don't know how that would happen but who knows).
P.S. This is all taking place inside of a chrome ext. I don't think that has anything to do with it but who knows. Also the position on the elements is relative as I have seen others post about scrollHeight issues with position: absolute.
Edit to make more specific:
Hopefully this helps, the cells I am trying to measure are in a containing div that gets its display set to none several different ways. What is happening above happens in one of the instances where I have navigated away (set display to none) from the container and then navigated back to it (set display to block). You can do this in about a dozen ways inside of the chrome ext. but it only says they don't have a scrollHeight (or any height for that matter I tried using getBoundingClientRect() and it returned 0 for everything) for one case which is why I am confused.
Also might be useful to know, on navigation back to the container the cells are rebuilt every time and as I stated above my function to get the scrollHeight is called at the end of the function to build the cells. So everytime you get back to the container my function is also called.
I would recommend looking at these (I couldn't locate a similar link for Chrome):
MSDN: http://msdn.microsoft.com/en-us/library/ms530302%28v=VS.85%29.aspx
MOZ: https://developer.mozilla.org/en/Determining_the_dimensions_of_elements
It seems that scrollHeight is only valid if something is large enough to need to scroll, so I would assume that 0 means no scrolling needed (all content is visible). From looking at the above links, maybe what you want is clientHeight.
I figured out the problem, It had to do with the what I was trying to check after different elements were set to display none. For some reason for 5-8 cells it was checking other content instead of the page I was on and then switching to the current content. Changing some Id's ultimately let me fix it.
I am relatively new at javascript, and found an interesting behavior that I can't explain today. I have a custom <hr> (with an image) on a website, which displays oddly in IE7 and below. To overcome this, I wanted to use replaceChild() in combination with getElementsByTag(). Initially, I simply tried to loop over the list, so:
var hrules = document.getElementsByTagName('hr');
for (var i=0; i < hrules.length; i++) {
var newHrule = document.createElement("div");
newHrule.className = 'myHr';
hrules[i].parentNode.replaceChild(newHrule, hrules[i]);
document.write(i);
}
However, this does not work: it actually only gets half the elements, skipping every other one. Printing i gives half-integer values of the actual number of <hr> elements in the document (e.g. if there are 7 <hr/> elements, it prints 4. By contrast, the following does work:
var hrules = document.getElementsByTagName('hr');
var i = 0;
while (i < hrules.length) {
var newHrule = document.createElement("div");
newHrule.className = 'myHr';
hrules[i].parentNode.replaceChild(newHrule, hrules[i]);
document.write(i);
}
i is printed the same number of times as there are hrules in the document (but of course is always 0, since I'm not incrementing it), and the hrules are replaced correctly. I recognize that the while here might as well be while(true)--it's just going until it runs out of <hr> elements, but appears to stop after that (it's not printing any more 0s).
I've tried this with a number of different types of elements, and observed that this only occurs when replacing one kind of element with another. I.e., replacing p with div, span with p, etc. If I replace p with p, div with div, etc. the original example works correctly.
Nothing in the documentation I've found (w3schools, various Google search, here, etc.) suggests an obvious answer.
What is going on here? First, why does the second example I offered work - is replaceChild() iterating over the elements automatically? Second, why is the behavior different for different types of element?
document.getElementsByTagName is a live access to all the HR elements in the document - it's updated whenever you change the document. You don't get a snapshot of all the HRs in the document whenever you call it.
So, with the first code, you are both incrementing i and reducing the size of hrules.length each time round the loop. This explains why you only see half the steps you expect.
Here's the solution I ended up using, in case anyone else (like #Pav above) is curious.
var hrules = document.getElementsByTagName('hr');
/* Each repetition will delete an element from the list */
while (hrules.length) {
var newHrule = document.createElement("div");
newHrule.className = 'ieHr';
/* Each iteration, change the first element in the list to a div
* (which will remove it from the list and thereby advance the "head"
* position forward. */
hrules[0].parentNode.replaceChild(newHrule, hrules[0]);
}
Essentially, what happens is you get a list of all the hrules in the document. This list is dynamically updated as you interact with it (see Matthew Wilson's answer). Each time you change the first element of the list to a div, it gets removed from the list, and the list is updated accordingly. The result is that you simply need to act on the first element of the list each time until the length of the list is 0.
That's admittedly a little counterintuitive, but it's how the list works.