New - unofficial - feature: TABS - Hide/Show multiple blocks horizontally

I updated the blcok IDs, but not sure what the CTA IDs are? :slight_smile: the “cta3”? That’s my code:

<!DOCTYPE html>
<html>
<head>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter&display=swap');
        
         .button-group {
            display: flex;
            justify-content: center;
        }

        .button {
            background-color: #2B5472;
            color: white;
            font-family: 'Inter', sans-serif;
            font-size: 22px; 
            padding: 10px 20px; 
            margin-right: 30px;
            padding-top: 8px;
            padding-bottom: 8px;
            border-radius: 14px;
            border: none;
            cursor: pointer;
        }
        
        .button:hover {
        background-color: #CFE6D0;
        color: #ffffff;
       }
        
        .button:focus {
        outline: none;
       }

        .button:active, .active {
            background-color: #36B37E;
            color: #ffffff;
            outline: none;
        }

        @media (max-width: 480px) {
            .button-group {
                display: flex;
                flex-direction: column;
                align-items: flex-start;
            }

            .button {
                display: none;
            }

            .dropdown {
                display: block;
                width: 100%;
                margin-top: 10px;
                padding: 14px 16px; 
                border-radius: 12px;
                border: none;
                background-color: #36B37E;
                color: white;
                font-family: 'Inter', sans-serif;
                font-size: 18px;
                cursor: pointer;
            }
        }

        @media (min-width: 481px) {
            .button-group {
                display: flex;
                justify-content: center;
            }

            .dropdown {
                display: none;
            }
        }
    </style>
</head>
<body>
    <div class="button-group">
        <button class="button active" onclick="runButton1Script()">All Jobs</button>
        <button class="button" onclick="runButton2Script()">Matching Jobs</button>
        <button class="button" onclick="runButton3Script()">My Applications</button>
        <button class="button" onclick="runButton4Script()">Sent Applications</button>
        <select class="dropdown" onchange="runDropdownScript(this.value)">
            <option value="button1">All Jobs</option>
            <option value="button2">Matching Jobs</option>
            <option value="button3">My Applications</option>
            <option value="button4">Sent Applications</option>
        </select>
    </div>
    <script>
        const buttons = document.querySelectorAll('.button');
        buttons.forEach(button => {
            button.addEventListener('click', function() {
                buttons.forEach(b => b.classList.remove('active'));
                this.classList.add('active');
            });
        });

        function runDropdownScript(value) {
            switch(value) {
                case 'button1':
                    runButton1Script();
                    break;
                case 'button2':
                    runButton2Script();
                    break;
                case 'button3':
                    runButton3Script();
                    break;
                case 'button4':
                    runButton4Script();
                    break;
                default:
                    break;
            }
        }
    </script>
</body>
</html>

