Overwrite LESS vars dynamically


Hi, I'm wondering if it's possible to overwrite or set less vars dynamically with Beans.

My goal is to have a field in the theme customizer where one can set/change the main theme color that is defined as a variable in the theme's less file and used in several places (backgrounds, borders, headings).

I have found a similar discussion about font selection in the customizer, where you, Thierry, are advising to

Dynamically set the UIkit global font variables using the Beans compiler API.

https://community.getbeans.io/discussion/adding-options-to-customiser/

In the lessphp class there's a setVariables() method that seems to be doing what I want, but I have no clue how to utilize this with the Beans compiler API. The method is described here: http://leafo.net/lessphp/docs/#setting_variables_from_php

Can you please give me a hint where I'd have to start?


Hi Timo,

There is a magic ability in Beans which allows you to add callback function to the Compiler so that you can return dynamic content. The trick though is that everything has to re-compile when you make changes in the customizer so that the changes reflect on the fly. Though right?

Well the good news is that I found you question quite fun and put a draft code for together which can be used in your child theme:

add_action( 'customize_register', 'example_customizer_fields' );
/**
 * Register customizer fields.
 */
function example_customizer_fields() {

    $fields = array(
         array(
            'id'      => 'example_link_colors',
            'label'   => __( 'Links Color', 'example' ),
            'type'    => 'WP_Customize_Color_Control',
            'default' => '#07D',
        ),
        // More fields...
    );

    beans_register_wp_customize_options( $fields, 'example_section', array(
        'title' => __( 'Example Styling', 'example' ),
    ) );

}

add_action( 'beans_uikit_enqueue_scripts', 'example_uikit_enqueue_assets', 5 );
/**
 * Enqueue the function return the dynamic LESS.
 */
function example_uikit_enqueue_assets() {

    beans_compiler_add_fragment( 'uikit', 'example_get_dynamic_less', 'less' );

}

add_filter( 'beans_uikit_euqueued_styles_args', 'example_add_compiler_cache_version' );
/**
 * Enqueue the function return the dynamic LESS.
 *
 * @param array $args An array of UIkit style compiler arguments.
 *
 * @return array An array of UIkit style compiler arguments.
 */
function example_add_compiler_cache_version( $args ) {

    $args['example_version'] = md5( @serialize( example_get_customizer_settings() ) );

    return $args;

}

/**
 * Getter for customizer settings.
 */
function example_get_customizer_settings() {

    return array(
        'example_link_colors' => get_theme_mod( 'example_link_colors', '#07D' ),
        // More fields...
    );

}

/**
 * Getter for dynamic LESS.
 */
function example_get_dynamic_less() {

    $settings = example_get_customizer_settings();

    // LESS ouptut below. This may move in a `style.php` which can be included here.
    return '@global-link-color: ' . esc_html( $settings['example_link_colors'] ) . ';';

}

So let me explaine the code snippet above.

  1. First it register and example customizer section (Example Styling) and an example field (Links color).
  2. Then it adds the example_get_dynamic_less() to the UIkit less compiler.
  3. Then it does the magic. Take the settings content, md5 it (create a dynamic key) and adds it to the compile arguments. This means when a setting changes, the dynamic key changes and it tells Beans compiler to recompile. It is worse noting that it only kicks in when settings changes so it won't affect the site speed by any means once the settings are saved :-)
  4. Getter function containing all the settings used for the dynamic key and for the dynamic LESS values.
  5. Getter function containing the dynamic LESS.

That is it, to test the snippet above you may paste it in your child theme functions.php, go to the Theme Customizer, open the Example Styling section and play with the Link Color setting.

Now you may go ahead and adapt this snippet according to your needs.

Happy coding,

Edited 5 months, 4 weeks ago by Thierry Muller


Hi Thierry,

thank you very much for that code snippet and the explanation! It works very well out of the box, I only had to change the priority of the example_uikit_enqueue_assets callback in the beans_uikit_enqueue_scripts action hook to something higher than 10 to overwrite previously defined vars of my main less file.

That's really fantastic, it offers great possibilities for theme customization by a user!

And one thing: I first thought you made a typo in the name of the beans_uikit_euqueued_styles_args filter, but this filter actually works!

Thanks again, Timo


That's a very interesting feature ! I bookmark this :-)

Thank you Thierry !


