Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AdvancedSelect data-hs-select -- how to detect change/ input? #252

Closed
RSchmitzHH opened this issue Jan 15, 2024 · 6 comments
Closed

AdvancedSelect data-hs-select -- how to detect change/ input? #252

RSchmitzHH opened this issue Jan 15, 2024 · 6 comments

Comments

@RSchmitzHH
Copy link

RSchmitzHH commented Jan 15, 2024

Hi there!

I love your new AdvancedSelect, but, unfortunately, cannot get it working. Specifically, I have trouble getting the selected value and/ or detecting a change event.

What I want to build (see my try below):
Starting from your Advanced Select => Tags example, a select for several of a number of specialties (which are dynamically fetched from my api endpoint in fetchSpecialties*). The selected values shall be emitted with an 'input' emit.

It works fine if I remove the data-hs-select='{...}', i.e., use a usual select multiple.

[ * : To achieve that the selectable values in the Advanced Select are initialised by those fetched from my DB, I had to wrap the advanced select using a v-if="specialtiesFetched". I have removed this from the minimal example below. I also tried without and it seems not to be related to the issue. ]

What I tried (all included in my code snippet below as I tried them):

  • add an onChange callback in the selection element (seems to be disabled following this discussion: AdvancedSelect change toggleClasses dynamically not working #190 (comment)_ - right?)
  • Use HSStaticMethods.autoInit() + setting el.oChanges (as discussed there; same, so also disabled?)
  • using v-model like in a usual select (no change to variable)
  • leaving out the dynamic fetch of the selectable values and removing the v-if (did not help)

I am on preline/select v2.0.2.

Following #190 (comment), I have learnt that there shall be a v2.0.3 which addresses my issue (is that so?), but unfortunately cannot find it using npm i.

I would very much appreciate any help how to get the selected values out of this beautiful select! :)

<script lang="ts">

import {ref, onMounted} from 'vue';
import {getApiClient} from "@/apiclient/client";
import {HSSelect, HSStaticMethods} from "preline";

interface SpecialtyInputProps {
  value: string[];
}

export default {
  inheritAttrs: false

  props: {
    value: {
      type: Array as () => string[],
      required: false,
    },
  },

  setup(props: SpecialtyInputProps, {emit}: { emit: (event: 'input', value: SpecialtyInputProps['value']) => void }) {
    const specialties = ref<string[]>(['a', 'b', 'c']);
    const specialtiesSelected = ref<string[]>([]);

    onMounted(async () => {
      emit('input', ['',]);
      setTimeout(() => {
        console.log('HSS init')
        HSStaticMethods.autoInit();
        const el = HSSelect.getInstance("#specialtySelect");
        if (el) {
          // check if the element exists on dom
          el.onchange = (val: any) => {
            console.log(val);
            updateValue();
          };
          console.log('onchange:' + el.onchange);
        } else {
          console.log("no element found");
        }
      }, 100);
    });

    const updateValue = () => {
      emit('input', specialtiesSelected.value);
    };

    return {
      specialties,
      updateValue,
      specialtiesSelected
    };
  },
};

</script>