<script>
  function getURLParameter(name) {
    const urlParams = new URLSearchParams(window.location.search);
    return urlParams.get(name);
  }

  window.addEventListener('DOMContentLoaded', (event) => {
    document.getElementById("table2").style.display = "block";
    document.getElementById("form2").style.display = "none";
    document.getElementById("chart2").style.display = "none";
    document.getElementById("list2").style.display = "none";
    
    const tabParam = getURLParameter('tab');
    if (tabParam) {
      if (tabParam === 'table') {
        document.getElementById("table2").style.display = "block";
        document.getElementById("form2").style.display = "none";
        document.getElementById("chart2").style.display = "none";
        document.getElementById("list2").style.display = "none";
      } else if (tabParam === 'form') {
        document.getElementById("form2").style.display = "block";
        document.getElementById("table2").style.display = "none";
        document.getElementById("chart2").style.display = "none";
        document.getElementById("list2").style.display = "none";
      } else if (tabParam === 'chart') {
        document.getElementById("chart2").style.display = "block";
        document.getElementById("form2").style.display = "none";
        document.getElementById("table2").style.display = "none";
        document.getElementById("list2").style.display = "none";
      } else if (tabParam === 'list') {
        document.getElementById("list2").style.display = "block";
        document.getElementById("form2").style.display = "none";
        document.getElementById("table2").style.display = "none";
        document.getElementById("chart2").style.display = "none";
      }
    }
  });

  window.addEventListener('block-loaded-cta3', () => {
    console.log('CTA Block loaded');

    const buttonClickHandler = (e) => {
      const button = e.target.closest('#cta3 a[data-element="button"]:nth-child(1)');
      if (button) {
        e.preventDefault();
        document.getElementById("table2").style.display = "block";
        document.getElementById("form2").style.display = "none";
        document.getElementById("chart2").style.display = "none";
        document.getElementById("list2").style.display = "none";
        history.pushState(null, null, '?tab=table');
      }
      const button2 = e.target.closest('#cta3 a[data-element="button"]:nth-child(2)');
      if (button2) {
        e.preventDefault();
        document.getElementById("form2").style.display = "block";
        document.getElementById("table2").style.display = "none";
        document.getElementById("chart2").style.display = "none";
        document.getElementById("list2").style.display = "none";
        history.pushState(null, null, '?tab=form');
      }
      const button3 = e.target.closest('#cta3 a[data-element="button"]:nth-child(3)');
      if (button3) {
        e.preventDefault();
        document.getElementById("chart2").style.display = "block";
        document.getElementById("form2").style.display = "none";
        document.getElementById("table2").style.display = "none";
        document.getElementById("list2").style.display = "none";
        history.pushState(null, null, '?tab=chart');
      }
       const button4 = e.target.closest('#cta3 a[data-element="button"]:nth-child(4)');
      if (button4) {
        e.preventDefault();
        document.getElementById("chart2").style.display = "none";
        document.getElementById("form2").style.display = "none";
        document.getElementById("table2").style.display = "none";
        document.getElementById("list2").style.display = "block";
        history.pushState(null, null, '?tab=list');
    };

    document.body.addEventListener('click', buttonClickHandler);
  });
</script>```

cta3 or #cta3 is the id of the CTA block for my example, yes

1 Like

Some people have noticed that when we use this code in a sliding modal, and I use softr CTAs with the “scroll to section” function, it redirects to a new tab and not in the modal.

I have the impression that it doesn’t work… Is it a softr bug because the scroll to section doesn’t work? I imagine it’s correctable with hardcoded buttons

It works for me (test it by clicking “open lateral” button for Project A here => https://test-play.softr.app/#list2)
Are you sure the scroll to section points to the page itself?

Thanks Matthieu, I’ll look into it, it’s really weird!

Hi Matthieu and others,

As my custom-code skills have progressed a little bit, I’d still hope an answer can be found to have this plague of excess white space addressed:

Preference 1: have the excess white space removed in its entirety → @matthieu_chateau does the screen print with details of this white space help you understand what it is and if it can be removed?

Preference 2: be able to color the background in line with the rest of the website → @matthieu_chateau do you know how I can address this piece of the website (I don’t see any ID) / what custom code addresses its background coloring?

This is what I have tried so far, but without any luck:

#div.content section {background: rgb(249,249,255);
background: radial-gradient(circle, rgba(249,249,255,1) 0%, rgba(255,255,255,1) 100%);}

#div.content {background: rgb(249,249,255);
background: radial-gradient(circle, rgba(249,249,255,1) 0%, rgba(255,255,255,1) 100%);}

#div.content section {background: rgb(249,249,255);
background: radial-gradient(circle, rgba(249,249,255,1) 0%, rgba(255,255,255,1) 100%);}

#extra-div section {background: rgb(249,249,255);
background: radial-gradient(circle, rgba(249,249,255,1) 0%, rgba(255,255,255,1) 100%);}

#div.extra-div section {background: rgb(249,249,255);
background: radial-gradient(circle, rgba(249,249,255,1) 0%, rgba(255,255,255,1) 100%);}

Details of this white space

element: <div class="extra-div" style="background: rgb(255, 255, 255); height: 235px; width: 100%;"></div>
selector: body > div.content > div.extra-div>
full XPath: /html/body/div[2]/div[33]

From the screen print it looks like it’s part of block “klaar3”, but if I disable that block, this line sticks to the previous block.

Hi @thijs

I have no idea what it is without seeing the full page in the Softr studio and all custom codes applied to this page as I never had this behaviour.

Update: I found that this is related to <!-- Stick footer to the bottom if the App is shorter than the viewport --> script. Which is made by Softr => untouchable.

There must be a reason why you have such a behaviour while the script is supposed to work without doing this. And the reason can’t be found just by screenshots, impossible to debug without having everything in hands, it doesn’t work like this.

You can ask one of the Softr customer service people to check in your app or give me an access to it (I won’t stay more than 15 min on it => I have work to do and Christmas is coming)

Hi Matthieu,

I understand you have things to do… So to make things easier I made an example website: https://adina2775.softr.app

In creating this example, I noticed the excess space is caused by the largest block AFTER a footer has been added. So it somehow has to do with this footer.

What e-mail address can I send the invitation to?

Sent by direct message.

Update: No large space or huge space seen during the test, everyhting works fine on my end.

Hi, Great work! Can you advise how I link the buttons to the actual blocks. Somehow, i have difficulties to figure this out. Could you provide an example? Also how do i make the content of the blocks variable based on the linked Record.

For example. I have a page with “List with vertical cards and tag” block. These card link into detail pages where i created this tabs section. I have data pulling from airtable into these detail pages.

Thanks in advance

Important update

This is true for the tab feature as well as for any other custom code using javascript.

From now on, as blocks load faster than the footer code is being executed, all javascript codes should be inside the header custom code of the page level (same for app level by the way), unless a third party tool tells you to insert a code in the footer.

There might be some exceptions but as soon as you use window.addEventListener('block-loaded-BLOCKID', () => { in a code => header.
As soon as you use javascript that should be run on page load => header.

Otherwise the script might work 50 to 70% of the time… Which is not ideal

2 Likes

Great job Matthieu!! That’s really helpful. Thank you so much!

I changed the blocked-loaded listener to: window.addEventListener('DOMContentLoaded', () => {
and seems to be behaving.

Great custom code, I have been using it a lot in one of my projects! Surprised it isn´t a standard feature in Softr.

There is one thing that I have noticed that is behaving a bit weird when it comes to Calendar blocks. If the calendar block is connected to the first button, and therefore is displayed right away, everything works as it´s supposed to. But if the calendar block is displayed first when pressing another button, the items in the calendar are “minimized” to “X more” as the block does when there is a lot of items on the same date, even though there´s only one item. Triggering for example a filter so the calendar reloads fixes it.

See images below before and after triggering a filter in the calendar block:

image
image

Hi,

I know exactly what’s wrong, it’s specific to the calendar block.

Can you show your script for tabs (and tell me what is the calendar ID in this script) so I can quickly debug it?

Sure thing, thanks!
List3 (yes, I am lazy with the naming of blocks :grinning:) is the calendar block.

<!DOCTYPE html>
<html>
<head>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter&display=swap');
        
         .button-group {
            display: flex;
            justify-content: center;
        }

        .button {
            background-color: #4B664D;
            color: white;
            font-family: 'Inter', sans-serif;
            font-size: 15px; 
            padding: 10px 20px; 
            margin-right: 30px;
            padding-top: 8px;
            padding-bottom: 8px;
            border-radius: 14px;
            border: none;
            cursor: pointer;
        }
        
        .button:hover {
        background-color: #f5e507;
        color: #182939;
       }
        
        .button:focus {
        outline: none;
       }

        .button:active, .active {
            background-color: #f5e507;
            color: #182939;
            outline: none;
        }

        @media (max-width: 480px) {
            .button-group {
                display: flex;
                flex-direction: column;
                align-items: flex-start;
            }

            .button {
                display: none;
            }

            .dropdown {
                display: block;
                width: 100%;
                margin-top: 10px;
                padding: 10px 20px; 
                border-radius: 14px;
                border: none;
                background-color: #4B664D;
                color: white;
                font-family: 'Inter', sans-serif;
                font-size: 15px;
                cursor: pointer;
            }
        }

        @media (min-width: 481px) {
            .button-group {
                display: flex;
                justify-content: center;
            }

            .dropdown {
                display: none;
            }
        }
    </style>
</head>
<body>
    <div class="button-group">
        <button class="button active" onclick="runButton1Script()">Arbete kommande vecka</button>
        <button class="button" onclick="runButton2Script()">Företagsinfo</button>
        <button class="button" onclick="runButton3Script()">Alla arbeten</button>
        <select class="dropdown" onchange="runDropdownScript(this.value)">
            <option value="button1">Arbete kommande vecka</option>
            <option value="button2">Företagsinfo</option>
            <option value="button3">Alla arbeten</option>
        </select>
    </div>
    <script>
        const buttons = document.querySelectorAll('.button');
        buttons.forEach(button => {
            button.addEventListener('click', function() {
                buttons.forEach(b => b.classList.remove('active'));
                this.classList.add('active');
            });
        });

        function runDropdownScript(value) {
            switch(value) {
                case 'button1':
                    runButton1Script();
                    break;
                case 'button2':
                    runButton2Script();
                    break;
                case 'button3':
                    runButton3Script();
                    break;
                default:
                    break;
            }
        }
    </script>
</body>
</html>

<script>
    // hide blocks 
    window.addEventListener('DOMContentLoaded', (event) => {
        document.getElementById("list1").style.display = "block";
        document.getElementById("list2").style.display = "none";
        document.getElementById("list3").style.display = "none"
    });

    function runButton1Script() {
        document.getElementById("list1").style.display = "block";
        document.getElementById("list2").style.display = "none";
        document.getElementById("list3").style.display = "none";;
        console.log("Button 1 clicked"); 
    }

         function runButton2Script() {
        document.getElementById("list2").style.display = "block";
        document.getElementById("list1").style.display = "none";
        document.getElementById("list3").style.display = "none";
        console.log("Button 2 clicked");
    }
          function runButton3Script() {
        document.getElementById("list3").style.display = "block";
        document.getElementById("list1").style.display = "none";
        document.getElementById("list2").style.display = "none";
        console.log("Button 3 clicked");
    }

</script>
</html>```

Try this one, replace the last script by:
I added window.dispatchEvent(new CustomEvent('reload-block-list3')); , it’s mandatory for the calendar block when using the tabs feature.

<script>
    // hide blocks 
    window.addEventListener('DOMContentLoaded', (event) => {
        document.getElementById("list1").style.display = "block";
        document.getElementById("list2").style.display = "none";
        document.getElementById("list3").style.display = "none"
    });

    function runButton1Script() {
        document.getElementById("list1").style.display = "block";
        document.getElementById("list2").style.display = "none";
        document.getElementById("list3").style.display = "none";;
        console.log("Button 1 clicked"); 
    }

         function runButton2Script() {
        document.getElementById("list2").style.display = "block";
        document.getElementById("list1").style.display = "none";
        document.getElementById("list3").style.display = "none";
        console.log("Button 2 clicked");
    }
          function runButton3Script() {
        document.getElementById("list3").style.display = "block";
        document.getElementById("list1").style.display = "none";
        document.getElementById("list2").style.display = "none";
        window.dispatchEvent(new CustomEvent('reload-block-list3'));
        console.log("Button 3 clicked");
    }

</script>

Works great, thank you!

2 Likes

@matthieu_chateau Thanks for this solution, this works great on the calendar block, which we were having issues with as well.

We’re seeing the same behavior on the open street map block but the reload event doesn’t seem to have any effect on that block type. Does it require a different syntax?

Code below and screenshot of the behavior also attached.

    <style>
@import url('https://fonts.googleapis.com/css2?family=Inter&display=swap');
         .button-group {
            display: flex;
            justify-content: center;
        }

        .button {
            font-family: 'Inter', sans-serif;
            font-size: 14px; 
            font-weight: 500;
            //border-radius: 0;
            border-top-left-radius: 10px;
            border-top-right-radius: 10px;
            border: none;
            cursor: pointer;
            background-color: white;
            color: #2F3133;
            outline: none;
            border: none;
            text-align: center;
            border-bottom: 2px solid #D4D4D4;
            border-left: 2px solid #D4D4D4;
            border-right: 2px solid #D4D4D4;
            border-top: 2px solid #D4D4D4;
            transition .3s;
            margin-right: 0em;
            margin-left: 0em;
            padding-right: 20px;
            padding-left: 20px;
            padding-bottom: 8px;
            padding-top: 8px;
            }

        .button:active, .active {
           font-size: 14px; 
            font-weight: bold;
            //border-radius: 0;
            border-top-left-radius: 10px;
            border-top-right-radius: 10px;
            border: none;
            cursor: pointer;
            background-color: transparent;
            color: #2F3133;
            outline: none;
            border: none;
            text-align: center;
            //border-bottom: 2px solid gray;
            border-left: 1px solid #C8C8C8;
            border-right: 1px solid #C8C8C8;
            border-top: 1px solid #C8C8C8;
            transition .3s;
            margin-right: 0em;
            margin-left: 0em;
            padding-right: 20px;
            padding-left: 20px;
            padding-bottom: 8px;
            padding-top: 8px;
                     
        }
        
        button:focus {outline:0;}
        
        button:hover
    </style>
    <div class="button-group">
        <button class="button active" onclick="runButton1Script()">Projects</button>
        <button class="button" onclick="runButton2Script()">Project Map</button>
        <button class="button" onclick="runButton6Script()">Projects by Company</button>
        <button class="button" onclick="runButton3Script()">Project Stats</button>
        <button class="button" onclick="runButton4Script()">Calendar View</button>
        <button class="button" onclick="runButton5Script()">Gantt View</button>
    </div>
     <script>
    const buttons = document.querySelectorAll('.button');
        buttons.forEach(button => {
        button.addEventListener('click', function() {
            buttons.forEach(b => b.classList.remove('active'));
            this.classList.add('active');
        });
    });
</script>

<script>
    // hide blocks
    window.addEventListener('DOMContentLoaded', (event) => {
        document.getElementById("block2").style.display = "block";
        document.getElementById("block1").style.display = "none";
        document.getElementById("chart1").style.display = "none";
        document.getElementById("chart2").style.display = "none";
        document.getElementById("chart3").style.display = "none";
        document.getElementById("chart5").style.display = "none";
        document.getElementById("chart6").style.display = "none";
        document.getElementById("chart7").style.display = "none";
        document.getElementById("calendar1").style.display = "none";
        document.getElementById("gantt1").style.display = "none";
        document.getElementById("projects").style.display = "none";
    });
    
    function runButton1Script() {
        document.getElementById("block2").style.display = "block";
        document.getElementById("block1").style.display = "none";
        document.getElementById("chart1").style.display = "none";
        document.getElementById("chart2").style.display = "none";
        document.getElementById("chart3").style.display = "none";
        document.getElementById("chart5").style.display = "none";
        document.getElementById("chart6").style.display = "none";
        document.getElementById("chart7").style.display = "none";
        document.getElementById("calendar1").style.display = "none";
        document.getElementById("gantt1").style.display = "none";
        document.getElementById("projects").style.display = "none";
        console.log("Button 1 clicked"); 
    }
    
    function runButton2Script() {
        window.dispatchEvent(new CustomEvent('reload-block-block1'));
        document.getElementById("block1").style.display = "block";
        document.getElementById("block2").style.display = "none";
        document.getElementById("chart1").style.display = "none";
        document.getElementById("chart2").style.display = "none";
        document.getElementById("chart3").style.display = "none";
        document.getElementById("chart5").style.display = "none";
        document.getElementById("chart6").style.display = "none";
        document.getElementById("chart7").style.display = "none";
        document.getElementById("calendar1").style.display = "none";
        document.getElementById("gantt1").style.display = "none";
        document.getElementById("projects").style.display = "none";
        console.log("Button 2 clicked"); 
    }
    
    function runButton3Script() {
        document.getElementById("chart1").style.display = "block";
        document.getElementById("chart2").style.display = "block";
        document.getElementById("chart3").style.display = "block";
        document.getElementById("chart5").style.display = "block";
        document.getElementById("chart6").style.display = "block";
        document.getElementById("chart7").style.display = "block";
        document.getElementById("block1").style.display = "none";
        document.getElementById("block2").style.display = "none";
        document.getElementById("calendar1").style.display = "none";
        document.getElementById("gantt1").style.display = "none";
        document.getElementById("projects").style.display = "none";
        console.log("Button 3 clicked"); 
    }
    
    function runButton4Script() {
        window.dispatchEvent(new CustomEvent('reload-block-calendar1'));
        document.getElementById("calendar1").style.display = "block";
        document.getElementById("chart1").style.display = "none";
        document.getElementById("chart2").style.display = "none";
        document.getElementById("chart3").style.display = "none";
        document.getElementById("chart5").style.display = "none";
        document.getElementById("chart6").style.display = "none";
        document.getElementById("block1").style.display = "none";
        document.getElementById("block2").style.display = "none";
        document.getElementById("chart7").style.display = "none";
        document.getElementById("gantt1").style.display = "none";
        document.getElementById("projects").style.display = "none";
        console.log("Button 4 clicked"); 
    }
    
    function runButton5Script() {
        document.getElementById("gantt1").style.display = "block";
        document.getElementById("calendar1").style.display = "none";
        document.getElementById("chart1").style.display = "none";
        document.getElementById("chart2").style.display = "none";
        document.getElementById("chart3").style.display = "none";
        document.getElementById("chart5").style.display = "none";
        document.getElementById("chart6").style.display = "none";
        document.getElementById("block1").style.display = "none";
        document.getElementById("block2").style.display = "none";
        document.getElementById("chart7").style.display = "none";
        document.getElementById("projects").style.display = "none";
        console.log("Button 5 clicked"); 
    }
    
    function runButton6Script() {
        document.getElementById("projects").style.display = "block";
        document.getElementById("gantt1").style.display = "none";
        document.getElementById("calendar1").style.display = "none";
        document.getElementById("chart1").style.display = "none";
        document.getElementById("chart2").style.display = "none";
        document.getElementById("chart3").style.display = "none";
        document.getElementById("chart5").style.display = "none";
        document.getElementById("chart6").style.display = "none";
        document.getElementById("block1").style.display = "none";
        document.getElementById("block2").style.display = "none";
        document.getElementById("chart7").style.display = "none";
        console.log("Button 6 clicked"); 
    }
</script>

We only see the issue when the open street map block is not visible by default (or “tab” 1).

If you swap your maps so that the open street map is the second option and not shown by default, that’s the scenario where we run into it.