Getting values of global stylesheet in jQuery - javascript

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.

Related

How can I get the final css property used for a dom node? [duplicate]

Let's say the rule is as follows:
.largeField {
width: 65%;
}
Is there a way to get '65%' back somehow, and not the pixel value?
Thanks.
EDIT: Unfortunately using DOM methods is unreliable in my case, as I have a stylesheet which imports other stylesheets, and as a result the cssRules parameter ends up with either null or undefined value.
This approach, however, would work in most straightforward cases (one stylesheet, multiple separate stylesheet declarations inside the head tag of the document).
Most easy way
$('.largeField')[0].style.width
// >>> "65%"
This is most definitely possible!
You must first hide() the parent element. This will prevent JavaScript from calculating pixels for the child element.
$('.parent').hide();
var width = $('.child').width();
$('.parent').show();
alert(width);
See my example.
Now... I wonder if I'm first to discover this hack:)
Update:
One-liner
element.clone().appendTo('body').wrap('<div style="display: none"></div>').css('width');
It will leave behind a hidden element before the </body> tag, which you may want to .remove().
See an example of one-liner.
I'm open to better ideas!
There's no built-in way, I'm afraid. You can do something like this:
var width = ( 100 * parseFloat($('.largeField').css('width')) / parseFloat($('.largeField').parent().css('width')) ) + '%';
You could access the document.styleSheets object:
<style type="text/css">
.largeField {
width: 65%;
}
</style>
<script type="text/javascript">
var rules = document.styleSheets[0].rules || document.styleSheets[0].cssRules;
for (var i=0; i < rules.length; i++) {
var rule = rules[i];
if (rule.selectorText.toLowerCase() == ".largefield") {
alert(rule.style.getPropertyValue("width"));
}
}
</script>
Late, but for newer users, try this if the css style contains a percentage:
$element.prop('style')['width'];
A jQuery plugin based on Adams answer:
(function ($) {
$.fn.getWidthInPercent = function () {
var width = parseFloat($(this).css('width'))/parseFloat($(this).parent().css('width'));
return Math.round(100*width)+'%';
};
})(jQuery);
$('body').html($('.largeField').getWidthInPercent());​​​​​
Will return '65%'. Only returns rounded numbers to work better if you do like if (width=='65%'). If you would have used Adams answer directly, that hadn't worked (I got something like 64.93288590604027). :)
Building on timofey's excellent and surprising solution, here is a pure Javascript implementation:
function cssDimensions(element) {
var cn = element.cloneNode();
var div = document.createElement('div');
div.appendChild(cn);
div.style.display = 'none';
document.body.appendChild(div);
var cs = window.getComputedStyle
? getComputedStyle(cn, null)
: cn.currentStyle;
var ret = { width: cs.width, height: cs.height };
document.body.removeChild(div);
return ret;
}
Hope it's helpful to someone.
I have a similar issue in Getting values of global stylesheet in jQuery, eventually I came up with the same solution as above.
Just wanted to crosslink the two questions so others can benefit from later findings.
Convert from pixels to percentage using cross multiplication.
Formula Setup:
1.)
(element_width_pixels/parent_width_pixels) = (element_width_percentage / 100)
2.) element_width_percentage =
(100 * element_width_pixels) / parent_width_pixels
The actual code:
<script>
var $width_percentage = (100 * $("#child").width()) / $("#parent").width();
</script>
A late response but wanted to add on for anyone 2020+ who stumbles across this. Might be more for niche cases but I wanted to share a couple options.
If you know what the initial % value is you can also assign these values to variables in the :root of the style sheet. i.e
:root {
--large-field-width: 65%;
}
.largeField {
width: var(--large-field-width);
}
When you want to access this variable in JS you then simply do the following:
let fieldWidth = getComputedStyle(document.documentElement).getPropertyValue('--large-field-width');
// returns 65% rather than the px value. This is because the % has no relative
// size to the root or rather it's parent.
The other option would be to assign the default styling at the start of your script with:
element.style.width = '65%'
It can then be accessed with:
let width = element.style.width;
I personally prefer the first option but it really does depend on your use case. These are both technically inline styling but I like how you can update variable values directly with JS.
You could put styles you need to access with jQuery in either:
the head of the document directly
in an include, which server side script then puts in the head
Then it should be possible (though not necessarily easy) to write a js function to parse everything within the style tags in the document head and return the value you need.
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 to get 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
}
You can use the css(width) function to return the current width of the element.
ie.
var myWidth = $("#myElement").css("width");
See also:
http://api.jquery.com/width/
http://api.jquery.com/css/

