Deselect ion-segment

@ionic/vue": "^8.0.0
“vue”: “^3.3.0”,

This is my code for IonSegment. I know about the events @ion-change and @ion-select, the problem is that both trigger only if the value is “new”, so if you click twice on the same button, the second time it doesn’t trigger. So I resulted to having a click function on each button and having a variable that holds the previous model value. First, this is my component:

// I know I can have v-for but just so u see more clearly the values
<ion-segment ref="ionSegmentRef" v-model="calorieSpan">
     <ion-segment-button value="lowerThan" @click="segmentClicked">do 350</ion-segment-button>
     <ion-segment-button value="fromTo" @click="segmentClicked">350-500</ion-segment-button>
     <ion-segment-button value="higherThan" @click="segmentClicked">od 500</ion-segment-button>
</ion-segment>

I am using v-model but I did try with :value=calorieSpan and it doesn’t work either. This is my click function:

 segmentClicked() {
            if (this.previousValue === this.calorieSpan) {
                console.log("making null")
                this.calorieSpan = null
                // this is just outta desperation, tried with modelValue and value too
                this.$refs.ionSegmentRef.$el.modelvalue = null
                this.previousValue = null
            } else this.previousValue = this.calorieSpan
        }

So like this, it does make the value null, however the button doesn’t “un-check” it seemingly stays active. In order to de-select it I would have to dig deep and remove the styles which just overall with all the rest sounds so stupid to do.

Am I doing something wrong? Shouldn’t it just deselect when I null the v-model? It is unchecked when it mounts but once you click on one option there is no way to deselect anymore.

Cant we add something like there is on the radio group? allow-empty-selection? Or am I messing up somewhere? Please help.

showcase

Pls heeeelp

Interesting use case! I would argue you are using ion-segment for an unintended use case. Just my opinion though as another Ionic Vue user :smile: It seems they were designed to always have one selected.

What if you just add an additional option “None”?

You’re probably right, I just noticed it actually doesn’t null the value at all? I put {{ calorieSpan }} above my component to check if the value changed and even after the click function that should make it null function runs it remains the same :sob: what a shame, I love the styling of this.

video3

As for the other None option, I tried adding a forth display: none option that I would “click” programatically but the button itself is in a shadow root :smiley: so I just don’t how to access it, this is as far as I got (it’s included in the making null part of the click function):

this.$refs.ionSegmentRef.$el.lastElementChild.click()

which doesn’t work cause that’s not the button. But the more I try to more I think I will just do it right :smiley: and style a radio group

I agree, it does look nice!

Having a hidden one is a good idea! Can’t you just set the value of the ion-segment to the value of the “none” ion-segment-button? That is how I programtically set the selected button.

I tried to do this:

<ion-segment ref="ionSegmentRef" :value="calorieSpan">
     <ion-segment-button value="lowerThan" @click="segmentClicked">do 350</ion-segment-button>
     <ion-segment-button value="fromTo" @click="segmentClicked">350-500</ion-segment-button>
     <ion-segment-button value="higherThan" @click="segmentClicked">od 500</ion-segment-button>
     <ion-segment-button value="test" class="d-none" @click="segmentClicked">od 500</ion-segment-button>
</ion-segment>

And like this it doesn’t change the calorieSpan variable at all when clicking :frowning: it does change probably within the segment because the active buttons keep moving as I am clicking but my calorieSpan variable doesn’t react and stays the same

I also tried this this.$refs.ionSegmentRef.$el.value = "test" and it didn’t change to the button, I tried to also remove the d-none class (which is display: none) and it still wouldn’t change it

At this point I feel like the ion-segment has some personal beef with me :smiley:

For anyone facing the same issue I created this:

<ion-grid class="ion-no-padding">
    <ion-row ref="calorieButtonRow" class="ion-btn-row">
        <ion-col v-for="(item, key) in calorieSpans" :key="key" cols="4">
            <ion-button
                :id="key"
                selected="false"
                :border="key === 'fromTo'"
                expand="block"
                color="white"
                class="mx-0"
                @click="optionSelected(key)"
                >{{ item.kcal }}
            </ion-button>
        </ion-col>
    </ion-row>
</ion-grid>

<style scoped>
.ion-btn-row {
    background-color: rgba(var(--ion-color-caloriespan-rgb));
    border-radius: 10px;
    padding-left: 2.5px;
    padding-right: 2.5px;
}
ion-button {
    --background-hover: rgba(var(--ion-color-caloriespan-rgb));
    --background-activated: rgba(var(--ion-color-caloriespan-rgb));
    margin-top: 2.5px;
    margin-bottom: 2.5px;
}
ion-button[selected="false"] {
    --color-hover: rgba(var(--ion-color-terShade5-rgb));
}
ion-button[selected="false"]::part(native) {
    background-color: rgba(var(--ion-color-caloriespan-rgb));
}
ion-button[selected="true"]::part(native) {
    background-color: rgba(var(--ion-color-white-rgb));
}
ion-button[border="true"] {
    border-right: 1px solid rgba(var(--ion-color-terShade2-rgb));
    border-left: 1px solid rgba(var(--ion-color-terShade2-rgb));
}
</style>

The calorieSpan color is

--ion-color-caloriespan: #eee;
--ion-color-caloriespan-rgb: 238, 238, 238;

This part is to put the border only on the middle element :border="key === 'fromTo'"

And finally this is the function

optionSelected(key) {
            const el = document.getElementById(key)
            const isAlreadySelected = el.getAttribute("selected") === "true"
            if (isAlreadySelected) // code for when the btn is already selected 
            else {
                const previous = this.$refs.calorieButtonRow.$el.querySelector('[selected="true"]')
                if (previous) previous.setAttribute("selected", false)
                // additional code for selecting the btn
            }
            el.setAttribute("selected", !isAlreadySelected)
        },

gid3

Thank you for all your help :))

Well, I just figured it out for you with ion-segment :grin:

I think the missing key was not stopping propagation of the click event.

<ion-segment :value="selected">
  <ion-segment-button value="all" @click.prevent.stop="handleButtonClick">
    <ion-label>All</ion-label>
  </ion-segment-button>
  <ion-segment-button
    value="favorites"
    @click.prevent.stop="handleButtonClick"
  >
    <ion-label>Favorites</ion-label>
  </ion-segment-button>
  <ion-segment-button
    value="no-favorites"
    @click.prevent.stop="handleButtonClick"
  >
    <ion-label>Not My Favorites</ion-label>
  </ion-segment-button>
  <ion-segment-button value="none" style="display: none">
    <ion-label>None</ion-label>
  </ion-segment-button>
</ion-segment>
const selected = ref('all');

function handleButtonClick(event: PointerEvent): void {
  const newValue = event.target.value;

  if (newValue === selected.value) {
    selected.value = 'none';
  } else {
    selected.value = newValue;
  }
}
1 Like

Omggg you’re a genius! That’s so much better than mine! Marking this as the solution and implementing asap :star_struck: thank youuu SO MUCH

1 Like