Hey Timo, thanks for the heads up on the typography mistake! I made a note of it and it will be fix in the coming releases, with backwards compatibility so your current code won't break indeed πŸ˜‰

Happy coding,

Edited 5 months, 3 weeks ago by Thierry Muller


Hey there!

I recognize this topic is a little older, but thought it would be wise to keep all of these questions in one place.

I'm using this code to add 2 color options to the 'Colors' section. This is how I've modified your code:

add_action( 'customize_register', 'example_customizer_fields' );
/**
 * Register customizer fields.
 */
function example_customizer_fields() {

    $fields = array(
         array(
            'id'      => 'text_color',
            'label'   => __( 'Text Color', 'tm-beans' ),
            'type'    => 'WP_Customize_Color_Control',
            'default' => '#000',
        ),
         array(
            'id'      => 'footer_background',
            'label'   => __( 'Footer Background Color', 'tm-beans' ),
            'type'    => 'WP_Customize_Color_Control',
            'default' => '#000',
        ),
    );

    beans_register_wp_customize_options( $fields, 'colors');
}

add_action( 'beans_uikit_enqueue_scripts', 'example_uikit_enqueue_assets', 5 );
/**
 * Enqueue the function return the dynamic LESS.
 */
function example_uikit_enqueue_assets() {

    beans_compiler_add_fragment( 'uikit', 'example_get_dynamic_less', 'less' );

}

add_filter( 'beans_uikit_euqueued_styles_args', 'example_add_compiler_cache_version' );
/**
 * Enqueue the function return the dynamic LESS.
 *
 * @param array $args An array of UIkit style compiler arguments.
 *
 * @return array An array of UIkit style compiler arguments.
 */
function example_add_compiler_cache_version( $args ) {

    $args['example_version'] = md5( @serialize( example_get_customizer_settings() ) );

    return $args;

}

/**
 * Getter for customizer settings.
 */
function example_get_customizer_settings() {

    return array(
        'text_color' => get_theme_mod( 'text_color', '#000' ),
        'footer_background' => get_theme_mod( 'footer_background', '#000' ),
    );

}

/**
 * Getter for dynamic LESS.
 */
function example_get_dynamic_less() {

    $settings = example_get_customizer_settings();

    // LESS ouptut below. This may move in a `style.php` which can be included here.
   return array(
        '@global-text-color: ' . esc_html( $settings['text_color'] ) . ';',
        '@global-footer-background: ' . esc_html( $settings['footer_background'] ) . ';'
        );

}

I'm getting the colors in the customizer just fine, and they're saving, but I'm trying to use this in my style.less of my child theme:

.tm-footer { background: @global-footer-background; } The variable appears to be empty. What am I doing wrong here?

Edited 5 months ago by Caitlin Alexander


Hi Caitlin, it is perfectly fine to post new questions on an "old" thread :-)

I think your issue is just due to file ordering, could you shate the snippet you are using to enqueue your styles.less and let me know if it is place before of after the snippet share in your last reply?

Based on your reply, I will spin up and test snippet which combine everything.

Thanks,


Thanks so much Thierry!

Interestingly I'm noticing if I only try to return a single variable in

example_get_dynamic_less()

it works, no matter which variable I try...just not both. So that's odd!

I enqueue style.less at the top of functions.php, and this customizer snippet is all the way at the bottom. Let me know if that needs moved!

Edited 5 months ago by Caitlin Alexander


Then the ordering should be fine. However I just picked up something else in your snippet, example_get_dynamic_less() should not return an array, it must return a string containing LESS syntax. Change it with the following:

return '
@global-text-color: ' . esc_html( $settings['text_color'] ) . ';
@global-footer-background: ' . esc_html( $settings['footer_background'] ) . ';
';

Edited 5 months ago by Thierry Muller


Thanks so much Thierry! That was the issue. Not sure how to tell in the future when certain functions need to return arrays or strings for multiple lines of code...but happy to know for this case!


Fantastic!

To understand what a function needs to return, you have to understand where and what is the function used for. In this case, example_get_dynamic_less() is used as a callback function called in the Beans Compiler class here. It is a bit complex in this case but don't worry to much about it, usually it is easier to understand what a function is doing πŸ˜‰

Happy coding,


Hi Thierry, I'm currently working on a modular page builder plugin that makes use of acf and beans (that's why my working title is lablab, which is a nice looking bean species πŸ˜‰ ).

