Convert css styles to inline styles with javascript keeping the style units - javascript

We have a corporate content management system that allows for rich text editing/html markup, but does not allow for head elements or style sheets to be uploaded, attached, or used in any way. It provides some rich text editing controls and also access to the source html, but just for the html fragment -- there is no head, no body. We also have no access the whole system that presents these bits of markup on the page. The only way to style the content is through inline style attributes on the elements. It is best, it isn't pretty, but that is what we have and I'm trying to make the best of a bad situation.
We also have high standards for visual presentation and would like to be able to quickly produce and modify/update content and keep it looking nice. It is difficult to correctly apply formatting using the system. For anybody who has tried to markup anything more than a paragraph or two with an RTE, you probably know what I mean. It seems like we should have a different system, but has anybody worked for a large company before? Just sayin.
We do have access to another location where we could "author" and "store" actual styled content and then "compile it" for copypasta into the other system. In other words, we could author/design using css and best practices and then we could run some code that could convert those element, class, and id formatting into inline styles.
I did my research and found this thread which also lead me to this code.
These both are very helpful in exploring solutions, but I've run into an issue. These solutions use the javascript getComputedStyle() method. There are some other options for properties to only look at other properties or to be recursive on the children of the element provide, but basically it boils down to this. (Since getComputeStyle returns an object and not an array, there is also a prototype/polyfill to allow iterating over an object with forEach, but none of that is part of the issue I'm facing.)
const computedStyle = getComputedStyle(element);
computedStyle.forEach(property => {
element.style[property] = computedStyle.getPropertyValue(property);
});
This works well for css attributes like font-size:24px or margin:0 15px. The issue I'm running into are when I'm using units other than px. For example, if I'm trying to make something that has width:50%. getComputedStyle() converts the 50% to the actual number of pixels that 50% is currently using.
In the notes section of the MDN web docs I see that this is expected behavior. Although I'm not quite clear on what that last line means.
...An example difference between pre- and post-layout values includes the
resolution of percentages for width or height, as those will be
replaced by their pixel equivalent only for used values.
So what I'm trying to do is convert something like this
.container{width:50%;}
<div class="container">
into something like this
<div class="container" style="width:50%">
Does anyone know of a way to complete this type of transformation?
PS: If it matters we'll be using the more basic attributes in our css -- no transitions, grid, prefixing, etc. We still need to support IE 11 -- if that tells you anything. We won't need to account for every edge case or browser. Just some basic stuff so that all our H1 look the same.

Couldn't find any way to do this using the built in getComputedStyle(). It also returned too many properties that I wasn't interested in. So I came up with a different approach. Basically to use the same function to loop through an element (and maybe all its children elements) and the use Element.matches() to get all the css rules that apply to the element and apply the properties as they were specified in the stylesheet.
I modified this answer a bit to get the rules from the stylesheet.
Has the added benefit that we can pull either from all the document stylesheets or just from a specific one that is needed for preparing the code to go into our content management systems's rich text editor.
function applyInline(element, recursive = true) {
if (!element) {
throw new Error("No element specified.");
}
const matches = matchRules(element);
// we need to preserve any pre-existing inline styles.
var srcRules = document.createElement(element.tagName).style;
srcRules.cssText = element.style.cssText;
matches.forEach(rule => {
for (var prop of rule.style) {
let val = srcRules.getPropertyValue(prop) || rule.style.getPropertyValue(prop);
let priority = rule.style.getPropertyPriority(prop);
element.style.setProperty(prop,val,priority);
}
});
if (recursive) {
element.children.forEach(child => {
applyInline(child, recursive);
});
}
}
function matchRules(el, sheets) {
sheets = sheets || document.styleSheets;
var ret = [];
for (var i in sheets) {
if (sheets.hasOwnProperty(i)) {
var rules = sheets[i].rules || sheets[i].cssRules;
for (var r in rules) {
if (el.matches(rules[r].selectorText)) {
ret.push(rules[r]);
}
}
}
}
return ret;
}

Related

using a variable in css class names, and dynamically giving it attributes

