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.
Related
I'm working on a site containing a confusing chain of asynchronous effects. Often, effects do/undo each other several times before stopping. I am having a very hard time following the spaghetti code.
Is it possible to set a callback to run any time a DOM element is manipulated so I can follow the chain of effects the code is applying to it?
Edit: I am currently adding dozens of console.logs everywhere and wanted a cleaner approach.
One approach is hooking into the core jQuery functions and log the information you want. Here's some very rough code on a few functions that i've hijacked i.e hide and animate.
var log_for_functions = 'hide animate'.split(' ');
$.each( log_for_functions , function(){
var function_name = this;
var original_function = $.fn[ original_function_name ]; // get a reference to old function name
$.fn[ function_name ] = function () {
var r = original_function.apply(this, arguments);
console.log( function_name + ' called on element ', this );
return r;
}
});
Now if you hide an element using jquery $('#test').hide() then you will get the following output.
hide called on element [context: #test, selector: "#test"]
You can change this to fit your needs and even hook into other functions.
Not without setInterval.
You could just have the code that applies an effect also note what changed.
You could do this with data attributes.
<div data-state="some state" ...
console.log(targetElement.getAttribute("data-state");
targetElement.setAttribute("data-state","changes");
MakeChanges(targetElement);
The following article introduces two techniques that might work in your case, Mutation Observers and Keyframes:
http://www.backalleycoder.com/2012/08/06/css-selector-listeners/
For each technique the article points to the related library.
This is probably very basic but I'm stalling ...
On page load, I need to save the html content of my element into a variable. I have other code in the page that will change the html content of the element. So I need to be able to revert the value back to it's default (what it was on page load). The issue is that my variable's value is being changed to most recent value.
How can I make the initial value I assign to the variable "stick"?
currentElementsHTML = $("#myDOMElement"),
currentElementsHTMLDefaultValue = currentElementsHTML.html()
... do stuff that changes currentElementsHTML
... revert to currentElementsHTMLDefaultValue whenever i need to
There are many ways you can store some data and make it available later, some of these require a knowledge of the way JavaScript's scope works - others just rely on jQuery methods.
the first things that come to mind
global variable
The bad way to do this would be to store the value as a global var:
function at_the_start(){
/// notice there is no var keyword, this means the variable will be global
global_html = $('element').html();
}
function later_on(){
$('element').html( global_html );
}
You shouldn't do this because your data will "pollute the global namespace" - which basically means that other code will easily be able to access your variable (and mess around with it) and that you could inadvertantly overwrite some other code's global data - especially if you use a rather general variable name.
local variable kept in scope
A better way to do this would be to use the power of JavaScript for your own ends, namely its scope abilities, there are some good points to read here -- What is the scope of variables in JavaScript?:
function my_code(){
var html = $('element').html();
/* Do stuff here */
$('element').html( html );
}
The above relies on a local variable and the fact that you must keep everything in the same function call. As it is most likely you will be relying on a mixture of user triggered events, you can't really use the above. This is because you will have many functions used in different locations and they can't all share the same local variable. Or can they?
The following is what I call a "global local" variable - completely most likely not its real name, but it describes things as I see them:
function my_code(){
/// this variable is local, due to the var keyword
/// but it will be accessible in both the functions below
var html_local = '';
var my_function_to_start = function(){
html_local = $('element').html();
}
var after_other_things_have_happened = function(){
$('element').html( html_local );
}
/// you can even apply these functions to say an event handler
/// and the variable will be remembered because it exists within
/// the "after_other_things_have_happened" function's scope.
$('another.element').click(after_other_things_have_happened);
}
The above works because JavaScript functions can always access variables defined in previous parent blocks / parent scopes or parent functions.
jQuery data
Considering you are using jQuery, jQuery offers a very simple method for storing arbitrary data and you don't need to know anything about scope or local and global vars. It's taken me a while to write this and so obviously by this time other posters have correctly stated that the following is a good idea - jQuery Data:
$('element').data( 'old_html', $('element').html() );
This can then be accessed any time after by using:
$('element').data( 'old_html' );
So...
$('element').html( $('element').data( 'old_html' ) );
Will put the value back - this is stored along with the element so whereever you can access $('element') you'll be able to get at the data assigned to it.
Some other less relevant ways (but still methods of data storage)
storing as a property of an object
Another useful ability sometimes, is that JavaScript treats nearly every datatype as an object. This means you can add properties to nearly anything. The following is actually quite possible if a little odd.
var a = new String('This is a string');
a.withAProperty = 'another string';
alert(a);
alert(a.withAProperty);
I occasionally use this to create pseudo static properties on functions, like so:
var my_function = function(){
if ( ! my_function.staticProp ) {
my_function.staticProp = 'abc';
}
/* use my_function.staticProp for something here */
}
var another_function(){
/* you can also access my_function.staticProp here
but only after my_function has been called once */
}
/* and my_function.staticProp here, but only
after my_function has been called once */
This almost has the same affect of using a global var (especially if you apply it to global functions) but means your value is stored on top of your functions namespace, cutting down the possibility of collisions with other code quite drastically. It does still mean outside code can influence the content of your var -- which can actually be a benefit depending on what you want to do.
storing content in the dom
Depending on what you wish to store, it can sometimes be of benefit to record that data in the DOM. The most obvious of these would be to write the data into a hidden input or hidden element. The benefit of the latter is that you can still navigate this data (using the likes of jQuery or document.getElementById) if it happens to take the form of markup information (as yours does). This can also be beneficial way of avoiding memory leaks caused by circular references - if you are dealing with large amounts of data - as long as you make sure to empty your variables involved in the transporting of the data.
$.ajax('request_html.php').done(function(data){
$('<div id="hidden_html" />').hide().html(data).appendTo('body');
data = null;
/// you only need mullify data if you were to have other
/// sub/child functions within this callback, mainly being wary
/// of closures - which are functions that are defined in a certain
/// scope chain, but are then returned or put to use outside of
/// that chain - i.e. like event listeners.
/// nullify vars and removing large properties is still good practice though.
});
Then when you want to retrieve:
$('#hidden_html').html();
And in the meantime between those two points you can obviously still traverse the data:
$('#hidden_html h1 > a[name=first]');
You associate the original HTML with the same DOM element, that way it won't disappear:
$("#myDOMElement").data("initial-html", $("#myDomElement").html());
something like that, but not tested yet:
$(function() {
$('#id').data('store', $('#id').html());
});
...
$('#id').html(data('store'));
Set it and forget it.
If you push the contents of .html() into a variable, it will stay there unless you do something with that variable to remove it:
var original = $("#foo").html(); // original HTML is now in 'origina'
This won't change unless you change it.
Storing data on the element with $.data()
It might be more advantageous for you to store it as data (using jQuery's .data method) on the element itself though:
var element = $("#foo");
element.data( "original", element.html() );
This way you can always access it at a later time:
console.log( element.data( "original" ) );
Record, Reset, and Restore Demo: http://jsfiddle.net/ft8M9/
Works on many items too
// Access all elements to restore
var restore = $(".restore");
// Save original HTML, and set new HTML
restore.each(function(i,o){
var that = $(this);
that.data("original", that.html())
.html("Changed " + i);
});
// After 2 seconds, restore original HTML, remove stored data
setTimeout(function(){
restore.each(function(i,o){
var that = $(this);
that.html( that.data("original") )
.removeData( "original" );
});
}, 2000);
Demo: http://jsfiddle.net/ft8M9/1/
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>
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"));
I have a question, which I can't seem to decide on my own so I'll ask here. The question is simple: whether to use inline JavaScript events or adding them afterwards? The theory in the background isn't that simple though:
I have a JS object that returns HTML. Whenever you create this object, the returned HTML will be used for another object's HTML. Therefore, adding events is not straight-forward. See:
secret.object = function() {
this.init = function() {
var html = '<div>and lots of other HTML content</div>';
return html;
};
}
This is a sample object that is created within this code:
for ( var i = 0; i < countObjects; i++) {
var obj = arguments[0].content[i];
generatedContent += spawnSecret(); /* The spawnSecret() is a method that initializes the object, and calls its init() method that returns the HTML.
}
and then later on I create a new object whose property "content" will be set to "generatedContent". It needs to add the events within the secret object I have, nowhere else. And since my system is built like this, I see only two ways around this: use inline events or build HTML using method calling instead of returning.
Hopefully, this wasn't too hard to understand.
If you created the elements using document.createElement() (but didn't append them to the DOM) and kept a reference to them, then you could populate them with the text content and attach event handlers to them, without having to use inline events.
When you are ready to reveal your 'secret' you could then append them to the DOM, rather than dumping in a text string of HTML tags and content.
I cant see it making much of a difference - if you just render your events using onclick etc. JavaScript event handlers they will be evaluated as soon as you append your generated HTML to the document, rather than you having to call attachEvent() or whatever.