Less.js: Creating Custom Functions

Created on 16 Feb 2012  ·  28Comments  ·  Source: less/less.js

Hi,

It would be nice to define Custome Functions like:

  darkfadein(@color, @value) {
    return fadein(darken(@color, @value));
  }

  .foo {
    color: darkfadein(#333, 10%);
  }

should be compiled to:

  .foo {
    color: #1a1a1a;
  }
consider closing feature request low priority

Most helpful comment

I find a hack : if you declare a global js function, you can use it later !

@fn: ~`fn = function(a) { return a; }`;

@arg: 8px;

p {
    font-size: ~`fn("@{arg}")`;
}

All 28 comments

Maybe to differentiate it from a mix-in and keep closer to CSS syntax, it could be:

@darkfadein(@color, @value): fadein(darken(@color, @value));

Kind of like a variable that depends on parameters (which if you think on is kind of a function =)

+1 to @souldreamer's syntax.

But with @souldreamer's syntax it woul not be posible to write some
values into variables and use them again.

may useing this syntax:

  @darkfadein(@color, @value) {
     @foo: darken(@color, @value);
     return: fadein(@foo);
  }

may it would be possible in later versions to do something like this:

  @darkfadein(@color, @value) {
     @foo: darken(@color, @value);
     for(@i = 0; @i < 5; @i++): {
       @foo: darken(@foo, @i);
     };
     return: fadein(@foo);
  }

I have a similar need to use a function of some sort to return a value. I think there are two possible solutions. One solution would be to extend the variable syntax as @Deltachaos outlined above, which is essentially:

@grid-width(@columns, @column-width) {
    @computedWidth = @columns*@column-width;
    return @computedWidth;
}
div {
    width: @grid-width(6,60);
}

A second approach would be to use mixins. Since the compiler treats mixins as JavaScript functions anyway, _adding a return feature to mixins_ would likely be a simple remedy. Here's what it would look like:

.grid-width(@columns, @column-width) {
    @computedWidth = @columns*@column-width;
    return @computedWidth;
}
div {
    width: .grid-width(6,60);
}

SASS's "functional directives" provide a similar outcome, though I believe this mixin solution would be much more elegant.

Issues 508 and 609 are also related to this.

I think the second syntax of @tylertate would be the best. I seams to be the easiest because as you said less already parses mixins as JavaScript.

Would somethink like this be possible with mixins?

.grid-width(@columns, @column-width) {
    @computedWidth = @columns*@column-width;
    for (var i = 0; i <= 36; i++) {
      @computedWidth = darken(@computedWidth, i);
    }
    return @computedWidth;
}
div {
    width: .grid-width(6,60);
}

I'd rather see something like a defined LESS plugin syntax, and move programming-like logic to a JavaScript LESS plugin. These suggestions are inconsistent with current LESS syntax and design.

Agree.. theres a bug about that marked documentation since its relatively simple to add functions when you know how.

So what was the resolution?

We need to document it. see https://github.com/cloudhead/lesscss.org/issues/54

and the linked issue from less.js shows how you can add a function to less in the browser

less = { functions: { rgbstr: function (color) {var str = color; return new(less.tree.Quoted)('"' + str + '"', str,true,1);}}};

at the moment there is no way to plugin functions into the node version, but there should be

Thanks, I appreciate the response and the position to get JavaScript out of LESS's syntax.

However, not being forced to associate a mixin's return value with a specific property seems like such a clear use case. Anyone that uses a grid is going to want to do what @Deltachaos was trying to do. It would be great to be able to achieve this without dropping down to the level of creating a plugin.

Its a tricky thing - if you need to have loops or if's it needs to live in a plugin.

However if it is calling 3 less functions with specific values, I agree it makes sense to be able to extract that inside less in order to make the syntax DRY - it doesn't make sense to have to write a plugin to extract the fact that you want darken by 5% into one place.

I have re-opened but this might be duplicated somewhere else, we'll see.

at the moment variables from an mixins scope all get copied to the outer scope - which is kind of a way fo returning variables.. but its causing horrible problems and I want to remove it.

duplicate of #538

"variables from an mixins scope all get copied to the outer scope"

Ugh, really? Yes, let's remove that behavior. I'd rather variables are marked for export, or some other thing than just automatic leaking. That's not an expected behavior to me. Variables should be block-scoped.

yep, its like supporting functions via a back door bug

I find a hack : if you declare a global js function, you can use it later !

@fn: ~`fn = function(a) { return a; }`;

@arg: 8px;

p {
    font-size: ~`fn("@{arg}")`;
}

@fabienevain have found same hack just now :)