<template>

  <div class="relative">

    Selected: {{ specialtiesSelected }}.
    Props: {{ value }}.

    <select
        id="specialtySelect"
        v-model="specialtiesSelected"
        @change="updateValue"
        multiple
        data-hs-select='{
      "placeholder": "Select option...",
      "toggleTag": "<button type=\"button\"></button>",
      "toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative flex text-nowrap w-full cursor-pointer bg-white border border-gray-200 rounded-lg text-start text-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400 dark:focus:outline-none dark:focus:ring-1 dark:focus:ring-gray-600",
      "dropdownClasses": "mt-2 z-50 w-full max-h-[300px] p-1 space-y-0.5 bg-white border border-gray-200 rounded-lg overflow-hidden overflow-y-auto dark:bg-slate-900 dark:border-gray-700",
      "optionClasses": "py-2 px-4 w-full text-sm text-gray-800 cursor-pointer hover:bg-gray-100 rounded-lg focus:outline-none focus:bg-gray-100 dark:bg-slate-900 dark:hover:bg-slate-800 dark:text-gray-200 dark:focus:bg-slate-800",
      "mode": "tags",
      "tagsClasses": "relative ps-0.5 pe-9 min-h-[46px] flex items-center flex-wrap text-nowrap w-full border border-gray-200 rounded-lg text-start text-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400 dark:focus:outline-none dark:focus:ring-1 dark:focus:ring-gray-600",
      "tagsItemTemplate": "<div class=\"flex flex-nowrap items-center relative z-10 bg-white border border-gray-200 rounded-full p-1 m-1 dark:bg-slate-900 dark:border-gray-700\"><div class=\"h-6 w-6 me-1\" data-icon></div><div class=\"whitespace-nowrap\" data-title></div><div class=\"inline-flex flex-shrink-0 justify-center items-center h-5 w-5 ms-2 rounded-full text-gray-800 bg-gray-200 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 text-sm dark:bg-gray-700/50 dark:hover:bg-gray-700 dark:text-gray-400 cursor-pointer\" data-remove><svg class=\"flex-shrink-0 w-3 h-3\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg></div></div>",
      "tagsInputClasses": "absolute inset-0 w-full py-3 px-4 pe-9 flex-1 text-sm rounded-lg focus-visible:ring-0 dark:bg-slate-900 dark:text-gray-400",
      "optionTemplate": "<div class=\"flex items-center\"><div class=\"h-8 w-8 me-2\" data-icon></div><div><div class=\"text-sm font-semibold text-gray-800 dark:text-gray-200\" data-title></div><div class=\"text-xs text-gray-500\" data-description></div></div><div class=\"ms-auto\"><span class=\"hidden hs-selected:block\"><svg class=\"flex-shrink-0 w-4 h-4 text-blue-600\" xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\"><path d=\"M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425a.247.247 0 0 1 .02-.022Z\"/></svg></span></div></div>"
    }'
        class="hidden"
    >

      <option v-for="specialty in specialties"
              :value="specialty" data-hs-select-option='{
              "description": "Lorem ipsum",
        "icon": "<img class=\"inline-block rounded-full\" src=\"https://images.unsplash.com/photo-1531927557220-a9e23c1e4794?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=facearea&facepad=2&w=300&h=300&q=80\" />"
      }'>{{ specialty }}
      </option>

    </select>

    <div class="absolute top-1/2 end-3 -translate-y-1/2">
      <svg class="flex-shrink-0 w-3.5 h-3.5 text-gray-500 dark:text-gray-500" xmlns="http://www.w3.org/2000/svg"
           width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
           stroke-linecap="round" stroke-linejoin="round">
        <path d="m7 15 5 5 5-5"/>
        <path d="m7 9 5-5 5 5"/>
      </svg>
    </div>
  </div>
</template>

Thank you very much!

@RSchmitzHH RSchmitzHH changed the title AdvacedSelect data-hs-select -- how to detect change/ input? AdvancedSelect data-hs-select -- how to detect change/ input? Jan 16, 2024
@Bootstrap-Paradox
Copy link

Hey @RSchmitzHH! Have the same issue, for some reason most components won't work well with the hs tags, till there's no fix try placing an Click event listener on a wrapper for select and access the ref for select element and it should in theory provide the value.

I'm using Qwik and this was the only method that worked, it should be the same with vue. But dynamic changes to select won't capture the change.

@richt222
Copy link

@RSchmitzHH My use-case and setup is slightly different to yours. But, instead of...

el.onchange = (val) => { console.log(val) },

Could you use?...

el.on('change', (val) => { console.log(val) });

The latter works for me, whereas the former does not. Hope that helps!

@RSchmitzHH
Copy link
Author

@RSchmitzHH My use-case and setup is slightly different to yours. But, instead of...

el.onchange = (val) => { console.log(val) },

Could you use?...

el.on('change', (val) => { console.log(val) });

The latter works for me, whereas the former does not. Hope that helps!

Hi @richt222 your idea was a good one, thank you so much!

on('change', ...) detects if I select/ deselect option from the list.

What it does not detect, however, is if you deselect options by clicking on their own small "x" in the tag list. (I mean the x directly beside the tag name, in the example from the docs beside the pre-selected "Christina".)

Do you or does anyone have an idea which event I might listen to to detect deselections by x-clicks?

Again, many thanks for your help (to the two of you!)

