Query property value of a css class even if not in use - javascript

I have a situation where I would like to know the value of the 'width' property of a css class defined as 'foo' (eg: ".foo { width:200px}" ).
However, there may not actually exist (yet) an element with said class in the dom.
I want to be able to access this value from Javascript (to calculate percentages of other divs for setting a complex layout).
I am pretty sure the answer is that this is probably not possible, but I think it would be a really useful feature and would enable design/layout properties to be effectively defined in CSS.
Is there any way to do this? (without hacks like rendering divs offscreen, then querying their class properties).
Perhaps it could be implemented as a standard browser javascript library - possibly a document.styles object? thus, my property could be found from within javascript as: "document.styles.foo.width" - How do I submit a proposal to the w3c?

A solution would be to create an element and not add it to the document.
But there is a cleaner solution as in javascript you can easily iterate over stylesheets and their content :
for (var i=0; i<document.styleSheets.length; i++) {
var styleSheet = document.styleSheets[i];
var cssRules = styleSheet.rules; // chrome, IE
if (!cssRules) cssRules = styleSheet.cssRules; // firefox
for (var ir=cssRules.length; ir-->0;) {
var rule = this.cssRules[ir];
if (rule.selectorText==".foo") {
var width = parseInt(rule.style.getPropertyValue('width'), 10);
// do what you want
}
}
}
As you're iterating on a yet parsed structure, this is an efficient solution.

Related

Change innerHTML set on the fly

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.

Checking if Element has Style Defined, not from Default Stylesheet

