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"));
Related
I need to change on the fly the value set on every node using the innerHTML.
The closest solution I found is:
...
Object.defineProperty(Element.prototype, 'innerHTML', {
set: function () {
// get value (ok)
var value = arguments[0];
// change it (ok)
var new_value = my_function(value);
// set it (problem)
this.innerHTML = new_value; // LOOP
}
}
...
But obviously it's an infinite loop.
Is there a way to call the original innerHTML set?
I also try the Proxy way but i could not make it work.
More details:
I am working on an experimental project which uses a reverse proxy to generate and add CSP policies to a website, so:
the owner of the website will be aware of these "overwrites"
i needed to handle any js code client generated which could trigger the
policy
i need to modify it before the Content Security Policy engine evalution! (this is the main problem which requires this "non so good" solution)
Obligatory warning:
Overriding the setter and getter for any property of Element.prototype is bound to be bad idea in any production-level code. If you use any libraries that rely on innerHTML to work as it should or if there are other developers in the project that don't know of these changes, things might get weird. You will also loose the ability to use innerHTML "normally" in other parts of the app.
That said, as you haven't provided any information about why you would want to do this, I'm just going to assume that you know about the caveats and you still want to override the browser's own functionality, perhaps for development purposes.
Solution: You are overriding the browser's native setter for the Element.prototype.innerHTML, but you also need the original setter to achieve your goal. This can be done using Object.getOwnPropertyDescriptor, which is sort of the "counterpart" of Object.defineProperty.
(function() {
//Store the original "hidden" getter and setter functions from Element.prototype
//using Object.getOwnPropertyDescriptor
var originalSet = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML').set;
Object.defineProperty(Element.prototype, 'innerHTML', {
set: function (value) {
// change it (ok)
var new_value = my_function(value);
//Call the original setter
return originalSet.call(this, new_value);
}
});
function my_function(value) {
//Do whatever you want here
return value + ' World!';
}
})();
//Test
document.getElementById('test').innerHTML = 'Hello';
<div id="test"></div>
There's no straightforward way to do this with an arbitrary HTML string, no.
A problem is you're using an arbitrary HTML string. The only way currently to set arbitrary HTML on an element is with innerHTML. You'd have to find a different way to set arbitrary HTML on an element, for example appending the HTML to a temporary node and grabbing its contents:
// Attempt: build a temporary element, append the HTML to it,
// then grab the contents
var div = document.createElement( 'div' );
div.innerHTML = new_value;
var elements = div.childNodes;
for( var i = 0; i < elements.length; i++ ) {
this.appendChild( elements[ i ] );
}
However this suffers the same problem, div.innerHTML = new_value; will recurse forever because you're modifying the only entry point to arbitrary HTML setting.
The only solution I can think of is to implement a true, complete HTML parser that can take an arbitrary HTML string and turn it into DOM nodes with things like document.createElement('p') etc, which you could then append to your current element with appendChild. However that would be a terrible, overengineered solution.
All that aside, you shouldn't do this. This code will ruin someone's day. It violates several principles we've come to appreciate in front end development:
Don't modify default Object prototypes. Anyone else who happens to run this code, or even run code on the same page (like third party tracking libraries) will have the rug pulled out from under them. Tracing what is going wrong would be nearly impossible - no one would think to look for innerHTML hijacking.
Setters are generally for computed properties or properties with side effects. You're hijacking a value and changing it. You face a sanitization problem - what happens if someone sets a value a second time that was already hijacked?
Don't write tricky code. This code is unquestionably a "tricky" solution.
The cleanest solution is probably just using my_function wherever you need to. It's readable, short, simple, vanilla programming:
someElement.innerHTML = my_function(value);
You could alternatively define a method (I would do method over property since it clobbers the value from the user), like:
Element.prototype.setUpdatedHTML = function(html) {
this.innerHTML = my_function(html);
}
This way when a developer comes across setUpdatedHTML it will be obviously non-standard, and they can go looking for someone hijacking the Element prototype more easily.
Libraries I've seen have DOM wrappers that inclusively handle only the first element of the list in some case, like:
return this[0].innerHTML
and use the whole list in some other like:
for( var i=0, l=this.length; ++i<l; ) this[i].className = cls;
return this
Why is this approach accepted?
I think singling out the first element defeats the purpose of having methods that apply the same thing on the rest of the list. Isn't it bad to have dubious functions? I know it suits many people..but it feels inconsistent and I'm interested in why this is accepted so widely.
EDIT as an example:
jQuery.html()
If the selector expression matches more than one element, only the
first match will have its HTML content returned.
why not all?
the hide() method in bonzo, from Dustin Diaz
//...
hide: function () {
return this.each(function (el) {
el.style.display = 'none'
})
}
why not only the first?
The accessor methods in jQuery return single values because it's simpler and more generally useful. If the .html() API were to return the value if innerHTML for all elements, that'd mean it'd have to return an array. That, in turn, would mean that in the most common case of wanting the contents of a single element, you'd have to add the array access. There's also the problem of knowing exactly which returned value goes with which selected element. In other words, if .html() returned an array of element contents:
var contentList = $('.someClass, span, .hidden .container').html();
If "contentList" were just a simple array, what use would it be? How would the code know for each element which DOM node it came from? Of course there are solutions to this, but again the simple case is made complicated in order to support a rare general case.
It's possible of course to get the list yourself with .map(). I think this is just an issue of smart, practical, pragmatic API design.
I have written this code (this is a snippet) that doesn't seem to be working. I have isolated it to here.
grab = window.document.getElementById;
grab("blueBox") // i.e. grab("blueBox").onclick [...]
Is it possible to create references to native function in javascript. I am doing something with the grabbed element, I just left it out for example. The grab function doesn't seem to work.
I am using FireFox's most recent version
The way you're doing it will mess up the assignment of the this value for the function.
grab = window.document.getElementById;
grab("blueBox") // i.e. grab("blueBox").onclick [...]
here this will be the global object. Try:
grab.apply(window.document, ["blueBox"])
or in newer browsers:
grab = window.document.getElementById.bind(window.document);
to get directly define what this will be.
The first step here is always the JavaScript console. Firebug is your friend. Tell us the error message if it doesn't mean anything to you.
In the mean time, here is a workaround:
var grab = function(id) { return window.document.getElementById(id); }
function grab(id) {
return window.document.getElementById(id);
}
grab("blueBox");
The reason is because the function getElementById is not being called as a method of document, so its this keyword doesn't reference the right object. Using call as suggested in other answers shows that when this references the document, getElementById works.
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).
How can one assign a Javascript namespace to an HTML element and call functions defined in said namespace on this element?
I asked this other question:
Attaching JavaScript to the prototype of an ASCX client side instance
The previous question answered the how to do it, but now I am curious how this works on a pure Javascript/HTML level. And I'm no closer to figuring it out.
Let assume I have an HTML page with just a textbox:
<html>
<body>
<div>
<input type="text" id="MyTextBox" />
</div>
</body>
</html>
In a browser, I can do document.getElementById('MyTextBox').
My question is however, using just javascript, how can I assign the object returned a javascript type so that from the object I can call functions defined in the namespace?
For instance, I want to do:
var x = document.getElementById('MyTextBox');
x.SetTheText('blah');
and in my custom namespace/type/class I would have defined SetTheText as
function SetTheText(text) {
this.value = text;
}
How do I say MyTextBox is an object that can run functions defined in a JS namespace.
I hope this makes sense
Basically, you can add any kind of property to (almost) any kind of JS object. And if you add a function to an object, that function's this will be the object.
In other words
var x = document.getElementById('MyTextBox'); // a DOM object
x.setTheText = function(text) {
this.value = text;
};
x.setTheText('blah'); // bingo
If you want to extend an entire class of objects, you can do that too, via the prototype
HTMLTextAreaElement.prototype.setTheText = function(text) {
this.value = text;
};
someRandomTextArea.setTheText("blah"); // bingo. Again.
However, this is not really recommended, as you're messing with objects that are outside your control (i.e. it's fragile as other scripts and browser updates and whatnot might get in the way). Also, you might break some other piece of code by doing this.
A better solution (in many ways) is the jQuery solution of wrapping an unmodified DOM element in another object, and calling methods on the wrapper object rather than the element directly (Personally, I rather like the "pretty" code that can come from just extending native JS objects, but alas, it's not safe, so I'm trying to quit that.)
p.s. Classes are Captilized in javascript; methods are camelCase. I've edited the code accordingly. It's not something that's enforced by anything, but style's style.
Edit: For comparison, you can look at the prototype.js library, which works its magic by extending the DOM. Again, in my opinion, it makes for some very pretty code compared to jQuery's constant invocations of $(...).xyz(...), but it's still a slightly dangerous route to take
You should not modify host objecs, all of the major libraries agree on that. Use a wrapper object instead:
<input type="text" id="inp0">
<script type="text/javascript">
function CreateCustomElement(el) {
this.element = el;
}
CreateCustomElement.prototype = {
setTheValue: function(text) {
this.element.value = text;
}
}
var x = new CreateCustomElement(document.getElementById('inp0'));
x.setTheValue('foo');
</script>