@RSchmitzHH
Copy link
Author

RSchmitzHH commented Jan 17, 2024

Okay, I took inspiration from the two of you and finally got a working version which I will post below for reference and until there is a proper fix by the authors.

Basic tricks

  • If an element is selected/ deselected by clicking on it in the select's dropdown list, this event can be captured by el.on('change', (val) => {do something}) as @richt222 suggested
  • However, looking into the code of the advanced select component, this is the only point where an event is fired at all (when an element is clicked in the dropdown), so it seems there is no event to detect if an element is deselected by clicking on the tags's "X" button in the tag list
  • Therefore, to capture removal of tags by X-clicks, I in deed added a click event listener for the wrapping environment as @Bootstrap-Paradox suggested. I check whether the click's target was the X button by checking its html and get the associated tag's value by parentElement.parentElement . I am of course aware that this is a pretty dirty fix, but it is the only working one I could get. So, as said, I will post it below until there is a proper fix by the authors with a proper event fired by a click on the X button (or is there some that we have overlooked? Then please intervene...)
<script lang="ts">

import { ref, onMounted, watch } from 'vue';
import { getApiClient } from '@/apiclient/client';
import { HSSelect, HSStaticMethods } from 'preline';

interface SpecialtyInputProps {
  value: string[];
}

export default {
  inheritAttrs: false,

  props: {
    value: {
      type: Array as () => string[],
      required: false,
    },
  },

  setup(props: SpecialtyInputProps, { emit }: { emit: (event: 'input', value: SpecialtyInputProps['value']) => void }) {
    const specialties = ref<string[]>([]);
    const specialtiesFetched = ref<boolean>(false);
    const specialtiesSelected = ref<string[]>([]);

    const fetchSpecialties = async () => {
      try {
        const response = await (await getApiClient()).specialties.getAvailableSpecialties();
        response.forEach((specialty) => {
          specialties.value.push(specialty);
        });
      } catch (error) {
        console.error('Error fetching specialties:', error);
      }
      specialtiesFetched.value = true;
    };

    onMounted(async () => {
      await fetchSpecialties();
      emit('input', ['']);
      setTimeout(() => {
        console.log('HSS init');
        HSStaticMethods.autoInit();
        const el = HSSelect.getInstance('#specialtySelect');
        if (el) {
          // check if the element exists on dom
          el.on('change', (val: any) => {
            specialtiesSelected.value = val;
            console.log('change')
            console.log(val);
            updateValue(el);
          });
        } else {
          console.log('no element found');
        }
      }, 100);
    });

    watch(
      () => props.value,
      (value) => {
        specialtiesSelected.value = value;
      },
      { immediate: true, deep: true },
    );

    const updateValue = (el: any) => {
      // Emit 'input' event when the input fields change
      emit('input', specialtiesSelected.value);
    };

    const handleTagRemoveClick = (el: any) => {
      // el is PointerClick event, el.target is the element that was clicked
      // path from (X) to string of associated tag is: el.target.parentElement.parentElement.innerText
      const elt = el.target.parentElement?.parentElement?.innerText;
      // ugly fix to check whether the click was on the remove button: get its html
      const targetHtml = el.target.innerHTML;
      const removeButtonHtml1 = "<path d=\"M18 6 6 18\"></path><path d=\"m6 6 12 12\"></path>";
      const removeButtonHtml2 = "<svg class=\"flex-shrink-0 w-3 h-3\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"></path><path d=\"m6 6 12 12\"></path></svg>";

      if (targetHtml != removeButtonHtml1 && targetHtml != removeButtonHtml2) {
        return
      }
      if (elt) {
        console.log("Got a click to remove element: " + elt + ".")
        // remove from specialtiesSelected
        const index = specialtiesSelected.value.indexOf(elt);
        if (index > -1) {
          specialtiesSelected.value.splice(index, 1);
        }
      }
    }

    return {
      specialties,
      updateValue,
      specialtiesFetched,
      specialtiesSelected,
      handleTagRemoveClick,
    };
  },
};
</script>

