3

I am trying to set the display style of different elements based on their type, while matching a set of rules using less.

Using & after the rules have been defined appends the element type to the end of the rule and either with or without a space this is obviously not working.

I am sure there is a less repetitive, more optimal way to write the following:

&.admin {
    .service_provider.security_class_admin,
    .service_provider.security_class_admin_manager,
    .service_provider.security_class_admin_user,
    .service_provider.security_class_admin_manager_user {
        display: block;
    }
    th {
        &.service_provider.security_class_admin,
        &.service_provider.security_class_admin_manager,
        &.service_provider.security_class_admin_user,
        &.service_provider.security_class_admin_manager_user {
            display: table-cell;
        }
    }
    button {
        &.service_provider.security_class_admin,
        &.service_provider.security_class_admin_manager,
        &.service_provider.security_class_admin_user,
        &.service_provider.security_class_admin_manager_user {
            display: inline-block;
        }
    }
}

I would very much appreciate some help in finding the best solution to this.

ssc-hrep3
  • 15,024
  • 7
  • 48
  • 87
user2115620
  • 101
  • 1
  • 10

2 Answers2

2

Currently, there is no way to insert a selector in the middle of a selector chain. It can either be added at the start or the end only. Adding at the end wouldn't work for your case because the element type selector can only be at the start.

You can use write a mixin such that it has all the common selectors and accepts a ruleset as input.

&.admin{
    .common-selectors({display: block;});
    th{
        .common-selectors({display: table-cell;}, append);
    }
    button{
        .common-selectors({display: inline-block;}, append);
    }
}

.common-selectors(@props, @type:child){
    & when (@type = append) { /* type check because the element type is appended */
        &.service_provider.security_class_admin,
        &.service_provider.security_class_admin_manager,
        &.service_provider.security_class_admin_user,
        &.service_provider.security_class_admin_manager_user{
            @props();
        }
    }
    & when not (@type = append) { /* type check because default shouldn't be appended */
        .service_provider.security_class_admin,
        .service_provider.security_class_admin_manager,
        .service_provider.security_class_admin_user,
        .service_provider.security_class_admin_manager_user{
            @props();
        }
    }    
}

Snippet based on Compiled output of the above code:

.admin .service_provider.security_class_admin,
.admin .service_provider.security_class_admin_manager,
.admin .service_provider.security_class_admin_user,
.admin .service_provider.security_class_admin_manager_user {
  display: block;
}
.admin th.service_provider.security_class_admin,
.admin th.service_provider.security_class_admin_manager,
.admin th.service_provider.security_class_admin_user,
.admin th.service_provider.security_class_admin_manager_user {
  display: table-cell;
}
.admin button.service_provider.security_class_admin,
.admin button.service_provider.security_class_admin_manager,
.admin button.service_provider.security_class_admin_user,
.admin button.service_provider.security_class_admin_manager_user {
  display: inline-block;
}
div,
span,
button,
th,
tr,
table {
  border: 1px solid;
  margin: 10px;
}
<div class='admin'>
  <div class='service_provider security_class_admin'>Admin</div>
  <div class='service_provider security_class_admin_manager'>Manager</div>
  <div class='service_provider security_class_admin_user'>User</div>
  <div class='service_provider security_class_admin_manager_user'>Manager User</div>
</div>
<div class='admin'>
  <span class='service_provider security_class_admin'>Admin</span>
  <span class='service_provider security_class_admin_manager'>Manager</span>
  <span class='service_provider security_class_admin_user'>User</span>
  <span class='service_provider security_class_admin_manager_user'>Manager User</span>
</div>
<div class='admin'>
  <button class='service_provider security_class_admin'>Admin</button>
  <button class='service_provider security_class_admin_manager'>Manager</button>
  <button class='service_provider security_class_admin_user'>User</button>
  <button class='service_provider security_class_admin_manager_user'>Manager User</button>
