how to find common in inline styles? - javascript

I have an array of sequential dom element nodes which may or may not have inline styles. I need to end up with an object or array with only keys and values common to all the nodes. Needs to work in IE8+, chrome and FF.
I can't even get one nodes styles into an array without a bunch of other stuff being included as well.
I've tried to use node[x].style but it seems to return a lot of extraneous stuff and other problems.
//g is node array
s=[];
for(k in g)
{
if(g.hasOwnProperty(k) && g[k]) s[k]=g[k];
}
console.log(s);
gives me ["font-weight", cssText: "font-weight: bold;", fontWeight: "bold"] which is close but I only want fontWeight: "bold" in the array. In any case, this only works in chrome.
The only idea I have at the moment that might work is using the cssText and splitting on semi-colons and splitting again on colons but that seems an ugly and slow way to do it especially as I then need to compare to a bunch of nodes and do the same to their styles.
So, I'm hoping someone can come up with a simple elegant solution to the problem posed in the first paragraph.

If you truly want ONLY styles that are specified inline in the HTML for the object, then you will have to deal with text of the style attribute as you surmised.
The .style property will show you more styles than were specified on the object itself (showing you default values for some styles) so you can't use that.
Here's a function that takes a collection of DOM nodes and returns a map of common styles (styles that are specified inline and are the same property and value on every object):
function getCommonStyles(elems) {
var styles, styleItem, styleCollection = {}, commonStyles = {}, prop, val;
for (var i = 0; i < elems.length; i++) {
var styleText = elems[i].getAttribute("style");
if (styleText) {
// split into an array of individual style strings
styles = styleText.split(/\s*;\s*/);
for (var j = 0; j < styles.length; j++) {
// split into the two pieces of a style
styleItem = styles[j].split(/\s*:\s*/);
// only if we found exactly two pieces should we count this one
if (styleItem.length === 2) {
prop = styleItem[0];
val = styleItem[1];
// if we already have this style property in our collection
if (styleCollection[prop]) {
// if same value, then increment the cntr
if (styleCollection[prop].value === val) {
++styleCollection[prop].cntr;
}
} else {
// style tag didn't exist so add it
var newTag = {};
newTag.value = val;
newTag.cntr = 1;
styleCollection[prop] = newTag;
}
}
}
}
}
// now go through the styleCollection and put the ones in the common styles
// that were present for every element
for (var prop in styleCollection) {
if (styleCollection[prop].cntr === elems.length) {
commonStyles[prop] = styleCollection[prop].value;
}
}
return(commonStyles);
}
Working demo: http://jsfiddle.net/jfriend00/JW7CZ/

Related

Trying to create a custom method using prototype property but i get Cannot read property 'replace' of undefined

