4

I'm trying to use a hidden <iron-list>, but I need to understand why it's rendering doesn't update.

This is the related issue: https://github.com/PolymerElements/iron-list/issues/263

This is the code: http://jsbin.com/vagumebupe/9/edit?html,console,output

<!doctype html>
<head>
  
  <meta charset="utf-8">

  <base href="https://polygit.org/polymer+:master/components/">

  <script src="webcomponentsjs/webcomponents-lite.min.js"></script>
  
  <link href="polymer/polymer.html" rel="import">

  <link rel="import" href="iron-flex-layout/iron-flex-layout.html">
  <link rel="import" href="iron-ajax/iron-ajax.html">
  <link rel="import" href="paper-icon-button/paper-icon-button.html">
  <link rel="import" href="iron-icon/iron-icon.html">
  <link rel="import" href="iron-icons/iron-icons.html">
  <link rel="import" href="paper-styles/color.html">
  <link rel="import" href="paper-styles/typography.html">
  <link rel="import" href="app-layout/app-toolbar/app-toolbar.html">
  <link rel="import" href="paper-menu/paper-menu.html">
  <link rel="import" href="paper-item/paper-item.html">
  <link rel="import" href="paper-badge/paper-badge.html">
  <link rel="import" href="iron-list/iron-list.html">
  
  <style is="custom-style">
  body {
    @apply(--layout-fullbleed);
  }
</style>

</head>

<body unresolved>

  <x-app></x-app>
  