<template>
  <div class="relative" v-if="specialtiesFetched" @click="handleTagRemoveClick">

        Selected: {{ specialtiesSelected }}. Props: {{ value }}.

        <select
          id="specialtySelect"
          multiple
          data-hs-select='{
          "placeholder": "Select option...",
          "toggleTag": "<button type=\"button\"></button>",
          "toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative flex text-nowrap w-full cursor-pointer bg-white border border-gray-200 rounded-lg text-start text-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400 dark:focus:outline-none dark:focus:ring-1 dark:focus:ring-gray-600",
          "dropdownClasses": "mt-2 z-50 w-full max-h-[300px] p-1 space-y-0.5 bg-white border border-gray-200 rounded-lg overflow-hidden overflow-y-auto dark:bg-slate-900 dark:border-gray-700",
          "optionClasses": "py-2 px-4 w-full text-sm text-gray-800 cursor-pointer hover:bg-gray-100 rounded-lg focus:outline-none focus:bg-gray-100 dark:bg-slate-900 dark:hover:bg-slate-800 dark:text-gray-200 dark:focus:bg-slate-800",
          "mode": "tags",
          "tagsClasses": "relative ps-0.5 pe-9 min-h-[46px] flex items-center flex-wrap text-nowrap w-full border border-gray-200 rounded-lg text-start text-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-slate-900 dark:border-gray-700 dark:text-gray-400 dark:focus:outline-none dark:focus:ring-1 dark:focus:ring-gray-600",
          "tagsItemTemplate": "<div class=\"flex flex-nowrap items-center relative z-10 bg-white border border-gray-200 rounded-full p-1 m-1 dark:bg-slate-900 dark:border-gray-700\"><div class=\"h-6 w-6 me-1\" data-icon></div><div class=\"whitespace-nowrap\" data-title></div><div class=\"inline-flex flex-shrink-0 justify-center items-center h-5 w-5 ms-2 rounded-full text-gray-800 bg-gray-200 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400 text-sm dark:bg-gray-700/50 dark:hover:bg-gray-700 dark:text-gray-400 cursor-pointer\" data-remove><svg class=\"flex-shrink-0 w-3 h-3\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg></div></div>",
          "tagsInputClasses": "absolute inset-0 w-full py-3 px-4 pe-9 flex-1 text-sm rounded-lg focus-visible:ring-0 dark:bg-slate-900 dark:text-gray-400",
          "optionTemplate": "<div class=\"flex items-center\"><div class=\"h-8 w-8 me-2\" data-icon></div><div><div class=\"text-sm font-semibold text-gray-800 dark:text-gray-200\" data-title></div><div class=\"text-xs text-gray-500\" data-description></div></div><div class=\"ms-auto\"><span class=\"hidden hs-selected:block\"><svg class=\"flex-shrink-0 w-4 h-4 text-blue-600\" xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\"><path d=\"M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425a.247.247 0 0 1 .02-.022Z\"/></svg></span></div></div>"
        }'
          class="hidden"
        >
          <option
            v-for="specialty in specialties"
            :value="specialty"
            data-hs-select-option='{
                  "description": "Lorem ipsum",
            "icon": "<img class=\"inline-block rounded-full\" src=\"https://images.unsplash.com/photo-1531927557220-a9e23c1e4794?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=facearea&facepad=2&w=300&h=300&q=80\" />"
          }'
          >
            {{ specialty }}
          </option>
        </select>

        <div class="absolute top-1/2 end-3 -translate-y-1/2">
          <svg
            class="flex-shrink-0 w-3.5 h-3.5 text-gray-500 dark:text-gray-500"
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
          >
            <path d="m7 15 5 5 5-5" />
            <path d="m7 9 5-5 5 5" />
          </svg>
        </div>

  </div>
</template>

<style scoped></style>

Many thank again @richt222 and @Bootstrap-Paradox and hope this code might help others until there is the heartly awaited fix :D

@jahaganiev
Copy link
Member

Hey folks - thanks for sharing the workarounds, we've added to our docs the backlink for this issue in case it'll be useful others.

https://preline.co/docs/frameworks-vuejs.html

Cheers!

@ZedObaia
Copy link
Contributor

ZedObaia commented Jul 2, 2024

@jahaganiev Does it make sense to emit a change event there as well ? I can help with the PR

olegpix added a commit that referenced this issue Oct 14, 2024
…g-close-click

fixes #252 fire change event when the tag close icon is clicked and item is removed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants