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;
}
Related
I'm working on a JS class that creates several elements and attaches them to the DOM, and I would like for the class to also set the style attribute properties of those elements. I have a function already that does this for other attributes:
function setAttrs(el, attrValArray){
for(var i=0; i<attrValArray.length;i++){
el.setAttribute(attrValArray[i][0],attrValArray[i][1]);
}
}
The attrValArray input is a 2D array that would look something like
attrValArray = [["class","container"],["id","cc1]];
So what I want is to be able to create a similar array for style property pairs such as:
propValArray = [["display","inline-block"],["position","relative"]];
Which I would then pass to a similar setStyles function.
I could use the same setAttribute method, but instead of looping over the array and setting attributes individually I would have to construct a long string and pass the whole thing as the second argument of setAttribute since I am actually setting many properties of only 1 attribute. But I'd like to avoid this because of the fact that it would override any existing inline styles (not that I use inline styles, but for the sake of principle).
The better option is to set the properties of style on the element. I.e.
el.style.<property-to-set> = "property value";
This does not overwrite anything other than the specific property being set. But I don't see any way to select the "property-to-set" using values from a list. Is there an appropriate way around this?
To be clear, I already know that I can simply assign the desired style attributes to the to-be-created element classes/ids, and was actually wondering if that is the technically correct thing to do based on "good practices" and whatnot. That is what I plan on doing if there is no satisfying alternative.
Also, as stated in the question, I'm strictly interested in a pure JS solution, no JQuery or any other such library/framework.
I don't see any way to select the "property-to-set" using values from a list.
Unless I'm misunderstanding the question, you would do it pretty much the exact same way:
function setStyles(el, cssArray){
for(var i=0; i<cssArray.length;i++){
el.style[cssArray[i][0]] = cssArray[i][1];
}
}
Remember with objects, you can access properties using dot notation object.property or bracket notation object['property']. When you use bracket notation, the text inside the brackets is evaluated, just like when looking up indexes in an array (arrays are really just special objects with number properties).
Alternatively, you could build one long string out of all the property/values pairs and use setAttribute or cssText:
function setStyles(el, cssArray){
let cssText = '';
for(var i=0; i<attrValArray.length;i++){
cssText += cssArray[i][0] + ':' + cssArray[i][1] + ';';
}
el.style.cssText = cssText;
// or el.setAttribute('style', cssText);
}
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.
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.
It appears that sometimes object.setAttribute(attrib,value) isn't equivalent to object.attrib=value in javascript?
I've got the following code, which works fine:
var lastMonthBn = document.createElement('input');
lastMonthBn.value='<'; // This works fine
lastMonthBn.type='button'; // This works fine
But the following code doesn't:
var div = document.createElement('div');
div.class = 'datepickerdropdown'; // No luck here!
So i need to use the following:
div.setAttribute('class','datepickerdropdown');
My question is, why? From reading this, I thought that object.setAttribute(blah,value) was the same as object.blah=value??
Properties and Attributes aren't really the same, however the DOM exposes standard attributes through properties.
The problem you're facing specifically with the class attribute is that class is a future reserved word.
In some implementations the use of a future reserved word can cause a SyntaxError exception.
For that reason, the HTMLElement DOM interface provides a way to access the class attribute, through the className property:
var div = document.createElement('div');
div.className = 'datepickerdropdown';
Remember, attributes aren't the same as properties, for example:
Immagine a DOM element that looks like this:
<div></div>
If you add a custom attribute to it, e.g.:
myDiv.setAttribute('attr', 'test');
An attribute will be added to the element:
<div attr="test"></div>
Accessing attr as a property on the div element, will simply give you undefined (since is not a property).
myDiv.foo; // undefined
If you bind a property to an element, e.g.:
myDiv.prop = "test";
The getAttribute method will not be able to find it, (since is not an attribute):
myDiv.getAttribute('test'); // null
Note: IE wrongly messes up attributes and properties. :(
As I've said before, the DOM exposes standard attributes as properties, but there are some exceptions that you'll have to know:
The class attribute, is accessible through the className property (the problem you have).
The for attribute of LABEL elements, is accessible through the htmlFor property (collides with the for statement).
Attributes are case-insensitive, but the language bindings for JavaScript properties are not, so the convention is to use the names is camelCase to access attributes through properties, for example the ones formed by two words, e.g. cellSpacing, colSpan, rowSpan, tabIndex, maxLength, readOnly frameBorder, useMap.
It should be noted that browsers like Safari will NOT run JavaScript if keywords like "class" or "int" are present.
So it's a cross-browser support sort of thing. "class" is present in JS2.0 [I believe a package system is available there too]
...
I should also note that in IE, setAttribute [for non-class things, since setAttribute should be use-able for other members such as "style"] can be glitchy.
Besides the ID, if you say you want a unique identifier for an HTML element (let’s say a div).
I browsed the DOM for something (like a number or string) that was unique for each element; but the DOM was big and I failed to find that on the Internet.
Is there a property (in the DOM obviously) that is unique only to that element? (Other than the ID and also you don't specify it, but it comes when the DOM is constructed.)
Depending on the objective, here are two suggestions.
Unless you actually need to express the id as some kind of string, you can save the normal DOM reference.
If you do need to express it as a string for some reason, then you'll need to assign a unique id yourself.
var getId = (function () {
var incrementingId = 0;
return function(element) {
if (!element.id) {
element.id = "id_" + incrementingId++;
// Possibly add a check if this ID really is unique
}
return element.id;
};
}());
The only other identifier I can think of is the XPath of the element in the document.
For instance, the title link inside the heading of this very page has an XPath of
/html/body/div[3]/div[2]/div/h1/a
But like Pekka already said, it depends on what you want to do. And I don’t think you can get the XPath easily from the DOM in JavaScript, despite XPath being available nowadays in JavaScript engines.
Internet Explorer has a property, "uniqueID", for every element. The problem is that the other browsers don't support it.
You can use a library or roll your own to create a unique identifier. jQuery has .data():
Store arbitrary data associated with the matched elements or return
the value at the named data store for the first element in the set of
matched elements.
I just encountered the same situation, and while I was looking into some DOM elements in the Chrome developer tools inspector, I noticed that they all seem to have a property like jQuery11230892710877873282 assigned with a unique number.
Obviously the number after 'jQuery' is different every time you load the page. My guess is that jQuery is generating this internally every time it tries to access or manipulate any DOM element.
I played a little bit with it, and it looks like elements that are never accessed/manipulated by jQuery may not have this property, but the moment you do something like $(elem), the property will be there. So, since we're using both jQuery and Lodash, I devised the following function to return a unique ID regardless of whether the element actually has a DOM id attribute.
_.reduce(
$(elem),
function(result, value, key) {
if(_.startsWith(key, 'jQuery'))
return value;
},
0)
There is the name attribute that can be addressed by document.getElementByName.
I don't think other unique identifiers exist - even though you could simulate one by setting a property (like title) to a unique value, and then query for that. But that is kludgy.