I'm trying to modify some code for dragging out files from your browser window I found here:
http://www.thecssninja.com/javascript/gmail-dragout
In my code I want to use for loops so I can handle a large number of files without repeating code over and over.
This is what I have:
file0
file1
file2
file3
<script type = "text/javascript>
var file_count = 3;
var fileDetails = [];
var files = new Array(file_count);
for(var i=0; i<=file_count; i++) {
files[i] = document.getElementById(i);
}
if(typeof files[0].dataset === "undefined") {
for (i=0; i<=file_count; i++) {
fileDetails[i] = files[i].getAttribute("data-downloadurl");
}
}else {
for (i=0; i<=file_count; i++) {
fileDetails[i] = files[i].dataset.downloadurl;
}
}
//I'm pretty sure the problem starts here.
//If you substitue i for a specific element from the array and comment out the for loop, the script works just fine for the element specified.
for(i=0; i<=file_count; i++) {
files[i].addEventListener("dragstart",function(evt){
evt.dataTransfer.setData("DownloadURL",fileDetails[i]);
},false);
}
I'm fairly certain the problem starts where I have it labeled, I'm not sure why there is a problem or how to solve it.
Some things I should point out:
This only works in Chrome. This is not an issue for me.
I want this to handle 20+ files.
The problem does indeed start where you think it does
//I'm pretty sure the problem starts here.
//If you substitue i for a specific element from the array and comment out the for loop, the script works just fine for the element specified.
for(i=0; i<=file_count; i++) {
files[i].addEventListener("dragstart",function(evt){
evt.dataTransfer.setData("DownloadURL",fileDetails[i]);
},false);
}
Basically your dragstart event listener is bound to i, which at the time of execution is the last element in the list.
The following will work better.
//I'm pretty sure the problem starts here.
//If you substitue i for a specific element from the array and comment out the for loop, the script works just fine for the element specified.
for(i=0; i<=file_count; i++) {
files[i].addEventListener("dragstart", (function(idx) {
return function(evt){
evt.dataTransfer.setData("DownloadURL",fileDetails[idx]);
}
})(i),false);
}
In the above code you will be creating and returning an object of type function with the correct value of i bound - essentially the closure is created at a different level and thus when accessed will get the correct data.
Related
I have a html5 Canvas animation that I am doing on Adobe Animate and tweaking with some code.
I have a portion on the animation that will be like a combobox with all the links to navigate through the different frames. The thing is, I don't want to be creating a bunch of EventListener to many buttons because from experience I know that doesn't work so well. So I am trying to think of a more creative solution. This is my idea.
Create an array that will contain all the buttons.
Assing a variable for each target frame.
Create a for loop with a function inside that assigns the listener to the selected button and then points it to the desired frame (variable)
This is what I have got so far, (not much)
var combobox = [this.btncasco , this.btnbanyera , this.btnLumbrera , this.btnproapopa, this.btnestriborbabor ];
for (var i=0; i<combobox.length; i++) {
var clipcasco = gotoAndStop(0);
var clipbanyera = gotoAndStop(2);
var cliplumbera = gotoAndStop(4);
var clipproapoa = gotoAndStop(6);
var clipestriborbabor = gotoAndStop(8);
}
Would that be feasible ?
In your example, you are just assigning the result of gotoAndStop (with no scope, so likely you're getting an error in the console)
I think you are looking for something like this:
for (var i=0; i<combobox.length; i++) {
// This is kind of complex, but if you just reference "i" in your callback
// It will always be combobox.length, since it references the value of i
// after the for loop completes variable.
// So just store a new reference on your button to make it easy
combobox[i].index = i*2; // x2 lines up with your code 0,2,4,etc.
// Add a listener to the button
combobox[i].on("click", function(event) {
// Use event.target instead of combobox[i] for the same reason as above.
event.target.gotoAndStop(event.target.index);
}
}
You might have the same problem as your other StackOverflow post where the button is undefined (check the console). There is actually a bug in Animate export right now where children of a clip are not immediately available. To get around this, you can call this.gotoAndStop(0); at the start to force it to update the children.
Is there any way to associate the pac-container div for an autocomplete with the input element that it's attached to? Ideally, I'd like to be able to set the ID of each pac-container to something like "pac-", so that I can delete them if the input goes away, and to make it easier to test the autocompletes with Selenium.
This question's answer has one solution, but it's not remotely sustainable, as Google has a tendency to change various minified property names (For example, what was once Mc is now Rc)
I've also tried modifying the last pac-container div on the page whenever a new autocomplete is added, like so:
function attachAutocomplete(id) {
var input = document.getElementById(id);
var autocomplete = new google.maps.places.Autocomplete(input);
$(".pac-container:last").attr("id", "pac-" + id);
}
This works fine with new autocompletes beyond the ones on the page when it's loaded, but for some reason there's a delay between the first couple of autocompletes being assigned and their pac-containers showing up. (Here's a fiddle that should illustrate this approach and how it fails)
Is there some method I'm missing?
I solved the issue not by associating every .pac-container to its input type form field, but, resetting all .pac-container items, every time an autocomplete input type is focused.
So basically:
1) let's assume we have 3 address input to which google.maps.places.Autocomplete is set up
2) using jquery, let's bind focus event to these 3 inputs
$('#address_1, #address_2, #address_2'.focus(function (e)
{
$('.pac-container').each( function() {
$(this).html( '' );
});
});
3) bind focusout event to these 3 inputs, and select the corresponding selected address
If you are unwilling to make assumptions about property names/structure (totally reasonable), then you are basically left with querying the Autocomplete object itself for the object that has the property className:"pac-container" (or some other identifying feature).
There are libraries that can help with this, such as JSONPath, JSONQuery, and many more that could help you do this. Without adding any additional libraries, though, we can roll our own breadth-first-search through the Autocomplete object's nested hierarchy with the caveat that I am not a Javascript expert, and that while this works right now, I can't promise that I didn't miss an edge case that will break this in the future.
Unfortunately, due to the timing issue you pointed out, you will need to run this at some point after the initial instantiation of the Autocomplete object. The good news is that if you don't immediately need the pac-container object reference, you can defer finding it until you need it, so long as you keep the Autocomplete object reference in scope.
The function ends up looking like this:
var autocompleteDropdown = breadthFirstSearch(autocomplete, function(val) {
return val.className === "pac-container";
}
Where the breadthFirstSearch function is defined as:
function breadthFirstSearch(object, matchesCriteria) {
var queue = [];
var visited = [];
queue.push(object);
visited.push(object);
while (queue.length) {
var val = queue.shift();
if (val && typeof val == "object") {
if (matchesCriteria(val)) {
return val;
}
if (Object.prototype.toString.call(val) == "[object Array]") {
for (var i=0; i<val.length; i++) {
breadthFirstSearchProcessValue(val[i], queue, visited);
}
}
else if (val instanceof Node) {
if (val.hasChildNodes()) {
var children = val.childNodes;
for (var i=0; i<children.length; i++) {
breadthFirstSearchProcessValue(children[i], queue, visited);
}
}
}
else {
for (var property in val) {
breadthFirstSearchProcessValue(val[property], queue, visited);
}
}
}
}
}
function breadthFirstSearchProcessValue(val, queue, visited) {
for (var i=0; i<visited.length; i++) {
if (visited[i] === val) {
return;
}
}
queue.push(val);
visited.push(val);
}
In sum, this is obviously a pretty intensive way of just accessing a property (which Google should make visible natively, grumble grumble). I would recommend trying one of the more "naive" but faster methods from the StackOverflow question you linked, and fall back to this when that method returns null/empty.
I want to get all the <a> tags from an Html page with this JavaScript method:
function() {
var links = document.getElementsByTagName('a');
var i=0;
for (var link in links){i++;}
return i;
}
And i noticed it's won't return the correct number of a tags.
Any idea what can by the problem?
Any idea if there is any other way to get all the href in an Html ?
Edit
I tried this method on this html : http://mp3skull.com/mp3/nirvana.html .
And i get this result:"1". but there are more results in the page.
You don't need a loop here. You can read length property.
function getACount() {
return document.getElementsByTagName('a').length;
}
You don't have to loop over all of them just to count them. HTMLCollections (the type of Object that is returned by getElementsByTagName has a .length property:
$countAnchors = function () {
return document.getElementsByTagName('a').length;
}
Using getElementsByTagName("a") will return all anchor tags, not only the anchor tags that are links. An anchor tags needs a value for the href property to be a link.
You might be better off with the links property, that returns the links in the page:
var linkCount = document.links.length;
Note that this also includes area tags that has a href attribute, but I assume that you don't have any of those.
UPDATE Also gets href
You could do this
var linkCount = document.body.querySelectorAll('a').length,
hrefs= document.body.querySelectorAll('a[href]');
EDIT See the comment below, thanks to ttepasse
I would cast them to an array which you then slice up, etc.
var array = [];
var links = document.getElementsByTagName("a");
for(var i=0; i<links.length; i++) {
array.push(links[i].href);
}
var hrefs = array.length;
The JavaScript code in the question works as such or, rather, could be used to create a working solution (it’s now just an anonymous function declaration). It could be replaced by simpler code that just uses document.getElementsByTagName('a').length as others have remarked.
The problem however is how you use it: where it is placed, and when it is executed. If you run the code at a point where only one a element has been parsed, the result is 1. It needs to be executed when all a elements have been parsed. A simple way to ensure this is to put the code at the end of the document body. I tested by taking a local copy of the page mentioned and added the following right before the other script elements at the end of document body:
<script>
var f = function() {
var links = document.getElementsByTagName('a');
var i=0;
for (var link in links){i++;}
return i;
};
alert('a elements: ' + f());
</script>
The results are not consistent, even on repeated load of the page on the same browser, but this is probably caused by some dynamics on the page, making the number of a elements actually vary.
What you forget here was the length property. I think that code would be:
var count = 0;
for (var i = 0; i < links.length; i++) {
count++;
}
return count;
Or it would be:
for each (var link in links) {
i++;
}
length is used to determine or count the total number of the element which are the result.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for (For Loop)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for_each...in (Foreach Loop)
I'm trying to design a site (possibly to make into a Chrome extension) that will allow me to mark which characters have done which events in an online game I play. I'd love it if I had the option to save the values of the checkboxes between visits (and ideally, character names as well), but I'm not great at coding. Also, I'd strongly prefer it if this information could be saved/cleared/reloaded on command, and not otherwise.
The page in question, please pardon me if my code isn't efficient or attractive. I actually prefer to work with it a bit more bunched up, but I tidied it for your benefits.
There had been a suggestion here that I was trying to work with:
var i, checkboxes = document.querySelectorAll('input[type=checkbox]');
function save() {
for (i = 0; i < checkboxes.length; i++) {
localStorage.setItem(checkboxes[i].value, checkboxes[i].checked);
}
}
function load_() {
for (i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = localStorage.getItem(checkboxes[i].value) === 'true' ? true:false;
}
}
But while it works fine in the test, it won't work when I put it into my document (you'll see the var i at the top still, the rest of the code I'd put above the reset function).
Help?
This line:
var i, checkboxes = document.querySelectorAll('input[type=checkbox]');
is in the head of your document and runs immediately. None of the checkboxes exist at the time that it runs.
I'd suggest putting that script at the end of your body element or placing it in a window.onload = function() {}.
If you inspect the DOM structure in the working jsFiddle link you posted, you'll see it places the script at the end of the body element.
I have two tables on my html page with exact same data but there may be few difference which need to be highlighted.
I and using the below Javascript but seems innerHTML does not work as expected-
function CompareTables()
{
var table1 = document.getElementById("table1")
var table2 = document.getElementById("table2")
for(var i=1; i < table1.rows.length; i++)
{
for(var j=1; j < table2.rows.length; j++){
var tab1Val = table1.rows[i].cells[0].innerHTML;
var tab2Val = table2.rows[j].cells[0].innerHTML;
alert(tab1Val.toUpperCase()+"----"+tab2Val.toUpperCase());
var changes =RowExists(table2,tab1Val);
if(!changes[0])
{
table1.rows[i].style.backgroundColor = "red";
instHasChange = true;
}
}
function RowExists(table,columnValue)
{
var hasColumnOrChange = new Array(2);
hasColumnOrChange[0] = false;
for(var i=1; i < table.rows.length; i++)
{
if(table.rows[i].cells[0].innerHTML == columnValue) /*** why these two does not match**/
{
hasColumnOrChange[0] = true;
}
return hasColumnOrChange;
}
}
Please suggest what wrong here.
(table.rows[i].cells[0].innerHTML == columnValue) never returns true even if all values same.
most browsers have bugs with innerHTML and it is not a recommended property to use. different browsers will do different things, usually messing with whitespace, adding/removing quotes, and/or changing the order of attributes.
long story short, never rely on innerHTML.
In it's place, I would recommend using some of the DOM traversal functions, such as .firstChild and .nodeValue. Notice that these are sensitive of white space, and will have to be tweaked if you have anything else in your TD than just text.
http://jsfiddle.net/tdN5L/
if (table.rows[i].cells[0].firstChild.nodeValue === columnValue)
Another option, as pointed out by Micah's solution, is using a library such as jQuery, which will let you ignore most of these browser issues and DOM manipulation pain. I would not recommend bringing in the overhead of jQuery just for this issue, though.
related:
Firefox innerHTML Bug?
innerHTML bug IE8
innerHTML removes attribute quotes in Internet Explorer
Try and use Jquery's method .text
Depending on the browser(Firefox and Chrome's) innerHTML does not work
JQuery takes care of that issue for you.