<dom-module id="x-app">
  <style>
      :host {
        @apply(--layout-fit);
        @apply(--layout-vertical);
        @apply(--paper-font-common-base);
        font-family: sans-serif;
      }
      app-toolbar {
        background: var(--paper-pink-500);
        box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.3);
        color: white;
        z-index: 1;
        color: white;
        --paper-toolbar-title: {
          font-size: 16px;
          line-height: 16px;
          font-weight: bold;
          margin-left: 0;
        };
      }
      app-toolbar paper-icon-button {
        --paper-icon-button-ink-color: white;
      }
      #itemsList,
      #selectedItemsList {
        @apply(--layout-flex);
      }
      .item {
        @apply(--layout-horizontal);
        cursor: pointer;
        padding: 16px 22px;
        border-bottom: 1px solid #DDD;
      }
      .item:focus,
      .item.selected:focus {
        outline: 0;
        background-color: #ddd;
      }
      .item.selected .star {
        color: var(--paper-blue-600);
      }
      .item.selected {
        background-color: var(--google-grey-300);
        border-bottom: 1px solid #ccc;
      }
      .avatar {
        height: 40px;
        width: 40px;
        border-radius: 20px;
        box-sizing: border-box;
        background-color: #ddd;
      }
      .pad {
        @apply(--layout-flex);
        @apply(--layout-vertical);
        padding: 0 16px;
      }
      .primary {
        font-size: 16px;
      }
      .secondary {
        font-size: 14px;
      }
      .dim {
        color: gray;
      }
      .star {
        width: 24px;
        height: 24px;
      }
      paper-badge {
        -webkit-transition: all 0.1s;
        transition: all 0.1s;
        opacity: 1;
        margin-top: 5px;
      }
      paper-badge[label="0"] {
        opacity: 0;
      }
      #starredView {
        width: 200px;
        border-left: 1px solid #ddd;
      }
      paper-item {
        white-space: nowrap;
        cursor: pointer;
        position: relative;
      }
      paper-item:hover::after {
        content: "-";
        width: 16px;
        height: 16px;
        display: block;
        border-radius: 50% 50%;
        background-color: var(--google-red-300);
        margin-left: 10px;
        line-height: 16px;
        text-align: center;
        color: white;
        font-weight: bold;
        text-decoration: none;
        position: absolute;
        right: 15px;
        top: calc(50% - 8px);
      }
      .noSelection {
        color: #999;
        margin-left: 10px;
        line-height: 50px;
      }
      .twoColumns {
        @apply(--layout-flex);
        @apply(--layout-horizontal);
        overflow: hidden;
      }
      #starredView {
        @apply(--layout-vertical);
      }
    </style>
    <template>
      <app-toolbar>
        <div title>Selection using iron-list</div>
        <div>
          <paper-icon-button icon="icons:more-horiz" alt="hidden" on-tap="_toggleHidden"></paper-icon-button>
          <paper-icon-button icon="icons:add" alt="add" on-tap="_changeContactList"></paper-icon-button>
          <paper-badge label$="[[selectedItems.length]]"></paper-badge>
          
        </div>
      </app-toolbar>

        <!-- Main List for the items -->
        <iron-list id="itemsList" items="[[data]]" selected-items="{{selectedItems}}" selection-enabled multi-selection hidden="{{hidden}}">
          <template>
            <div>
              <div tabindex$="[[tabIndex]]" aria-label$="Select/Deselect [[item.name]]" class$="[[_computedClass(selected)]]">
                <img class="avatar" src="[[item.image]]">
                <div class="pad">
                  <div class="primary">
                    [[item.name]]
                  </div>
                  <div class="secondary dim">[[item.shortText]]</div>
                </div>
                <iron-icon icon$="[[iconForItem(selected)]]" class="star"></iron-icon>
              </div>
              <div class="border"></div>
            </div>
          </template>
        </iron-list>
    </template>
  <script>
    
    addEventListener('WebComponentsReady', function() {
      
      Polymer({
        is: "x-app",
        behaviors: [
          Polymer.IronResizableBehavior
        ],
        listeners: {
          'iron-resize': '_onIronResize'
        },
        properties: {
          hidden: {
            type: Object,
            notify: true,
            value: false
          },
          selectedItems: {
            type: Object
          },
          data: {
            type: Object,
            notify: true,
            value : [
                      {
                        "name": "Liz Grimes",
                        "image": "https://s3.amazonaws.com/uifaces/faces/twitter/enda/73.jpg",
                        "shortText": "est ad reprehenderit occaecat consequat"
                      },
                      {
                        "name": "Frazier Lara",
                        "image": "https://s3.amazonaws.com/uifaces/faces/twitter/guillogo/73.jpg",
                        "shortText": "consectetur culpa adipisicing voluptate enim"
                      }
                    ]
          }
        },
        iconForItem: function(isSelected) {
          return isSelected ? 'star' : 'star-border';
        },
        _computedClass: function(isSelected) {
          var classes = 'item';
          if (isSelected) {
            classes += ' selected';
          }
          return classes;
        },
        _unselect: function(e) {
          this.$.itemsList.deselectItem(e.model.item);
        },
        _changeContactList: function() {
          this.data = [
            {
              "name": "Shelley Molina",
              "image": "https://s3.amazonaws.com/uifaces/faces/twitter/smalonso/73.jpg",
              "shortText": "laboris do velit ipsum non"
            }
          ];
          console.log('Replace data !')
        },
        _toggleHidden: function(){
          this.hidden = !this.hidden;
          console.log('Hidden : ' + this.hidden)
        },
        _onIronResize: function() {
           console.log('Resize');
        }

      });
      
    });
    
  </script>
</dom-module>
  
</body>
tony19
  • 125,647
  • 18
  • 229
  • 307

1 Answers1

5