I have a set of classes named class1 up to classN, and I want each class classK to get the color rgb(k%256, 0, 0).
N in this case is dynamic and has no theoretical upper limit, as the elements with these class names are generated based on user input and some js code.
I was thinking of having CSS kind of like this:
class[k]{
color: rgb(k%256, 0, 0);
}
However, I have found no way of making CSS rules that vary based on a variable in the class name.
I briefly explored trying to use CSS variables, but those are more like constants and can't accommodate all classes 1 to N simultaneously.
Alternatively I could use javascript for the styling and have something like:
for(let i = 0; i < N; i++){
document.getElementByClassName(`class${k}`).forEach(e => e.style = `color: rgb(${k}, 0, 0)`);
}
However, this is not very scaleable if I want to add more complex CSS (which I most likely will, the rgb value I'm using now is proof of concept), it is very inefficient and I much prefer having CSS handle as much of my styling as possible.
Looking around on the internet I did find this post on stackoverflow that asks pretty much the same question, and received an answer of "this is impossible". However, that post was barely viewed and from 2015, so there might be a new feature since then that could help me.

Specify a target for less compilation within browser

I have a page with a short form to input some less values. I then want to take those values and recompile the less (from within the browser, with less.modifyVars(); ), but be able to specify the target file. This is so that I can access it from an iframe from within the page.
Does anyone know how I can specify a target for less compilation in the browser?
I'm not too familiar with in-browser "less.js" usage so this is quite rough answer.
Basically, no, you can't specify a target for automatic compilation.
#1
So you will have to explicitly parse a less file of interest into a string containing the result CSS styles and then apply these result styles to whatever HTML target you need. E.g. using javascript:
var parser = new(less.Parser)(options);
parser.parse(less_src_string, function (err, tree) {
var css_result_string;
if (err) {
// handle errors
} else {
css_result_string = tree.toCSS(env);
}
// ...
});
I'm not aware of any good examples/demos of such in-browser LESS usage so I'm afraid the less.js and lessc sources themselves are the only documentation for this stuff.
#2
The other way around is to do it in reverse, i.e. instead of including "less.js" into the frame where you set your variables, include it into your target frame and then set your config frame script to use modifyVars() of the less object defined in the target frame. That could be much more simple than #1.

Removing Chrome's "translate" DOM Property

I'm working with some legacy code where the original developers made heavy use of generating HTML DOM nodes with a non-standard attribute named translate
<span translate="[{"shown":"My Account","translated":"My Account","original":"My Account","location":"Text","scope":"Mage_Customer"}]">My Account</span>
and then traversing/searching for those nodes with javascript code like the following.
if (!$(target).match('*[translate]')) {
target = target.up('*[translate]');
}
The problem I'm trying to solve is, it appears that Google Chrome automatically adds a translate attribute to every DOM node in the document, and that this DOM node's value is a boolean true. You can see this by running the following Javascript from Chrome's javascript console
> document.getElementsByTagName('p')[0].translate
true
>
Is there anyway to tell Chrome not to populate these attributes? Their presence is wrying havoc with the legacy code. PrototypeJS's match and up nodes treat these boolean object attributes as matches, while the code I'm dealing with is specifically looking for DOM nodes with an attribute named translate. I'd like to find a solution for my problem that doesn't involved rewriting the old Javascript to use methods like hasAttribute.
I tried (as a wild guess) adding the meta attributes mentioned in this article,
<meta name=”google” value=”notranslate”>
<meta name=”google” content=”notranslate”>
but the nodes in the page still has a boolean true translate attribute.
(if it matters, this is Magento's inline translation system I'm talking about here)
The best I've been able to come up with so far is going through every DOM element in the page defining a getter that checks for the existence of an attribute. (the Object.__defineGetter__ guard clause ensures no errors in browsers that don't support modern Javascript)
if(Object.__defineGetter__)
{
var hasTranslateAttribute = function(){
return $(this).hasAttribute("translate");
};
document.observe("dom:loaded", function() {
$$('*').each(function(theElement){
theElement.__defineGetter__("translate", hasTranslateAttribute);
});
});
}
I tried defining a getting on Object.prototype and Element.prototype, but it seems like the browser's native translate is defined higher up the chain, so you need to redefine things on a per element basis.
Replace the nonstandard attribute translate by an attribute like data-translate, which is virtually guaranteed to be and to remain undefined in HTML specifications and in browsers. The data-* attributes were invented to prevent issues like this, and they can also be used to fix them.

Compound Javascript Elements

