8

It's possible to use libraries in less.js to dynamically regenerate css from less files within the browser. If there was an easy way to modify less code, this would be an extremely powerful method of dynamically updating a site's css.

Imagine you had a colour that was used 100 times throughout a large site. If you wanted to change that color dynamically just using javascript, you would need to update every bit of css that had that colour (perhaps 50 lines of code).

With what I'm imagining all you would need to write is something like this:

$('@mainColour').value('#F04');

I'm thinking of having a go at this myself, but it sounds like a huge project and I wonder if someone has already started something like this?

edit: to clarify, ideally what I want to be able to do is take a string of Less code, programatically edit it (perhaps using a jquery-like selector syntax) and then spit it out as modified Less. Ideally the code is in Javascript (but not necessarily client side) The example I give above is one possible application but maybe not a good one (where there might be better more common ways of achieving it).

Michael Bylstra
  • 5,042
  • 4
  • 28
  • 24
  • your "to clarify" section confuses me: if you don't need this dynamic, why not just keep a globals.less file with all the definitions and modify stuff there? anyways, dynamic manipulation seems possible, i've played around a bit and posted an answer :) – kritzikratzi Sep 19 '12 at 02:18

6 Answers6

14

it is definitely possible, but i had to modify the less sourcecode a bit (which i think is fine, considering it's really not meant to be done :) )

i suppose everyone wants to see a demo first, click here: http://jsfiddle.net/kritzikratzi/BRJXU/1/ (script-window contains only the modified less.js-source, everything of interest is in the html window)

kk, i'll first explain my patch, usage is at the end.

patch consists of three parts

add a utility function

less.Overrides = {}; 
less.Override = function( variableName, value ){
    if( !value ){
        delete less.Overrides[variableName]; 
    }
    else{
        less.Overrides[variableName] = value; 
    }
    less.refreshStyles(); 
}; 

save the property into an object and tell less to update it's styles.

modify the parse function

   function parse(str, callback ){
        ... 

        var overrides = "\n\n"; 
        for( var key in less.Overrides ){
            overrides += key + ": " + less.Overrides[key] + ";\n"; 
        }

        str += overrides; 

all we do here is serialize the overridden properties and add them to the end of every file that is parsed.

modify the loadStyles function

    if (styles[i].type.match(typePattern) || styles[i].hasAttribute( "lessText" )) {
        var lessText; 
        if( styles[i].hasAttribute( "lessText" ) ){
            lessText = styles[i].getAttribute( "lessText" );
        }
        else{
            lessText = styles[i].innerHTML || ''; 
            styles[i].setAttribute( "lessText", lessText );
        }
    ....

by default less will replace the type parameter from <style type='text/less'> to type='text/css' and forgot about the less-source. to prevent this the original less-source is stored and loaded.

usage and conclusion

<style type="text/less">
    @color: green; 

    #header{ color: @color; }
</style>
<div id="header">i'm the header!</div>
<a href="#" onclick="less.Override('@color', 'red');">make it red</a> 

this works just fine on my computer and i have to admit it looks very neat. i haven't tested external less files, if they don't work it should be easy to fix.

i still think it's not the best idea to use this in a production environment (for reasons mentioned by others already).

kritzikratzi
  • 19,662
  • 1
  • 29
  • 40
4

While I agree with @Baz1inga that in general this would be easier to do by adding and removing classes, I also know there are certain cases where LESS-style variables work much better (e.g. if the color is sometimes foreground, sometimes background, or is lightened in certain places). This is definitely do-able; in fact, here's some tested code that will do it (minus the jQuery-style syntax; any particular reason for needing that?):

function update_css(newcss) {
    var id = "styleholder";
    if ((css = document.getElementById(id)) === null) {
        css = document.createElement('style');
        css.type = 'text/css';
        css.id = id;
        document.getElementsByTagName('head')[0].appendChild(css);
    }
    if (css.styleSheet) { // IE
        try {
            css.styleSheet.cssText = newcss;
        } catch (e) {
            throw new(Error)("Couldn't reassign styleSheet.cssText.");
        }
    } else {
        (function (node) {
            if (css.childNodes.length > 0) {
                if (css.firstChild.nodeValue !== node.nodeValue) {
                    css.replaceChild(node, css.firstChild);
                }
            } else {
                css.appendChild(node);
            }
        })(document.createTextNode(newcss));
    }
}

lessvars = {mycolor: "red"};

maincode = "div { color: @mycolor; }"; // this would be a long string, loaded via AJAX from a LESS file on the server

function compile_less(variables) {
    var variable_less = "";
    for (var variable in variables) {
        variable_less += "@" + variable + ": " + variables[variable] + ";";
    }
    new(less.Parser)({
        optimization: less.optimization
    }).parse(variable_less + maincode, function (e, root) {
        update_css(root.toCSS());
    });
}

compile_less(lessvars);

function set_less_var(name, value) {
    lessvars[name] = value;
    compile_less(lessvars);
}

The "update_css" function above is derived from the "createCSS" function in less.js; the rest I wrote. You can now, at any time, do something like this, to change the color and havethe effects appear immediately in the site content:

set_less_var("mycolor", "green");

(Note that, of course, your "maincode" should probably be loaded from .less files in the background -- I just assigned them here to variables for simplicity.)

Just for fun (as I don't recommend it) -- and to show you that I think my code does what you want -- here's a function that allows you to use the above code to do $("@mycolor").value("black");:

function $(varname) {
    return {
        value: function(val) {
            set_less_var(varname.slice(1), val);
        }
    }
}
jamalex
  • 109
  • 6
  • Not exactly what I had in mind (programmatically editing and replacing existing less code rather than generating overriding css using LESS) but this could be a very useful technique for certain situations. I'll admit the jquery syntax idea is pretty half baked. The problem is say you want to edit the less rule | .btn { color: @darkRed } | but you don't necessarily know what line number the rule is on. – Michael Bylstra Apr 23 '12 at 05:08
  • It doesn't require editing/replacing anything beyond the value of the variables (which then "override" all the places where that variable is used in the code). You don't need to know anything about line numbers; in your example, you would just run compile_less("@darkRed: #900;") to change the color of the btn class. If you have multiple variables, it could be messier; let me pull together a more general approach. – jamalex Apr 23 '12 at 05:34
  • I've edited the code to use a more general syntax: `set_less_var("mycolor", "green");` (it then automatically changes this variable so the new color gets used everywhere @mycolor is used, automatically updating the page in the browser without refreshing). – jamalex Apr 23 '12 at 05:44
  • I added an extra function, just for demonstration purposes, that allows you to use your originally requested syntax, `$("@mycolor").value("black");` – jamalex Apr 23 '12 at 05:52
  • Lets say you import this less file (with line numbers): `1: @mainColor {'red'} 2: .btn {darken(@mainColor, 30%)}`. With your function if I did `set_less_var("mainColor", "green")`, I don't think .btn would be affected (which is what I would want to happen). The program would have to modify the existing less code and then parse that. I'll admit it's a pretty obscure requirement. The idea is you could do `$('@mainColor').value('green')` and line 1 would be altered or you might do something like `$('.btn').value('darken(@mainColor, 50%'))` – Michael Bylstra Apr 23 '12 at 06:50
2

If you use the less compiler locally in your browser, there is now a feature to modify less variables:

less.modifyVars({
 '@buttonFace': '#5B83AD',
 '@buttonText': '#D9EEF2'
});
Peter
  • 621
  • 1
  • 6
  • 16
1

first of all javascript can't write to a file. The best you'll be able to do is get Javascript to read and edit the XML then post that data to a server-side script to write to file.

well in general people use a different class to address this issue and replace the existing class with the new class rather than go edit the css file itself, which sounds pretty weird to me..

I stumbled upon this blogpost may be this is what you are looking for.. he shows different ways to get news stylesheets based on your requirement.

Baz1nga
  • 15,485
  • 3
  • 35
  • 61
  • The particular application I have in mind for this functionality is pretty non-standard, so I'd agree that the idea is 'weird' for general front end development (where class substituion is appropriate), but not for what I have in mind. As for browser JS not being able to write to a file (server side JS can), I would intend to read an existing less file into memory, edit it programmatically in-memory and then spit the resultant code out to the screen or send it to a server. The blog describes something related to what I want to do but I particularly want to be able to edit LESS, not CSS. – Michael Bylstra Apr 23 '12 at 04:44
1

If you can do c# and want to do this server-side, the port dotless supports plugins where you implement a visitor pattern to programmatically alter the less ast before it is spit out...

Luke Page
  • 8,136
  • 1
  • 20
  • 22
1

it might, just might be a good idéer but if your css / html is right this shouldn't be necessary at all, you just have to css in the right way you could stack your classes if you have alot of "a" tags. If you have very big websites your customers can be quite picky about some little changes like font then its good to customize your outcome and then it is very easy to just css your way out of it, than to make more variables to make your outcome

If you wanna change 1 color your just use your find tool and use find and replace.

please just use some css & comon knowlegde to get your result the more scripts manipulating your website the more load time.

Best regards

SP

Simon Dragsbæk
  • 2,367
  • 3
  • 30
  • 53