Break CSS into rules - javascript

I have the content of a CSS file as a string I got with an AJAX request.
I need to break it into rules that I can feed into styleSheet.insertRule
What is the best way to achieve this correctly and efficiently ? I do not want to include/write a full CSS parser just for that if possible ...
I considered simply splitting on '}' but this will not work if there are #media clauses which created nested curly brackets, and I am not sure it is correct otherwise ...
Thanks!

You can simply create a new <style> element using the stylesheet text you have and insert it into the <head> element.
var style = `
div {
color: blue;
}`;
setTimeout(function() {
var elem = document.createElement("style");
elem.innerHTML = style;
document.head.appendChild(elem);
}, 1000);
<div>
Some text
</div>

The solution from Nisarg Shah probably does the job, but here's an alternative. It uses the css.js library to parse the CSS string into a JS array, which can then be inserted using stylesheet.insertRule().
The two advantages this method has:
It breaks the CSS into rules as stated in the question
It would allow styles to be filtered or altered before insertion
Also, this is a simplified solution that will not work with more complicated CSS containing media queries or similar rules. For a more robust solution, the function would need to handle those types of style rules, and more info on how to do that can be found in the MDN docs.
Here's the approach:
Get the CSS from your API
Parse the CSS into a JS array of objects
Loop through the CSS rules and create the CSS style string
Add the style string to the stylesheet with insertRule()
var cssString = `
div {
color: green;
font-size: 24px;
}`;
setTimeout(function() {
var parsedStyles = addStylesAndReturnParsedStyles(cssString);
console.log(parsedStyles)
}, 1000);
var addStylesAndReturnParsedStyles = function(cssString) {
var parser = new cssjs();
var parsed = parser.parseCSS(cssString);
var styleEl = document.createElement('style'),
styleSheet;
// Append style element to head
document.head.appendChild(styleEl);
// Grab style sheet
styleSheet = styleEl.sheet;
parsed.forEach(style => {
var selector = style.selector;
var rules = style.rules;
var propStr = '';
for (var i = 0; i < rules.length; i++) {
var prop = rules[i];
propStr += prop.directive + ':' + prop.value + ';\n';
}
// Insert CSS Rules
styleSheet.insertRule(selector + '{' + propStr + '}', styleSheet.cssRules.length);
});
return parsed;
};
<script src="https://cdn.rawgit.com/jotform/css.js/master/css.min.js"></script>
<div>Here's some content</div>

Related

How to get startOffset/endOffset of selected html text?

I'm trying to wrap selected text from a "contenteditable" div in a given tag. Below seems to be working ok but startOffset/endOffset doesn't include HTML text. My question is how do I get the Range object to count the html tags if they exist in the selection?
getSelectedText: function() {
var range;
if (window.getSelection) {
range = window.getSelection().getRangeAt(0);
return [range.startOffset, range.endOffset];
}
}
toggleTagOnRange: function(range, tag, closeTag) {
var removeExp, val;
if (closeTag == null) {
closeTag = tag;
}
val = this.get("value");
removeExp = RegExp("<" + tag + ">(.+)</" + closeTag + ">");
if (removeExp.test(val)) {
this.set("value", val.replace(removeExp, function(match, $1) {
return $1;
}));
} else {
if (range.length > 1) {
val = val.splice(range[1], "</" + closeTag + ">").splice(range[0], "<" + tag + ">");
this.set("value", val);
}
}
return this.get("val");
}
// this is called from a bold button click handler.
this.toggleTagOnSelection(this.getSelectedText(), 'strong');
Interested in other solutions if you've got them.
Honestly, things can get pretty nasty when trying to write code for this type of thing yourself. There are a lot of cases you need to cover, like when you're selecting text across multiple <p> tags for example. You don't need to reinvent the wheel. Look into a library like rangy where they have already taken care of the nitty gritty details. Specifically for your situation, if you can get by with using CSS styles instead of using tag elements like <strong>, look into the CSS Class Applier Module, which allows you to do this simply by doing:
var cssApplier = rangy.createCssClassApplier("someClass", {normalize: true});
cssApplier.toggleSelection();
Where .someClass is a CSS class containing whatever styles you need to apply.

changing CSS class definition

