Adding Aliases for HTMLElement Methods, why doesn't this work? - javascript

I'm aware this isn't "best practice" but I'm curious why this code doesn't work. For reference, I'm using Chrome 63 at the moment.
Typing getElementById everywhere is obnoxious. Suppose I have the following code:
var D = document;
D.getID = D.getElementById;
D.getTags = D.getElementsByTags;
HTMLElement.prototype.getID = HTMLElement.prototype.getElementById;
HTMLElement.prototype.getTags = HTMLElement.prototype.getElementsByTags;
If I try to use these new names, e.g. D.getID("foo").getID("bar"), then I get the error D.getID("foo").getID("bar") is not a function. What's the deal? (To be sure, D.getID("foo") works just fine)

Since IDs are supposed to be unique in a document, there's no HTMLElement.prototype.getElementById() method. The only object that has this method is document.
You can add this method to HTMLElement by assigning the function that's bound to document:
HTMLElement.prototype.getID = document.getElementById.bind(document);
Note that even though you can use this to call the method from a particular element, it will search the entire document, not just within that element. You could use querySelector to limit it:
HTMLElement.prototype.getID = function(id) {
return this.querySelector('#' + id);
}
Your code for getTags just uses the wrong name for the long method. It should be:
HTMLElement.prototype.getTags = HTMLElement.prototype.getElementsByTagName
There's no need for the assignments to D.getID and D.getTags, since Document inherits from HTMLElement.

Related

Using Symbol as DOM / HTMLElement attributes

In general custom HTML element attributes are discouraged but in some cases one have the choice between either having a collection of nodes that one saves and traverse for comparison or setting a property. As for the former that is often enough not a good choice.
Usually I would use dataset for this, but what if that is not a good option due to the script being used on any site where one have no control of what that site itself does?
Say one want to tag all <video> elements with a custom status property could one use Symbol() attached directly to the element? I have tested using Symbol() on dataset but it seems to be lost sometimes. As in: I can set it, log it, next call it is sometimes there, sometimes not. (Which also makes a use-case harder to make) Not sure why. Might have something to do with it being a media element? Symbol() is perhaps not valid as "dataset property name"?
Question the is if it is OK to set it directly on the element?
const symbol = Symbol.for("test");
// in some function:
// instead of:
some_element.dataset[symbol] = "foo";
// do:
some_element[symbol] = "foo";
Have not had any trouble with the latter (as in property disappearing).
Or could one perhaps use something like:
Object.defineProperty(element, symbol, {
readonly: true,
value: "foo"
});
If your worry is that the site you have no control over could theoretically modify the data attribute - the same could occur with a symbol if the other site was really intent on breaking things.
const element = document.querySelector('div');
// Your code
const symbol = Symbol.for("test");
element[symbol] = "foo";
// Site's code
const [sym] = Object.getOwnPropertySymbols(element);
element[sym] = "bar";
// Your code, later
console.log(element[symbol]);
<div></div>
or, the site itself could use Symbol.for too.
const element = document.querySelector('div');
// Your code
const symbol = Symbol.for("test");
element[symbol] = "foo";
// Site's code
element[Symbol.for('test')] = "bar";
// Your code, later
console.log(element[symbol]);
<div></div>
Defining the property as readonly could work, but it'd also make you unable to alter the property later, which you might find would eventually be a problem.
A better solution would be to, instead of putting a property onto the object, to create your own collection that associates the elements with your custom values. If your own collection is scoped to only your own script (like with an IIFE), it'll then be impossible for other scripts to interfere with. This is a great place for a WeakMap. For example:
// Your code
(() => {
const map = new WeakMap();
const element = document.querySelector('div');
// Set the value:
map.set(element, 'foo');
// Retrieve the value:
console.log(map.get(element));
})();
<div></div>
The WeakMap values will continue to work and be unmodifiable by outside sources as long as the elements still exist in memory.

WebDriverIO select using elements index

I am using WebDriverIO to try to access (ie. getText, getAttribute, click, etc) an element after creating a list of elements. I am easily able to implement this element if I am using the browser.element() method, but the moment I use browser.elements(), I cannot access the individual objects in the array. According to the WebDriverIO docs, I should be able to access them using the value property.
Here is my pseudo-code. I assumed that these two functions should return the same thing:
usingElement() {
return browser.element('.someCss');
}
usingElements() {
return browser.elements('.someCss').value[0];
}
When I try to use the first block of code, it works perfectly fine.. but
when I try to use the second block, it gives me an error saying usingElements.click is not a function or usingElements.getText is not a function, etc.
How can I isolate a single element object after using the browser.elements() method?
I guess you might need to use one of the below two ways:
Way 1:
var elmnts = browser.elements('.someCss');
var element = elmnts.value[0].ELEMENT;
browser.elementIdClick(element);
Way 2:
var element = $$('.someCss')[0];
element.click();
Thanks,
Naveen
Your index reference was placed in the wrong spot. Try:
var myElement = browser.elements('.someCss')[0];
myElement.click();
You don't need to reference the value property, as WebdriverIO is smart enough to infer that for you.

