Is it possible to compute resulting css style on the element manually (without need to render it)?
Lets say I'm supposed to have an HTML structure:
<p style="some_style1">
<span style="some_style2">
<span style="some_style3">
TEXT
</span>
</span>
</p>
I know what are some_style1, some_style2, some_style3 in terms of JS object (for example i have data for each element like: {font: 'Times New Roman' 12px bold; text-align: center;})
I want to MANUALLY (without need to render in browser the whole structure) compute resulting style that will effect "TEXT".
What algorithm (or solution) should I use?
There exist browsers that don't need rendering in a window (headless browser). You can load a page and query what you want. It won't be easier than in a normal browser to obtain what you ask though.
JSCSSP is a CSS parser written in cross-browser JavaScript that could be a first step to achieve what you want from scratch or quite. Give it a stylesheet and it'll tell you what a browser would've parsed. You still must manage:
the DOM,
inheritance of styles,
determine which rules apply to a given element with or without class, id, attributes, siblings, etc
priorities of selectors
etc
Its author is D. Glazman, co-chairman of the W3C CSS group and developer of Kompozer, NVu and BlueGriffon so it should parse CSS as expected :)
The simplest thing I can think of is to wrap the whole thing in a a container that you set display: none on, and append it to the DOM. The browser won't render it, but you'll then be able to query the computed style.
Here's an example showing how jQuery can't find the style information when the structure isn't connected to the DOM, but when it is, it can:
jQuery(function($) {
// Disconnected structure
var x = $("<p style='color: red'><span style='padding: 2em'><span style='background-color: white'>TEXT</span></span></p>");
// Get the span
var y = x.find("span span");
// Show its computed color; will be blank
display("y.css('color'): " + y.css('color'));
// Create a hidden div and append the structure
var d = $("<div>");
d.hide();
d.append(x);
d.appendTo(document.body);
// Show the computed color now; show red
display("y.css('color'): " + y.css('color'));
// Detach it again
d.detach();
function display(msg) {
$("<p>").html(String(msg)).appendTo(document.body);
}
});
Live copy | source
I can't guarantee all values will be exactly right, you'll have to try it and see; browsers may defer calculating some things until/unless the container is visible. If you find that some properties you want aren't calculated yet, you may have to make the div visible, but off-page (position: absolute; left: -10000px);
I found some articles about this: Can jQuery get all styles applied to an element on Stackoverflow.
Also this one on quirksmode: Get Styles that shows the following function:
function getStyle(el,styleProp)
{
var x = document.getElementById(el);
if (x.currentStyle)
var y = x.currentStyle[styleProp];
else if (window.getComputedStyle)
var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(styleProp);
return y;
}
This allows you to query for style properties
Styles override each other in the order in which they're defined: So anything in some_style3 that overrides the same selector in some_style2, say, will do. Otherwise, it will just be a union of the sets of selectors.
EDIT Some selectors won't override, but instead act relatively on a previous definition, so you've got to be careful about that.
Related
I'm trying to write a JS function that would give empty heading elements (h1, and h2...)a “role” attribute value of “presentation”.
This is my first time working with accessibility in my projects and would love some help!
If they're empty, do they need to be there at all? The most correct thing is just to remove them.
However, you can use document.querySelectorAll() to get all the headings, then look inside each one to see whether they are empty. If they are, you can set the role attribute. The following code is very quick and dirty, but will get you some of the way.
var headings = document.querySelectorAll("h1,h2,h3,h4,h5,h6");
// iterate through each heading
Array.prototype.forEach.call (headings, function (node) {
// remove all white space
var theTextContent = node.textContent.replace(/\s/g,'');
// see if there's anything left in the string
if (theTextContent.length < 1) {
// node contains no visible text, mark it as presentation
node.setAttribute("role", "presentation");
}
} );
BUT this is a risky heuristic. Some headings might not contain text nodes, yet still appear as text on screen (e.g. they may have a background image in CSS representing a text in bitmap form). Instead of adding role="presentation" to these, you absolutely should add an aria-label with the correct heading text, otherwise you'll be violating at least two WCAG success criteria. ("Images of Text" and "Headings and Labels").
If you were using style attributes it might look something like this:
<h1 aria-label="welcome" style="background:url(welcome.png);"></h1>
I am trying to add an ellipsis to the mid-point in a string with the following complications:
I don't know how long the string is going to be
I only know the max-width and min-width of the parent element
The string may or may not fit into it's parent and not require ellipses
I have a plunk here to illustrate it. The script only assumes one instance, but you should get the idea:
(function(){
// variables
var parent = document.querySelectorAll(".wrapper")[0],
parentWidth = parent.clientWidth,x = 0, elem, hellip
txtStr = document.querySelector("#shorten"),
strWidth = txtStr.clientWidth,
strTxt = txtStr.innerText,
ending = document.createElement("span"),
endTxt = strTxt.slice(Math.max(strTxt.length - (strTxt.length / 4))) || endTxt;
txtStr.style.overflow = "hidden"
txtStr.style.textOverflow = "ellipsis"
ending.appendChild(document.createTextNode(endTxt))
ending.classList.add("ellipsis")
document.querySelectorAll(".wrapper")[0].appendChild(ending)
var ell = function(a, b){
if (a <= b){
ending.classList.add("visible")
}
else {
ending.classList.remove("visible")
}
}
ell(parentWidth, strWidth) // We need to display any changes immediately
window.onresize = function(){ // if the window is resized, we also need to display changes
hellip = document.querySelectorAll(".ellipsis")[0].clientWidth
parentWidth = parent.clientWidth
// the use of 'calc()' is because the length of string in px is never known
txtStr.style.width = "calc(100% - " + hellip + "px"
ell(parentWidth, strWidth)
}
})();
It's a bit clunky, but demonstrates the idea.
The issue I am having in React 16, is that the string is not rendered at the point I need to measure it to create the bit of text at the end. Therefore, when the new node is created it has no dimensions and cannot be measured as it doesn't exist in the DOM.
The functionality works - sort of as the screen resizes, but that's beside the point. I need to get it to do the do at render time.
The actual app is proprietary, and I cannot share any of my code from that in this forum.
EDIT: Another thing to bare in mind (teaching to suck eggs, here) is that in the example, the script is loaded only after the DOM is rendered, so all of the information required is already there and measurable.
Thank you to all that looked at this, but I managed to figure out the problem.
Once I worked out the finesse of the lifecycle, it was actually still quite tricky. The issue being measuring the original string of text. Looking back now, it seems insignificant.
Essentially, I pass a few elements into the component as props: an id, any required padding, the length of the ending text required for context and the text (children).
Once they are in, I need to wait until it is mounted until I can do anything as it all depends on the DOM being rendered before anything can be measured. Therefore, componentDidMount() and componentDidUpdate() are the stages I was interested in. componentWillUnmount() is used to remove the associated event listener which in this instance is a resize event.
Once mounted, I can get the bits required for measuring: the element and importantly, its parent.
getElements(){
return {
parent: this.ellipsis.offsetParent,
string: this.props.children
}
}
Then, I need to make sure that I can actually measure the element so implement some inline styles to allow for that:
prepareParentForMeasure(){
if(this.getElements().parent != null){
this.getElements().parent.style.opacity = 0.001
this.getElements().parent.style.overflow = 'visible'
this.getElements().parent.style.width = 'auto'
}
}
As soon as I have those measurements, I removed the styles.
At this point, the script will partially work if I carry on down the same path. However, adding an additional element to work as a guide is the kicker.
The returned element is split into three elements (span tags), each with a different purpose. There is the main bit of text, or this.props.children, if you like. This is always available and is never altered. The next is the tail of the text, the 'n' number of characters at the end of the string that are used to contextually display the end of the string - this is given a class of 'ellipsis', although the ellipsis is actually added to the original and first element. The third is essentially exactly the same as the first, but is hidden and uninteractable, although it does have dimensions. This is because the first two - when rendered - have different widths and cannot be relied upon as both contribute to the width of the element, whereas the third doesn't.
<Fragment>
<span className='text'>{this.props.children}</span>
<span className='ellipsis'>{this.tail()}</span>
<span className='text guide' ref={node => this.ellipsis = node}>
{this.props.children}</span>
</Fragment>
These are in a fragment so as to not require a surrounding element.
So, I have the width of the surrounding parent and I have the width of the text element (in the third span). which means that if I find that the text string is wider than the surrounding wrapper, I add a class to the ellipsis span of 'visible', and one to the 'text' element of 'trimmed', I get an ellipsis in the middle of the string and I use the resize event to make sure that if someone does do that, all measurements are re-done and stuff is recalculated and rendered accordingly.
I need to check if a CSS rule exists because I want to issue some warnings if a CSS file is not included.
What is the best way of doing this?
I could filter through window.document.styleSheets.cssRules, but I'm not sure how cross-browser this is (plus I notice on Stack Overflow that object is null for styleSheet[0]).
I would also like to keep dependencies to a minimum.
Is there a straightforward way to do this? Do I just have to create matching elements and test the effects?
Edit: If not, what are the cross-browser concerns of checking window.document.styleSheets?
I don't know if this is an option for you, but if it's a single file you want to check, then you can write your error message and toggle the style to hide it in that file.
<span class="include_error">Error: CSS was not included!</span>
CSS file:
.include_error {
display: none;
visibility: hidden;
}
I test for proper CSS installation using javascript.
I have a CSS rule in my stylesheet that sets a particular id to position: absolute.
#testObject {position: absolute;}
I then programmatically create a temporary div with visibility: hidden with that ID and get the computed style position. If it's not absolute, then the desired CSS is not installed.
If you can't put your own rule in the style sheet, then you can identify one or more rules that you think are representative of the stylesheet and not likely to change and design a temporary object that should get those rules and test for their existence that way.
Or, lastly, you could try to enumerate all the external style sheets and look for a particular filename that is included.
The point here is that if you want to see if an external style sheet is included, you have to pick something about that style sheet that you can look for (filename or some rule in it or some effect it causes).
Here is what I got that works. It's similar to the answers by #Smamatti and #jfriend00 but more fleshed out. I really wish there was a way to test for rules directly but oh well.
CSS:
.my-css-loaded-marker {
z-index: -98256; /*just a random number*/
}
JS:
$(function () { //Must run on jq ready or $('body') might not exist
var dummyElement = $('<p>')
.hide().css({height: 0, width: 0})
.addClass("my-css-loaded-marker")
.appendTo("body"); //Works without this on firefox for some reason
if (dummyElement.css("z-index") != -98256 && console && console.error) {
console.error("Could not find my-app.css styles. Application requires my-app.css to be loaded in order to function properly");
}
dummyElement.remove();
});
I would use a css selector like this from within your jquery widget.
$('link[href$="my-app.css"]')
If you get a result back it means there is a link element that has a href ending with "my-app.css"
Next use this function to validate a specific css property on an element you are depending on. I would suggest something specific to you styles like the width of a container rather something random like -9999 zindex
var getStyle = function(el, styleProp) {
var x = !!el.nodeType ? el : document.getElementById(el);
if (x.currentStyle)
var y = x.currentStyle[styleProp];
else if (window.getComputedStyle)
var y = document.defaultView.getComputedStyle(x, null).getPropertyValue(styleProp);
return y;
}
Like this
getStyle($('#stats-container')[0], "width")
or
getStyle("stats-container", "width")
If you are worried about not being able to edit other people's stylesheets, you can proxy them through a stylesheet of your own, using import
#import url('http://his-stylesheet.css');
.hideErrorMessage{ ... }
This is enough if you just want to know if your code is trying to load the stylesheet but won't help if you need to know if the foreign stylesheet was then loaded correctly.
What I need is to iterate over the dom at some start element and then go through all elements below the start element.
Here is what I was doing so far.
function iterDomFromStartElem = function(startElem, callBackFunc) {
if (startElem !== null) {
var items = startElem.getElementsByTagName("*");
for (var i = 0; i < items.length; i++) {
callBackFunc(items[i]);
}
}
}
The reason why I need to iterate over the dom from some start element is because our team recently got a request to implement font resizing; however, we developed are site statically with font-size in many different places using pixels. I realize that the easier approach would be to refactor the existing code, set a static font size at the root of the page, and use em's/percentages else where, so that if the business owner wanted to have a resize control on the pages, all we would have to do is increase the font-size in one spot. This refactor would require many hours, and i have been tasked with this using the least amount of man hours.
So, then, I have a call back defined like so,
function resizeFont(startElem, scale) {
iterDomFromStartElem(startElem, function(node) {
// get current size of node, apply scale, increase font size
}
}
Using this raw javascript would work but i'm having trouble getting font-size if its declared inside a css class.
I know that jquery has a css property and if I had a jquery object I could do $(this).css(....), so,
when I call callBackFunc(items[i]), how can I convert the items[i] into a jquery object so that in my call back function, I can do node.css(......)?
I guess I could do $(items[i].id), perhaps that would be the simplest.
Is there an easier way with javascript to determine the font size even if that font size is declared in a css class and that css class is attached to the element?
Preface: I think you're better off fixing the problem properly. You might save an hour or two now by taking a shortcut, but it's likely to cost you in the long term.
But re your actual question:
how can I convert the items[i] into a jquery object so that in my call back function, I can do node.css(......)?
If you pass a raw DOM object into $(), jQuery will return a wrapper around it. You don't have to go via the ID.
You can also get a jQuery instance for all descendant elements of a given starting point, like this:
var x = $("#starting_point *");
...although you'd still end up creating a lot of temporary objects if you then looped through it, like this:
$("#starting_point *").each(function() {
// Here, `this` is the raw DOM element
});
Here's an example of looping all elements under a given starting point with jQuery, in this case showing their tag and id (if any) and turning them blue (live copy):
$("#start *").each(function() {
display(this.tagName + "#" + (this.id || "?"));
$(this).css("color", "blue");
});
Note I said under. If you also want to include #start, the selector changes to #start, #start *.
Here's a complete example of increasing the font size of elements starting with (and including) a given start point, where the font size is variously set by inline and stylesheet styles (live copy):
CSS:
.x13 {
font-size: 13px;
}
.x17 {
font-size: 17px;
}
.x20 {
font-size: 20px;
}
HTML:
<input type="button" id="btnBigger" value="Bigger">
<div id="start" class="x13">
This is in 13px
<p style="font-size: 15px">This is in 15px
<span class="x17">and this is 17px</span></p>
<ul>
<li id="the_list_item" style="10px">10px
<strong style="font-size: 8px">8px
<em class="x20">five</em>
</strong>
</li>
</ul>
</div>
JavaScript:
jQuery(function($) {
$("#btnBigger").click(function() {
$("#start, #start *").each(function() {
var $this = $(this),
fontSize = parseInt($this.css("font-size"), 10);
display("fontSize = " + fontSize);
$this.css("font-size", (fontSize + 2) + "px");
});
});
function display(msg) {
$("<p>").html(msg).appendTo(document.body);
}
});
I have an element in my document that has a background color and image set through a regular CSS rule.
When a certain event happens, I want to animate that item, highlighting it (I'm using Scriptaculous, but this question applies to any framework that'll do the same).
new Effect.Highlight(elHighlight, { startcolor: '#ffff99', endcolor: '#ffffff', afterFinish: fnEndOfFadeOut });
The problem i'm facing is that after the animation is done, the element is left with the following style (according to FireBug):
element.style {
background-color:transparent;
background-image:none;
}
Which overrides the CSS rule, since it's set at the element level, so I'm losing the background that the item used to have...
What I'm trying to do is, in the callback function I'm running after the animation is done, set the style properties to a value that'll make them "go away".
var fnEndOfFadeOut = function() {
elHighlight.style.backgroundColor = "xxxxx";
elHighlight.style.backgroundImage = "xxxxx";
}
What I'm trying to figure out is what to put in "xxxx" (or how to do the same thing in a different way).
I tried 'auto', 'inherit', and '' (blank string), and neither worked (I didn't really expect them to work, but I'm clueless here).
I also tried elHighlight.style = ""; which, expectably, threw an exception.
What can I do to overcome this?
I know I can put a span inside the element that I'm highlighting and highlight that span instead, but I'm hoping I'll be able to avoid the extra useless markup.
Chances are you're not setting the style on the correct element. It's probably being set somewhere up the line in a parent node.
elHighlight.style.backgroundColor = "";
elHighlight.style.backgroundImage = "";
You can also remove all the default styling by calling:
elHighlight.style.cssText = "";
In any case, you'll still have to do this on the specific element that is setting these properties, which means you may need to do a recursion on parentNode until you find it.
Try
elHighlight.style.removeProperty('background-color')
elHighlight.style.removeProperty('background-image')
have you tried elHightlight.style.background = "";?
I have a highlighter code on my site and this works
function highlight(id) {
var elements = getElementsByClass("softwareItem");
for (var ix in elements){
elements[ix].style.background = ""; //This clears any previous highlight
}
document.getElementById(id).style.background = "#E7F3FA";
}
An HTML element can have multiple CSS classes. Put your highlight information inside a CSS class. Add this class to your element to highlight it. Remove the class to undo the effect.