Suppose I have this class:
.MyClass{background:red;}
This class applies to several divs. I want to change the color of the background to orange by changing the color defined in MyClass.
Now, I know I could do $('.MyDiv').css('background', 'orange');
But my question is really this: how do I change the CSS class definition so that MyClass elements now have background:orange;? I want to be able to change several CSS color properties from one color to another.
Thanks.
Actually altering your stylesheet is pretty challenging. Much more easily, though, you can switch out your stylesheet for a different one, which may be sufficient for your purposes. See How do I switch my CSS stylesheet using jQuery?.
For actually altering the stylesheet content, How to change/remove CSS classes definitions at runtime? will get you started.
It is difficult to find the rule you want because you have to iterate through the document.styleSheets[i].cssRules array. (and compare your class name with the selectorText attribute)
So my solution to this problem is to add a new CSS class, remove the old CSS class from the HTML element and add this class instead of it.
var length = getCssRuleLength();
var newClassName = "css-class-name" + length;
//remove preview css class from html element.
$("#your-html-element").removeClass("css-class-name");
$("#your-html-element").removeClass("css-class-name" + (length-1));
$("#your-html-element").addClass(newClassName);
//insert a css class
insertCssRule("." + newClassName + ' { max-width: 100px; }', length);
function getCssRuleLength() {
var length = 0;
if (document.styleSheets[1].cssRules) {
length = document.styleSheets[1].cssRules.length;
} else if (document.styleSheets[1].rules) { //ie
length = document.styleSheets[1].rules.length;
}
return length;
}
function insertCssRule(rule, index) {
if (document.styleSheets[1].cssRules) {
document.styleSheets[1].insertRule(rule, index);
} else if (document.styleSheets[1].rules) { //ie
document.styleSheets[1].addRule(rule, index);
}
}
Here's my answer in case anyone stumbles upon this. Give your elements a new class name that doesn't already exist, then dynamically add a style segment:
var companyColor = 'orange' //define/fetch the varying color here
var style = '<style>.company-background {background-color: ' + companyColor + '; color: white;}</style>';
$('html > head').append($(style));
//give any element that needs this background color the class "company-background"
You have 2 options
add a new stylesheet that overrides this .MyClass
have a second class with the different property, and change the class Name on these elements
Looking at your question, I think a better approach is to switch MyClass with something else using JavaScript rather than to change the properties of the class dynamically.
But if you are still keen you can switch CSS stylesheets with jQuery http://www.cssnewbie.com/simple-jquery-stylesheet-switcher/
var changeClassProperty = function(sheetName, className, propertyName, newValue, includeDescendents) {
var ending = '$';
setValue = '';
if (includeDescendents === true) {
ending = '';
}
if (typeof(newValue) != 'undefined') {
setValue = newValue;
}
var list = document.styleSheets;
for (var i = 0, len = list.length; i < len; i++) {
var element = list[i];
if (element['href'] && element['href'].match(new RegExp('jquery\.qtip'))) {
var cssRules = element.cssRules;
for (j = 0, len2 = cssRules.length; j < len2; j++) {
var rule = cssRules[j];
if (rule.selectorText.match(new RegExp(className + ending))) {
cssRules[j].style.backgroundColor = setValue;
console.log(cssRules[j].style.backgroundColor);
}
}
}
}
}
changeClassProperty('jquery.qtip', 'tipsy', 'backgroundColor', 'yellow');
You'd be much better off adding and removing classes instead of attempting to change them.
For example
.red {
background: red;
}
.orange {
background: orange;
}
$('#div').click(function(){
$(this).removeClass('red').addClass('orange');
});

Is there any way to reset :after/:before CSS rules for an element?