Constructor function to create DOM elements

I'm trying to use a constructor function to help create DOM elements but I'm wondering if there was a preferred way to do so. I know I could use a framework to do with this but I'd like to implement it using vanilla JavaScript.
Both ways shown below seem to work but I haven't used the new operator with functions very much. Is there any difference between the two ways? Would I be better just to use a plain old function in this situation and not use new?
// First way
function Title(text){
this.element = document.createElement('h2');
this.element.innerText = text;
return this.element;
}
// Second way
function Title(text){
var element = document.createElement('h2');
element.innerText = text;
return element;
}
var title = new Title("Hello");
document.body.appendChild(title);
Your first way does't seem to be correct. Though it works, you seems you haven't understood how new works in JavaScript. When you use new with a function, the following steps are taken:
An empty object is created, something like {}.
All this references inside the function refer to that empty object.
this is used to populate that empty object as needed.
implicitly this is returned. (If you explicitly return, this will be ignored.)
Note that in a constructor function, if you explicitly return something other than this, the returned value is not instanceof that constructor function. Only this is instanceof the constructor function.
Therefore, the first way has nothing to do with logic of new. It's logically the same as the second one.

HTML DOM Extension = bad, but is this OK?

So I realize that in no way do I want to do:
Element.protoype.myfunc = function () {}
But, is this the same or not and is this a good practice?
var e = document.querySelector(q);
e.html = function (html) {
this.innerHTML = html;
}
e.html("Am I in trouble?");
Extending Element will not work in all browsers (notably IE<8). See also this SO question
Extending single elements may result in memory leaks: if such elements are deleted, the method can still exist, containing a link to the non existent element. See this link (it's about handler methods, but it can also apply to extension methods afaik).

Overriding native function?

The native document.createElement() is silly-stupid (it takes only a tag name and no attributes). How come I can't override it? How come this doesn't work?
var originalFunction = document.createElement;
document.createElement = function(tag, attributes) {
var element = originalFunction(tag);
if (attributes) {
for (var attribute in attributes) {
element.setAttribute(attribute, attributes[attribute]);
}
}
return element;
};
The problem is that browsers blow up when you try to replace a native function. Since document is not a JavaScript primitive, you can't create a prototype for it either. WTF.
As far as I can tell the problem is that a call to the document.createElement() function even when referenced has to be from the document. So modify your code:
var element = originalFunction.call(document, tag);
FWIW (informational): you can override "native" methods, in some cases, and in some browsers at least. Firefox lets me do this:
document.createElement = function(f) { alert(f); };
Which then does as you expect when invoked. But your whole block of code above throws an error, at least via Firebug.
Philosophically, you should be able to do this. You can certainly, say, redefine methods on the Array object, etc. But the window (DOM) methods are not covered by ECMAScript, and so they're probably allowed to be implementation-dependent. And of course, they are this way for security reasons.
Why not just use the method in your own function- write it the way you want, and never write document.createElement again....
document.create= function(tag, parent, attributes){
tag= document.createElement(tag);
for(var p in attributes){
if(p== 'css') tag.style.cssText= attributes.css;
else if(p== 'text') tag.appendChild(document.createTextNode(attributes.text));
else tag[p]= attributes[p];
}
if(parent) parent.appendChild(tag);
return tag;
}
document.create('p',document.body,{css:'font-style:italic',className:'important',title:'title',
text:'whatever text you like'});
As far as I know you cannot override native methods for security reasons. For non-native methods it's no problem at all.
There's no way to override that, however you can do some hack around if you not affraid of passing non conventional parameter(s) to a native function. So the thing about createElement that its ment to be the part of the XML DOM, thus you can create whatever tagname you want. And here is the trick, if you pass your attributes as a part of the first parameter (the tagname), separating them with the delimiters of your choise, and then listening to the onchange event of the DOM and if your delimiters are presented in any tag, replace them with the proper markup, using RegExp for example.
The proxy pattern mentioned at JavaScript: Overriding alert() should work for this.
It's mentioned in jquery docs but doesn't look like it actually has a dependency on jQuery.
More info here: http://docs.jquery.com/Types#Proxy%5FPattern
Try this.
document.constructor.prototype.createElement = _ => `P for "pwned"`;
console.log(document.createElement("P"));

Categories