I've got this page I'm doing some tests in Javascript and jQuery: JS Tests
I've got a few questions on how to create, not sure if this is right term, but compound controls via Javascript. In something like Flash, you'd create the Object class, have the getters and setters, draw your images, etc. In JS, it seems to be a very different thought process. My main question is How do you create multiple elements with getters and setters to be rendered, filtered, and interacted with in Javascript?
The main code regarding this example sits with:
var html = (function(){
// var FRAG = $(document.createDocumentFragment());
htmlBox = $(document.createElement("div"));
var eTitle = $(document.createElement("h4"));
var ePrice = $(document.createElement("p"));
// set class first
htmlBox.addClass("box")
htmlBox.css({
backgroundColor : color
})
// set text values
eTitle.text(title);
ePrice.text("$" + price);
htmlBox.append(eTitle)
htmlBox.append(ePrice)
return htmlBox;
})();
... inside the Box() class. If someone could take a look at the source and let me know what isn't quite right, that'd be great.
EDIT
Here's the final result for this example. Some logistics to work out, but what I'm after.
http://geerswitch.in/tests/obj/
As for the jQuery creating nodes, the built in JS version works fine for this, and some research on Google shows that the non-jquery way is faster in most cases anyway (and looks worse, imo)
You're doing it almost right. You've created a Box class to represent your higher-order UI element, you're instantiating it for each element, and your main program is manipulating the elements through its interface. The only thing you're missing is the split between the public interface and the private implementation. There's nothing to prevent me from doing myBox.price += 10 right now, even though the Box interface clearly implies that price should be set at construction and never modified.
JavaScript doesn't have visibility modifiers like "private" and "public", but you can create the same effect yourself. Check out Douglas Crockford's explanation for the details. Crockford is an opinionated genius when it comes to JavaScript, and he's the brains behind JSLint and JSON.

Returning metadata with CSS

I have a CSS stylesheet that's dynamically created on the server, and returned via a <link> tag. Is it possible to return any metadata with this stylesheet, that I could read with JavaScript?
(The use case: the stylesheet I return is a combination of several smaller ones. I want my JavaScript code to be able to detect which smaller ones were included.)
I considered adding some custom properties to an element:
body {
-my-custom-prop1:0;
-my-custom-prop2:0;
}
But when I try to read these with:
window.getComputedStyle(document.body)['-my-custom-prop1']
they're not returned. Any other ideaas?
EDIT: I ended up taking a slightly different approach. Instead of adding a <link> tag, I made an AJAX request to get the stylesheet, and added its text to a <style> tag. This way I could use the HTTP response headers to include metadata. Of course, this won't work across domains, like a <link> tag does.
See example of the following →
Though I think this technique is ill-advised, here's something I developed that I've tested to work in Chrome, FF, Safari and IE8.
First, I picked the list-style-image property to be used to store the meta data since it can contain any string in the url() parameter and yet wasn't going to be used under any normal circumstances in the body CSS.
Then I implemented a common cross-browser function to getComputedStyle since this isn't available in all browsers.
Then I parsed the return property to get only the data within the url(''), resulting in these functions:
var getStyle = function(el, cssprop) {
if (el.currentStyle) {
return el.currentStyle[cssprop];
} else if (document.defaultView && document.defaultView.getComputedStyle) {
return document.defaultView.getComputedStyle(el, "")[cssprop];
} else {
return (el.style) ? el.style[cssprop] : 0;
}
};
var pullCSSMeta = function() {
var aMeta = getStyle(document.body, 'listStyleImage').split('/'),
meta = aMeta[aMeta.length - 1];
return decodeURIComponent(meta.substr(0, meta.length - 1).replace(/"$/,''));
};
If you need to store more than one piece of information you could comma-delineate the data or potentially even store a JSON string. I hope you have a good reason for wanting to do this as I think there are better ways to store meta data... but there you go!
See example →
The returned object actually represents the CSS 2.1 used values, not the computed values. https://developer.mozilla.org/en/DOM/window.getComputedStyle
You might be able to interrogate these styles via another method though:
http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
I asked a related question a while ago. It turns out that you have to parse the stylesheet text manually with javascript. I decided it was not worth the bother and found a different solution to my problem. You could use some clever tricks like standard properties on bogus classes would work I guess.

Categories

Resources