</div>
<table>
  <tr>
    <th class='service_provider security_class_admin'>Admin</th>
    <th class='service_provider security_class_admin_manager'>Manager</th>
    <th class='service_provider security_class_admin_user'>User</th>
    <th class='service_provider security_class_admin_manager_user'>Manager User</th>
  </tr>
</table>

Or a shorter approach for the mixins would be to use the universal selector (*) to block display for all elements.

&.admin{
    * {
        .common-selectors({display: block;});
    }
    th{
        .common-selectors({display: table-cell;});
    }
    button{
        .common-selectors({display: inline-block;});
    }
}

.common-selectors(@props, @type:child){
    &.service_provider.security_class_admin,
    &.service_provider.security_class_admin_manager,
    &.service_provider.security_class_admin_user,
    &.service_provider.security_class_admin_manager_user{
        @props();
    }
}

Snippet based on Compiled output of the above code:

.admin *.service_provider.security_class_admin,
.admin *.service_provider.security_class_admin_manager,
.admin *.service_provider.security_class_admin_user,
.admin *.service_provider.security_class_admin_manager_user {
  display: block;
}
.admin th.service_provider.security_class_admin,
.admin th.service_provider.security_class_admin_manager,
.admin th.service_provider.security_class_admin_user,
.admin th.service_provider.security_class_admin_manager_user {
  display: table-cell;
}
.admin button.service_provider.security_class_admin,
.admin button.service_provider.security_class_admin_manager,
.admin button.service_provider.security_class_admin_user,
.admin button.service_provider.security_class_admin_manager_user {
  display: inline-block;
}
div,
span,
button,
th,
tr,
table {
  border: 1px solid;
  margin: 10px;
}
<div class='admin'>
  <div class='service_provider security_class_admin'>Admin</div>
  <div class='service_provider security_class_admin_manager'>Manager</div>
  <div class='service_provider security_class_admin_user'>User</div>
  <div class='service_provider security_class_admin_manager_user'>Manager User</div>
</div>
<div class='admin'>
  <span class='service_provider security_class_admin'>Admin</span>
  <span class='service_provider security_class_admin_manager'>Manager</span>
  <span class='service_provider security_class_admin_user'>User</span>
  <span class='service_provider security_class_admin_manager_user'>Manager User</span>
</div>
<div class='admin'>
  <button class='service_provider security_class_admin'>Admin</button>
  <button class='service_provider security_class_admin_manager'>Manager</button>
  <button class='service_provider security_class_admin_user'>User</button>
  <button class='service_provider security_class_admin_manager_user'>Manager User</button>
</div>
<table>
  <tr>
    <th class='service_provider security_class_admin'>Admin</th>
    <th class='service_provider security_class_admin_manager'>Manager</th>
    <th class='service_provider security_class_admin_user'>User</th>
    <th class='service_provider security_class_admin_manager_user'>Manager User</th>
  </tr>
</table>
Harry
  • 87,580
  • 25
  • 202
  • 214
  • nice one, thanks Harry, this will certainly help optimise things. – user2115620 Jun 13 '16 at 11:20
  • That's fine. I'm learning LESS right now and this is a good solution. The use of mixins is really helper – Marcos Pérez Gude Jun 13 '16 at 11:28
  • @user2115620: I actually made a mistake because of which the output of the original mixin wouldn't have been the same as your code. I've modified it now, please take a note. This is because the common selectors work in two ways. When nested under the element types, they should be appended to the parent whereas in the one outside the element type selector, it shouldn't be. So, there is a need for one more level of check. – Harry Jun 13 '16 at 11:34
  • 1
    That `when ()` statement was unknown for me. Thank you for the correction `:)` – Marcos Pérez Gude Jun 13 '16 at 11:42
  • 1
    @MarcosPérezGude: Its like an `if` construct. Check the value of the variable and then decide whether the contents should be executed or not :) – Harry Jun 13 '16 at 11:44
1

So thanks to your help, plus a for-in loop I learnt about from the following post: How to generate CSS with loop in less

I now have:

