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

Update: display the number of items for each tab.

Demo here:
ezgif-4-dcb26ccdd3


There are three tabs. Tab 1 for the number of open projects. Tab 2 for the number of on going projects. Tab 3 for the number of closed projects

How to

1) Airtable Setup

Everything should happen inside the users table of your Airtable base.
In my example I want to display the number of open projects, on going projects and closed projects of the logged in user.

So in the users table I created three rollup fields counting the number of projects linked to the user each time by filterting with the correct status. The formula of the rollup field is COUNTA(values)
An example below for open Projects

The field to count the open projects is called “Rollup Open Projects”
The field to count the open projects is called “Rollup Ongoing Projects”
The field to count the open projects is called “Rollup Closed Projects”

=> this is important for what’s following

2) Go to Softr studio

Add a cta block with 3 buttons.
These 3 buttons should have no action setup!


Now what should be inserted in the label of these buttons?

  • Text in the first button will be: Open Projects (<span>{LOGGED_IN_USER:FIELD:Rollup Open Projects}</span>)

  • Text in the first button will be: On Going Projects (<span>{LOGGED_IN_USER:FIELD:Rollup Ongoing Projects}</span>)

  • Text in the first button will be: Closed Projects (<span>{LOGGED_IN_USER:FIELD:Rollup Closed Projects}</span>)

Every text after FIELD: is the actual Airtable field you want to point to (case sensitive!)


Then go to the header custom code of the page
Add this code (explanation below):

<style>
#cta1 a[data-element="button"]:nth-child(1) {
  background-color: #FFFFFF !important;
  color: #228B22 !important;
  transition: all 0.3s ease;
}

#cta1 a[data-element="button"].active:nth-child(1),
#cta1 a[data-element="button"]:nth-child(1):hover,
#cta1 a[data-element="button"].active:nth-child(1):hover {
  background-color: #228B22 !important;
  color: #fff !important;
  transform: rotate(-3deg);
  transition: all 0.3s ease;
}

#cta1 a[data-element="button"]:nth-child(2) {
  background-color: #FFFFFF !important;
  color: #4D3DD8 !important;
  transition: all 0.3s ease;
}

#cta1 a[data-element="button"].active:nth-child(2),
#cta1 a[data-element="button"]:nth-child(2):hover,
#cta1 a[data-element="button"].active:nth-child(2):hover {
  background-color: #4D3DD8 !important;
  color: #fff !important;
  transform: rotate(-3deg);
  transition: all 0.3s ease;
}

#cta1 a[data-element="button"]:nth-child(3) {
  background-color: #FFFFFF !important;
  color: #F36364 !important;
  transition: all 0.3s ease;
}

#cta1 a[data-element="button"].active:nth-child(3),
#cta1 a[data-element="button"]:nth-child(3):hover,
#cta1 a[data-element="button"].active:nth-child(3):hover {
  background-color: #F36364 !important;
  color: #fff !important;
  transform: rotate(-3deg);
  transition: all 0.3s ease;
}

</style>


<script>
window.addEventListener('block-loaded-cta1', () => {
    if (window['logged_in_user']) {
        const user = window['logged_in_user'];

        const openProjectsData = user['Rollup Open Projects'];
        const ongoingProjectsData = user['Rollup Ongoing Projects'];
        const closedProjectsData = user['Rollup Closed Projects'];

        if (typeof openProjectsData !== 'undefined' && (openProjectsData || openProjectsData === 0)) {
            console.log('CTA1 loaded: Open Projects');
            $("#cta1 a:nth-child(1) span.MuiBox-root span").html(String(openProjectsData));
        }

        if (typeof ongoingProjectsData !== 'undefined' && (ongoingProjectsData || ongoingProjectsData === 0)) {
            console.log('CTA1 loaded: Ongoing Projects');
            $("#cta1 a:nth-child(2) span.MuiBox-root span").html(String(ongoingProjectsData));
        }

        if (typeof closedProjectsData !== 'undefined' && (closedProjectsData || closedProjectsData === 0)) {
            console.log('CTA1 loaded: Closed Projects');
            $("#cta1 a:nth-child(3) span.MuiBox-root span").html(String(closedProjectsData));
        }
    }
});
</script>


