bindingpolymer-1.0dom-repeat

polymer class binding inside dom-repeat not working


I'm trying to bind a computed value to an element inside a dom-repeat template.

<iron-selector class="dropdown-content" selected="0">
   <template is="dom-repeat" items="[[items]]">
      <div class$="item {{_itemClass(selectedItems)}}" data-value="[[item]]" on-click="_onItemClick">[[item]]</div>
   </template>
</iron-selector>

My goal here is to update the dropdown items styling when the user selects one of them. When this happens, I update the selectedItems array by pushing the item value to it. This will trigger the rendering of the selection box but will fail to call the _itemClass(selectedItems) expression.

What am I missing here? I read in the documentation that you must specify the properties that will trigger the binding and I'm doing that by adding the selectedItems to the binding expression with no luck at all.

I attached a snippet with the current behaviour.

Any help will be welcome.

<!doctype html>
<head>
  
  <meta charset="utf-8">
  <!-- polygit -->
  <base href="https://polygit.org/components/"> <!-- saves typing! -->
  <script src="webcomponentsjs/webcomponents-lite.min.js"></script>
  <link rel="import" href="iron-flex-layout/iron-flex-layout-classes.html">
  <link rel="import" href="iron-icons/iron-icons.html">
  <link rel="import" href="iron-dropdown/iron-dropdown.html">
  <link rel="import" href="iron-selector/iron-selector.html">
  <!-- style -->
  <style>
    x-example {
      width: 250px;
    }
  </style>
</head>
<body>
  
<x-example></x-example>
  
<dom-module id="x-example">
  <style include="iron-flex iron-flex-alignment"></style>
  <style>
    :host {
      position: relative;
      border: 1px solid blue;
      display: block;
    }
    
    .body {
      @apply --layout-horizontal;
    }

    .body .selection {
      border: 1px solid green;
      margin: 2px;
      flex-grow: 1
    }

    .body .selection .item {
      display: inline-block;
      margin: 2px;
      background: lightgreen;
      padding: 2px 5px;
      border: 1px solid green;
      border-radius: 4px;
      color: darkblue;
    }

    .body .selection .item .text {
      display: inline-block;
    }

    .body .selection .item iron-icon {
      width: 18px;
      cursor: pointer;
    }

    .body .dropdown {
      border: 1px solid yellow;
      margin: 2px;
      cursor: pointer;
      min-height: 40px;
    }

    .body .dropdown iron-icon {
      margin: auto;
    }

    .options iron-dropdown {
      border: 1px solid grey;
      border-radius: 6px;
      overflow: hidden;
    }

    .options .item {
      cursor: pointer;
    }

    iron-selector>* {
      padding: 8px;
      background: aqua;
    }

    .horizontal-section {
      padding: 0;
    }

    .iron-selected {
      background-color: var(--google-blue-500);
      color: white;
    }
    
  </style>
  <template>
 
        <div class="body">
            <div class="flex selection">
                <template is="dom-repeat" items="[[selectedItems]]">
                    <div class="item">
                        <div class="text">[[item]]</div>
                        <iron-icon class="dropdown-trigger" icon="icons:clear" on-click="_onRemoveItem"></iron-icon>
                    </div>
                </template>
            </div>
            <div class="dropdown" on-click="_toggleDropdown">
                <iron-icon icon="icons:arrow-drop-down"></iron-icon>
            </div>
        </div>
        <div class="options">
            <iron-dropdown auto-fit-on-attach="true" id="dropdown" horizontal-align="left" vertical-align="top">
                <iron-selector class="dropdown-content" selected="0">
                    <template is="dom-repeat" items="[[items]]">
                        <div class$="item {{_itemClass(selectedItems)}}" data-value="[[item]]" on-click="_onItemClick">[[item]]</div>
                    </template>
                </iron-selector>
            </iron-dropdown>
        </div>
    
  </template>
  <script>
    
    // only need this when both (1) in the main document and (2) on non-Chrome browsers
    addEventListener('WebComponentsReady', function() {
      
      Polymer({
        is: "x-example",
        properties: {
          items: {
            type: Array,
            value: ["One", "Two", "Three"]
          },
          selectedItems: {
            type: Array,
            value: []
          }
        },
        _toggleDropdown: function() {
          this.$.dropdown.toggle();
        },
        _onItemClick: function(e) {
          var value = e.srcElement.dataValue;
          if (this.selectedItems.indexOf(value) >= 0) {
            return;
          }
          this.push("selectedItems", value)
          this._refreshDropdown();
        },
        _refreshDropdown: function() {
          this.async(function() {
            this.$.dropdown.position();
          })
        },
        _onRemoveItem(e) {
          let index = this.selectedItems.indexOf(e.srcElement.dataValue);
          this.splice("selectedItems", index, 1);
          this._refreshDropdown();
        },
        _itemClass: function() {
          console.log("item class called")
        }
        
      });
      
    });
    
  </script>
</dom-module>
  
</body>


Solution

  • Your binding is _itemClass(selectedItems) which only triggers when the variable selectedItems changes, not when the array mutates.

    You've got two options:

    1) Either change your binding to _itemClass(selectedItems.*), which will get triggered by this.push and other array mutating methods

    2) Change the variable each time:

    this.set('selectedItems', [].concat(this.selectedItems).concat(value));
    

    A working example with the first solution (and 2nd solution commented out) is available here