Unable to set number in .data() method

I am trying to set a number as my data type dynamically using .data() method in jQuery, but so far no luck. This works using the .attr() method as I have listed in below. Why does the .data() method not work with numbers?
var container = $(this).find('#container'); // element which should have the data
Attempt 1:
container.data(24, "opacity:0;");
Attempt 2:
container.data("24", "opacity:0;");
The following code works using .attr():
container.attr("data-123", 1223);
My personal code:
function loader($div, $page) {
$div.load(siteURL + $page + '/ #container', function() {
var container = $(this).find('#container');
container.data("24", "opacity:0;");
container.attr("data-24", "opacity:0;"); //this works...
});
}
loader($('section#about'), 'about');
UPDATE: Here is a jsFiddle
Historically jQuery supported the data() method by keeping track of values set using it in a separate data structure. This allowed you do store things like objects using the API.
While retrieving data, the API will check both the data attribute as well as well as its internal store for the value.
Setting, however still goes straight to the internal store.
Since the question has changed significantly:
$.data will fail when sending a number.
Doing this in the console you will see the following (none of these affect the markup of the element itself):
// Error
$('div').data(24, 'foo')
TypeError: Object 24 has no method 'replace'
// Success
$('div').data("24", 'foo')
b.fn.b.init[966]
$('div').data("24")
"foo"
// Success
$('div').data("24", 24)
b.fn.b.init[966]
$('div').data("24")
24
None of these will affect a data attribute on the element itself. The end result of the markup will be:
<div>Hello</div>
If you are looking to set a data-xxx attribute on the element, or any attribute for that matter, an elements attribute must begin with an alpha character:
// Error
$('div').attr("24", "opacity:0")
InvalidCharacterError: An invalid or illegal character was specified, such as in an XML name.
// Success
$('div').attr("data-24", "opacity:0")
b.fn.b.init[966]
The end result of the successful call will be:
<div data-24="opacity:0">Hello</div>
skrollr does not use jQuery's .data function it parses the DOM elements attribute list which is why using .attr("data-24" works as the attribute is added to the DOM attribute list
.data("24","somevalue") does not update the DOM elements attribute list,
.attr("data-24","somevalue") however does update the DOM elemetns attribute list which allows skrollr to parse the new style.
FROM SKROLLR.JS
Starting at Line 343:
//Iterate over all attributes and search for key frame attributes.
var attributeIndex = 0;
var attributesLength = el.attributes.length;
for (; attributeIndex < attributesLength; attributeIndex++) {
var attr = el.attributes[attributeIndex];
if(attr.name === 'data-anchor-target') {
anchorTarget = document.querySelector(attr.value);
if(anchorTarget === null) {
throw 'Unable to find anchor target "' + attr.value + '"';
}
continue;
}
//Global smooth scrolling can be overridden by the element attribute.
if(attr.name === 'data-smooth-scrolling') {
smoothScrollThis = attr.value !== 'off';
continue;
}
//Global edge strategy can be overridden by the element attribute.
if(attr.name === 'data-edge-strategy') {
edgeStrategy = attr.value;
continue;
}
var match = attr.name.match(rxKeyframeAttribute);
if(match === null) {
continue;
}
var constant = match[1];
//If there is a constant, get it's value or fall back to 0.
constant = constant && _constants[constant.substr(1)] || 0;
//Parse key frame offset. If undefined will be casted to 0.
var offset = (match[2] | 0) + constant;
var anchor1 = match[3];
//If second anchor is not set, the first will be taken for both.
var anchor2 = match[4] || anchor1;
var kf = {
offset: offset,
props: attr.value,
//Point back to the element as well.
element: el
};
keyFrames.push(kf);
//"absolute" (or "classic") mode, where numbers mean absolute scroll offset.
if(!anchor1 || anchor1 === ANCHOR_START || anchor1 === ANCHOR_END) {
kf.mode = 'absolute';
//data-end needs to be calculated after all key frames are know.
if(anchor1 === ANCHOR_END) {
kf.isEnd = true;
} else {
//For data-start we can already set the key frame w/o calculations.
//#59: "scale" options should only affect absolute mode.
kf.frame = offset * _scale;
delete kf.offset;
}
}
//"relative" mode, where numbers are relative to anchors.
else {
kf.mode = 'relative';
kf.anchors = [anchor1, anchor2];
}
}
container.data("24", "opacity:0;"); works right? First parameter must be a String (as per jQuery spec).
Because the data() method expects a string.
Hence I guess it'll be breaking if you pass it a number!
Can you try using the jQuery object itself:
var container = $(this).find('#container');
jQuery.data(container, "24", "opacity:0;");
alert("24 is equal to: " + jQuery.data(container, "24") );
You should note this will not affect the DOM as it uses jQuery's local storage.
JSFiddle: http://jsfiddle.net/markwylde/8Q5Yh/1/
container.data("24") actually works for me.
It depends on the version of jQuery too. When you call .data as a setter, all versions will call either .split or .replace which are String methods, not Number methods. Using .data(24) as an accessor seems to work past version 1.8.
This may also be browser dependent as dataset is not available in some browsers.
My advice would be to use a descriptive name for the data rather than just a number (unless you're talking about form 24 or something, but then why not use form24?)
EDIT: Using .data does not alter the HTML if the dataset attribute is available on the element. Using .attr always sets an attribute which will alter the HTML. This has nothing to do with using strings vs. numbers.

Why is the property opacity available for reading but not font-size?

If I want to read the opacity value in to javacript I can just use
element.style.opacity
but if I want the fontSize I have to use the function below.
function findFontSize( element_id )
{
var element = document.getElementById( element_id );
// var theCSSprop = element.style.fontSize; // Does not work
// var theCSSprop = element.getPropertyValue("font-size"); // Does not work
var theCSSprop = window.getComputedStyle( element, null ).getPropertyValue("font-size"); // This works
alert( theCSSprop );
}
Related
http://jsfiddle.net/tUc5v/
Why is this?
There is a different syntax for explicitly defined css styles and inherited styles. I'm guessing (though your jsfiddle doesn't match the question) that opacity is being explicitly set, but fontSize is inherited.
UPDATE:
Found this old comment, thought I'd give a little more...
If an element does not have a style explicitly defined in a stylesheet or inline then it falls back to the computed style which is not accessible via the element.style.property way.
Another difference is, explicit styles on the style object are camelCase, but computed styles are hyphen-case.
Another thing to note is that properties accessed via the style object are 3x-4x faster than window.getComputedStyle (or document.defaultView.getComputedStyle).
Here's a basic function that can do this for any style (it doesn't check for incorrect input, etc..)
/**
*
* #param el Element
* #param CSS property in hyphen case
* #param pseudo pseudo selector (optional, e.g. '::before')
*/
function getStyleValue(el, property, pseudo) {
// convert hyphen-case to camelCase
const elStyle = el.style[property.replace(/(\-[a-z])/g, $1 => $1.toUpperCase().replace('-',''))];
return ((elStyle !== '' && !pseudo)
? elStyle
: window.getComputedStyle(el, pseudo).getPropertyValue(property));
}