So I'm messing around with JS trying to create a method which adds CSS style to elements without removing currently applied style:
// ==================================================================================================
// this method adds style to an element using CSS inline syntax without removing unaltered CSS styles
// ==================================================================================================
Element.prototype.cssStyle = function (style)
{
let styleChars = style.split(''); // split each character of the style arg as an item in an array
let positions = [];
for(let i=0; i<styleChars.length; i++){
if(styleChars[i] === '-'){
positions.push(i+1);
};
};
positions.forEach(function (position){ // for each match
styleChars.splice(position, 1, style[position].toUpperCase()); // make that character uppercase
});
styleChars.splice(0, 0, '[["'); // add a "[[" item on the first position
styleChars.splice(styleChars.length, 0, '"]]'); //add a "[[" on the last position
style = styleChars.join('') // join back the array into a string
style = style.replace(/:/g, "\",\"").replace(/;/g, "\"],[\"").replace(/-/g, ""); // replace some character in order to make the string look like an array
style = JSON.parse(style); // parse the string into an array
for(let i=0; i<style.length; i++){ // for each item in the array
let property = style[i][0].replace(/ */, ""); // remove some characters which might inhibit normal execution
let value = style[i][1].replace(/;/, "").replace(/ */, ""); //remove some characters which might inhibit normal execution
this.style[property] = value // change style of the element
};
return this.getAttribute('style'); //return all inline CSS styles
}
so if I try to style an element like this:
Element.cssStyle('background-color: white; color: #000')
It works as expected, but if I add a ; at the end of the parameter string I get this
Element.cssStyle('background-color: white; color: #000;')
'Uncaught TypeError: Cannot read property 'toUpperCase' of undefined'
Even though I don't see any apparent issues with the replace method, what could it be?
Replacing whitespace at that exact line works just fine, but trying to replace ; I get that error.
Also how badly written is my code?
Thanks!
Here is an example:
Element.prototype.cssStyle = function(styleStr) {
let styles = styleStr.split(';')
styles.forEach(style => {
if (!style.trim()) return;
let name = style.split(':')[0].trim();
let value = style.split(':')[1].trim();
this.style[name] = value;
})
return this.getAttribute('style'); //return all inline CSS styles
}
let testEl = document.getElementById("test")
console.log(testEl.cssStyle("color: white; background-color: black;"))
<p id="test">This is a test paragraph</p>
A few things to note:
This does NOT parse all CSS but I believe it works for your examples.
It is NOT recommended to modify the prototype of an object because if you are using someone else's code along with yours you might run into problems of overwriting each other's modifications.
The code works by splitting the string into each style segment and then it loops over those with forEach and changes the style of the element using this.style
Documentation:
Array.forEach()
String.trim()
String.split()
Hopefully, this helps.
Here is how I would do it:
Element.prototype.cssStyle = function(str) {
// Split styles
const styles = str.split(';');
// For each of them
for (let style of styles) {
// Get the property and value without extra spaces (using trim)
const [property, value] = style.split(':').map(s => s.trim());
// If none of them is empty
if (property.length && value.length) {
const camelCaseProperty = kebakCaseToCamelCase(property);
this.style[camelCaseProperty] = value;
}
}
return this.getAttribute('style');
};
function kebakCaseToCamelCase(str) {
return str.replace(/-(.)/g, (match, capture) => capture.toUpperCase());
}
document.querySelector('span')
.cssStyle('display: block; background-color: red; color: white;');
<span>Hello world</span>
but as #anbcodes proved it in his answer, I think you can even skip the camel case conversion

Javascript childNodes

