I'm developing a Chrome extension, and I'm adding an onmouseover handler to each of the images on a page. When the user mouses over an image, it's URL should be stored in a variable. I know I can easily get the value of the src attribute of the image, but I want the full URL. The src attribute stores the path of the image on the server. For example, when you right click an image in Google Chrome, you get the "Copy Image URL" option, which copies the image's URL to the clipboard.
Is there any way to achieve this? Thanks.
Instead of imageElement.getAttribute("src") or $("img.something").attr("src"), which reads the original markup, use imageElement.src property which will always give you the full URL.
var imgFullURL = document.querySelector('img.something').src;
or:
var imgFullURL = $('img.something')[0].src;
To extract host name, path name etc. - parse the url with URL() constructor, which works in modern browsers or use the legacy method via creating a temporary a node.
You can use window.location to get the page you are currently on and the following will give you the URL parts you need:
window.location.protocol = "http:"
window.location.host = "stackoverflow.com"
window.location.pathname = "/questions/32828681/how-to-get-url-of-an-image-in-javascript"
So, likely, you will need protocol, then "//", then host and finally the image src.
So the TL;DR is this:
(function() {
const imageInfo = new Object();
imageInfo.source = '';
window.addEventListener('mouseover', function (event) {
var currentElement = event.target;
// console.log(event.target);
if (currentElement.tagName === 'IMG') {
// console.log(currentElement.outerHTML + "is a photo");
imageInfo.source = currentElement.src;
// console.log("src is :" + imageInfo.source)
return imageInfo.source;
}
})
})();
See CodePen:
How to find the src URL for a photo by Trevor Rapp on
CodePen
This is how I thought about solving the problem in the most basic steps:
get the function to fire.
get the function to add an event listener that will perform an action on a mouseover event.
make that action know what the mouse is currently over.
figure out if what the mouse is currently over is an image or not.
create logic that will respond if it is.
that action that logic should do is return the source URL.
I will need to store that source URL if I am going to have to return it.
Here are how each of those solutions looked:
get the function to fire.
An IFFE is a great way to get a function to fire without having to worry about polluting the name space.
//skeleton for an IFFE statement
(function() {
})();
get the function to add an event listener that will perform an action on a mouseover event.
An event listener that could fire anywhere would have to be attached to the window or the document.
make that action know what the mouse is currently over.
This part will be combined with part 2. Event listener's first parameter is what type of event you want to listen for -- in this case 'mouseover. So now our code looks like this
(function () {
window.addEventListener('mouseover', function (event) {
//do stuff here
}
})()
figure out if what the mouse is currently over is an image or not.
*To figure out which element the mouse if currently over you would use Event.target.
The MDN definition for that is: *
The target property of the Event interface is a reference to the object onto which the event was dispatched. It is different from Event.currentTarget when the event handler is called during the bubbling or capturing phase of the event. --Event.Target
*So the code would then look like this: *
(function () {
window.addEventListener('mouseover', function (event) {
//get the current element the mouse is over
var currentElement = event.target;
}
})()
create logic that will respond if it is.
This was a little trickier since a photo or IMG can be presented in various ways.
I chose to create a solution for the simplest way, which is assuming that the web developer used the more syntactically correct version of an tag. However, there are many times when they may choose to apply a 'background-image' CSS property to a normal . Other things to consider could be the use of iframes, which can make detecting the attributes of child elements very frustrating since they don't allow bubbling to occur. To tell if an element is an , you can simply use elem.tagName === "IMG" for your logic check. While not included in the above code, if you wanted to check if a div is using the 'background-image', you could use something like element.getAttribute('style').includes('term') and switch out 'term' for something like 'url' or 'jpg' or 'png.' Kind of clunky and hacky, but just a thought. Anyway, the code would then become
(function () {
window.addEventListener('mouseover', function (event) {
//get the current element the mouse is over
var currentElement = event.target;
if (currentElement.tagName === 'IMG') {
//do stuff
}
}
})()
that action that logic should do is return the source URL.
Once you get the logic done and you have properly selected the element, then you can use element.src to get the source URL.
I will need to store that source URL if I am going to have to return it.
You can do this anyway you want, but I played around with instantiating an object since it sounded like the value would need to change often, but you didn't necessarily need to store previous values.
And so the final product could be something like this
(function() {
const imageInfo = new Object();
imageInfo.source = '';
window.addEventListener('mouseover', function (event) {
var currentElement = event.target;
// console.log(event.target);
if (currentElement.tagName === 'IMG') {
// console.log(currentElement.outerHTML + "is a photo");
imageInfo.source = currentElement.src;
// console.log("src is :" + imageInfo.source)
return imageInfo.source;
}
})
})();
Related
I'm working for a friend that needs to manipulate an interactive PDF with the following needs:
there are 2 main "layers" and 2 buttons. When I press button1 only the layer1 is visible, when I press button2, the layer2 is the only visible.
He can upload a javascript for each button with the action that must start on the click, and this is the code I wrote for him.
var layerToHideLabel = "testlayer1";
var layerToShowLabel = "testlayer2";
showField(layerToShowLabel);
hideField(layerToHideLabel);
function showField(fieldName){
var field = null;
field = FindOCG(fieldName);
if(field != null){
setVisibility(field,true);
}
}
function hideField(fieldName){
var field = null;
field = FindOCG(fieldName);
if(field != null){
setVisibility(field,false);
}
}
function setVisibility(field, status){
field.state=status;
}
function FindOCG(cName) {
var aOCGs = this.getOCGs();
for(var i=0; aOCGs && i<aOCGs.length;i++) {
if(aOCGs[i].name == cName) return aOCGs[i];
}
return null;
}
The getOCGs function is a built-in functionality for the document, and of course, when I launch the method I get the following error:
this.getOCGs is not a function
I think the issue is context related, as the script is launched on the click of the button, so maybe this refers to the button and not to the document.
I tried lot of combinations to retrieve the document from the OCG, with no luck. I tried document, $doc, doc, Document, but nothing. I'm afraid it's not even possible, maybe I should try to upload some code at document level, but I don't know if I can add some onchange functionality.
This is the guide I've been following so far:
https://acrobatusers.com/tutorials/create_use_layers
Thanks in advance
Instead of 'this.getOCGs()', try calling 'event.target.doc.getOCGs()'.
In a function defined in one of the folder-level JavaScripts files, 'this' is undefined. Calling functions should pass the Doc to any function at this level that needs it.
The Doc object can often be accessed through event objects, which are created for each event by which a JavaScript is executed:
● For mouse, focus, blur, calculate, validate, and format events, event.target returns the Field object that initiated the event. You can then access the Doc object through the Doc method of the Field object.
Is it possible to bind an onload event to each image, declaring it once? I tried, but can't manage to get it working... (this error is thrown: Uncaught TypeError: Illegal invocation)
HTMLImageElement.prototype.onload = function()
{
console.log(this, "loaded");
};
P.S: I also tried returning this, but doesn't seem to be the issue here... any suggestions / explanations on why my current code isn't working?
You can't set a handler on the prototype, no.
In fact, I'm not aware of any way to get a proactive notification for image load if you haven't hooked load on the specific image element, since load doesn't bubble.
I only two know two ways to implement a general "some image somewhere has loaded" mechanism:
Use a timer loop, which is obviously unsatisfying on multiple levels. But it does function. The actual query (document.getElementsByTagName("img")) isn't that bad as it returns a reference to the continually updated (live) HTMLCollection of img elements, rather than creating a snapshot like querySelectorAll does. Then you can use Array.prototype methods on it (directly, to avoid creating an intermediary array, if you like).
Use a mutation observer to watch for new img elements being added or the src attribute on existing img elements changing, then hook up a load handler if their complete property isn't true. (You have to be careful with race conditions there; the property can be changed by the browser even while your JavaScript code is running, because your JavaScript code is running on a single UI thread, but the browser is multi-threaded.)
You get that error because onload is an accessor property defined in HTMLElement.prototype.
You are supposed to call the accessor only on HTML elements, but you are calling the setter on HTMLImageElement.prototype, which is not an HTML element.
If you want to define that function, use defineProperty instead.
Object.defineProperty(HTMLImageElement.prototype, 'onload', {
configurable: true,
enumerable: true,
value: function () {
console.log(this, "loaded");
}
});
var img = new Image();
img.onload();
Warning: Messing with builtin prototypes is bad practice.
However, that only defines a function. The function won't be magically called when the image is loaded, even if the function is named onload.
That's because even listeners are internal things. It's not that, when an image is loaded, the browser calls the onload method. Instead, when you set the onload method, that function is internally stored as an event listener, and when the image is loaded the browser runs the load event listeners.
Instead, the proper way would be using Web Components to create a custom element:
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
var img = document.createElement('img');
img.src = this.getAttribute('src');
img.addEventListener('load', function() {
console.log('loaded');
});
this.appendChild(img);
};
document.registerElement('my-img', {prototype: proto});
<my-img src="/favicon.ico"></my-img>
There is not much browser support yet, though.
This provides a notification for any image loading, at least in Opera (Presto) and Firefox (haven't tried any other browser). The script tag is placed in the HEAD element so it is executed and the event listener installed before any of the body content is loaded.
document.addEventListener('load', function(e) {
if ((!e.target.tagName) || (e.target.tagName.toLowerCase() != 'img')) return;
// do stuff here
}, true);
Of course, by changing the filtering on tagName it will also serve to respond to the loading of any other element that fires a load event, such as a script tag.
I've written something similar some time ago to check if an image is loaded or not, and if not, show a default image. You can use the same approach.
$(document).ready(function() {
// loop every image in the page
$("img").each(function() {
// naturalWidth is the actual width of the image
// if 0, img is not loaded
// or the loaded img's width is 0. if so, do further check
if (this.naturalWidth === 0) { // not loaded
this.dataset.src = this.src; // keep the original src
this.src = "image404.jpg";
} else {
// loaded
}
});
});
I have a function, which at the end of its task assigns a button to a new id.
function altChecker() {
var doc = document,
out = [],
appButton = doc.getElementById('appButton'),
//re = /click-me/gi,
output = doc.createElement('p');
output.setAttribute('id', 'output');
EventUtility.addHandler(appButton, 'click', function(e) {
//I have not included all the function details to concentrate on the question
appButton.id = 'appButtonNextChecker';
var appButtonNextChecker = doc.getElementById('appButtonNextChecker');
nextChecker(appButtonNextChecker);
});
}
function nextChecker(newBtnName) {
EventUtility.addHandler(newBtnName, 'click', function(e) {
$('#output').innerHTML = "";
console.log('next Checker, button!')
});
}
So basically there is one button in the DOM assigned to appButton ID initially, and then I change it doing:
appButton.id = 'appButtonNextChecker';
when the altChecker function fires...
Then I assign the button to a new variable, and pass in the variable to the next function...
var appButtonNextChecker = doc.getElementById('appButtonNextChecker');
nextChecker(appButtonNextChecker);
While I can see the buttons' ID change in the DOM, and I see the console.log fire in the nextChecker function,
$('#output').innerHTML = ""; //doesn't fire
AND the altChecker function fires as well (again)?! Haven't I severed the connection to the click function when I reassigned the new ID?
Any help would be appreciated!
Javascript doesn't remember that you initially attached the event through it's id. The event is attached to the element itself, not the ID. It's not like CSS that way.
In fact your variables are still holding the same element as well, so there's no need to create a new variable after changing the ID, either. Since you're using jQuery you can just type $(appButton).unbind(); to remove the event handler. You may also want to look into .on() and .off()
The problem is that you're trying to use the innerHTML property in a jQuery's object.
That property belongs to Element, and it will not work in the way you're using it.
You can use the document.getElementById method, and it will work fine:
document.getElementById('output').innerHTML = '';
Or you can use jQuery's html method:
$('#output').html('');
And you can even use the first element of the jQuery's array, and use innerHTML again:
$('#output')[0].innerHTML = '';
It's up to you, but the first option will be faster, for sure.
I'm trying to execute JavaScript functions that are called when a event (for example onClick event) is performed on a web page with JavaScript code. I'm getting the function from the event like this :
var attributval = document.getElementsByTagName("a").getAttribute('onClick');
and I'm trying to execute this object (which a JavaScript function in fact) as a function (suppose we have <a onClick = alert('whatever');> on this example, I tried:
var attributval = document.getElementsByTagName("a").getAttribute('onClick');
attributval() = function(){attributval};
attributval();
but it didn't work.
A DOM attribute is not the same as a JavaScript property (even though they can have the same name onclick). You should use
var attributval = document.getElementsByTagName("a")[0].onclick;
to retrieve a function (or null) from the JS object (as opposed to getAttribute(), which will most likely return a toString() for the property).
Now, attributval() = is illegal syntax, as attributval() is not an l-value (you cannot assign to it).
attributval(); will work but without the second line (which is illegal JavaScript) it will invoke the original A element onclick handler (if one is defined) or throw an exception (if the onclick handler is null).
Skip trying to create a function around the function. Just call it:
var attributval = document.getElementsByTagName("a")[0].onclick;
attributval();
try
var attributval = document.getElementsByTagName("a")[0].getAttribute('onClick');
By using get attribute you are returning a string so your only way is to use eval(onclickString) or var fn = new Function(onClickString); fn();
attributval is simply a string, correct? If you trust this code, execute it with eval(attributval) -- however any reference to this won't work.
What you probably want is to manually trigger an event. jQuery makes that easy.
If you want to do more than a click, then Chris McDonald's answer at Is it possible to trigger a link's (or any element's) click event through JavaScript? seems to fit the bill, although you might need to heed the third comment.
I thought I'd add a short answer on how to work with events using jQuery, since it seems relevant.
// Select the link using it's ID field (assuming it has one)
var myLink = $('a#myLink')
// Add a click event to the link
myLink.on('click', function(e) {
console.log("I've been clicked!");
});
// Trigger the click event manually. This would result in the above
// function being run. Interestingly, this will not cause the browser
// to follow the link like a real click would
myLink.trigger('click');
// Remove the click event (this removes ALL click events)
myLink.off('click');
// Add a click event to the link that only runs once, then removes itself
myLink.one('click', function() {
alert("I'll only bother you once!");
});
// Add a click event that you can identify from other click events.
// This means that you can trigger it or remove it without bothering other
// click events
myLink.on('click.myClick', function() {
alert("This click event has been identified as 'myClick'");
});
// Now you can trigger it without triggering other click events
myLink.trigger('click.myClick');
// And remove it, also with no harm coming to other click events
myLink.off('click.myClick');
Hope this helps
Let's say I have a page that loads pages dynamically. As each page loads into the DOM, events for the elements in that page are added.
If the user loads another page, the elements loaded previously will be removed from the DOM. Naturally, because the elements themselves no longer exist, any events mapped to those elements cease to function.
However, are they also removed? Or are they sitting in the user's memory, taking up space?
Follow-up:
Were a function defined as such:
var event = $('foobar').addEvent('click', function() {
alert(1);
});
One could easily remove the event with event = null (or so I'd assume!)...
but what if the event were not saved to a local variable?
$('foobar').addEvent('click', function() {
alert(1);
});
Thanks!
first of all. what? this makes no sense:
var event = $('foobar').addEvent('click', function() {
alert(1);
});
it does not save the event into a local variable as you seem to think. it saves a reference to the foobar element object into the event variable - most mootools element methods will return this for chaining, which is the element itself and not the result of the method (unless it's a getter like '.getStyle').
it then depends on how you get rid of the element what happens next. first off, element.destroy, found here: https://github.com/mootools/mootools-core/blob/master/Source/Element/Element.js#L728
it will remove the element from the dom and from memory, and empty it in a safe way. it will be reliant on the browser's GC to clean up once it's gone, mootools won't do any spectacular GC for you for the element itself but it does run the special clean function on the child nodes as well: var children = clean(this).getElementsByTagName('*');.
the clean method also gets rid of any event handlers and storage attached to the child elements of the div.
THEN. events added by mootools go into element storage. Element storage is in an object behind a closure which the element proto uses. To test it, we will re-implement it and make it puncturable (a global object called storage) so we can check what happens to the reference after the parent is gone:
http://jsfiddle.net/dimitar/DQ8JU/
(function() {
var storage = this.storage = {}; // make it puncturable
var get = function(uid){
return (storage[uid] || (storage[uid] = {}));
};
Element.implement({
retrieve: function(property, dflt){
var storage = get($uid(this)), prop = storage[property];
if (dflt != null && prop == null) prop = storage[property] = dflt;
return prop != null ? prop : null;
},
store: function(property, value){
var storage = get($uid(this));
storage[property] = value;
return this;
},
eliminate: function(property){
var storage = get($uid(this));
delete storage[property];
return this;
}
});
})();
// read it.
var link = document.getElement("a");
var uid = link.uid; // will reference the mootools unique id for it
// add an event handler
link.addEvent("click", function(e) {
console.log("hi");
this.destroy();
// see what's left in storage for that element.
console.log(storage[uid]);
// storage should be empty.
console.log(storage);
});
link.getFirst().addEvent("mouseenter", function() {
console.log("over");
});
// check to see if it is there via element storage API.
console.log(link.retrieve("events").click);
// check to see if it's there via our puncture
console.log(this.storage[uid]);
// see all events in storage, inc child element:
console.info(this.storage);
what all this proves is, mootools cleans up all you need cleaned. as long as you don't use any inline onclick= stuff on elements you work with, you're going to be fine. Between mootools' garbage collection and the browser, you are well covered. just be aware you can stack up multiple events on a single element if the callbacks are anonymous.
Interesting question... have a read of this: https://developer.mozilla.org/en/DOM/element.addEventListener#Memory_issues
To remove the event listener using jQuery, see http://api.jquery.com/unbind/
I have found that older versions of IE seems to have issues with adding and removing lots of elements with events binded to them. The main cause is circular references that cannot be garbage collected. You can find more information here: http://msdn.microsoft.com/en-us/library/Bb250448
Setting to null will not remove the event, it would just remove the reference to the event. You need to use both element.removeEventListener and element.detachEvent (depending on browser), or if you are using jquery unbind should work.
Also, there are tools available to detect leaks, this one works well (according to coworker): http://blogs.msdn.com/b/gpde/archive/2009/08/03/javascript-memory-leak-detector-v2.aspx