Is there any way to (robustly) reset any possible :after and :before CSS rules for a newly created element?
Usually you can just set the style rules you want to reset on the element directly (with !important if you want to be sure), but I don't know of any way of changing rules defined in :after on the element only.
(Only has to work with Chrome, if at all possible.)
An example at jsFiddle.
The content added with the :before/:after rules is affecting the value returned by clientHeight.
There is a DOM2 API for that matter. The correct way to do this is
document.getOverrideStyle(p, ':after').display = 'none'; // or
document.getOverrideStyle(p, ':after').cssText = 'display: none !important;';
Unfortunately, no browser has implemented it. (Webkit returns null, Firefox has no such method). It looks like CSS3 doesn't even bother talking about that anymore, maybe because the usecases are very rare.
So you're gonna have to do some id/className magic as suggested above or in the other thread
I'd just assign a class name to the new elements that does not have :before / :after content.
Example - http://jsfiddle.net/84kZK/1/
Ah, okay. You can write new CSS that resets the offending :before/:after pseudo-elements:
function resetPsuedo(el) {
if (!el.id) el.id = makeId();
var selector = "#" + el.id;
var head = document.getElementsByTagName('head')[0],
style = document.createElement('style'),
rules = document.createTextNode(selector + ":before, " + selector + ":after { content: '' }");
style.type = 'text/css';
if(style.styleSheet)
style.styleSheet.cssText = rules.nodeValue;
else style.appendChild(rules);
head.appendChild(style);
}
function makeId() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for (var i=0; i < 15; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
​Assigning a random ID to the element you pass in (if it doesn't have one) allows you to hack-up inline styles—rather than accessing el.beforeStyle, you can use CSS selectors: el#rkhjr828t9g:before.
You may need to add more rules to fully reset the styles. jsFiddle: view me!
http://www.w3.org/TR/CSS21/generate.html#before-after-content
​The :before and :after pseudo-elements interact with other boxes as
if they were real elements inserted just inside their associated
element.
For example, the following document fragment and style sheet:
<p> Text </p> p:before { display: block; content: 'Some'; }
...would render in exactly the same way as the following document
fragment and style sheet:
<p><span>Some</span> Text </p> span { display: block }
Similarly, the following document fragment and style sheet:
<h2> Header </h2> h2:after { display: block; content: 'Thing'; }
...would render in exactly the same way as the following document
fragment and style sheet:
<h2> Header <span>Thing</span></h2> h2 { display: block; }
span { display: block; }
Use ruleSelector("ref::before")[0].style instead of document.getOverrideStyle(ref, ':before').
http://jsfiddle.net/s3fv8e5v/4/
<!DOCTYPE html>
<title>CSS</title>
<style>
body {
font: 200%/1.45 charter;
}
ref::before {
content: '\00A7';
letter-spacing: .1em;
}
</style>
<article>The seller can, under Business Law <ref>1782</ref>, offer a full refund to buyers. </article>
<script>
function ruleSelector(selector) {
function uni(selector) {
return selector.replace(/::/g, ':') // for Firefox
}
return Array.prototype.filter.call(Array.prototype.concat.apply([], Array.prototype.map.call(document.styleSheets, function(x) {
return Array.prototype.slice.call(x.cssRules);
})), function(x) {
return uni(x.selectorText) === uni(selector);
});
}
var toggle = false,
pseudo = ruleSelector("ref::before").slice(-1);
document.querySelector("article").onclick = function() {
pseudo.forEach(function(rule) {
if (toggle = !toggle)
rule.style.color = "red";
else
rule.style.color = "black";
});
}
</script>

Javascript get CSS style for ID

I would like to be able to get the style from a CSS element which is not used on the webpage. For example, this is the page:
<html>
<head>
<style type="text/css">
#custom{
background-color: #000;
}
</style>
</head>
<body>
<p>Hello world</p>
</body>
</html>
as you can see the ID 'custom' has a value but is not used within the document. I would like to get all the values for 'custom' without using it in the page. The closest I have come is:
el = document.getElementById('custom');
var result;
var styleProp = 'background-color';
if(el.currentStyle){
result = el.currentStyle[styleProp];
}else if (window.getComputedStyle){
result = document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp);
}else{
result = "unknown";
}
Create a new element with given ID and append it to the document. Then just read the value and remove the element.
Example:
var result,
el = document.body.appendChild(document.createElement("div")),
styleProp = 'background-color',
style;
el.id = 'custom';
style = el.currentStyle || window.getComputedStyle(el, null);
result = style[styleProp] || "unknown";
// Remove the element
document.body.removeChild(el);
I don't think the answer was a very good one as it requires adding elements to the DOM which may disrupt what you are trying to do or create visual glitches.
Using document.styleSheets I think you can get a less invasive solution. (see here)
try something like:
var result;
var SS = document.styleSheets;
for(var i=0; i<SS.length; i++) {
for(var j=0; j<SS[i].cssRules.length; j++) {
if(SS[i].cssRules[j].selectorText == "#custom") {
result = SS[i].cssRules[j].style;
}
}
}
This should give you an object which will contain all of the styles as the keys. keep in mind you may need a little more complex algorithm if the style is redeclared within the stylesheets.

Overriding !important style