I am trying to make a childNode be invisible so that the user will not be able to see it.
function hideLetters() {
var squares = document.querySelectorAll( "#squarearea div" );
for ( var i = 0; i < squares.length; i++ ) {
squares[ i ] = hide( squares[ i ] );
}
}
function hide( squares ) {
var nodeList = squares.childNodes;
nodeList.style.display = "none";
squares.childNodes = nodeList;
return squares;
}
I have been trying to make the child nodes found within squares invisible so that they do not appear on the screen. Please note that I am only using JavaScript, HTML, and CSS for this project.
You need to apply it to every element of the node list:
squares.childNodes.forEach(node => node.style.display = "none");
Try this one
Array.prototype.slice.call(squares.childNodes).forEach(node => node.style.display = 'none')
There were a few things incorrect about your code and I took the liberty of taking out bits that didn't merit staying in given what you were trying to do.
In no way are you manipulating squares other than looping over it. In your code you said squares[i] = hide(squares[i] - not to put to fine a point on it, but this is worthless and does nothing. The list itself is a reference to the nodes, not the nodes themselves. You can think of every item in the list like a sign-post that tells the code where to look. So when that node is changed it doesn't need to be updated in the list because the list is simply saying "this is what you want to look at", it doesn't actually contain a copy of the node.
because of the reasons listed above you don't need to return anything from your hide function.
The nodeList in your hide function needs to be iterated over and each node manipulated individually. It's worth noting that you can't say "adjust all of these" at any point in JavaScript unless you yourself create a function that allows that functionality, but under the hood you're still going through every list or array one by one.
Your nodeList is aptly named. It is a list of nodes. Most people, at least the newer people to JavaScript(no shame for that, we all learn sometime), assume that tags(a.e. <div></div>, <a></a>, <span></span>) are nodes. And yes, you're right, they are! But the text within those tags are completely separate and individual nodes as well. This means that when you iterate over all the nodes you probably aren't just getting Element Nodes you might be getting Text Nodes or Document Fragment Nodes or Entity Nodes, etc.
While we iterate over the nodeList we need to separate out the nodes that we can hide(those with a style object that's able to be manipulated) and we do this by comparing the built-in nodeType property that's in every node with the Node.ELEMENT_NODE property. If it returns true we know absolutely that the node is an Element Node.
After we've checked that what we're manipulating is an element, we simply set it's display property (which is normally "block") to the value "none" and in that way hiding it on the DOM.
The code below, I think, is what you're looking for.
function hideLetters() {
let squares = document.querySelectorAll("#squarearea div");
for(let i = 0; i < squares.length; i++) {
hide( squares[i] );
});
}
function hide(squares) {
var nodeList = squares.childNodes;
for(let i = 0; i < nodeList.length; i++) {
if (nodeList[i].nodeType === Node.ELEMENT_NODE) {
nodeList[i].style.display = "none";
}
});
}
It's worth noting that you could simply use .children instead of .childNodes to return only the elements of a parent node. I don't know if you had a reason for wanting all nodes to be searched through, but this would condense the iteration down to simply setting the style property:
var nodeList = squares.children;
nodeList.forEach(node => node.style.display = "none");
function hideLetters() {
let squares = document.querySelectorAll("#squarearea div");
for (let i = 0; i < squares.length; i++) {
hide(squares[i]);
};
}
function hide(squares) {
var nodeList = squares.childNodes;
for (let i = 0; i < nodeList.length; i++) {
if (nodeList[i].nodeType === Node.ELEMENT_NODE) {
nodeList[i].style.display = "none";
}
};
}
hideLetters();
#squarearea div {
border: solid 1px black;
width: 10px;
padding: 3px;
margin: 10px;
}
<div id="squarearea">
<div><span>a</span></div>
<div><span>b</span></div>
<div><span>c</span></div>
<div><span>d</span></div>
<div><span>e</span></div>
<div><span>f</span></div>
<div><span>g</span></div>
</div>

JavaScript get list of styles currently applied to an element

