Path and ChildNodes [duplicate] - javascript

When I run event.path[n].id in Firefox, I get this error. It works in other browsers.
event.path undefined

The path property of Event objects is non-standard. The standard equivalent is the composedPath method. But it was new when the question was asked (2016); it's well-established as of this update in January 2023.
So you may want to try composedPath and fall back to path (or just use composedPath now it's established):
// Written in ES5 for compatibility with browsers that weren't obsolete
// yet when the question was posted, although they are now
var path = event.composedPath ? event.composedPath() : event.path;
if (path) {
// You got some path information
} else {
// This browser doesn't supply path information
}
Obviously that won't give you path information if the browser doesn't supply it, but it allows for both the old way and the new, standard way, and so will do its best cross-browser.
Example:
// Written in ES5 for compatibility with browsers that weren't obsolete
// yet when the question was posted, although they are now
document.getElementById("target").addEventListener("click", function (e) {
// Just for demonstration purposes
if (e.path) {
if (e.composedPath) {
console.log("Supports `path` and `composedPath`");
} else {
console.log("Supports `path` but not `composedPath`");
}
} else if (e.composedPath) {
console.log("Supports `composedPath` (but not `path`)");
} else {
console.log("Supports neither `path` nor `composedPath`");
}
// Per the above, get the path if we can, first using the standard
// method if possible, falling back to non-standard `path`
var path = event.composedPath ? event.composedPath() : event.path;
// Show it if we got it
if (path) {
console.log("Path (" + path.length + ")");
Array.prototype.forEach.call(path, function(entry) {
console.log(entry === window ? "window" : entry.nodeName);
});
}
});
.as-console-wrapper {
max-height: 100% !important;
}
<div id="target">Click me</div>
According to MDN, all major browsers support composedPath as of January 2023. Chrome (and other Chromium-based browsers) supported both path (it was a Chrome innovation) and composedPath until v109 when path was removed. (The obsolete browsers IE11 and Legacy Edge [Microsoft Edge prior to v79 when it became a Chromium-based browser] didn't support either of them.)
If you ran into a browser that doesn't support either of them, I don't think you can get the path information as of when the event was triggered. You can get the path via e.target.parentNode and each subsequent parentNode, which is usually the same, but of course the point of composedPath is that it's not always the same (if something modifies the DOM after the event was triggered but before your handler got called).

You can create your own composedPath function if it's not implemented in the browser:
function composedPath (el) {
var path = [];
while (el) {
path.push(el);
if (el.tagName === 'HTML') {
path.push(document);
path.push(window);
return path;
}
el = el.parentElement;
}
}
The returned value is equivalent to event.path of Google Chrome.
Example:
document.getElementById('target').addEventListener('click', function(event) {
var path = event.path || (event.composedPath && event.composedPath()) || composedPath(event.target);
});

This function serves as a polyfill for Event.composedPath() or Event.Path
function eventPath(evt) {
var path = (evt.composedPath && evt.composedPath()) || evt.path,
target = evt.target;
if (path != null) {
// Safari doesn't include Window, but it should.
return (path.indexOf(window) < 0) ? path.concat(window) : path;
}
if (target === window) {
return [window];
}
function getParents(node, memo) {
memo = memo || [];
var parentNode = node.parentNode;
if (!parentNode) {
return memo;
}
else {
return getParents(parentNode, memo.concat(parentNode));
}
}
return [target].concat(getParents(target), window);
}

Use composePath() and use a polyfill for IE:
https://gist.github.com/rockinghelvetica/00b9f7b5c97a16d3de75ba99192ff05c
include above file or paste code:
// Event.composedPath
(function(e, d, w) {
if(!e.composedPath) {
e.composedPath = function() {
if (this.path) {
return this.path;
}
var target = this.target;
this.path = [];
while (target.parentNode !== null) {
this.path.push(target);
target = target.parentNode;
}
this.path.push(d, w);
return this.path;
}
}
})(Event.prototype, document, window);
and then use:
var path = event.path || (event.composedPath && event.composedPath());

I had the same issue. I need the name of the HTML element. In Chrome I get the name with path. In Firefox I tried with composedPath, but it returns a different value.
For solving my problem, I used e.target.nodeName. With target function you can retrieve the HTML element in Chrome, Firefox and Safari.
This is my function in Vue.js:
selectFile(e) {
this.nodeNameClicked = e.target.nodeName
if (this.nodeNameClicked === 'FORM' || this.nodeNameClicked === 'INPUT' || this.nodeNameClicked === 'SPAN') {
this.$refs.singlefile.click()
}
}

Related

How to support .closest in IE11

I'm using svelte+rollup+rollup-plugin-polyfill
SCRIPT438: Object doesn't support property or method 'closest'
still occurs even though I include
polyfill(['#webcomponents/webcomponentsjs','element-closest']),
in my rollup.js.
the call occurs in this code:
function onDocumentClick (e) {
if (!e.target.closest('.autocomplete')) close();
}
why the polyfill does not exist? and though, how to correctly use it? I'm thinking of just replacing .closest with one IE11 supports.
Use Polyfill
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.msMatchesSelector ||
Element.prototype.webkitMatchesSelector;
}
if (!Element.prototype.closest) {
Element.prototype.closest = function(s) {
var el = this;
do {
if (Element.prototype.matches.call(el, s)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
Closest in detail and Polyfill

Check if URL() suported

How to check if the class URL() is supported in the current browser?
Based on the docs it's not supported in IE
I want to use it to get a domain out of a string like that:
var text = ...
var domain = new URL(text).host;
You could do a feature check with
if ("URL" in window)
However this won't validate whether the functionality is correct. you might want to consider adding a polyfill.
Note that IE/Edge seem to really make built in constructors as objects, meaning typeof Ctor === "object" is true in those browsers. So if they add support in Edge for it, the checks for "function" will be invalid.
You can't do this with complete reliability. You could come close by testing if URL exists and is a function:
if (typeof URL !== "function") {
// It is not supported
}
You could then perform further tests to see how much it looks like a duck:
function URL_is_supported() {
if (typeof URL !== "function") {
return false;
}
// Beware: You're calling the function here, so it it isn't the expected URL function it might have undesired side effects
var url = new URL("http://example.com");
if (url.hostname !== "example.com") {
return false;
}
// and whatever other tests you wanted to do before you're convinced
return true;
}
to check if anything is supported on a root level (window) just try to access it on a condition level. E.G.
(window.URL) OR JUST (typeof URL === "function")
var a = window.URL ? window.URL(text).host : ....
also remembering that the fact of window has a "URL" property doesn't mean that it is a class/function and that it is what you expect
so the best approach would be check using the typeof version which at least guarantee that it is a function
The closest you can get to check if URL is truly supported is checking its prototype and static functions
function isURLSupported(){
if(typeof window.URL!=="function" || typeof URL.createObjectURL !== "function" || typeof URL.revokeObjectURL !== "function"){
return false;
}
var oURLprototype= ["host","hostname","href","origin","password","pathname","port","protocol","search","searchParams","username"];
for(var i=0;i<oURLprototype.length;i++){
if(URL.prototype.hasOwnProperty(oURLprototype[i])===undefined){
return false;
}
}
return true;
}
For those who will support implementations that have classes as type Object not Function
--Is function credit to https://stackoverflow.com/a/7356528/835753
function isFunction(functionToCheck) {
var getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
}
function isURLSupported(){
if(!isFunction(window.URL) || !isFunction(URL.createObjectURL) || !isFunction(URL.revokeObjectURL)){
return false;
}
var oURLprototype= ["host","hostname","href","origin","password","pathname","port","protocol","search","searchParams","username"];
for(var i=0;i<oURLprototype.length;i++){
if(URL.prototype.hasOwnProperty(oURLprototype[i])===undefined){
return false;
}
}
return true;
}

event.path is undefined running in Firefox

When I run event.path[n].id in Firefox, I get this error. It works in other browsers.
event.path undefined
The path property of Event objects is non-standard. The standard equivalent is the composedPath method. But it was new when the question was asked (2016); it's well-established as of this update in January 2023.
So you may want to try composedPath and fall back to path (or just use composedPath now it's established):
// Written in ES5 for compatibility with browsers that weren't obsolete
// yet when the question was posted, although they are now
var path = event.composedPath ? event.composedPath() : event.path;
if (path) {
// You got some path information
} else {
// This browser doesn't supply path information
}
Obviously that won't give you path information if the browser doesn't supply it, but it allows for both the old way and the new, standard way, and so will do its best cross-browser.
Example:
// Written in ES5 for compatibility with browsers that weren't obsolete
// yet when the question was posted, although they are now
document.getElementById("target").addEventListener("click", function (e) {
// Just for demonstration purposes
if (e.path) {
if (e.composedPath) {
console.log("Supports `path` and `composedPath`");
} else {
console.log("Supports `path` but not `composedPath`");
}
} else if (e.composedPath) {
console.log("Supports `composedPath` (but not `path`)");
} else {
console.log("Supports neither `path` nor `composedPath`");
}
// Per the above, get the path if we can, first using the standard
// method if possible, falling back to non-standard `path`
var path = event.composedPath ? event.composedPath() : event.path;
// Show it if we got it
if (path) {
console.log("Path (" + path.length + ")");
Array.prototype.forEach.call(path, function(entry) {
console.log(entry === window ? "window" : entry.nodeName);
});
}
});
.as-console-wrapper {
max-height: 100% !important;
}
<div id="target">Click me</div>
According to MDN, all major browsers support composedPath as of January 2023. Chrome (and other Chromium-based browsers) supported both path (it was a Chrome innovation) and composedPath until v109 when path was removed. (The obsolete browsers IE11 and Legacy Edge [Microsoft Edge prior to v79 when it became a Chromium-based browser] didn't support either of them.)
If you ran into a browser that doesn't support either of them, I don't think you can get the path information as of when the event was triggered. You can get the path via e.target.parentNode and each subsequent parentNode, which is usually the same, but of course the point of composedPath is that it's not always the same (if something modifies the DOM after the event was triggered but before your handler got called).
You can create your own composedPath function if it's not implemented in the browser:
function composedPath (el) {
var path = [];
while (el) {
path.push(el);
if (el.tagName === 'HTML') {
path.push(document);
path.push(window);
return path;
}
el = el.parentElement;
}
}
The returned value is equivalent to event.path of Google Chrome.
Example:
document.getElementById('target').addEventListener('click', function(event) {
var path = event.path || (event.composedPath && event.composedPath()) || composedPath(event.target);
});
This function serves as a polyfill for Event.composedPath() or Event.Path
function eventPath(evt) {
var path = (evt.composedPath && evt.composedPath()) || evt.path,
target = evt.target;
if (path != null) {
// Safari doesn't include Window, but it should.
return (path.indexOf(window) < 0) ? path.concat(window) : path;
}
if (target === window) {
return [window];
}
function getParents(node, memo) {
memo = memo || [];
var parentNode = node.parentNode;
if (!parentNode) {
return memo;
}
else {
return getParents(parentNode, memo.concat(parentNode));
}
}
return [target].concat(getParents(target), window);
}
Use composePath() and use a polyfill for IE:
https://gist.github.com/rockinghelvetica/00b9f7b5c97a16d3de75ba99192ff05c
include above file or paste code:
// Event.composedPath
(function(e, d, w) {
if(!e.composedPath) {
e.composedPath = function() {
if (this.path) {
return this.path;
}
var target = this.target;
this.path = [];
while (target.parentNode !== null) {
this.path.push(target);
target = target.parentNode;
}
this.path.push(d, w);
return this.path;
}
}
})(Event.prototype, document, window);
and then use:
var path = event.path || (event.composedPath && event.composedPath());
I had the same issue. I need the name of the HTML element. In Chrome I get the name with path. In Firefox I tried with composedPath, but it returns a different value.
For solving my problem, I used e.target.nodeName. With target function you can retrieve the HTML element in Chrome, Firefox and Safari.
This is my function in Vue.js:
selectFile(e) {
this.nodeNameClicked = e.target.nodeName
if (this.nodeNameClicked === 'FORM' || this.nodeNameClicked === 'INPUT' || this.nodeNameClicked === 'SPAN') {
this.$refs.singlefile.click()
}
}

console.log.apply not working in IE9

Looks like I've re-invented the wheel, but somehow this isn't working in Internet Explorer 9, but does in IE6.
function debug()
if(!window.console) {
window.console = { log: function() { /* do something */ } };
}
console.log.apply(console, arguments);
}
Related:
Apply() question for javascript
F12 Debugger tells me that this "object" (console.log) does not support method 'apply'.
Is it not even recognized as a function?
Any other pointers or ideas?
The second part of an answer I gave recently answers this question too. I don't consider this a duplicate of that one so, for convenience, I'll paste it here:
The console object is not part of any standard and is an extension to the Document Object Model. Like other DOM objects, it is considered a host object and is not required to inherit from Object, nor its methods from Function, like native ECMAScript functions and objects do. This is the reason apply and call are undefined on those methods. In IE 9, most DOM objects were improved to inherit from native ECMAScript types. As the developer tools are considered an extension to IE (albeit, a built-in extension), they clearly didn't receive the same improvements as the rest of the DOM.
For what it's worth, you can still use some Function.prototype methods on console methods with a little bind() magic:
var log = Function.prototype.bind.call(console.log, console);
log.apply(console, ["this", "is", "a", "test"]);
//-> "thisisatest"
So you could fix up all the console methods for IE 9 in the same manner:
if (Function.prototype.bind && window.console && typeof console.log == "object"){
[
"log","info","warn","error","assert","dir","clear","profile","profileEnd"
].forEach(function (method) {
console[method] = this.bind(console[method], console);
}, Function.prototype.call);
}
This replaces the "host" functions with native functions that call the "host" functions. You can get it working in Internet Explorer 8 by including the compatibility implementations for Function.prototype.bind and Array.prototype.forEach in your code, or rewriting the above snippet to incorporate the techniques used by those methods.
See also
console.log typeof is "object" instead of "function" - Microsoft Connect (Live account required)
There is also Paul Irish's way of doing it. It is simpler than some of the answers above, but makes log always output an array (even if only one argument was passed in):
// usage: log('inside coolFunc',this,arguments);
// http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
window.log = function(){
log.history = log.history || []; // store logs to an array for reference
log.history.push(arguments);
if(this.console){
console.log( Array.prototype.slice.call(arguments) );
}
};
Several of IE's host object functions aren't really JavaScript functions and so don't have apply or call. (alert, for example.)
So you'll have to do it the hard way:
function debug()
var index;
if(!window.console) {
window.console = { log: function() { /* do something */ } };
}
for (index = 0; index < arguments.length; ++index) {
console.log(arguments[index]);
}
}
I came across the same IE trouble and made a routine for it.
It is not as fancy as all the above implementations, but it works in ALL modern browsers.
I tested it with Firefox (Firebug), IE 7,8,9 Chrome and Opera.
It makes use of the evil EVAL, but you will only want to debug in development.
Afterwards you will replace the code with debug = function () {};
So here it is.
Regards, Hans
(function(ns) {
var msgs = [];
// IE compatiblity
function argtoarr (args,from) {
var a = [];
for (var i = from || 0; i<args.length; i++) a.push(args[i]);
return a;
}
function log(arg) {
var params = "", format = "", type , output,
types = {
"number" : "%d",
"object" : "{%o}",
"array" : "[%o]"
};
for (var i=0; i<arg.length; i++) {
params += (params ? "," : "")+"arg["+i+"]";
type = types[toType(arg[i])] || "%s";
if (type === "%d" && parseFloat(arg[i]) == parseInt(arg[i], 10)) type = "%f";
format += (format ? "," : "")+type;
}
// opera does not support string format, so leave it out
output = "console.log("+(window.opera ? "" : "'%f',".replace("%f",format))+"%p);".replace("%p",params);
eval(output);
}
ns.debug = function () {
msgs.push(argtoarr(arguments));
if (console !== undefined) while (msgs.length>0) log(msgs.shift());
}
})(window);
Oops forgot my toType function, here it is.
function toType(obj) {
if (obj === undefined) return "undefined";
if (obj === null) return "null";
var m = obj.constructor;
if (!m) return "window";
m = m.toString().match(/(?:function|\[object)\s*([a-z|A-Z|0-9|_|#]*)/);
return m[1].toLowerCase();
}
Ok, it works when you write it this way:
function debug()
if(!window.console) {
window.console = {};
console.log = function() { /* do something */ };
}
console.log.apply(console, arguments);
}
Odd behaviour... but if you write it this way 'console.log' gets recognized as a function.
The reason I came to this question was that I as trying to 'spicy' the console.log function for a specific module, so I'd have more localized and insightful debug info by playing a bit with the arguments, IE 9 broke it.
#Andy E answer is great and helped me with lots of insight about apply. I just don't take the same approach to support IE9, so my solution is running the console only on "modern browsers" (being that modern means whatever browsers that behave the way I expect =)
var C = function() {
var args = Array.prototype.slice.call(arguments);
var console = window.console;
args[0] = "Module X: "+args[0];
if( typeof console == 'object' && console.log && console.log.apply ){
console.log.apply(console, args);
}
};
Try:
function log(type) {
if (typeof console !== 'undefined' && typeof console.log !== 'undefined' &&
console[type] && Function.prototype.bind) {
var log = Function.prototype.bind.call(console[type], console);
log.apply(console, Array.prototype.slice.call(arguments, 1));
}
}
log('info', 'test', 'pass');
log('error', 'test', 'fail');
Works for log, debug, info, warn, error, group or groupEnd.

How do you check if a JavaScript Object is a DOM Object?

I'm trying to get:
document.createElement('div') //=> true
{tagName: 'foobar something'} //=> false
In my own scripts, I used to just use this since I never needed tagName as a property:
if (!object.tagName) throw ...;
So for the second object, I came up with the following as a quick solution -- which mostly works. ;)
The problem is, it depends on browsers enforcing read-only properties, which not all do.
function isDOM(obj) {
var tag = obj.tagName;
try {
obj.tagName = ''; // Read-only for DOM, should throw exception
obj.tagName = tag; // Restore for normal objects
return false;
} catch (e) {
return true;
}
}
Is there a good substitute?
This might be of interest:
function isElement(obj) {
try {
//Using W3 DOM2 (works for FF, Opera and Chrome)
return obj instanceof HTMLElement;
}
catch(e){
//Browsers not supporting W3 DOM2 don't have HTMLElement and
//an exception is thrown and we end up here. Testing some
//properties that all elements have (works on IE7)
return (typeof obj==="object") &&
(obj.nodeType===1) && (typeof obj.style === "object") &&
(typeof obj.ownerDocument ==="object");
}
}
It's part of the DOM, Level2.
Update 2: This is how I implemented it in my own library:
(the previous code didn't work in Chrome, because Node and HTMLElement are functions instead of the expected object. This code is tested in FF3, IE7, Chrome 1 and Opera 9).
//Returns true if it is a DOM node
function isNode(o){
return (
typeof Node === "object" ? o instanceof Node :
o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string"
);
}
//Returns true if it is a DOM element
function isElement(o){
return (
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
);
}
The accepted answer is a bit complicated, and does not detect all types of HTML elements. For example, SVG elements are not supported. In contrast, this answer works for HTML as well as SVG, etc.
See it in action here: https://jsfiddle.net/eLuhbu6r/
function isElement(element) {
return element instanceof Element || element instanceof HTMLDocument;
}
Cherry on top: the above code is IE8 compatible.
No need for hacks, you can just ask if an element is an instance of the DOM Element:
const isDOM = el => el instanceof Element
All solutions above and below (my solution including) suffer from possibility of being incorrect, especially on IE — it is quite possible to (re)define some objects/methods/properties to mimic a DOM node rendering the test invalid.
So usually I use the duck-typing-style testing: I test specifically for things I use. For example, if I want to clone a node I test it like this:
if(typeof node == "object" && "nodeType" in node &&
node.nodeType === 1 && node.cloneNode){
// most probably this is a DOM node, we can clone it safely
clonedNode = node.cloneNode(false);
}
Basically it is a little sanity check + the direct test for a method (or a property) I am planning to use.
Incidentally the test above is a good test for DOM nodes on all browsers. But if you want to be on the safe side always check the presence of methods and properties and verify their types.
EDIT: IE uses ActiveX objects to represent nodes, so their properties do not behave as true JavaScript object, for example:
console.log(typeof node.cloneNode); // object
console.log(node.cloneNode instanceof Function); // false
while it should return "function" and true respectively. The only way to test methods is to see if the are defined.
A simple way to test if a variable is a DOM element (verbose, but more traditional syntax :-)
function isDomEntity(entity) {
if(typeof entity === 'object' && entity.nodeType !== undefined){
return true;
}
else{
return false;
}
}
Or as HTMLGuy suggested (short and clean syntax):
const isDomEntity = entity =>
typeof entity === 'object' && entity.nodeType !== undefined
You could try appending it to a real DOM node...
function isDom(obj)
{
var elm = document.createElement('div');
try
{
elm.appendChild(obj);
}
catch (e)
{
return false;
}
return true;
}
How about Lo-Dash's _.isElement?
$ npm install lodash.iselement
And in the code:
var isElement = require("lodash.iselement");
isElement(document.body);
This is from the lovely JavaScript library MooTools:
if (obj.nodeName){
switch (obj.nodeType){
case 1: return 'element';
case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
}
}
The using the root detection found here, we can determine whether e.g. alert is a member of the object's root, which is then likely to be a window:
function isInAnyDOM(o) {
return (o !== null) && !!(o.ownerDocument && (o.ownerDocument.defaultView || o.ownerDocument.parentWindow).alert); // true|false
}
To determine whether the object is the current window is even simpler:
function isInCurrentDOM(o) {
return (o !== null) && !!o.ownerDocument && (window === (o.ownerDocument.defaultView || o.ownerDocument.parentWindow)); // true|false
}
This seems to be less expensive than the try/catch solution in the opening thread.
Don P
old thread, but here's an updated possibility for ie8 and ff3.5 users:
function isHTMLElement(o) {
return (o.constructor.toString().search(/\object HTML.+Element/) > -1);
}
var IsPlainObject = function ( obj ) { return obj instanceof Object && ! ( obj instanceof Function || obj.toString( ) !== '[object Object]' || obj.constructor.name !== 'Object' ); },
IsDOMObject = function ( obj ) { return obj instanceof EventTarget; },
IsDOMElement = function ( obj ) { return obj instanceof Node; },
IsListObject = function ( obj ) { return obj instanceof Array || obj instanceof NodeList; },
// In fact I am more likely t use these inline, but sometimes it is good to have these shortcuts for setup code
I think prototyping is not a very good solution but maybe this is the fastest one:
Define this code block;
Element.prototype.isDomElement = true;
HTMLElement.prototype.isDomElement = true;
than check your objects isDomElement property:
if(a.isDomElement){}
I hope this helps.
This could be helpful: isDOM
//-----------------------------------
// Determines if the #obj parameter is a DOM element
function isDOM (obj) {
// DOM, Level2
if ("HTMLElement" in window) {
return (obj && obj instanceof HTMLElement);
}
// Older browsers
return !!(obj && typeof obj === "object" && obj.nodeType === 1 && obj.nodeName);
}
In the code above, we use the double negation operator to get the boolean value of the object passed as argument, this way we ensure that each expression evaluated in the conditional statement be boolean, taking advantage of the Short-Circuit Evaluation, thus the function returns true or false
According to mdn
Element is the most general base class from which all objects in a Document inherit. It only has methods and properties common to all kinds of elements.
We can implement isElement by prototype. Here is my advice:
/**
* #description detect if obj is an element
* #param {*} obj
* #returns {Boolean}
* #example
* see below
*/
function isElement(obj) {
if (typeof obj !== 'object') {
return false
}
let prototypeStr, prototype
do {
prototype = Object.getPrototypeOf(obj)
// to work in iframe
prototypeStr = Object.prototype.toString.call(prototype)
// '[object Document]' is used to detect document
if (
prototypeStr === '[object Element]' ||
prototypeStr === '[object Document]'
) {
return true
}
obj = prototype
// null is the terminal of object
} while (prototype !== null)
return false
}
console.log(isElement(document)) // true
console.log(isElement(document.documentElement)) // true
console.log(isElement(document.body)) // true
console.log(isElement(document.getElementsByTagName('svg')[0])) // true or false, decided by whether there is svg element
console.log(isElement(document.getElementsByTagName('svg'))) // false
console.log(isElement(document.createDocumentFragment())) // false
I think that what you have to do is make a thorough check of some properties that will always be in a dom element, but their combination won't most likely be in another object, like so:
var isDom = function (inp) {
return inp && inp.tagName && inp.nodeName && inp.ownerDocument && inp.removeAttribute;
};
In Firefox, you can use the instanceof Node. That Node is defined in DOM1.
But that is not that easy in IE.
"instanceof ActiveXObject" only can tell that it is a native object.
"typeof document.body.appendChild=='object'" tell that it may be DOM object, but also can be something else have same function.
You can only ensure it is DOM element by using DOM function and catch if any exception. However, it may have side effect (e.g. change object internal state/performance/memory leak)
Perhaps this is an alternative? Tested in Opera 11, FireFox 6, Internet Explorer 8, Safari 5 and Google Chrome 16.
function isDOMNode(v) {
if ( v===null ) return false;
if ( typeof v!=='object' ) return false;
if ( !('nodeName' in v) ) return false;
var nn = v.nodeName;
try {
// DOM node property nodeName is readonly.
// Most browsers throws an error...
v.nodeName = 'is readonly?';
} catch (e) {
// ... indicating v is a DOM node ...
return true;
}
// ...but others silently ignore the attempt to set the nodeName.
if ( v.nodeName===nn ) return true;
// Property nodeName set (and reset) - v is not a DOM node.
v.nodeName = nn;
return false;
}
Function won't be fooled by e.g. this
isDOMNode( {'nodeName':'fake'} ); // returns false
You can see if the object or node in question returns a string type.
typeof (array).innerHTML === "string" => false
typeof (object).innerHTML === "string" => false
typeof (number).innerHTML === "string" => false
typeof (text).innerHTML === "string" => false
//any DOM element will test as true
typeof (HTML object).innerHTML === "string" => true
typeof (document.createElement('anything')).innerHTML === "string" => true
This is what I figured out:
var isHTMLElement = (function () {
if ("HTMLElement" in window) {
// Voilà. Quick and easy. And reliable.
return function (el) {return el instanceof HTMLElement;};
} else if ((document.createElement("a")).constructor) {
// We can access an element's constructor. So, this is not IE7
var ElementConstructors = {}, nodeName;
return function (el) {
return el && typeof el.nodeName === "string" &&
(el instanceof ((nodeName = el.nodeName.toLowerCase()) in ElementConstructors
? ElementConstructors[nodeName]
: (ElementConstructors[nodeName] = (document.createElement(nodeName)).constructor)))
}
} else {
// Not that reliable, but we don't seem to have another choice. Probably IE7
return function (el) {
return typeof el === "object" && el.nodeType === 1 && typeof el.nodeName === "string";
}
}
})();
To improve performance I created a self-invoking function that tests the browser's capabilities only once and assigns the appropriate function accordingly.
The first test should work in most modern browsers and was already discussed here. It just tests if the element is an instance of HTMLElement. Very straightforward.
The second one is the most interesting one. This is its core-functionality:
return el instanceof (document.createElement(el.nodeName)).constructor
It tests whether el is an instance of the construcor it pretends to be. To do that, we need access to an element's contructor. That's why we're testing this in the if-Statement. IE7 for example fails this, because (document.createElement("a")).constructor is undefined in IE7.
The problem with this approach is that document.createElement is really not the fastest function and could easily slow down your application if you're testing a lot of elements with it. To solve this, I decided to cache the constructors. The object ElementConstructors has nodeNames as keys with its corresponding constructors as values. If a constructor is already cached, it uses it from the cache, otherwise it creates the Element, caches its constructor for future access and then tests against it.
The third test is the unpleasant fallback. It tests whether el is an object, has a nodeType property set to 1 and a string as nodeName. This is not very reliable of course, yet the vast majority of users shouldn't even fall back so far.
This is the most reliable approach I came up with while still keeping performance as high as possible.
Test if obj inherits from Node.
if (obj instanceof Node){
// obj is a DOM Object
}
Node is a basic Interface from which HTMLElement and Text inherit.
For the ones using Angular:
angular.isElement
https://docs.angularjs.org/api/ng/function/angular.isElement
This will work for almost any browser. (No distinction between elements and nodes here)
function dom_element_check(element){
if (typeof element.nodeType !== 'undefined'){
return true;
}
return false;
}
differentiate a raw js object from a HTMLElement
function isDOM (x){
return /HTML/.test( {}.toString.call(x) );
}
use:
isDOM( {a:1} ) // false
isDOM( document.body ) // true
// OR
Object.defineProperty(Object.prototype, "is",
{
value: function (x) {
return {}.toString.call(this).indexOf(x) >= 0;
}
});
use:
o={}; o.is("HTML") // false
o=document.body; o.is("HTML") // true
here's a trick using jQuery
var obj = {};
var element = document.getElementById('myId'); // or simply $("#myId")
$(obj).html() == undefined // true
$(element).html() == undefined // false
so putting it in a function:
function isElement(obj){
return (typeOf obj === 'object' && !($(obj).html() == undefined));
}
Not to hammer on this or anything but for ES5-compliant browsers why not just:
function isDOM(e) {
return (/HTML(?:.*)Element/).test(Object.prototype.toString.call(e).slice(8, -1));
}
Won't work on TextNodes and not sure about Shadow DOM or DocumentFragments etc. but will work on almost all HTML tag elements.
A absolute right method, check target is a real html element
primary code:
(function (scope) {
if (!scope.window) {//May not run in window scope
return;
}
var HTMLElement = window.HTMLElement || window.Element|| function() {};
var tempDiv = document.createElement("div");
var isChildOf = function(target, parent) {
if (!target) {
return false;
}
if (parent == null) {
parent = document.body;
}
if (target === parent) {
return true;
}
var newParent = target.parentNode || target.parentElement;
if (!newParent) {
return false;
}
return isChildOf(newParent, parent);
}
/**
* The dom helper
*/
var Dom = {
/**
* Detect if target element is child element of parent
* #param {} target The target html node
* #param {} parent The the parent to check
* #returns {}
*/
IsChildOf: function (target, parent) {
return isChildOf(target, parent);
},
/**
* Detect target is html element
* #param {} target The target to check
* #returns {} True if target is html node
*/
IsHtmlElement: function (target) {
if (!X.Dom.IsHtmlNode(target)) {
return false;
}
return target.nodeType === 1;
},
/**
* Detect target is html node
* #param {} target The target to check
* #returns {} True if target is html node
*/
IsHtmlNode:function(target) {
if (target instanceof HTMLElement) {
return true;
}
if (target != null) {
if (isChildOf(target, document.documentElement)) {
return true;
}
try {
tempDiv.appendChild(target.cloneNode(false));
if (tempDiv.childNodes.length > 0) {
tempDiv.innerHTML = "";
return true;
}
} catch (e) {
}
}
return false;
}
};
X.Dom = Dom;
})(this);
Each DOMElement.constructor returns function HTML...Element() or [Object HTML...Element] so...
function isDOM(getElem){
if(getElem===null||typeof getElem==="undefined") return false;
var c = getElem.constructor.toString();
var html = c.search("HTML")!==-1;
var element = c.search("Element")!==-1;
return html&&element;
}
I have a special way to do this that has not yet been mentioned in the answers.
My solution is based on four tests. If the object passes all four, then it is an element:
The object is not null.
The object has a method called "appendChild".
The method "appendChild" was inherited from the Node class, and isn't just an imposter method (a user-created property with an identical name).
The object is of Node Type 1 (Element). Objects that inherit methods from the Node class are always Nodes, but not necessarily Elements.
Q: How do I check if a given property is inherited and isn't just an imposter?
A: A simple test to see if a method was truly inherited from Node is to first verify that the property has a type of "object" or "function". Next, convert the property to a string and check if the result contains the text "[Native Code]". If the result looks something like this:
function appendChild(){
[Native Code]
}
Then the method has been inherited from the Node object. See https://davidwalsh.name/detect-native-function
And finally, bringing all the tests together, the solution is:
function ObjectIsElement(obj) {
var IsElem = true;
if (obj == null) {
IsElem = false;
} else if (typeof(obj.appendChild) != "object" && typeof(obj.appendChild) != "function") {
//IE8 and below returns "object" when getting the type of a function, IE9+ returns "function"
IsElem = false;
} else if ((obj.appendChild + '').replace(/[\r\n\t\b\f\v\xC2\xA0\x00-\x1F\x7F-\x9F ]/ig, '').search(/\{\[NativeCode]}$/i) == -1) {
IsElem = false;
} else if (obj.nodeType != 1) {
IsElem = false;
}
return IsElem;
}
(element instanceof $ && element.get(0) instanceof Element) || element instanceof Element
This will check for even if it is a jQuery or JavaScript DOM element
The only way to guarentee you're checking an actual HTMLEement, and not just an object with the same properties as an HTML Element, is to determine if it inherits from Node, since its impossible to make a new Node() in JavaScript. (unless the native Node function is overwritten, but then you're out of luck). So:
function isHTML(obj) {
return obj instanceof Node;
}
console.log(
isHTML(test),
isHTML(ok),
isHTML(p),
isHTML(o),
isHTML({
constructor: {
name: "HTML"
}
}),
isHTML({
__proto__: {
__proto__: {
__proto__: {
__proto__: {
constructor: {
constructor: {
name: "Function"
},
name: "Node"
}
}
}
}
}
}),
)
<div id=test></div>
<blockquote id="ok"></blockquote>
<p id=p></p>
<br id=o>
<!--think of anything else you want--!>

Categories

Resources