Skip to content

Part 2: Building the Playbar using Polymer

Arthur Pachachura edited this page Sep 20, 2017 · 6 revisions

Well done! We now have a working API that allows us to stream a single file to clients. Now, let's start putting that on our webpage.

IMPORTANT: Disabling your cache

There's one really annoying thing about web development, the browser cache. Most browsers aggressively cache files in order to send fewer requests to the server. However, when we are developing, we need to make sure that we see changes as soon as we make them. To ensure this, we use Chrome Developer Tools (DevTools) to disable the browser cache during development.

(If you're using a browser other than Chrome..... please use Chrome. Chromium is fine too.)

To disable cache (while DevTools is open):

  1. Navigate to localhost:8000 then right click anywhere on the page and click "Inspect Element". DevTools should pop up.
  2. Click the "Network" tab. Then, click the "Disable Cache" checkbox inside the Network tab.

IMPORTANT: This only disables cache while DevTools is open so keep DevTools open during development!

Now, anytime we want to view the changes we make to the site, all we have to do is refresh (F5 or MacOnlyWeirdKey+R).

Finally, make sure you can see the DevTools Console. This will display any errors so we can debug.

Step 1

Now that we're on localhost:8000 and there are no errors in the console, let's get started by creating a new component and adding it to our page. We will call this component the beats-playbar.

Open src/my-template.html. It should look like this:

<dom-module id="my-template">
  <template>
    <style include="shared-styles">
      :host {
        display: block;
      }
    </style>

    content

  </template>

  <script>
    class MyTemplate extends Polymer.Element {
      static get is() { return 'my-template'; }
    }

    window.customElements.define(MyTemplate.is, MyTemplate);
  </script>
</dom-module>

This is the template used to create new Polymer components. Now copy it to a new file called src/beats-playbar.html. Then, replace MyTemplate with BeatsPlaybar and replace my-template with beats-playbar. NOTE: Polymer requires that these names match the name of the HTML file, so be sure you use proper casing where required.

Here's beats-playbar.html:

<dom-module id="beats-playbar">
  <template>
    <style include="shared-styles">
      :host {
        display: block;
      }
    </style>

    content

  </template>

  <script>
    class BeatsPlaybar extends Polymer.Element {
      static get is() { return 'beats-playbar'; }
    }

    window.customElements.define(BeatsPlaybar.is, BeatsPlaybar);
  </script>
</dom-module>

Now, let's go into our application shell, beats-app.html. This is the main entrypoint for Polymer in our app. It should look like this:

<!-- ... -->

<link rel="import" href="mdi.html">
<link rel="import" href="shared-styles.html">
<link rel="import" href="app-container.html">

<dom-module id="beats-app">
<!-- ... -->
</dom-module>

Add our element, beats-playbar, to the import list at the top so that the top of the file looks like this.

<!-- ... -->
<link rel="import" href="mdi.html">
<link rel="import" href="shared-styles.html">
<link rel="import" href="app-container.html">

<link rel="import" href="beats-playbar.html">

<!-- ... -->

Now, that we have our custom element imported, let's add it into our layout. Find the div with class="layout-main" and insert our component so that the file looks like this:

<div class="layout-main">
  <beats-playbar></beats-playbar>
</div>

Now try it! All you have to do is refresh the page with DevTools open. You should see the word "content" on the screen.

Step 2

Next, we will apply basic styling to our playbar, including putting it at the bottom of the screen.

There are 3 parts to a Polymer component (and any webpage):

  • HTML: The structure of the document
  • CSS: How the document is styled, such as colors, etc.
  • JS: How the document interacts with the user and other documents ("the code")

We will now edit the CSS of beats-playbar.html so that it is at the bottom of our page and has a visible background-color.

Let's add a background-color to the playbar. In the style section of beats-playbar.html:

:host {
  display: block;
  background-color: black;
}

What's the problem?

Solution We can't see the text...
:host {
  display: block;
  background-color: black;
  color: white; /*now we can!*/
}

(If you didn't get this one, try searching "css set color" on Google. MDN (Mozilla Developer Network) should show up.)

Now, let's use a CSS variable to set the background-color to a nice color. Look inside shared-styles.html:

:host 
{
  --color-primary: var(--paper-indigo-900);
  --color-accent: var(--paper-deep-orange-600);
}

We have two custom CSS variables defined, --color-primary and --color-accent. You can change these to anything you wish. Here's a list of bright and ready-to-use Polymer-supported colors: Google Color Guide. To change the color to red 500, for example, use var(--paper-red-500).

Now, let's use our primary color in beats-playbar:

:host {
  display: block;
  background-color: var(--color-primary);
  color: white;
}

If the playbar is "IEEE indigo" now, 👍. We can use any variable in the :host section of shared-styles because we explicitly included it in our file with <style include="shared-styles">...</style>.

Next, let's push our playbar to the bottom of the page using fixed Positioning (read MDN guide on Positioning).

Possible Solutions
:host {
  display: block;
  background-color: var(--color-primary);
  color: white;
  position: fixed; /*fix on screen*/
  bottom: 0; /*fix so that playbar is 0px from bottom */
  left: 0; /*fix so that playbar is 0px from left */
  right: 0; /*fix so that playbar is 0px from right */
}

OR equally valid

:host {
  display: block;
  background-color: var(--color-primary);
  color: white;
  position: fixed; /*fix on screen*/
  bottom: 0; /*fix so that playbar is 0px from bottom */
  width: 100%; /*fix the screen width*/
}

Let's make it look a bit better by adding padding around the word "content". Try the padding (MDN docs) property.

Possible Solution
:host {
  display: block;
  background-color: var(--color-primary);
  color: white;
  position: fixed;
  bottom: 0;
  width: 100%;
  padding: 16px; /*16px padding on each side*/
}

Now, let's learn how to use a Polymer component, in this case a paper-card (Web Components). First, surround the "content" with a paper-card.

<template>
<style include="shared-styles">
 ...
</style>

<paper-card>
content
</paper-card>

</template>

Then, transfer all of our styles over to the card.

<style include="shared-styles">
  :host {
    display: block;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
  }
  paper-card {
    display: block;
    padding: 16px;
    background-color: var(--color-primary);
    color: white;
  }
</style>

Everything should look exactly the same (for now).

NOTE: Normally, we need to import paper-card into our HTML file. However, we don't need to import paper-card right now, because it is already imported in beats-app.html.

Next, let's add a paper-icon-button inside our paper-card:

<paper-card>
  <paper-icon-button icon="mdi:play"></paper-icon-button>
</paper-card>

Try tapping it! If you look closely enough, we can see a ripple effect, but since the ripple is black, it's quite hard to see. Let's make sure that every paper-icon-button we make in the playbar has a white ripple.

Where did mdi:play come from? Check out https://materialdesignicons.com for a full list of icons. Simply prefix the name with mdi: and you can use any icon on that page! More technically, this works because we imported mdi.html in beats-app.html.

Solution

Add this to your CSS:

paper-icon-button {
  --paper-icon-button-ink-color: white;
}

Couldn't find this? The property above is in the documentation of paper-icon-button (on WebComponents.org).

Since the play button is the focus of the playbar, let's give it a special style class...

<paper-card>
  <paper-icon-button icon="mdi:play" class="play"></paper-icon-button>
</paper-card>

... and style in to be both bigger and in our accent color.

paper-icon-button {
 /* ... */
}
paper-icon-button.play { /*the "dot" indicates a class */
  width: 48px;
  height: 48px;
  color: var(--color-accent);
}

👍

Step 3

Now, we will learn how to use Flex Containers to center content.

To accomplish this, we will reference the Polymer Flex Layout Guide.

iron-flex-layout defines several CSS Mixins that allow developers to layout pages with ease. A CSS Mixin looks similar to a CSS variable, except you call it like this: @apply --my-mixin. Mixins set certain properties on the elements they are applied on.

First, surround the paper-icon-button we already created with a new div with class bar-container (or whatever name you want).

<paper-card>
  <div class="bar-container">
    <paper-icon-button icon="mdi:play" class="play"></paper-icon-button>
  </div>
</paper-card>

Then, style the bar-container like so:

div.bar-container {
  @apply --layout-horizontal;
  @apply --layout-center-justified;
  @apply --layout-center;
}

And refresh. Notice how the play button is now centered! We created a flex container with a horizontal cross-axis (or the main layout axis). All items will be centered both along the cross-axis (center-justified) and the opposite axis (center).

NOTE: Again, this works because we already imported iron-flex-layout.html.

Now, add a rewind and fast forward button!

Possible Solution
<paper-card>
  <div class="bar-container">
    <paper-icon-button icon="mdi:rewind-outline"></paper-icon-button>
    <paper-icon-button icon="mdi:play" class="play"></paper-icon-button>
    <paper-icon-button icon="mdi:fast-forward-outline"></paper-icon-button>
  </div>
</paper-card>

We can also space the buttons so that they are further apart. Try this in CSS using margin-left and margin-right.

Possible Solution
paper-icon-button {
  --paper-icon-button-ink-color: white;
  margin-left: 6px;
  margin-right: 6px;
}

Checkpoint

Our beats-playbar.html should look like the one below. If it doesn't and still works to your liking, that's OK! Design is different for everyone.

Possible Playbar HTML
<dom-module id="beats-playbar">
  <template>
    <style include="shared-styles">
      :host {
        display: block;
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
      }
      paper-card {
        display: block;
        background-color: var(--color-primary);
        color: white;
        padding: 16px;
      }
      paper-icon-button {
        --paper-icon-button-ink-color: white;
        margin-left: 6px;
        margin-right: 6px;
      }
      paper-icon-button.play { /*the "dot" indicates a class*/
        width: 48px;
        height: 48px;
        color: var(--color-accent);
      }
      div.bar-container {
        @apply --layout-horizontal;
        @apply --layout-center-justified;
        @apply --layout-center;
      }
    </style>

    <paper-card>
      <div class="bar-container">
        <paper-icon-button icon="mdi:rewind-outline"></paper-icon-button>
        <paper-icon-button icon="mdi:play" class="play"></paper-icon-button>
        <paper-icon-button icon="mdi:fast-forward-outline"></paper-icon-button>
      </div>
    </paper-card>

  </template>

  <script>
    class BeatsPlaybar extends Polymer.Element {
      static get is() { return 'beats-playbar'; }
    }

    window.customElements.define(BeatsPlaybar.is, BeatsPlaybar);
  </script>
</dom-module>

Step 4

Next, we will add volume, repeat, and loop buttons using the same principles of flex layout. Be sure to keep open the Flex Layout Guide from before.

Let's surround our paper-icon-buttons with a div with class="center". Also, create divs with classes left and right, like so:

<paper-card>
  <div class="bar-container">
    <div class="left">

    </div>
    <div class="center">
      <paper-icon-button icon="mdi:rewind-outline"></paper-icon-button>
      <paper-icon-button icon="mdi:play" class="play"></paper-icon-button>
      <paper-icon-button icon="mdi:fast-forward-outline"></paper-icon-button>
    </div>
    <div class="right">

    </div>
  </div>
</paper-card>

Now, create whatever buttons you want to on the left side (repeat, shuffle, etc.) and add a volume button on the right side.

Possible Solution
<paper-card>
  <div class="bar-container">
    <div class="left">
      <paper-icon-button icon="mdi:repeat"></paper-icon-button>
      <paper-icon-button icon="mdi:shuffle"></paper-icon-button>
    </div>
    <div class="center">
      <paper-icon-button icon="mdi:rewind-outline"></paper-icon-button>
      <paper-icon-button icon="mdi:play" class="play"></paper-icon-button>
      <paper-icon-button icon="mdi:fast-forward-outline"></paper-icon-button>
    </div>
    <div class="right">
      <paper-icon-button icon="mdi:volume-high"></paper-icon-button>
    </div>
  </div>
</paper-card>

Then, let's change the styling of our bar-container and style the .left and .right classes. Here's the CSS:

div.bar-container {
  @apply --layout-horizontal;
  /*@apply --layout-center-justified; now it's just "justified"*/
  @apply --layout-justified;
  @apply --layout-center;
}
div.bar-container > .left {
  @apply --layout-horizontal;
  @apply --layout-start-justified;
}
div.bar-container > .right {
  @apply --layout-horizontal;
  @apply --layout-end-justified;
}

Notice how the left and right items spread out (that's the justified in the bar-container!) Also, we justified the .left and .right classes to their appropriate side (the > indicates that a component with class left/right must be immediately inside a div.bar-container).

TIP! Use > in CSS to show an immediate relationship between a parent and a child. parent > child means that the child is an immediate descendant of the parent.

Try it! Notice that the play button is no longer exactly in the center of the playbar. We can fix this by adding --layout-flex to both the .left and .right classes.

Possible Solution
div.bar-container > .left {
  @apply --layout-horizontal;
  @apply --layout-flex;
  @apply --layout-start-justified;
}
div.bar-container > .right {
  @apply --layout-horizontal;
  @apply --layout-flex;
  @apply --layout-end-justified;
}

Once that's done, the play button should be in the center! 👍

Finally, we will add a new element, a paper-slider (look this up on https://webcomponents.org and click "paper-slider" just under the "Overview" button on the left side of the screen). Add a paper-slider to the right side, just before the volume button. Give the paper-slider a class of volume and have it start at 50%.

Possible Solution
<div class="right">
  <paper-slider value="50" class="volume"></paper-slider>
  <paper-icon-button icon="mdi:volume-high"></paper-icon-button>
</div>

Then, style it so that the slider and knob color is white. (Remember: see the documentation.) Then, make sure the slider is exactly 100px wide (adjust to taste).

Possible Solution
paper-slider.volume {
  --paper-slider-active-color: white;
  --paper-slider-knob-color: white;
  width: 100px;
}

Checkpoint 2

Your file should look something like what's below. If it doesn't, it's OK as long as everything looks good to you!

Possible `beats-playbar.html`
<dom-module id="beats-playbar">
  <template>
    <style include="shared-styles">
      :host {
        display: block;
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
      }
      paper-card {
        display: block;
        background-color: var(--color-primary);
        color: white;
        padding: 16px;
      }
      paper-icon-button {
        --paper-icon-button-ink-color: white;
        margin-left: 6px;
        margin-right: 6px;
      }
      paper-icon-button.play {
        width: 48px;
        height: 48px;
        color: var(--color-accent);
      }
      div.bar-container {
        @apply --layout-horizontal;
        /*@apply --layout-center-justified;*/
        @apply --layout-justified;
        @apply --layout-center;
      }
      div.bar-container > .left {
        @apply --layout-horizontal;
        @apply --layout-flex;
        @apply --layout-start-justified;
      }
      div.bar-container > .right {
        @apply --layout-horizontal;
        @apply --layout-flex;
        @apply --layout-end-justified;
      }
      paper-slider.volume {
        --paper-slider-active-color: white;
        --paper-slider-knob-color: white;
        width: 100px;
      }
    </style>

    <paper-card>
      <div class="bar-container">
        <div class="left">
          <paper-icon-button icon="mdi:repeat"></paper-icon-button>
          <paper-icon-button icon="mdi:shuffle"></paper-icon-button>
        </div>
        <div class="center">
          <paper-icon-button icon="mdi:rewind-outline"></paper-icon-button>
          <paper-icon-button icon="mdi:play" class="play"></paper-icon-button>
          <paper-icon-button icon="mdi:fast-forward-outline"></paper-icon-button>
        </div>
        <div class="right">
          <paper-slider value="50" class="volume"></paper-slider>
          <paper-icon-button icon="mdi:volume-high"></paper-icon-button>
        </div>
      </div>
    </paper-card>

  </template>

  <script>
    class BeatsPlaybar extends Polymer.Element {
      static get is() { return 'beats-playbar'; }
    }

    window.customElements.define(BeatsPlaybar.is, BeatsPlaybar);
  </script>
</dom-module>

Step 5

Next, we will add another slider to represent the current position in the song.

Start by adding a new bar-container above the current one, but still inside the paper-card:

<paper-card>
  <div class="bar-container">
    
  </div>

  <div class="bar-container">
    ...
  </div>
</paper-card>

Then add a new paper-slider inside it with two times on the left and right, like this:

<paper-card>
  <div class="bar-container">
    <span class="time">0:00</span>
    <paper-slider value="0" min="0" max="120" class="position"></paper-slider>
    <span class="time">-2:00</span>
  </div>

  <div class="bar-container">
    ...
  </div>
</paper-card>

Why span? There isn't much difference between div and span, but it's more standard to use span when you are just wrapping text.

Finally, let's style it! We'll change the color of the bar to our accent color, stretch the bar to maximize its width, and add a little padding to the time displays.

paper-slider.position {
  @apply --layout-flex;
  --paper-slider-active-color: var(--color-accent);
   --paper-slider-knob-color: var(--color-accent);
}
span.time {
  padding: 12px;
  font-size: 18px;
  opacity: 0.7;
}

Ready for JS?

In the next part, we will be using JavaScript to make the playbar work!

Clone this wiki locally