List only rendered styles, not arbitrary ones that aren't applied
I've tried many things to get the styles applied to an element but have come up blank.
Please do not cite getComputedStyle as being a solution unless you can solve the junk returns issue.
The primary problem is that window.getComputedStyle(document.querySelector('ANY ELEMENT')).fill will return "rgb(0, 0, 0)", which is not the correct style in almost any instances, and has no apparent way to destinguish if its actually being applied or not.
The above example is not the only problem case; there are tons of rules returned by getComputedStyle which are wrong and will drastically change the look of the page if they are applied.
Static parsing is not an option as there are cases where the .css files are on another server with no cross-origin headers; which also hides styles usually found in document.styleSheets.
Is there any way to get a list of the applied styles and nothing else?
As requested this code will demonstrate the problem (on Chrome):
var all = document.getElementsByTagName('*');
for(var i in all)
if (all[i].style) all[i].style.cssText = window.getComputedStyle(all[i]).cssText;
EDIT: My answer has code which works on all browsers. I keep above to preserve comment thread.
Here are the version that don't need to check depth.
The problem in your code is the assign of inline style in the previous element will affect the getComputedStyle result of the next result. It mean the value of getComputedStyle is always changing in the loop. You can first store it in an array like this.
var all = document.getElementsByTagName('*');
tmpArr = []
for(var i in all) {
if (all[i].style) {
tmpArr[i] = window.getComputedStyle(all[i]).cssText;
}
}
for(var i in all) {
if (all[i].style) {
all[i].style.cssText = tmpArr[i]; ;
}
}
console.log("finish");
You can change tmpArr[i] = window.getComputedStyle(all[i]).cssText; to tmpArr[i] = window.getComputedStyle(all[i]).cssText + "-webkit-text-fill-color:#691099!important"; to test whether it work
It will be slow if you open the inspector since there are too much inline style, but it will solve the problem if all you need is just put the style to be inline style.
Partial Answer (Updated):
It is possible to get only the active styles by calling my function getRenderedStyles:
getRenderedStyles now bypasses active stylesheets for more accurate output.
function getRenderedStyles(element) {
var tmpele, tmpstyle, elestyle, varstyle, elecolor, eletag;
var styles = {};
var defstyle = {};
elestyle = window.getComputedStyle(element);
elecolor = elestyle.color;
eletag = element.tagName;
var frag = document.createDocumentFragment();
frag.appendChild(document.documentElement);
tmpele = document.appendChild(document.createElement(eletag));
tmpstyle = window.getComputedStyle(tmpele);
styles['color'] = elecolor===tmpstyle.color?undefined:elecolor;
tmpele.style.color = elecolor; // workaround for color propagation on other styles
for (var i in tmpstyle)
defstyle[i] = tmpstyle[i];
tmpele.remove();
document.appendChild(frag);
varstyle = element.style;
for (var i in varstyle) {
if ((((typeof varstyle[i])==="string"))&&(i!=="cssText")) {
if ((defstyle[i]!==elestyle[i]))
styles[i] = elestyle[i];
}
}
return styles;
}
Sadly there's a caviat as the browser still seemingly returns invalid styles in some cases. Often shifting the locations of elements.
To verify this you may run the following code, which takes into account parent/child inheritance, in an attempt to properly apply the current styles to the page:
function DOMDepth(element) {
var cur = element;
var deep = 0;
while(cur.parentNode)
deep++, cur = cur.parentNode;
return deep;
}
function getElementsByDepth() {
var all = document.getElementsByTagName('*');
var depth_map = {};
var deepest = 0;
for(var i in all) {
var depth = DOMDepth(all[i]);
deepest = depth>deepest?depth:deepest;
depth_map[depth] = depth_map[depth] || [];
depth_map[depth].push(all[i]);
}
depth_map['deepest'] = deepest;
return depth_map;
}
function inlineComputedStyles() {
var depth_map = getElementsByDepth();
for (var i = depth_map.deepest; i>0; i--) {
var elements = depth_map[i];
for (var j in elements) {
var styles = getRenderedStyles(elements[j]);
for (var k in styles) {
elements[j].style[k] = styles[k];
}
}
}
}
I have tested the preceeding and can confirm it does not suffer the color problems of the snippet in the question. Sadly I am uncertain as to why some elements still shift or if there's a way to fix it.
Special thanks to Kit Fung for pointing out the inheritance problem.

Native Javascript Selections

I need to use native Javascript and for some of these I need to select more than one attribute (ex. a div with a class and id). Here is a code sample of what I've got so far. The example has all single selections.
var $ = function (selector) {
var elements = [];
var doc = document, i = doc.getElementsByTagName("div"),
iTwo = doc.getElementById("some_id"), // #some_id
iThree = doc.getElementsByTagName("input"),
// ^ Lets say I wanted to select an input with ID name as well. Should it not be doc.getElementsByTagName("input").getElementById("idname")
iFour = doc.getElementsByClassName("some_class"); // some_class
elements.push(i,iTwo,iThree,iFour);
return elements;
};
Oh yes, I forgot to mention I cannot use querySelector at all...
It depends on the properties you want to select on. For example, you might pass an object like:
{tagname: 'div', class: 'foo'};
and the function might be like:
function listToArray(x) {
for (var result=[], i=0, iLen=x.length; i<iLen; i++) {
result[i] = x[i];
}
return result;
}
function getByProperties(props) {
var el, elements;
var baseProps = {id:'id', tagName:'tagName'};
var result = [];
if ('tagName' in props) {
elements = listToArray(document.getElementsByTagName(props.tagName));
} else if ('id' in props) {
elements = [document.getElementById(props.id)];
}
for (var j=0, jLen=elements.length; j<jLen; j++) {
el = elements[j];
for (var prop in props) {
// Include all with tagName as used above. Avoids case sensitivity
if (prop == 'tagName' || (props.hasOwnProperty(prop) && props[prop] == el[prop])) {
result.push(el);
}
}
}
return result;
}
// e.g.
getByProperties({tagName:'div', className:'foo'});
However it's a simplistic approach, it won't do things like child or nth selectors.
You can perhaps look at someone else's selector function (there are a few around) and follow the fork to support non–qSA browsers. These are generally based on using a regular expression to tokenise a selector, then apply the selector manually similar to the above but more extensivly.
They also allow for case sensitivity for values (e.g. tagName value) and property names to some extent, as well as map HTML attribute names to DOM property names where required (e.g. class -> className, for -> htmlFor, etc.).