.for(@list, @code) {
        & {
            .loop(@i:1) when (@i =< length(@list)) {
                @value: extract(@list, @i);

                @code();

                .loop(@i + 1);
            }

            .loop();
        }
    }

    .role-varients(@orgType, @variants, @props, @type:child){
        .for(@variants, {
            & when (@type = append) {
                &.@{orgType}.security_class_@{value} {
                    @props();
                }
            }
            & when not (@type = append) {
                .@{orgType}.security_class_@{value} {
                    @props();
                }
            }
        });
    }
    @admin-roles: admin, admin_manager, admin_user, admin_manager_user;
    @manager-roles: manager, admin_manager, manager_user, admin_manager_user;
    @user-roles: user, admin_user, manager_user, admin_manager_user;
    &.service_provider{
        &.admin{
            .role-varients(service_provider, @admin-roles, {display: block;});
            th{
                .role-varients(service_provider, @admin-roles, {display: table-cell;}, append);
            }
            button{
                .role-varients(service_provider, @admin-roles, {display: inline-block;}, append);
            }
        }
        &.manager{
            .role-varients(service_provider, @manager-roles, {display: block;});
            th{
                .role-varients(service_provider, @manager-roles, {display: table-cell;}, append);
            }
            button{
                .role-varients(service_provider, @manager-roles, {display: inline-block;}, append);
            }
        }
        &.user{
            .role-varients(service_provider, @user-roles, {display: block;});
            th{
                .role-varients(service_provider, @user-roles, {display: table-cell;}, append);
            }
            button{
                .role-varients(service_provider, @user-roles, {display: inline-block;}, append);
            }
        }
    }
    &.client{
        &.admin{
            .role-varients(client, @admin-roles, {display: block;});
            th{
                .role-varients(client, @admin-roles, {display: table-cell;}, append);
            }
            button{
                .role-varients(client, @admin-roles, {display: inline-block;}, append);
            }
        }
        &.manager{
            .role-varients(client, @manager-roles, {display: block;});
            th{
                .role-varients(client, @manager-roles, {display: table-cell;}, append);
            }
            button{
                .role-varients(client, @manager-roles, {display: inline-block;}, append);
            }
        }
        &.user{
            .role-varients(client, @user-roles, {display: block;});
            th{
                .role-varients(client, @user-roles, {display: table-cell;}, append);
            }
            button{
                .role-varients(client, @user-roles, {display: inline-block;}, append);
            }
        }
    }
    &.vendor{
        &.admin{
            .role-varients(vendor, @admin-roles, {display: block;});
            th{
                .role-varients(vendor, @admin-roles, {display: table-cell;}, append);
            }
            button{
                .role-varients(vendor, @admin-roles, {display: inline-block;}, append);
            }
        }
        &.manager{
            .role-varients(vendor, @manager-roles, {display: block;});
            th{
                .role-varients(vendor, @manager-roles, {display: table-cell;}, append);
            }
            button{
                .role-varients(vendor, @manager-roles, {display: inline-block;}, append);
            }
        }

        &.user{
            .role-varients(vendor, @user-roles, {display: block;});
            th{
                .role-varients(vendor, @user-roles, {display: table-cell;}, append);
            }
            button{
                .role-varients(vendor, @user-roles, {display: inline-block;}, append);
            }
        }
    }

I would use another for loop to handle the 3 main blocks [service_provider, client, vendor] but where I am using .@{value} I guess the scope is broken from having a loop within a loop and it does not produce the expected results.

Although the above still seems long, it is definitely better than writing each rule manually.

My solution came about thanks to Harry's input so I guess I should mark his answer as correct? Thanks again all ;)

Community
  • 1
  • 1
user2115620
  • 101
  • 1
  • 10
  • Acceptance marking is totally your choice but the general rule is to accept the answer which addressed the original question in full. Any additional stuff that you've done to improve on that answer can be posted as it can be helpful to others. If the other answer was partial and your answer is the complete one to the original question then you accept yours :) – Harry Jun 13 '16 at 14:11
  • 1
    thanks for the guidance Harry - you're answer solved the original issue for me, I just built on it ;) – user2115620 Jun 14 '16 at 12:52