@fabienevain It's work well, thanks~ :+1:

I found that you can create actual functions from the same eval jail by accessing process.mainModule... Only catch is that you may have to iterate over process.mainModule.children and match less.js if that order for some reason changes in future. I plan not to iterate just blindly trusting less is the third module.

Unfortunately you can't access the require, but you can access fs and other which are already required by less, which is plenty:

@anything: `(function() {
    // console.log(process.mainModule.children[0].exports); // node fs is here
    // console.log(process.mainModule.children[2].children) // children of less, more node modules!
    var less = process.mainModule.children[2].exports;

    less.functions.functionRegistry.add("firstfunc", function(a, context) {
        // console.log(a, context);
        return new less.tree.Color("00ff00");
    });

    less.functions.functionRegistry.add("secondfunc", function(a, context) {
        // console.log(a, context);
        return new less.tree.Color("ff0000");
    });
})()`;


test {
    background: firstfunc(white);
    color: secondfunc(black);
}

Neat thing about having real function is this context variable, which contains juicy details like the file being processed so you can e.g. create own svg data uri import with settings etc.

Edit I wonder why backticks are even introduced if JS is tried to be kept out. I like my LESS as copy & pasteable as possible so plugins are not good for me.

I like my LESS as copy & pasteable as possible so plugins are not good for me.

So you assume your code _is_ "copy&pasteable" even if you require node.js-based less.js compiler in your inline backtick hacks, but in the same time feel it goes wrong if you would use plugins? Doh!

@seven-phases-max my tools are pretty messed up. If I could control the lessc command line arguments I would use plugins probably. (Or have one master plugin where I stuff everything) But no, I've screwed my environment, and I have ~100 WP themes in Eclipse workspace which I just can't get rid of because all the build commands etc are stuck in there.

@Ciantic First of all you don't need any particular command line options to use a custom functions plugin - if necessary (#2479). Secondary I doubt whatever deeply screwed environment prohibits you to control compiler options (after all lessc "executable" is just an OS console script redirecting to actual node script - so one can easily inject _anything_ there one way or another).

Either way my comment was just about "copy&pasteable vs. plugins" adv. while it turns out to be just a workaround for a broken build tools/chain.

@seven-phases-max import plugin looks exactly the tool I need! Though I would like to define the functions inside my project, not in the global registry, this way I can edit the functions within the project and not worry about breaking billion less files if I make a change to global function.

@Ciantic

Though I would like to define the functions inside my project, not in the global registry, this way I can edit the functions within the project and not worry about breaking billion less files if I make a change to global function.

Read further in. The initial draft of my pull request went the easy route of global registration, but with some gained insight I later refined it to do scope-local registration. E.g.

.my-mixin() {
  @plugin "my-func.js";
  @value : my-func();
}

will not leak my-func outside of the mixin scope. Paths are ofcourse also relative to the file that contains the @plugin declaration, so everything is neatly bundled and shippable for consumption by third-parties; 100% _transparantly_.

That was my design goal for this feature. ^_^

less.js is need for adding custom color combiantion
problem occur during commit the code
it is supporting but need of less.js

I find a hack : if you declare a global js function, you can use it later !

@fn: ~`fn = function(a) { return a; }`;

@arg: 8px;

p {
    font-size: ~`fn("@{arg}")`;
}

@fabienevain How can I use less functions in @fn? Such as hsvsaturationunit and so on .Thx.

@hiyangguo

You shouldn't use inline JS expressions, period.
Build and register custom functions the appropriate way.
Read the documentation. It's all there: http://lesscss.org/features/#plugin-atrules-feature

@rjgotten OK, thank you very much.

Was this page helpful?
0 / 5 - 0 ratings