Getting values of global stylesheet in jQuery

When I use a style sheet definition like this on HTML page scope
#sideBar {
float: left;
width: 27.5%;
min-width: 275;
...
}
the following code does NOT return the value of the CSS defined width:
document.getElementById("sideBar").style.width;
In this article a function it is shown retrieving the correct value, when I try to do so it dos not really work cross browser. So I have tried something similar in jQuery but failed.
$("#sideBar").css("width'"); // 1st trial
$("#sideBar").width(); // 2nd trial
I do get the absolute pixel width, not he percentage value 27.5.
Is there a way to retrieve the percentage value as well?
Remark:
Similar (but not the same) to SO Question: get CSS rule's percentage value in jQuery.
var width = ( 100 * parseFloat($("#sideBar").css('width')) / parseFloat($("#sideBar").parent().css('width')) ) + '%';
reference get CSS rule's percentage value in jQuery
here is the fiddle http://jsfiddle.net/jSGTs/
Here is what I have done. Since all approaches did no really work reliable (cross browser etc.), I came across CSS parser/abstracter? How to convert stylesheet into object .
First I was about to use some fully blown CSS parsers such as
JSCSSP
jQuery CSS parser
which are powerful, but also heavyweight. Eventually I ended up with my own little function
// Get the original CSS values instead of values of the element.
// #param {String} ruleSelector
// #param {String} cssprop
// #returns {String} property of the style
exports.getCssStyle = function (ruleSelector, cssprop) {
for (var c = 0, lenC = document.styleSheets.length; c < lenC; c++) {
var rules = document.styleSheets[c].cssRules;
for (var r = 0, lenR = rules.length; r < lenR; r++) {
var rule = rules[r];
if (rule.selectorText == ruleSelector && rule.style) {
return rule.style[cssprop]; // rule.cssText;
}
}
}
return null;
};
When you need the exact value as defined in the global stylesheet you have to access the rules within the style-element.
This is not implemented in jQuery.
IE: rules-collection
Others: CSSRuleList (May be supported by IE8 or 9 too, can't tell you exactly)
There's nothing in jQuery, and nothing straightforward even in javascript. Taking timofey's answer and running with it, I created this function that works for getting any properties you want:
// gets the style property as rendered via any means (style sheets, inline, etc) but does *not* compute values
// domNode - the node to get properties for
// properties - Can be a single property to fetch or an array of properties to fetch
function getFinalStyle(domNode, properties) {
if(!(properties instanceof Array)) properties = [properties]
var parent = domNode.parentNode
if(parent) {
var originalDisplay = parent.style.display
parent.style.display = 'none'
}
var computedStyles = getComputedStyle(domNode)
var result = {}
properties.forEach(function(prop) {
result[prop] = computedStyles[prop]
})
if(parent) {
parent.style.display = originalDisplay
}
return result
}
The trick used here is to hide its parent, get the computed style, then unhide the parent.

Categories

Resources