Is there a way, using JavaScript, that I could deduce whether an element has a specific CSS definition applied to it, that wasn't inherited from the browser's stylesheet?
I need to know whether a given element has had its position definition explicitly defined (explicitly being a definition not from the browser's stylesheet).
What'd initially come to mind is executing an algorithm to compare said element's style definition with the default style definition for the same element. However, the scenario where the same definition as the default definition (defined in default stylesheet) is also important.
Say the element in question is a div. The default positioning, across browsers, is static. If it's value is absolute or relative, the answer's easy. But, if it's static, there's no "easy" way, that I know of, to determine if it was a 'user supplied styling' (stylesheet or inline-style).
Thinking of something akin to object.hasOwnProperty. However, in this case, it'd be called on the classlist and a property will be passed as a parameter; with a boolean return value indicating if the property has been set by a 'user defined definition'?
All elements have a standard position property of static. You can use that knowledge in conjunction with window.getComputedStyle to deduce whether the element has a custom defined position:
var elem = document.querySelector('....');
if (window.getComputedStyle(elem, null) !== 'static') {
// The element has custom position defined in either .style in via a css rule
}
This has the obvious crux that it does not catch the case when you specifically an element's position to be static. To do that efficiently, you'd have to see which CSS styles apply for your specific element and see if any of them is 'position'. You can use something like: https://github.com/Box9/jss to get that. (there used to be window.getMatchedCSSRules, but that's deprecated)
If you don't want to use a library to achieve this, you can manually parse all the document.styleSheets for rules and see if the selectorText matches your element.
This is just a method I wrote up in 5 minutes and works pretty fine (though take care, this is not the most performant method in the world):
function getStylesForElement (elem) {
var result = {};
[].slice.call(document.styleSheets).forEach(function (stylesheet) {
// some stylesheets don't have rules
if (!stylesheet.rules) return;
[].slice.call(stylesheet.rules).forEach(function (rule) {
// account for multiple rules split by a comma
rule.selectorText.split(',').forEach(function (selector) {
if (elem.matches(selector)) {
for (var index=0; index < rule.style.length; ++index) {
var prop = rule.style[index];
result[prop] = rule.style[prop];
}
}
});
});
});
return result;
}

JavaScript append additional script to native classList remove and toggle methods or complete replacement?

When using the methods remove or toggle from the classList object in Gecko browsers if there are no more classes assigned to the target element that element will still have a class attribute however it will be empty. This makes for messy code when submitting serialized code to the server and search engines probably don't appreciate empty attributes either (code to content ratio).
Is it possible to add additional code to the native methods remove or toggle of the classList object to remove the class attribute from an element if it is empty somehow or will I have to write my own replacement script for those methods?
If it can be done by appending to the native classList object then I need to know how to append the native object's existing methods.
If I need to outright replace the native methods remove or toggle how would do I implement it so that regardless of how the method is accessed (getElementById('a').classList, getElementsByTagName('a').classList, ‎getElementsByClassName('a').classList, etc) it will still work?
No frameworks.
How to serialize (X)HTML in JavaScript:
if (typeof window.XMLSerializer!=='undefined')
{
var r = document.getElementById('example_id');
var xml = new XMLSerializer().serializeToString(r);
}
I'd rather remove empty class attributes before serialising the DOM, than try to overwrite classList methods. Doing so is quite simple:
var r = document.getElementById('example_id');
var els = [].slice.call(r.querySelectorAll('[class=""]').concat(r.className ? [] : [r]);
els.forEach(function(el) {
el.removeAttribute('class');
});
var xml = new XMLSerializer().serializeToString(r);

Why does cloneNode exclude custom properties?

This is related to the question javascript cloneNode and properties.
I'm seeing the same behaviour. Node.cloneNode does not copy over any properties that I add myself (code from original post):
var theSource = document.getElementById("someDiv")
theSource.dictator = "stalin";
var theClone = theSource.cloneNode(true);
alert(theClone.dictator);
theClone does not contain any property "dictator".
I haven't been able to find any explanation for why this is the case. The documentation on MDN states that cloneNode "copies all of its attributes and their values", a line which is taken directly from the DOM specification itself.
This seems broken to me as it makes it next to impossible to do a deep copy of a DOM tree that contains custom properties.
Am I missing something here?
A property is not equal to an attribute.
Use setAttribute() and getAttribute() instead.
var theSource = document.getElementById("someDiv")
theSource.setAttribute('dictator','stalin');
var theClone = theSource.cloneNode(true);
alert(theClone.getAttribute('dictator'));
Not every property corresponds to an attribute. Adding a custom property to an element does not add an attribute, so what happens when you do that is not covered by the DOM spec.
In fact, what happens when you add a property to a host object (such as a DOM node) is completely unspecified and is by no means guaranteed to work, so I'd strongly recommend against doing it. Instead, I'd suggest using wrappers if you want to extend the functionality of host objects (as jQuery and many other libraries do).
Tested this. cloneNode does include the custom attribute in the clone, but that attribute can't be retrieved directly. Try:
var theSource = document.getElementById("someDiv")
theSource.dictator = "stalin";
//or better / more cross browser compatible
theSource.setAttribute('dictator','stalin');
var theClone = theSource.cloneNode(true);
alert(theClone.getAttribute('dictator')); //so, use getAttribute
It may be a browser problem with cloning expando properties. I ran a testcase (see later) from this rather old bugzilla report. It didn't work in Chrome and Firefox (both latest versions).
//code from testcase # bugzilla
var a = document.createElement("div");
a.order = 50;
alert(a.order);
b = a.cloneNode(true);
alert(b.order);

JavaScript DOM remove element

I'm trying to test if a DOM element exists, and if it does exist delete it, and if it doesn't exist create it.
var duskdawnkey = localStorage["duskdawnkey"];
var iframe = document.createElement("iframe");
var whereto = document.getElementById("debug");
var frameid = document.getElementById("injected_frame");
iframe.setAttribute("id", "injected_frame");
iframe.setAttribute("src", 'http://google.com');
iframe.setAttribute("width", "100%");
iframe.setAttribute("height", "400");
if (frameid) // check and see if iframe is already on page
{ //yes? Remove iframe
iframe.removeChild(frameid.childNodes[0]);
} else // no? Inject iframe
{
whereto.appendChild(iframe);
// add the newly created element and it's content into the DOM
my_div = document.getElementById("debug");
document.body.insertBefore(iframe, my_div);
}
Checking if it exists works, creating the element works, but deleting the element doesn't. Basically all this code does is inject an iframe into a webpage by clicking a button. What I would like to happen is if the iframe is already there to delete it. But for some reason I am failing.
removeChild should be invoked on the parent, i.e.:
parent.removeChild(child);
In your example, you should be doing something like:
if (frameid) {
frameid.parentNode.removeChild(frameid);
}
In most browsers, there's a slightly more succinct way of removing an element from the DOM than calling .removeChild(element) on its parent, which is to just call element.remove(). In due course, this will probably become the standard and idiomatic way of removing an element from the DOM.
The .remove() method was added to the DOM Living Standard in 2011 (commit), and has since been implemented by Chrome, Firefox, Safari, Opera, and Edge. It was not supported in any version of Internet Explorer.
If you want to support older browsers, you'll need to shim it. This turns out to be a little irritating, both because nobody seems to have made a all-purpose DOM shim that contains these methods, and because we're not just adding the method to a single prototype; it's a method of ChildNode, which is just an interface defined by the spec and isn't accessible to JavaScript, so we can't add anything to its prototype. So we need to find all the prototypes that inherit from ChildNode and are actually defined in the browser, and add .remove to them.
Here's the shim I came up with, which I've confirmed works in IE 8.
(function () {
var typesToPatch = ['DocumentType', 'Element', 'CharacterData'],
remove = function () {
// The check here seems pointless, since we're not adding this
// method to the prototypes of any any elements that CAN be the
// root of the DOM. However, it's required by spec (see point 1 of
// https://dom.spec.whatwg.org/#dom-childnode-remove) and would
// theoretically make a difference if somebody .apply()ed this
// method to the DOM's root node, so let's roll with it.
if (this.parentNode != null) {
this.parentNode.removeChild(this);
}
};
for (var i=0; i<typesToPatch.length; i++) {
var type = typesToPatch[i];
if (window[type] && !window[type].prototype.remove) {
window[type].prototype.remove = remove;
}
}
})();
This won't work in IE 7 or lower, since extending DOM prototypes isn't possible before IE 8. I figure, though, that on the verge of 2015 most people needn't care about such things.
Once you've included them shim, you'll be able to remove a DOM element element from the DOM by simply calling
element.remove();
Seems I don't have enough rep to post a comment, so another answer will have to do.
When you unlink a node using removeChild() or by setting the innerHTML property on the parent, you also need to make sure that there is nothing else referencing it otherwise it won't actually be destroyed and will lead to a memory leak. There are lots of ways in which you could have taken a reference to the node before calling removeChild() and you have to make sure those references that have not gone out of scope are explicitly removed.
Doug Crockford writes here that event handlers are known a cause of circular references in IE and suggests removing them explicitly as follows before calling removeChild()
function purge(d) {
var a = d.attributes, i, l, n;
if (a) {
for (i = a.length - 1; i >= 0; i -= 1) {
n = a[i].name;
if (typeof d[n] === 'function') {
d[n] = null;
}
}
}
a = d.childNodes;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
purge(d.childNodes[i]);
}
}
}
And even if you take a lot of precautions you can still get memory leaks in IE as described by Jens-Ingo Farley here.
And finally, don't fall into the trap of thinking that Javascript delete is the answer. It seems to be suggested by many, but won't do the job. Here is a great reference on understanding delete by Kangax.
Using Node.removeChild() does the job for you, simply use something like this:
var leftSection = document.getElementById('left-section');
leftSection.parentNode.removeChild(leftSection);
In DOM 4, the remove method applied, but there is a poor browser support according to W3C:
The method node.remove() is implemented in the DOM 4 specification.
But because of poor browser support, you should not use it.
But you can use remove method if you using jQuery...
$('#left-section').remove(); //using remove method in jQuery
Also in new frameworks like you can use conditions to remove an element, for example *ngIf in Angular and in React, rendering different views, depends on the conditions...

Categories