Demos Back to Demos

Spotify Demo

1 of 25

Easily implement custom functionality like media embeds and custom events within your grids.

Result HTML CSS JS
Edit Download

Result

HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>ZingGrid: Blank Grid</title>
    <link href="https://fonts.googleapis.com/css?family=Rubik:400,700" rel="stylesheet">
    <script src="https://cdn.zinggrid.com/zinggrid.min.js" defer></script>
  </head>
  <body>
    
    <zing-grid 
        src="https://zinggrid-examples.firebaseio.com/spotify-demo"
        layout="row"
        loading
        pager
        page-size="6"
        page-size-options="6,12,24,50">
      <zg-caption>
        <h1>Spotify Charts <a href="https://spotifycharts.com/viral/global/daily/latest" target="_blank" crossorigin>Daily Global Viral 50</a></h1>
      </zg-caption>
    	<zg-colgroup>
        <zg-column index="position" type="number" header=" ">
          <span class="currently-playing">
            <b>Currently Playing</b>
            <svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
            	<path d="M10 0.454846V8.47925C10 8.47925 9.96875 9.85379 8.254 9.85379C6.3765 9.85379 6.25 8.73669 6.25 8.47833C6.25 7.92586 6.4855 7.32364 8.25 7.32364C9.06225 7.32364 9.25 7.13222 9.25 6.64439C9.25 6.64439 9.25 3.09962 9.25 2.7977C9.25 2.49579 9.23 2.38667 8.86425 2.46255C8.235 2.59299 4.125 3.41001 3.961 3.446C3.797 3.48199 3.75 3.57117 3.75 3.83617C3.75 4.32377 3.75 9.10806 3.75 9.62455C3.75 10.141 3.3145 11 1.75 11C0.1855 11 0 10.0835 0 9.62455C0 8.94301 0.5625 8.50332 1.75175 8.50332C2.8595 8.50332 3 8.217 3 7.79061C3 6.27004 3 1.82113 3 1.60106C3 1.38099 3.09775 1.23221 3.3415 1.18292C3.74925 1.10085 9.5365 0.00667707 9.5365 0.00667707C9.5365 0.00667707 10 -0.0902925 10 0.454846Z" fill="#62D26D"/>
            </svg>
          </span>
          <p>No. [[index.position]]</p>
        </zg-column>
        <zg-column index="image" type="image" header=" ">
          <div class="image--container">
						<img src="[[index.image]]" alt="Album Art">
            <div class="video-action--container">
              <span data-type="play-button">
                <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <path d="M20 0C8.95 0 0 8.95 0 20C0 31.05 8.95 40 20 40C31.05 40 40 31.05 40 20C40 8.95 31.05 0 20 0ZM16 29V11L28 20L16 29Z" fill="white"/>
                  </svg>
              </span>
              <span data-type="pause-button">
                <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <path fill-rule="evenodd" clip-rule="evenodd" d="M20 0C9 0 0 9 0 20C0 31 9 40 20 40C31 40 40 31 40 20C40 9 31 0 20 0ZM18 28H14V12H18V28ZM26 28H22V12H26V28Z" fill="white"/>
                </svg>
              </span>
            </div>
          </div>
        </zg-column>
        <zg-column index="trackName" header=" ">
        	<span class="currently-playing">
            <b>Currently Playing</b>
            <svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
            	<path d="M10 0.454846V8.47925C10 8.47925 9.96875 9.85379 8.254 9.85379C6.3765 9.85379 6.25 8.73669 6.25 8.47833C6.25 7.92586 6.4855 7.32364 8.25 7.32364C9.06225 7.32364 9.25 7.13222 9.25 6.64439C9.25 6.64439 9.25 3.09962 9.25 2.7977C9.25 2.49579 9.23 2.38667 8.86425 2.46255C8.235 2.59299 4.125 3.41001 3.961 3.446C3.797 3.48199 3.75 3.57117 3.75 3.83617C3.75 4.32377 3.75 9.10806 3.75 9.62455C3.75 10.141 3.3145 11 1.75 11C0.1855 11 0 10.0835 0 9.62455C0 8.94301 0.5625 8.50332 1.75175 8.50332C2.8595 8.50332 3 8.217 3 7.79061C3 6.27004 3 1.82113 3 1.60106C3 1.38099 3.09775 1.23221 3.3415 1.18292C3.74925 1.10085 9.5365 0.00667707 9.5365 0.00667707C9.5365 0.00667707 10 -0.0902925 10 0.454846Z" fill="#62D26D"/>
            </svg>
          </span>
          <div>[[record.trackName]]</div>
        </zg-column>
        <zg-column index="artist" header=" "></zg-column>
        <zg-column index="url" renderer="video" header=" " hidden>
          <template>
            <video controls name="media">
              <source src="[[index.url]]" type="audio/mpeg">
            </video>
          </template>
        </zg-column>
      </zg-colgroup>
    </zing-grid>
  </body>