Title pretty much sums it up.
The external style sheet has the following code:
td.EvenRow a {
display: none !important;
}
I have tried using:
element.style.display = "inline";
and
element.style.display = "inline !important";
but neither works. Is it possible to override an !important style using javascript.
This is for a greasemonkey extension, if that makes a difference.
There are a couple of simple one-liners you can use to do this.
Set a "style" attribute on the element:
element.setAttribute('style', 'display:inline !important');
or...
Modify the cssText property of the style object:
element.style.cssText = 'display:inline !important';
Either will do the job.
===
I've written a jQuery plugin called "important" to manipulate !important rules in elements, : http://github.com/premasagar/important
===
Edit:
As shared in the comments, the standard CSSOM interface (the API for JavaScript to interact with CSS) provides the setProperty method:
element.style.setProperty(propertyName, value, priority);
E.g:
document.body.style.setProperty('background-color', 'red', 'important');
element.style has a setProperty method that can take the priority as a third parameter:
element.style.setProperty("display", "inline", "important")
It didn't work in old IEs but it should be fine in current browsers.
I believe the only way to do this it to add the style as a new CSS declaration with the '!important' suffix. The easiest way to do this is to append a new <style> element to the head of document:
function addNewStyle(newStyle) {
var styleElement = document.getElementById('styles_js');
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.type = 'text/css';
styleElement.id = 'styles_js';
document.getElementsByTagName('head')[0].appendChild(styleElement);
}
styleElement.appendChild(document.createTextNode(newStyle));
}
addNewStyle('td.EvenRow a {display:inline !important;}')
The rules added with the above method will (if you use the !important suffix) override other previously set styling. If you're not using the suffix then make sure to take concepts like 'specificity' into account.
Building on #Premasagar's excellent answer; if you don't want to remove all the other inline styles use this
//accepts the hyphenated versions (i.e. not 'cssFloat')
addStyle(element, property, value, important) {
//remove previously defined property
if (element.style.setProperty)
element.style.setProperty(property, '');
else
element.style.setAttribute(property, '');
//insert the new style with all the old rules
element.setAttribute('style', element.style.cssText +
property + ':' + value + ((important) ? ' !important' : '') + ';');
}
Can't use removeProperty() because it wont remove !important rules in Chrome.
Can't use element.style[property] = '' because it only accepts camelCase in FireFox.
If you want to update / add single style in DOM Element style attribute you can use this function:
function setCssTextStyle(el, style, value) {
var result = el.style.cssText.match(new RegExp("(?:[;\\s]|^)(" +
style.replace("-", "\\-") + "\\s*:(.*?)(;|$))")),
idx;
if (result) {
idx = result.index + result[0].indexOf(result[1]);
el.style.cssText = el.style.cssText.substring(0, idx) +
style + ": " + value + ";" +
el.style.cssText.substring(idx + result[1].length);
} else {
el.style.cssText += " " + style + ": " + value + ";";
}
}
style.cssText is supported for all major browsers.
Use case example:
var elem = document.getElementById("elementId");
setCssTextStyle(elem, "margin-top", "10px !important");
Here is link to demo
If all you are doing is adding css to the page, then I would suggest you use the Stylish addon, and write a user style instead of a user script, because a user style is more efficient and appropriate.
See this page with information on how to create a user style
https://developer.mozilla.org/en-US/docs/Web/CSS/initial
use initial property in css3
<p style="color:red!important">
this text is red
<em style="color:initial">
this text is in the initial color (e.g. black)
</em>
this is red again
</p>
https://jsfiddle.net/xk6Ut/256/
One option to override CSS class in JavaScript is using an ID for the style element so that we can update the CSS class
function writeStyles(styleName, cssText) {
var styleElement = document.getElementById(styleName);
if (styleElement) document.getElementsByTagName('head')[0].removeChild(
styleElement);
styleElement = document.createElement('style');
styleElement.type = 'text/css';
styleElement.id = styleName;
styleElement.innerHTML = cssText;
document.getElementsByTagName('head')[0].appendChild(styleElement);
}
..
var cssText = '.testDIV{ height:' + height + 'px !important; }';
writeStyles('styles_js', cssText)
Rather than injecting style, if you inject a class(for eg: 'show') through java script, it will work. But here you need css like below. the added class css rule should be below your original rule.
td.EvenRow a{
display: none !important;
}
td.EvenRow a.show{
display: block !important;
}
There we have another possibility to remove a property value from the CSS.
Like using the replace method in js. But you have to know exactly the ID of the style, or you can write a for loop to detecting that by (count styles on the page, then check if any of those 'includes' or 'match' an !important value. & you can count also - how much contains them, or just simply write a global [regexp: /str/gi] replacing method)
Mine is very simple, but I attach a jsBin, for example:
https://jsbin.com/geqodeg/edit?html,css,js,output
First I set the body background in CSS for yellow !important, then I overrided by JS for darkPink.
Below is a snippet of code to set the important parameter for the style attribute using jquery.
$.fn.setFixedStyle = function(styles){
var s = $(this).attr("style");
s = "{"+s.replace(/;/g,",").replace(/'|"/g,"");
s = s.substring(0,s.length-1)+"}";
s = s.replace(/,/g,"\",\"").replace(/{/g,"{\"").replace(/}/g,"\"}").replace(/:/g,"\":\"");
var stOb = JSON.parse(s),st;
if(!styles){
$.each(stOb,function(k,v){
stOb[k] +=" !important";
});
}
else{
$.each(styles,function(k,v){
if(v.length>0){
stOb[k] = v+" !important";
}else{
stOb[k] += " !important";
}
});
}
var ns = JSON.stringify(stOb);
$(this).attr("style",ns.replace(/"|{|}/g,"").replace(/,/g,";"));
};
Usage is pretty simple.Just pass an object containing all the attributes you want to set as important.
$("#i1").setFixedStyle({"width":"50px","height":""});
There are two additional options.
1.To just add important parameter to already present style attribute pass empty string.
2.To add important param for all attributes present dont pass anything. It will set all attributes as important.
Here is it live in action. http://codepen.io/agaase/pen/nkvjr

Categories

Resources