0

I have returned an object from an API call to Last.FM that returns an object of the last 100 songs I listened to. If one is currently playing there is a nowplaying flag on the first item in the object.

I'm trying to bind a class to the markup if this is the case, and I'm close. The only problem is if the condition is met it's instead binding the class to everything iterated from my object.

What have I done wrong?

To understand here is an example snippet from the returned object

      {
        "artist": {
          "mbid": "9457a08d-a2d0-4f2d-9876-b8870612d54f",
          "#text": "The Last Poets"
        },
        "@attr": {
          "nowplaying": "true"
        },
        "mbid": "3066a862-6c9a-4e21-b6bd-53e43257cbb4",
        "album": {
          "mbid": "96e8dd6c-6b49-462e-a6ea-202a3e2b1116",
          "#text": "Understand What Black Is"
        },
        "streamable": "0",
        "url": "https:\/\/www.last.fm\/music\/The+Last+Poets\/_\/Understand+What+Black+Is",
        "name": "Understand What Black Is"
      },
      {
        "artist": {
          "mbid": "77b29df2-056e-409e-a1b2-5e2bbfe421c4",
          "#text": "Awolnation"
        },
        "album": {
          "mbid": "1f7c0747-0207-441d-acad-a55ec0530b1f",
          "#text": "Back From Earth"
        },
        "streamable": "0",
        "date": {
          "uts": "1560027115",
          "#text": "08 Jun 2019, 20:51"
        },
        "url": "https:\/\/www.last.fm\/music\/Awolnation\/_\/Sail",
        "name": "Sail",
        "mbid": "0960742a-7040-435e-a2ef-c11abec3b913"
      }     

and in the markup i have iterated over my object like this

                    <li v-for="(track, index) in recentTracks" :key="`track-${index}`" class="track" v-bind:class="'track-' + index" style="background-image: url('http://www.placecage.com/100/100')">
                        <span v-bind:class="{ 'playing': 'track.@attr.nowplaying' }">{{track.artist['#text']}} - {{track.name}}</span>
                    </li>
                </ul>

Which results in every li having the class of playing

2 Answers2

1

It seems like you've wrapped track.@attr.nowplaying in ', which makes it evaluate it as a string (and hence, always evaluates as true, giving ALL items the class of playing). Try removing the '.

                    <li v-for="(track, index) in recentTracks" :key="`track-${index}`" class="track" v-bind:class="'track-' + index" style="background-image: url('http://www.placecage.com/100/100')">
                        <span v-bind:class="{ 'playing': track.@attr.nowplaying }">{{track.artist['#text']}} - {{track.name}}</span>
                    </li>
                </ul>
MTran
  • 1,799
  • 2
  • 17
  • 21
  • I didn't know that about wrapping in a string. I did this because i get ```invalid expression: Invalid or unexpected token in { 'playing': track.@attr.nowplaying }``` when trying to do it without a string. I've tried `track['@attr'].nowplaying` as well but that gives me `Cannot read property 'nowplaying' of undefined` in the console – DipoOgunmodede Jun 09 '19 at 15:04
  • There's definitely a nowplaying property inside the @attr object, and yet I'm getting errors saying undefined. Same thing when I try and do track.date.uts – DipoOgunmodede Jun 09 '19 at 16:12
0

To get this working without modifying the data it would be this:

<span :class="{ playing: track['@attr'] && track['@attr'].nowplaying }">

The quotes around 'track.@attr.nowplaying' were causing it to be treated as a string, which is considered truthy for all items.

Simply removing the quotes won't work because the @ symbol cannot appear in a token (unlike the symbols $ and _, which can). So you need to use the square bracket form to access the property.

This still won't work because not all of your items have the @attr property. For those that don't, track['@attr'] will be undefined and trying to access nowplaying will result in an error. I've added an extra check for that to the start of my expression.

I also made a couple of incidental changes, using :class instead of v-bind:class and playing instead of 'playing'. Neither is required to get it to work.

This data appears to be XML converted to JSON, with the result being that it isn't particularly clean JSON. If it were me I think I'd do some manipulation to get the data into a nicer form before attempting to use it within the UI. e.g. Renaming properties and applying proper types where appropriate.

skirtle
  • 27,868
  • 4
  • 42
  • 57
  • Thanks for the advice. I don't know how to rename object properties yet. I don't understand why your method worked. I thought the class would only bind if track.@attr.nowplaying exists, and if not it'd do nothing, similar to an if statement is it because what i wrote is the same as `if (track.@attr.nowplaying == true)` and that property not existing isn't the same as false/null? – DipoOgunmodede Jun 09 '19 at 21:17
  • @DipoOgunmodede As you've suggested, it is just the same as an `if`. An `if` would have exactly the same problems. The issue here **is not** that `nowplaying` is `undefined`, it's that `@attr` is `undefined`. You're trying to access the `nowplaying` property of `undefined`, which will throw an error. This isn't a Vue thing, it's just how JavaScript handles attempts to access properties of `undefined` or `null`. – skirtle Jun 09 '19 at 23:30