</html>

CSS

html, body {
  box-sizing: border-box;
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  background:#000000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-family: 'Rubik', sans-serif;
}

zing-grid {
  min-height: 800px;
  max-width: 900px;
	margin: 0 auto;
  border: 0;
  background: #000000;
  font-family: 'Rubik', sans-serif;
  color: #ffffff;
  --theme-color-primary: #1ed65f;
  --zg-select-arrow-color: #1ed65f;
  --zg-control-bar-background: #000000;
  --zg-control-bar-border-bottom: #4b4a4a;
  --zg-head-background: #000000;
}

zing-grid[loading],
zing-grid[loading] zg-body { height: 700px; } 

zg-caption h1 {
  font-weight: bold;
  font-size: 37px;
  margin-bottom: -50px;
  border-bottom: 0px solid #333030;
}

zg-caption a {
  font-size: 17px;
  font-weight: normal;
  margin-left: 20px;
  color: #1ed65f;
  text-decoration: none;
}

zg-caption a:hover {
  color: #15a548;
}

zg-caption,
zg-head,
zg-body {
  background: #000000;
}

zg-head {
    border-bottom: 0px solid #4b4a4a;
  	margin-bottom: 15px;
}

zg-control-bar {
  background: #000000;
}

zg-body[layout="card"] {
  --zg-card-columns: 33.3%;
}

zg-row[layout="card"] {
  background: #181717;
  margin: 5px;
  border-radius: 5px;
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  border-left: 1px solid #282626;
  border-top: 1px solid #282626;
  border-right: 1px solid #191818;
  border-bottom: 1px solid #191818;
}

zg-head zg-row[layout="card"] {
  display: none;
}

zg-head zg-row[layout="card"] zg-head-cell {
  display: none;
}

.zg-head-cell-inner {
	border-bottom: 1px solid #4b4a4a;
  color: #1ed65f;
}

zg-body[layout="row"] zg-row {
	border-top: 1px solid #4b4a4a;
	border-bottom: 1px solid #4b4a4a;
}

zg-cell:first-child {
  margin-top: -25px;
  font-weight: bold;
  color: #1ed65f;
  font-size: 12px;
}

zg-row[layout="row"] zg-cell:first-child p {
  white-space: nowrap;
}

zg-cell:nth-child(2) {
  margin-top: -30px;
}

zg-cell:nth-child(2) img {
  border-radius: 5px;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-left: 1px solid #191818;
  border-top: 1px solid #191818;
  border-right: 1px solid #353232;
  border-bottom: 1px solid #353232;
  margin: 0 auto;
}

zg-row[layout="row"] zg-cell:nth-child(2) img {
  width: 100px;
  border: 0;
  border-radius: 0;
  -webkit-border-radius: 0;
  -moz-border-radius: 0;
  margin-bottom: -3px;
}

zg-cell:nth-child(3) {
  margin-top: -25px;
  font-size: 25px;
  font-weight: bold;
}

zg-row[layout="row"] zg-cell:nth-child(3) {
  margin-top: -25px;
  font-size: 20px;
}