The iron-list renders its items when the list is resized or when its items property changes. As an optimization, its _render() exits if the list is not visible. (It could be relatively expensive to render a list item, depending on the item's complexity and especially on mobile devices, so ignoring render when the list is hidden would save CPU cycles.)

When you unhide your list, the iron-resize event does not trigger because the physical dimensions of iron-list (or in this case x-app, which implements IronResizableBehavior) haven't changed, and therefore, the list doesn't update.

Following the advice from the docs (emphasis mine):

Resizing

iron-list lays out the items when it receives a notification via the iron-resize event. This event is fired by any element that implements IronResizableBehavior.

By default, elements such as iron-pages, paper-tabs or paper-dialog will trigger this event automatically. If you hide the list manually (e.g. you use display: none) you might want to implement IronResizableBehavior or fire this event manually right after the list became visible again. For example:

document.querySelector('iron-list').fire('iron-resize');

...you should manually trigger the iron-resize event upon unhiding the iron-list. To do that, we use an observer on your element's hidden property:

Polymer({
  ...
  properties: {
    hidden: {
      type: Object,
      notify: true,
      value: false,
      observer: '_hiddenChanged'
    },
    ...
  },
  _hiddenChanged: function(hidden) {
    if (!hidden) {
      this.$.itemsList.fire('iron-resize');
    }
  },
});

Here's a working demo:

<!doctype html>

<head>
  <meta name="description" content="Polymer iron-list items added while list hidden">

  <meta charset="utf-8">

  <base href="https://polygit.org/polymer+1.5.0/components/">

  <script src="webcomponentsjs/webcomponents-lite.min.js"></script>

  <link href="polymer/polymer.html" rel="import">

  <link rel="import" href="iron-flex-layout/iron-flex-layout.html">
  <link rel="import" href="iron-ajax/iron-ajax.html">
  <link rel="import" href="paper-icon-button/paper-icon-button.html">
  <link rel="import" href="iron-icon/iron-icon.html">
  <link rel="import" href="iron-icons/iron-icons.html">
  <link rel="import" href="paper-styles/color.html">
  <link rel="import" href="paper-styles/typography.html">
  <link rel="import" href="app-layout/app-toolbar/app-toolbar.html">
  <link rel="import" href="paper-menu/paper-menu.html">
  <link rel="import" href="paper-item/paper-item.html">
  <link rel="import" href="paper-badge/paper-badge.html">
  <link rel="import" href="iron-list/iron-list.html">

  <style is="custom-style">
    body {
      @apply(--layout-fullbleed);

    }
  </style>

</head>

<body unresolved>

  <x-app></x-app>

  <dom-module id="x-app">
    <style>
      :host {
        @apply(--layout-fit);

@apply(--layout-vertical);

@apply(--paper-font-common-base);

font-family: sans-serif;
      }
      app-toolbar {
        background: var(--paper-pink-500);
        box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.3);
        color: white;
        z-index: 1;
        color: white;
        --paper-toolbar-title: {
          font-size: 16px;
          line-height: 16px;
          font-weight: bold;
          margin-left: 0;
        }
        ;
      }
      app-toolbar paper-icon-button {
        --paper-icon-button-ink-color: white;
      }
      #itemsList,
      #selectedItemsList {
        @apply(--layout-flex);

      }
      .item {
        @apply(--layout-horizontal);

cursor: pointer;
        padding: 16px 22px;
        border-bottom: 1px solid #DDD;
      }
      .item:focus,
      .item.selected:focus {
        outline: 0;
        background-color: #ddd;
      }
      .item.selected .star {
        color: var(--paper-blue-600);
      }
      .item.selected {
        background-color: var(--google-grey-300);
        border-bottom: 1px solid #ccc;
      }
      .avatar {
        height: 40px;
        width: 40px;
        border-radius: 20px;
        box-sizing: border-box;
        background-color: #ddd;
      }
      .pad {
        @apply(--layout-flex);

@apply(--layout-vertical);

padding: 0 16px;
      }
      .primary {
        font-size: 16px;
      }
      .secondary {
        font-size: 14px;
      }
      .dim {
        color: gray;
      }
      .star {
        width: 24px;
        height: 24px;
      }
      paper-badge {
        -webkit-transition: all 0.1s;
        transition: all 0.1s;
        opacity: 1;
        margin-top: 5px;
      }
      paper-badge[label="0"] {
        opacity: 0;
      }
      #starredView {
        width: 200px;
        border-left: 1px solid #ddd;
      }
      paper-item {
        white-space: nowrap;
        cursor: pointer;
        position: relative;
      }
      paper-item:hover::after {
        content: "-";
        width: 16px;
        height: 16px;
        display: block;
        border-radius: 50% 50%;
        background-color: var(--google-red-300);
        margin-left: 10px;
        line-height: 16px;
        text-align: center;
        color: white;
        font-weight: bold;
        text-decoration: none;
        position: absolute;
        right: 15px;
        top: calc(50% - 8px);
      }
      .noSelection {
        color: #999;
        margin-left: 10px;
        line-height: 50px;
      }
      .twoColumns {
        @apply(--layout-flex);

@apply(--layout-horizontal);

overflow: hidden;
      }
      #starredView {
        @apply(--layout-vertical);

      }
    </style>
    <template>
      <app-toolbar>
        <div title>Selection using iron-list</div>
        <div>
          <paper-icon-button icon="icons:more-horiz" alt="hidden" on-tap="_toggleHidden"></paper-icon-button>
          <paper-icon-button icon="icons:add" alt="add" on-tap="_changeContactList"></paper-icon-button>
          <paper-badge label="[[selectedItems.length]]"></paper-badge>

        </div>
      </app-toolbar>

      <!-- Main List for the items -->
      <iron-list id="itemsList" items="[[data]]" selected-items="{{selectedItems}}" selection-enabled multi-selection hidden="{{hidden}}">
        <template>
          <div>
            <div tabindex$="[[tabIndex]]" aria-label$="Select/Deselect [[item.name]]" class$="[[_computedClass(selected)]]">
              <img class="avatar" src="[[item.image]]">
              <div class="pad">
                <div class="primary">
                  [[item.name]]
                </div>
                <div class="secondary dim">[[item.shortText]]</div>
              </div>
              <iron-icon icon="[[iconForItem(selected)]]" class="star"></iron-icon>
            </div>
            <div class="border"></div>
          </div>
        </template>
      </iron-list>
    </template>
    <script>
      HTMLImports.whenReady(function() {

        Polymer({
          is: "x-app",
          behaviors: [
            Polymer.IronResizableBehavior
          ],
          listeners: {
            'iron-resize': '_onIronResize'
          },
          properties: {
            hidden: {
              type: Object,
              notify: true,
              value: false,
              observer: '_hiddenChanged'
            },
            selectedItems: {
              type: Object
            },
            data: {
              type: Object,
              notify: true,
              value: [{
                "name": "Liz Grimes",
                "image": "https://s3.amazonaws.com/uifaces/faces/twitter/enda/73.jpg",
                "shortText": "est ad reprehenderit occaecat consequat"
              }, {
                "name": "Frazier Lara",
                "image": "https://s3.amazonaws.com/uifaces/faces/twitter/guillogo/73.jpg",
                "shortText": "consectetur culpa adipisicing voluptate enim"
              }]
            }
          },
          iconForItem: function(isSelected) {
            return isSelected ? 'star' : 'star-border';
          },
          _computedClass: function(isSelected) {
            var classes = 'item';
            if (isSelected) {
              classes += ' selected';
            }
            return classes;
          },
          _hiddenChanged: function(hidden) {
            if (!hidden) {
              console.log('firing iron-list.iron-resize');
              this.$.itemsList.fire('iron-resize');
            }
          },
          _unselect: function(e) {
            this.$.itemsList.deselectItem(e.model.item);
          },
          _changeContactList: function() {
            this.push('data', {
              "name": "Shelley Molina",
              "image": "https://s3.amazonaws.com/uifaces/faces/twitter/smalonso/73.jpg",
              "shortText": "laboris do velit ipsum non"
            });
            console.log('Replace data !')
          },
          _toggleHidden: function() {
            this.hidden = !this.hidden;
            console.log('Hidden : ' + this.hidden)
          },
          _onIronResize: function() {
            console.log('Resize');
          }

        });

      });
    </script>
  </dom-module>

