Home > Software engineering >  Trying to refactor css toggle switch slider checkbox svgs for its on/off states in alpine and tailwi
Trying to refactor css toggle switch slider checkbox svgs for its on/off states in alpine and tailwi

Time:07-28

Our system, built in laravel, has a toggle switch checkbox, Note: for the duration of this post, when I say slider, I mean a sliding toggle checkbox component, not a slider range value selector.

and as per the redesign, the design team wants it to have some adjustments to its size, color, and also add an SVG checkmark for when the slider is checked, etc.

I've successfully achieved what the design team wanted in terms of changes to the slider. desired slider. The problem is that the code is much more bloated now.

I have a stand-alone example PHP file you can run, built on top of tailwind and alpine. While we do use tailwind v3 in our application, I'm using tailwind v2 for this example so I can use the CDN for tailwind to help keep this example stand alone.

Here's what the old and newer toggles look like when you run my stand PHP code:

When both toggles are checked: (I'm comparing the older and newer toggle)

when both toggles are checked

When both toggles are not checked: (I'm comparing the older and newer toggle)

when both toggles are not checked

Here's the code for the stand-alone PHP file to see how this would work:

<!doctype html>
<html lang="en-US">
<head>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.10.2/cdn.min.js"
        integrity="sha512-B/OEIDaWXc61XNJlO0TQILX/mFbhx77bwQKzok6I8suB6WP9yvN8zgaiyLmekPr5eRNmjfpR40zos29ktaSHEg=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css"
      integrity="sha512-wnea99uKIC3TJF7v4eKk4Y lMz2Mklv18 r4na2Gn1abDRPPOeef95xTzdwGD9e6zXJBteMIhZ1 68QC5byJZw=="
      crossorigin="anonymous" referrerpolicy="no-referrer" />
<title>Update image preferences</title>

    <?php
    // using get in this example
    // real-world production example would use laravel, post and a database, etc
    // the checkbox starts as unchecked, and if checked and submitted, it will remember its state
    if(isset($_GET['image-is-active']) && $_GET['image-is-active'] === '1') {
        $imageIsActiveChecked = true;
    } else {
        $imageIsActiveChecked = false;
    }

    if(isset($_GET['an-older-slider']) && $_GET['an-older-slider'] === '1') {
        $anOlderSliderChecked = true;
    } else {
        $anOlderSliderChecked = false;
    }
?>
    <style>
        :root{
         --slider-off-bg-color: #636363; /* pulled from the user's color theme */
         --slider-on-bg-color: #0082CB; /* pulled from the user's color theme */
        }
    </style>
</head>
<body>
<!-- this form submits to the page itself in this example, so you can see how it handles the input and output-->
<!-- this would store to a database in the real world application -->
<form method="GET" action>
<section  x-data>
    <h1 >Update Image Preferences</h1>
<div  x-data="{ checked: <?= $imageIsActiveChecked? 'true' : 'false' ?>  }">



    <!-- this is the new slider our system uses, compare to the older slider under this.
    this newer slider is per the new design, which uses an SVG for a checkmark when the slider is on.
    but it very much bloats the code. And looks a like unnecessary JavaScript and overcomplicated, large code -->

<span x-data="{ on: <?= json_encode($imageIsActiveChecked) ?> }">
    <button type="button" 
            role="switch"
            x-on:click="on = !on"
            x-bind:style="`background-color: ${on ? 'var(--slider-on-bg-color)' : 'var(--slider-off-bg-color)'}`"
    >
         <span 
               :>
        <input type="checkbox"
               name="image-is-active" value="1" x-bind:checked="on" hidden>
      <span 
            x-bind:>
            <!-- toggle x checkmark -->
          <svg  fill="none" viewBox="0 0 12 12" hidden>
            <path d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
          </svg>
      </span>
      <span 
            x-bind:>
            <!-- toggle checkmark svg -->
          <svg  fill="currentColor" viewBox="0 0 12 12">
            <path d="M4.3 10.05L0 5.88241L1.90657 3.81932L4.4 6.17162L10.0916 0.550049L12 2.61314L4.3 10.05Z" fill="#636363"/>
          </svg>
      </span>
    </span>
</button>
</span>

    <span >
<span 
style="font-family: Roboto, Arial, sans-serif">Make this image active</span>
</span>

</div>




    <style>
        .switch-with-label {
            align-items: center;
            display: flex;
            flex-direction: row;
            flex-wrap: nowrap;
        }
        label.switch .slider {
            background-color: #ccc;
            border-radius: 34px;
            bottom: 0;
            cursor: pointer;
            left: 0;
            position: absolute;
            right: 0;
            top: 0;
            transition: .1s;
        }
        label.switch input {
            height: 0;
            opacity: 0;
            width: 0;
        }
        label.switch {
            display: inline-block;
            flex-shrink: 0;
            height: 32px;
            position: relative;
            width: 58px;
            margin-right: 20px;
        }
        input[type="radio"], input[type="checkbox"] {
            margin: 4px 0 0;
            margin-top: 1px \9;
            line-height: normal;
        }
        label.switch input:checked .slider {
            background-color: #0275ff;
        }
        label.switch input:checked .slider:before {
            transform: translateX(25px);
        }
        label.switch .slider:before {
            background-color: #fff;
            border-radius: 50px;
            bottom: 4px;
            content: "";
            height: 24px;
            left: 4px;
            position: absolute;
            transition: .1s;
            width: 24px;
        }
    </style>

    <!-- this is the older slider our system uses.
         the code is small and easier to maintain.
         But it needs to be updated to use the new design which has a checkmark when the slider is on.
         -->
    <div  x-data="{anOlderSlider: <?= json_encode($anOlderSliderChecked) ?> }">
        <label >
            <input type="checkbox" id="anOlderSlider"
                   x-bind:checked="anOlderSlider" value="1"
                   name="an-older-slider">
            <span ></span>
        </label>
        <label for="anOlderSlider">An older slider</label>
    </div>



<button
        
        type="submit">
    Submit
</button>

</section>
</form>
</body>
</html>

While it would be nice if I wrote an example that's completely on the frontend without a backend, there is a backend component in that these slider checkboxes get stored to a database and need to remember their state, including the ability to load the correct checked or not checked, and to allow changes. Also note that a lot of these slider checkboxes are used all over the place in the application. There needs to be the ability to easily adjust the color theme, and to easily check the checkbox state, since other components will tie into it, and need to know its state whenever it changes, such as hiding and showing other sliders, etc.

Basically the older slider looked about as smaller as this:

    <div  x-data="{anOlderSlider: <?= json_encode($anOlderSliderChecked) ?> }">
        <label >
            <input type="checkbox" id="anOlderSlider"
                   x-bind:checked="anOlderSlider" value="1"
                   name="an-older-slider">
            <span ></span>
        </label>
        <label for="anOlderSlider">An older slider</label>
    </div>

That plus CSS.

Having moved over to tailwind, and trying to add an SVG checkmark for the redesign, my code basically grew to this enormous thing:

<div  x-data="{ checked: <?= $imageIsActiveChecked? 'true' : 'false' ?>  }">
<span x-data="{ on: <?= json_encode($imageIsActiveChecked) ?> }">
    <button type="button" 
            role="switch"
            x-on:click="on = !on"
            x-bind:style="`background-color: ${on ? 'var(--slider-on-bg-color)' : 'var(--slider-off-bg-color)'}`"
    >
         <span 
               :>
        <input type="checkbox"
               name="image-is-active" value="1" x-bind:checked="on" hidden>
      <span 
            x-bind:>
            <!-- toggle x checkmark -->
          <svg  fill="none" viewBox="0 0 12 12" hidden>
            <path d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
          </svg>
      </span>
      <span 
            x-bind:>
            <!-- toggle checkmark svg -->
          <svg  fill="currentColor" viewBox="0 0 12 12">
            <path d="M4.3 10.05L0 5.88241L1.90657 3.81932L4.4 6.17162L10.0916 0.550049L12 2.61314L4.3 10.05Z" fill="#636363"/>
          </svg>
      </span>
    </span>
</button>
</span>

    <span >
<span 
style="font-family: Roboto, Arial, sans-serif">Make this image active</span>
</span>

</div>

While keeping the ability to easily adjust the state of the slider and its associated checkbox with a boolean PHP variable, I'm wondering how I can get this code down to something much more manageable.

I believe my button approach is very bad as well as having to listen to the click event. CSS should be able to help avoid this entirely. Also having to constantly write a massive thing of the SVGs seem avoidable. I'm wondering if it's possible to somehow avoid these SVGs entirely, or to somehow move it to the CSS so it doesn't clutter the HTML.

Also I'm unsure if trying to use tailwind for this might be a bad idea and open to suggestions. But basically the code is too complicated for such a simple component.

How can I go about refactoring this to something more maintainable and easier to manage, etc?

Things I've tried include trying to move a bunch of this to a laravel component:

@props(['checked'=>false,'offBgcolor'=>'#636363','onBgcolor'=>'#0082CB','click'=>'on = !on'])
<span x-data="{ on: @js((bool)$checked), onBgColor: @js($onBgcolor), offBgColor: @js($offBgcolor) }" {{ $attributes }}>
    {{ $slot }}
    <button type="button" 
            role="switch"
            @click="{!!  $click !!}"
            :style="`background-color: ${on ? onBgColor : offBgColor}`"
    >
         <span 
               :>
      <span 
            :>
          <x-svgs icon="toggle-x"  fill="none" viewBox="0 0 12 12" hidden />
      </span>
      <span 
            :>
          <x-svgs icon="toggle-checkmark"  fill="currentColor" viewBox="0 0 12 12" />
      </span>
    </span>
</button>
</span>

Then the code becomes:

<div 
     x-data="{ checked: {{ $imageIsActiveChecked? 'true' : 'false' }},  }">
    <x.sliding-checkbox checked="{!! $imageIsActiveChecked ? 'true' : '' !!}">
        <input type="checkbox"
               name="image-is-active" value="1" x-bind:checked="on" hidden>
    </x.sliding-checkbox>
    <span >
<span >Make this image active</span>
</span>
</div>

But the problem with this is that I need to load the PHP variable $imageIsActiveChecked twice in order to get the checked working. It also makes it very difficult to work with alpine since I might need to do other things since I might need a completely different click logic, and I'd need to then play around with multiple variables. Also it's ugly having to embed a checkbox inside of a sliding checkbox element. It seems overly complicated for achieving something that was so simple in the easier design, merely to make a few adjustments to the older design for the slider.

How can I make the older slider look like the newer design, so I can keep the code more maintainable?

Edit: By utilizing JavaScript's URLSearchParams, I managed to get this all on the frontend:

:root{
 --slider-off-bg-color: #636363; /* pulled from the user's color theme */
 --slider-on-bg-color: #0082CB; /* pulled from the user's color theme */
}

        .switch-with-label {
    align-items: center;
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
}
label.switch .slider {
    background-color: #ccc;
    border-radius: 34px;
    bottom: 0;
    cursor: pointer;
    left: 0;
    position: absolute;
    right: 0;
    top: 0;
    transition: .1s;
}
label.switch input {
    height: 0;
    opacity: 0;
    width: 0;
}
label.switch {
    display: inline-block;
    flex-shrink: 0;
    height: 32px;
    position: relative;
    width: 58px;
    margin-right: 20px;
}
input[type="radio"], input[type="checkbox"] {
    margin: 4px 0 0;
    margin-top: 1px \9;
    line-height: normal;
}
label.switch input:checked .slider {
    background-color: #0275ff;
}
label.switch input:checked .slider:before {
    transform: translateX(25px);
}
label.switch .slider:before {
    background-color: #fff;
    border-radius: 50px;
    bottom: 4px;
    content: "";
    height: 24px;
    left: 4px;
    position: absolute;
    transition: .1s;
    width: 24px;
}
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.10.2/cdn.min.js"
        integrity="sha512-B/OEIDaWXc61XNJlO0TQILX/mFbhx77bwQKzok6I8suB6WP9yvN8zgaiyLmekPr5eRNmjfpR40zos29ktaSHEg=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css"
      integrity="sha512-wnea99uKIC3TJF7v4eKk4Y lMz2Mklv18 r4na2Gn1abDRPPOeef95xTzdwGD9e6zXJBteMIhZ1 68QC5byJZw=="
      crossorigin="anonymous" referrerpolicy="no-referrer" />


<!-- this form submits to the page itself in this example, so you can see how it handles the input and output-->
<!-- this would store to a database in the real world application -->
<form method="GET" action>
<section  x-data>
    <h1 >Update Image Preferences</h1>
<div  x-data="{ checked: (new URLSearchParams(location.search)).get('image-is-active')==='1' }">



    <!-- this is the new slider our system uses, compare to the older slider under this.
    this newer slider is per the new design, which uses an SVG for a checkmark when the slider is on.
    but it very much bloats the code. And looks a like unnecessary JavaScript and overcomplicated, large code -->

<span x-data="{ on: checked }">
    <button type="button" 
            role="switch"
            x-on:click="on = !on"
            x-bind:style="`background-color: ${on ? 'var(--slider-on-bg-color)' : 'var(--slider-off-bg-color)'}`"
    >
         <span 
               :>
        <input type="checkbox"
               name="image-is-active" value="1" x-bind:checked="on" hidden>
      <span 
            x-bind:>
            <!-- toggle x checkmark -->
          <svg  fill="none" viewBox="0 0 12 12" hidden>
            <path d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
          </svg>
      </span>
      <span 
            x-bind:>
            <!-- toggle checkmark svg -->
          <svg  fill="currentColor" viewBox="0 0 12 12">
            <path d="M4.3 10.05L0 5.88241L1.90657 3.81932L4.4 6.17162L10.0916 0.550049L12 2.61314L4.3 10.05Z" fill="#636363"/>
          </svg>
      </span>
    </span>
</button>
</span>

    <span >
<span 
style="font-family: Roboto, Arial, sans-serif">Make this image active</span>
</span>

</div>


    <!-- this is the older slider our system uses.
         the code is small and easier to maintain.
         But it needs to be updated to use the new design which has a checkmark when the slider is on.
         -->
    <div  x-data="{anOlderSlider: (new URLSearchParams(location.search)).get('an-older-slider') === '1' }">
        <label >
            <input type="checkbox" id="anOlderSlider"
                   x-bind:checked="anOlderSlider" value="1"
                   name="an-older-slider">
            <span ></span>
        </label>
        <label for="anOlderSlider">An older slider</label>
    </div>



<button
        
        type="submit">
    Submit
</button>

</section>
</form>

CodePudding user response:

Is it necessary to use Tailwind for this issue?
Hope this custom checkbox could help you:

HTML

<main> <!-- or use any html wrapper element -->
  <input type="checkbox" id="customCheckbox">
  <label for="customCheckbox"></label>
</main>

CSS

#customCheckbox{
  visibility:hidden;
  width:0;
  height:0;
  margin:0;
}
label{
  position:relative;
  display:inline-block;
  width:60px;
  height:30px;
  border:1px solid #eeeeee;
  background-color:#d7d8da;
  border-radius: 15px;
  transition: background 0.3s linear;
  cursor:pointer;
}
label:after{
  content:'';
  position:absolute;
  left:1px;
  top:1px;
  width:28px;
  height:28px;
  border-radius: 15px;
  background-color:#ffffff;
  transition: left 0.3s ease-out;
}

#customCheckbox:checked ~ label{
  background-color: #42d77d;
}
#customCheckbox:checked ~ label:after{
  left: 31px;
  background-image: url('checkmark.svg');
  background-position: center;
  background-size: 16px;
}

Update:

Here is a working pen for this
https://codepen.io/Tyutyesz/pen/VwvNbbZ

  • Related