Search code examples
web-componentarcgisesriarcgis-js-api

How to deselect an already-selected web component list item?


I've been trying to make use of list web components from ESRI Calcite Design System.

With the code snippet below I cannot manage to deselect an already-selected list item when it's clicked a second time.

I'm not stuck with using the Calcite Design System. I could use another web component library (e.g., Material Design) but I cannot find any examples.

"use strict";

const listNode = document.getElementById("point-list")

// Cannot use the event listener below because nothing's fired when already-selected item is clicked          
//listNode.addEventListener("calciteListChange", onListClickHandler)

listNode.addEventListener("click", onListClickHandler)

const currentSelectedListItem = (function(){
  let currentItemValue
  return {
    is_same: function(selectedItemValue) {
      if (currentItemValue != selectedItemValue) {
        currentItemValue = selectedItemValue
        return false
      }
      else {
        return true
      }
    },
    reset: function() {
      currentItemValue = null
    }
  }
})()

function onListClickHandler(event) {
  const target = event.target
  const targetId = target.value

  if (currentSelectedListItem.is_same(targetId)) {
    console.log("already selected")
  
    // Test 1 (FAILS)
    target.toggleSelected()

    // Test 2 (FAILS)
    // target.removeAttribute("selected")
 
    // Test 4 (FAILS)
    // Array.from(listNode.childNodes).forEach(item => item.selected = false)

    // Test 5 (FAILS)
    // const listItems = listNode.querySelectorAll("calcite-value-list-item")
    // listItems.forEach(item => item.selected = false)
  }
  else {
    console.log("new selection")
    // Do some stuff
  }
}
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no"/>

    <title>deselect already-selected value list item</title>

    <script src="https://js.arcgis.com/4.25/"></script>
    <link rel="stylesheet" href="https://js.arcgis.com/4.25/esri/themes/light/main.css"/>

    <script type="module" src="https://js.arcgis.com/calcite-components/1.0.0-beta.99/calcite.esm.js"></script>
    <link rel="stylesheet" type="text/css" href=https://js.arcgis.com/calcite-components/1.0.0-beta.99/calcite.css />
  </head>

  <body>
    <calcite-shell class="calcite-tutorial">
      <calcite-shell-panel slot="panel-end">
        <calcite-panel heading="Point list">
          <calcite-value-list id="point-list">
            <calcite-value-list-item label="point 1" value="point_1" description="some description"></calcite-value-list-item>
            <calcite-value-list-item label="point 2" value="point_2" description="some description"></calcite-value-list-item>
            <calcite-value-list-item label="point 3" value="point_3" description="some description"></calcite-value-list-item>
          </calcite-value-list>
        </calcite-panel>
      </calcite-shell-panel>
    </calcite-shell>
  </body>
</html>

Could you please help me?


Solution

  • I think the following example does what you want.

    Note:

    • I'm not sure why toggleSelected() doesn't work.
    • I simplified tracking the last clicked item for my example by applying unique IDs to each list item and tracking that in the click event (I'm assuming no selection via keyboard for this example, but if you did allow that you would need to keep track of the selected item id and value another way).
    • I update the description of each list item in this example just to make it clear the selected state.
    • The focus rectangle stays on an item after deselecting it, so I get rid of that by moving focus to another selectable element in the page and then call blur. This is just an example of how that could be done, and is optional.
    • According to the documentation, the calcite-value-list has been deprecated, and replaced with calcite-list. I imagine this approach may work the same with the updated component: Updated List

    Here is my version:

    "use strict";
    
    const otherElement = document.getElementById("other-element");
    const listNode = document.getElementById("point-list")
    let lastSelectedItem;
    let lastSelectedValue;
    
    listNode.addEventListener("click", onListClickHandler)
    
    function onListClickHandler(event) {
      if (event.target.id === lastSelectedItem) {
        event.target.selected = !lastSelectedValue;
        
        // If you don't want to see the focus rectangle around the deselected item, then
        // selected another focusable element, and optionally call blur() on the new active element.
        if (event.target.selected == false) {
          otherElement.focus();
          document.activeElement.blur();
        }
      }
      lastSelectedItem = event.target.id;
      lastSelectedValue = event.target.selected
      
      // Just updating the descriptions in this example to show the selected state of each list item.
      for(let listItem of event.target.parentElement.children) {
        listItem.description = listItem.selected ? "Selected" : "NOT Selected";
      }
    }
    <html>
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no"/>
    
        <title>deselect already-selected value list item</title>
    
        <script src="https://js.arcgis.com/4.25/"></script>
        <link rel="stylesheet" href="https://js.arcgis.com/4.25/esri/themes/light/main.css"/>
    
        <script type="module" src="https://js.arcgis.com/calcite-components/1.0.0-beta.99/calcite.esm.js"></script>
        <link rel="stylesheet" type="text/css" href=https://js.arcgis.com/calcite-components/1.0.0-beta.99/calcite.css />
      </head>
    
      <body>
        <calcite-shell class="calcite-tutorial">
          <!-- Just adding another focusable element to show how to remove focus rectangle on deselected elements-->
          <label tabIndex="1" id="other-element">Some other focusable element</label>
          <calcite-shell-panel slot="panel-end">
            <calcite-panel heading="Point list">
              <calcite-value-list id="point-list">
                <calcite-value-list-item id="item_1" label="point 1" value="point_1" description="some description"></calcite-value-list-item>
                <calcite-value-list-item id="item_2" label="point 2" value="point_2" description="some description"></calcite-value-list-item>
                <calcite-value-list-item id="item_3" label="point 3" value="point_3" description="some description"></calcite-value-list-item>
              </calcite-value-list>
            </calcite-panel>
          </calcite-shell-panel>
        </calcite-shell>
      </body>
    </html>