<script>
 window.addEventListener('DOMContentLoaded', (event) => {
  document.getElementById("list1").style.display = "block";
  document.getElementById("list2").style.display = "none";
  document.getElementById("list3").style.display = "none";
});
window.addEventListener('block-loaded-cta1', () => {
    console.log('CTA block loaded');
    document.querySelector('#cta1 a[data-element="button"]:nth-child(1)').classList.add('active');
});

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

  const buttonClickHandler = (e) => {
    const button = e.target.closest('#cta1 a[data-element="button"]');
    if (button) {
      e.preventDefault();
      const activeButton = document.querySelector('#cta1 a[data-element="button"].active');
      if (button !== activeButton) {
        activeButton.classList.remove('active');
        button.classList.add('active');
        const buttonIndex = Array.from(button.parentNode.children).indexOf(button) + 1;
        if (buttonIndex === 1) {
          document.getElementById("list1").style.display = "block";
          document.getElementById("list2").style.display = "none";
          document.getElementById("list3").style.display = "none";
        } else if (buttonIndex === 2) {
          document.getElementById("list2").style.display = "block";
          document.getElementById("list1").style.display = "none";
          document.getElementById("list3").style.display = "none";
        } else if (buttonIndex === 3) {
          document.getElementById("list3").style.display = "block";
          document.getElementById("list1").style.display = "none";
          document.getElementById("list2").style.display = "none";
        }
      }
    }
  };
  
  document.body.addEventListener('click', buttonClickHandler);
});
</script>

Code Explanation

  • The style part
    For those used to my codes => I added a style effect
 Obviously very superfluous but still very cool. If you want to remove it, just remove transform: rotate(-3deg); and transition: all 0.3s ease; everywhere you find it. You can find more effects in this guide I made: Custom Code Guide for hover effects for list blocks

The rest is classic for the tab feature styles.

  • The first script
    const openProjectsData / const ongoingProjectsData / const closedProjectsData
    You can see that these are followed by the exact name of the Airtable field. It needs to be the exact name - case sensitive.

#cta1 a:nth-child(1) span.MuiBox-root span is the selector of the span tag inside the text of the button. It might be different if you use another cta block.

  • The last script
    Classic for the tab feature.
    You understand that list1 is the block I use to display the open projects of the logged in user
    list2 is the block I use to display the ongoing projects of the logged in user
    list3 is the block I use to display the closed projects of the logged in user

Obviously, each of the lists are filtered to only show the open projects/ongoing projects/closed projects and to only show the logged in user’s data.

END

3 Likes

Is this same process capable of being adapted for grid blocks?

Yes, any blocks can be used to be nested in a tab.

Sorry miscommunication. Instead of CTA block with action buttons as triggers, can a grid/feature grid be used with the links as triggers?

Yes, it shouldn’t be a problem.
The selectors might be different though.
Logic is the same, always.

Well, If the links of these blocks react the same as the cta buttons. Otherwise, some adjustments and additional work might be needed.

okay thank you. I tried replacing cta3 with grid and also replacing button with link but none of my tweaks seem to be working. I will keep trying. Thanks

1 Like

UPDATE: Native Softr Tab Container Has Been Released

What a journey it has been! It’s been almost two years since I developed this unofficial feature, and I started to receive a real roadmap list from you. Just for the record: from the release of this unofficial feature until today, it took me around 80 hours of work to help you with it and to improve it.

The thread is currently the most viewed and the most commented in the Softr community.

Which may explain the choice of Softr to release their own native solution (:saluting_face:)

You can still use this guide, of course. Some functionalities are still “fully custom.”

Another Important Note:

I strongly encourage you to use the new native Softr tab container, whether you want to customize it with custom code or not, as one of its functionality is better than mine + overall, it will be less work and less to maintain :point_down:

My tab feature relies on frontend JavaScript to hide/show different blocks.

The Softr tab container works a bit differently: The new Tab Container initially loads and renders only those blocks that are inside the active tab. So until a user selects a tab, the blocks inside that tab are not loaded/rendered. When switching back to some of the previously selected tabs, the state of the blocks is preserved.

I know a lot of you—despite my clear warnings, some of you are quite stubborn😅—used the custom tab feature as if it could handle a lot of blocks by magic. The more you add blocks, the more you have to leverage resources to load all blocks (as a reminder: it’s not a load/unload solution, it’s a hide/show solution, which is not the same).

This is not the case with the new Softr Tab Container, which will increase your page loading speed. So, please, use it!

This thread will still serve those of you who want specific customization for the new Softr tab container.

Thanks, everyone!

6 Likes
1 Like

@matthieu_chateau A big thank you for your custom tab code :medal_sports:

1 Like

Yup, sharing your awesome solutions is much appreciated

1 Like