Search code examples
javascriptarrayspolymerweb-component

Strange results in dom-repeat when changing array values with this.set


I am trying to improve an existing module so that I can send a nice PR to them. However, I am getting stuck. Basically, when I try to change the values of the _stars array using this.set('_stars.' + i, value ), dom-template seems to display things in a seemly random fashion.

The module's original author solved the problem by reassigning the whole array. At the beginning I though that they must have ignored Polymer's ability to change index values of an array. Now I wonder if they encountered the same type of weirdness...

If you use the original _updateStars, you will see that nothing works. Using the one marked as __updateStars, everything works precisely as expected.

The weirdness must have a pattern, but I couldn't work it out.

Please tell me I am missing something obvious!

<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../paper-icon-button/paper-icon-button.html">
<link rel="import" href="../iron-icons/iron-icons.html">
<link rel="import" href="../iron-icon/iron-icon.html">

<dom-module id="star-rating">
    <template>
        <style>
            iron-icon {
                color: var(--star-color, #4775D1);
            }
            paper-icon-button {
                color: var(--star-color, #4775D1);

                --paper-icon-button-disabled: {
                    color: #4775D1;
                };
            }
            [score] {
                @apply(--layout-horizontal);
                @apply(--layout-center);
            }
        </style>
        <div score>
            HERE: {{_o(_stars,_stars.*)}} EH
            <template id="domRepeat" is="dom-repeat" items="{{_stars}}" as="star">
                {{star}}
                <template is="dom-if" if="[[readOnly]]">
                    <iron-icon icon="{{star}}"></iron-icon>
                </template>
                <template is="dom-if" if="[[!readOnly]]">
                    <paper-icon-button id="item-{{index}}" on-click="_updateRate" icon="{{star}}"></paper-icon-button>
                </template>
            </template>

            <template is="dom-if" if="[[readOnly]]">
                <content select='[votes]'></content>
            </template>
        </div>
    </template>
    <script>
        Polymer({
            _o: function(o ){
              return JSON.stringify( o );
            },

            is: "star-rating",

            observers: [
                '_updateStars(rate)'
            ],
            properties: {
                _stars: {
                    type: Array,
                    value: function() { return ["star", "star", "star-half", "star", "star-border"]; },
                },
                // number of stars assigned for score
                rate: {
                    type: Number,
                    value: 0
                },
                // show votes and disble scoring option
                readOnly: {
                    type: Boolean,
                    value: false
                }
            },
            _updateRate: function (e) {
                var id = parseInt( e.currentTarget.id.split('-')[1] );
                this.rate = id + 1;
            },

            _updateStars: function (rate) {
                var intPart = Math.floor(rate);
                var decimalPart = rate % 1;
                debugger;
                for (var i = 0; i < 5; i++) {
                    this.set('_stars.' +  i,  (i < intPart) ?  'star' : 'star-border' ) ;
                }
                if (decimalPart >= 0.5) this.set('_stars.' + intPart, "star-half" );
            },

            __updateStars: function (rate) {
                var intPart = Math.floor(rate);
                var decimalPart = rate % 1;
                debugger;
                for (var i = 0; i < 5; i++) {
                    //this.set('_stars.' +  i,  (i < intPart) ?  'star' : 'star-border' ) ;
                    this._stars[ i ] =  (i < intPart) ?  'star' : 'star-border' ;
                }
                if (decimalPart >= 0.5) this._stars[ intPart ] =  "star-half";

                var array = this._stars;
                this._stars = [];
                this._stars = array;
            },

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

Solution

  • The __updateStars function works because you are creating new array and notifies Polymer's that a change has happen, and dom-repeat do their job.

    _updateStars function doesn't work because:

    Primitive array items are not supported. This is because primitives (like number, string and boolean values) with the same value are represented by the same object.

    Here you can find a short explanation about working with arrays. This is in Polymer 1.0