zg-cell:nth-child(4) {
  font-size: 15px;
  margin-top: -30px;
}

zg-cell:last-child {
  padding-bottom: 10px;
}

zg-row[layout="card"] video {
  width: 100%;
  margin-top: -80px;
}

 zg-row[layout="row"] video {
  margin-top: -80px;
}

.image--container { position:relative; }
.image--container img {  width:100%; }
.image--container [data-type] { 
  position:absolute;
  opacity:0;
  top: calc(50% - 20px);
  left: calc(50% - 20px);
}

.currently-playing {
  position:absolute;
  margin-top: -20px;
  font-size: 11px;
  color: #62D26D;
	opacity: 0;
}

/* styling for media state controls */
zg-body zg-row:hover .image--container [data-type="play-button"],
zg-body zg-row[playing]:hover .image--container [data-type="pause-button"],
zg-body zg-row[layout="row"][playing] [data-field-index="trackName"] .currently-playing,
zg-body zg-row[layout="card"][playing] [data-field-index="position"] .currently-playing { opacity:1; }
zg-body zg-row[playing]:hover .image--container [data-type="play-button"] { opacity:0; }

zg-body zg-row[layout="card"][playing] [data-field-index="position"] .currently-playing {
 right: 17px;
 margin-top: 11px;
}

/* row/card border styling */
zg-body zg-row { border:2px solid transparent; }
zg-body zg-row:hover {
  border:2px solid #62D26D;
  background: rgba(22, 62, 26, 0.5);
}

zg-body zg-row[playing] {
  background: rgba(62, 62, 62, 0.5);
}

zg-body zg-row[layout="card"][playing] {
  border:2px solid #62D26D;
}

zg-pager {
  border-top: 0;
  padding-top: 10px;
}

@media (max-width: 766px) {
  zing-grid[layout="card"] {
      max-width: 400px;
  }
}

zing-grid[layout="row"] {
  min-width: 700px;
}

JS

// event handler for when media has stopped
// to take away "playing" state
function handleMediaEnded(rowRef) {
  rowRef.removeAttribute('playing');
}

// render video markup
function video (index, cellDOMRef, cellRef) {
 	const videoRef = cellDOMRef.querySelector('video');
  const rowRef = cellDOMRef.parentNode;
  const mediaEventHandler = handleMediaEnded.bind(null, rowRef);
  // if we have a reference, lets attach
  // event listeners for when the media ends
  videoRef.addEventListener('ended', mediaEventHandler);
  
  // if row already playing (caused by layout switch)
  // remove the active state
  if (rowRef.hasAttribute('playing')) rowRef.removeAttribute('playing')
}


// handle clicking a row/card to start/stop a video
function _handleRowClick(e) {
  const rowDetails = e.detail.ZGTarget;
  const videoRef = rowDetails.querySelector('video');
  const allRows = document.querySelectorAll('zg-body zg-row');

  // pause all other videos 
  [...allRows].forEach(row => {
    const video = row.querySelector('video');
    // skip currently playing video
    if (videoRef == video) return;
    // pause all other videos
    // and remove active state
    if (!video.paused) {
      video.pause();
      row.removeAttribute('playing');
    }
  });
  
  // if we are playing
  if (videoRef) {
   	if (videoRef.paused) {
      videoRef.play();
      rowDetails.setAttribute('playing', 'true');
    } else {
    	videoRef.pause();
      rowDetails.removeAttribute('playing');
    }
  }
}

// window.onload event for Javascript to run after HTML
// because this Javascript is injected into the document head
window.addEventListener('load', () => {
  // Javascript code to execute after DOM content
  const zgRef = document.querySelector('zing-grid');
  // handle playing the songs
  zgRef.addEventListener('row:click', _handleRowClick);
  // handle changing layouts to keep playing track
});

Interested in this demo? Modify it to your needs in the ZingSoft Studio, our testing sandbox. It's free to sign up, and you can come back and edit at any time!

Edit in Studio