Select Element By CSS style (all with given style)

Is there a way to select all elements that have a given style using JavaScript?
Eg, I want all absolutely positioned elements on a page.
I would assume it is easier to find elements by style where the style is explicitly declared:
the style is non-inherited (such as positioning)
the style is not the default (as would be position:static).
Am I limited to those rules? Is there a better method for when those rules apply?
I would happily to use a selector engine if this is provided by one (ideally Slick - Mootools 1.3)
EDIT:
I came up with a solution that will only work with above rules.
It works by cycling through every style rule, and then selector on page.
Could anyone tell me if this is better that cycling through all elements (as recommended in all solutions).
I am aware that in IE I must change the style to lowercase, but that I could parse all styles at once using cssText. Left that out for simplicity.
Looking for best practice.
var classes = '';
Array.each(documents.stylesheets, function(sheet){
Array.each(sheet.rules || sheet.cssRules, function(rule){
if (rule.style.position == 'fixed') classes += rule.selectorText + ',';
});
});
var styleEls = $$(classes).combine($$('[style*=fixed]'));
You can keep Mootools, or whatever you use... :)
function getStyle(el, prop) {
var view = document.defaultView;
if (view && view.getComputedStyle) {
return view.getComputedStyle(el, null)[prop];
}
return el.currentStyle[prop];
}
​function getElementByStyle(style, value, tag)​ {
var all = document.getElementsByTagName(tag || "*");
var len = all.length;
var result = [];
for ( var i = 0; i < len; i++ ) {
if ( getStyle(all[i], style) === value )
result.push(all[i]);
}
return result;
}
For Mootools:
var styleEls = $$('*').filter(function(item) {
return item.getStyle('position') == 'absolute';
});
In jQuery you could use
$('*').filter( function(){
return ($(this).css('position') == 'absolute');
} );
[update]
Or even create a new selector.
got me interested and so here is one (its my 1st, so its not built for efficiency) to find elements by css property..
$.expr[':'].css = function(obj, index, meta, stack){
var params = meta[3].split(',');
return ($(obj).css(params[0]) == params[1]);
};
usage: $('optionalSelector:css(property,value)')
will return all elements (of optionalSelector) whose property = value
example: var visibleDivs = $('div:css(visibility,visible)');
will return all divs whose visibility is set to visible (works for the default visibility as well..)
There is no selector for CSS attributes, so you're pretty much stuck to looping through each element and checking it's position. Here's a jQuery method:
$("*").each(function() {
var pos = $(this).css('position');
if(pos == "absolute") {
// do something
}
else if (pos == "relative") {
// do something else
}
});
You can use Case statements instead of if/else as well.
Other than this solution, there is no selector per se that can search by CSS attributes (unless they were inline, maybe).