There's an option page with some settings available and I decided to implement the dynamic less technique discussed above. The plugin is written in an object oriented way, so I had to slightly modify the code snippets you provided.

It works, but only if I use static methods (which I actually wanted to avoid). If I pass a non-static method together with an instance of the class to beans_compiler_add_fragment(), the less fragments get compiled with every page refresh, although dev mode is off and I didn't change any option setting. So it's working this way, too, but without any caching benefits.

The method looks like this (the wrapper array is necessary to make the foreach loop in beans_compiler_add_fragment() treat the inner array as one fragment):

public function enqueue_options_styles_less_fragment(){

    beans_compiler_add_fragment( 'uikit', array( array( $this, 'get_options_styles_as_less_fragment' ) ), 'less' );
}

I think it has something to do with the serialization and md5 hash generation happening inside of the compiler class to generate a unique css filename. When passing an object to the compiler, the filename is different each time, while passing a static method makes the filename change only when user settings have changed as well.

I guess there's no other workaround, so here's my working code with static methods (maybe someone finds this useful).

    public function enqueue_options_styles_less_fragment(){

        beans_compiler_add_fragment( 'uikit', array( array( __CLASS__, 'get_options_styles_as_less_fragment' ) ), 'less' );
    }

    public static function get_options_styles(){

        return array(
            'lablab-highlight-color' => ( ! empty( get_field( 'lablab_highlight_color', 'options' ) ) ? get_field( 'lablab_highlight_color', 'options' ) : '#2179bd' ),
        );
    }

    public static function get_options_styles_as_less_fragment(){
        $style = self::get_options_styles();
        return '@lablab-highlight-color: ' . $style['lablab-highlight-color'] . ';';
    }

    public function add_compiler_cache_version( $args ){
        $args['lablab_options_styles'] = md5( @serialize( self::get_options_styles() ) );

        return $args;
    }

These are just the callbacks, they get hooked in a separate class.


Hey Timo,

Could you share the entire class as well as the code which initiates it so that I can do some testing?

Thanks,


Hi Thierry,

I will add the whole plugin to github soon. Just need to fix some things.


Sounds good, thanks Timo!


Hey Thierry,

sorry, there were some issues and I added some new features, but now everything's on github: Lablab Builder

It's still not finished, but it's working so far. There are two requirements:

  • Beans (or a child theme based on it) must be installed and activated (should be no problem)
  • Advanced Custom Fields Pro (lablab builder uses repeater and flexible content fields)

Additionally, you'll have to install at least one module to make any use of it. At the moment there is a slideshow, an accordion, a text editor and a simple spacer module (separate WordPress plugins, github repos can be found here ).

Concerning the compiler issue - it's still there, but now I know what's going on:

When this method is called,

public function enqueue_options_styles_less_fragment(){

    beans_compiler_add_fragment( 'uikit', array( array( $this, 'get_options_styles_as_less_fragment' ) ), 'less' );
}

$this is an instance of the Lablab_Builder_Public class that (amongst other things) stores the html output of lablab builder. So everytime the content is edited, this object will change ( I've used a lorem ipsum plugin to generate some random text, so the content changed with every page refresh).

Because the name of the css file generated by the Beans compiler is based on the hash value of the compiler arguments, it changes when these arguments change and the modified object causes them to change. That's why it doesn't happen when you use static method or normal function names as arguments of beans_compiler_add_fragment().

I set up a branch of lablab builder here that uses static methods for the dynamic Less generation thing.

When using this version, the css file won't be recompiled when you modify the content of a module.

Probably I could also move the dynamic Less generation functionality to a new class that is solely responsible for that and it should work without static methods.

Sorry for me getting my thoughts clear while writing... πŸ˜‰

Thanks, Timo


Hey Timo,

Your explanation is 100% spot on and makes complete sense. Beans compiler hash the fragments to dectect if something has changed which is the intended behaviour, therefore you have to make sure you control what is being passed to it :-) I would advise to abstract the class and makes sure every single element has appropriate properties in that class (ie. protected and not passing anything to the constructor).

Great work!


Hey Thierry,

thanks for your feedback.

I created a new class that reads the style options set by a user and adds a LESS fragment to the compiler. Now the object changes only when the style options were changed.

And because the modified object itself (actually its hash value) is triggering the compiler to recompile, it works even if I remove the dedicated method add_compiler_cache_version :-)

Write a reply

Login or register to write a reply, it's free!