Search code examples
sveltesvelte-3

limiting the number of checkbox in svelte form using disabled attribute with more than one group of options


A form that displays options for an item. The user suppose to select specific number of options for each item. Options example:

let optiongroups = [
       {
        groupname : "exactly1",
        grouplimit : 1,
        id : 123,
        optionitems : [
          {
            name : "One",
            price : 1
          },
          {
            name : "Two",
            price : 2
          },
          {
            name : "Three",
            price : 3
          },
          {
            name : "Four",
            price : 4
          }
        ]

       }

]

With one group of options, this code works:

<form name="optionsform" on:submit|preventDefault={()=>collectoptions(itemid, itemprice, itemname,  itemdescription, variationname, variationprice)}>

  {#each optiongroups as group}
  <div style="margin : 5px; border : solid green;">
  <li style="list-style : none;">
    {group.groupname} - {group.grouplimit}
    {#each group.optionitems as option}

    <li>
      <label>
        <input name={group.groupname}  type="checkbox" bind:group={checkresults} value={[{"name" : option.name},{ "price" : option.price } ]} on:click={(e)=>{handleclick(e, group.groupname, group.grouplimit, group.id)}} disabled={disableit === true}>
         {option.name} : {option.price}
   </label>
      
    </li>
    {/each}

  </li>
  </div>
  {/each}
<button type="submit" >Add To cart</button>
</form>

By using disabled inside my script to disable the input checkbox and it does work correctly but when I add more than group of options - it comes from a db and I don't know how many option groups each item has - so when I add more than one group of options:

let optiongroups = [
       {
        groupname : "exactly1",
        grouplimit : 1,
        id : 123,
        optionitems : [
          {
            name : "One",
            price : 1
          },
          {
            name : "Two",
            price : 2
          },
          {
            name : "Three",
            price : 3
          },
          {
            name : "Four",
            price : 4
          }
        ]

       },
       {
        groupname : "exactly2",
        grouplimit : 2,
        id : 369,
        optionitems : [
          {
            name : "2-One",
            price : 22
          },
          {
            name : "2-Two",
            price : 44
          },
          {
            name : "2-Three",
            price : 66
          },
          {
            name : "2-Four",
            price : 88
          }
        ]

       }]

If you run this code in a repl, you'll observe that when you click on options in the first group - exactly1- and exceed the limit of option you're required to choose, the rest of checkboxes in that group are disabled - which is the desired outcome- but when you start your selection in the second group - exactly2 - you will select one checkbox and then all of the rest get disabled too. I tried to experiment with moving the limitit variable in different ways but nothing is stopping the second group from being disabled too.

The disabled line, I tried to add different condition like :

disabled={disableit === true && checkresults['id'].includes(group['id'])}

But still, it disable the rest of the checkboxes in the second group too. So the user is allowed to check 3 checkboxes in total from the two groups instead of 1 option from the first group and 2 from the second group.

Wonder if anyone could figure out where is the bug or direct me to how to include more than one group of options and let user choose from each group the correct number of options and then restrict the rest of the checkboxes.


Solution

  • You cannot use a simple global value like disableit for all groups; instead you need to check state by group. E.g. if checkresults is initialized to a dictionary according to the available groups like this:

    let checkresults = Object.fromEntries(optiongroups.map(g => [g.groupname, []]));
    

    Then you can disable each group individually via:

    <input name={group.groupname}  type="checkbox"
           bind:group={checkresults[group.groupname]}
           value={[{ "name" : option.name }, { "price" : option.price }]}
           disabled={
               checkresults[group.groupname].length === group.grouplimit &&
               checkresults[group.groupname].some(x => x[0].name == option.name) == false
           }>
    

    REPL example

    The way that the value is set up makes this a bit verbose. Also, it would be easier to isolate the entire content of each group into a separate component, that way they can manage their own state locally and you do not need to use complex state access like checkresults[group.groupname] all the time.

    Example with separate component