</body>

modified jsbin

tony19
  • 125,647
  • 18
  • 229
  • 307
  • I don't get it. Resizing is one thing, but why doesn't it react to changing `items` property when hidden? It works when it is visible because the size changes while elements are redrawn? – Tomasz Pluskiewicz Jun 14 '16 at 05:47
  • @TomaszPluskiewicz, good question. The iron-list renders updates if it's resized or its `items` property changes and only *while visible* (as an optimization). See updated answer. – tony19 Jun 14 '16 at 06:19
  • Indeed. For the record, here's the relevant line in code: https://github.com/PolymerElements/iron-list/blob/3f9be822293e4ade5d3b472a7a31030392e4c18d/iron-list.html#L942 – Tomasz Pluskiewicz Jun 14 '16 at 06:39
  • I think iron-list component must listen hide behaviour and manage the refresh of the iron-list view. Moreover, in the [documentation](https://elements.polymer-project.org/elements/iron-list#method-notifyResize), we should use notifyResize() like this this.$.itemsList.notifyResize(). – Yohann Streibel Jun 14 '16 at 08:58
  • @YohannStreibel, Ok, I see...that's a behavior you think `iron-list` should have. Yes, `notifyResize()` also works. Note it's that same [documentation](https://elements.polymer-project.org/elements/iron-list#resizing) that explicitly suggests `.fire('iron-resize')` as an example. I don't see anything that says one should be used over the other. – tony19 Jun 14 '16 at 09:10