Why does this function work for literals but not for more complex expressions?

I am having some insidious JavaScript problem that I need help with. I am generating HTML from a JSON structure. The idea is that I should be able to pass a list like:
['b',{'class':'${class_name}'}, ['i', {}, 'Some text goes here']]
...and get (if class_name = 'foo')...
<b class='foo'><i>Some text goes here.</i></b>
I use the following functions:
function replaceVariableSequences(str, vars) {
/* #TODO Compiling two regexes is probably suboptimal. */
var patIdent = /(\$\{\w+\})/; // For identification.
var patExtr = /\$\{(\w+)\}/; // For extraction.
var pieces = str.split(patIdent);
for(var i = 0; i < pieces.length; i++) {
if (matches = pieces[i].match(patExtr)) {
pieces[i] = vars[matches[1]];
}
}
return pieces.join('');
}
function renderLogicalElement(vars, doc) {
if (typeof(doc[0]) == 'string') {
/* Arg represents an element. */
/* First, perform variable substitution on the attribute values. */
if (doc[1] != {}) {
for(var i in doc[1]) {
doc[1][i] = replaceVariableSequences(doc[1][i], vars);
}
}
/* Create element and store in a placeholder variable so you can
append text or nodes later. */
var elementToReturn = createDOM(doc[0], doc[1]);
} else if (isArrayLike(doc[0])) {
/* Arg is a list of elements. */
return map(partial(renderLogicalElement, vars), doc);
}
if (typeof(doc[2]) == 'string') {
/* Arg is literal text used as innerHTML. */
elementToReturn.innerHTML = doc[2];
} else if (isArrayLike(doc[2])) {
/* Arg either (a) represents an element
or (b) represents a list of elements. */
appendChildNodes(elementToReturn, renderLogicalElement(vars, doc[2]));
}
return elementToReturn;
}
This works beautifully sometimes, but not others. Example from the calling code:
/* Correct; Works as expected. */
var siblings = findChildElements($('kv_body'), ['tr']);
var new_id = 4;
appendChildNodes($('kv_body'),
renderLogicalElement({'id': new_id},
templates['kveKeyValue']));
/* Incorrect; Substitutes "0" for the expression instead of the value of
`siblings.length` . */
var siblings = findChildElements($('kv_body'), ['tr']);
var new_id = siblings.length; // Notice change here!
appendChildNodes($('kv_body'),
renderLogicalElement({'id': new_id},
templates['kveKeyValue']));
When I trap out the first argument of renderLogicalElement() using alert(), I see a zero. Why is this?? I feel like it's some JavaScript type thing, possibly having to do with object literals, that I'm not aware of.
Edit: I have this code hooked up to the click event for a button on my page. Each click adds a new row to the <tbody> element whose ID is kv_body. The first time this function is called, siblings is indeed zero. However, once we add a <tr> to the mix, siblings.length evaluates to the proper count, increasing each time we add a <tr>. Sorry for not being clearer!! :)
Thanks in advance for any advice you can give.
If new_id is 0, doesn't it mean that siblings.length is 0? Maybe there is really no sibling.
Perhaps siblings.length is actually 0? Try debugging further (e.g. with Firebug)
OK, I fixed it. As it turns out, I was modifying my source JSON object with the first function call (because in JS you are basically just passing pointers around). I needed to write a copy function that would make a new copy of the relevant data.
http://my.opera.com/GreyWyvern/blog/show.dml/1725165
I ended up removing this as a prototype function and just making a regular old function.

Categories

Resources