This checkpoint was updated on 6/16/2014 to fix a missing
require
statement in the JavaScript and correct an error in the<head>
tag.
In this checkpoint we'll create a two-tab account page for Bloc Jams. One tab will display a user profile, the other tab will display subscription information. We'll use jQuery to show and hide the different tabs.
Checkout a new branch:
$ git checkout -b profile-subscription-page
The profile page will have the same navigation bar and head as our previous pages. Add the profile.html
file to the /public
directory:
$ pwd #=> should be in the vagrant/bloc-jams directory. If not, cd into it.
$ touch public/profile.html
Add the standard head and navigation HTML:
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Bloc Jams</title>
+ <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.17.1/build/cssreset/cssreset-min.css">
+ <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,800,600,700,300' rel='stylesheet' type='text/css'>
+ <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
+ <link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
+ <link href="/stylesheets/app.css" rel="stylesheet">
+ <script src='/socket.io/socket.io.js'></script>
+ <script src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js'></script>
+ <script src='/javascripts/vendor.js'></script>
+ <script src='/javascripts/app.js'></script>
+ <script>
+ require('scripts/app');
+ </script>
+ </head>
+ <body>
+ <nav class="player-header-nav navbar">
+ <div class="container">
+ <div class='navbar-header'> <!-- Nav Bar -->
+ <a class='logo navbar-left'>
+ <img src="/images/blocjams.png">
+ </a>
+ </div>
+ </div>
+ </nav>
+ </body>
+</html>
We'll use the Bootstrap pagination classes for the tab navigation elements. The rest of the elements will have custom classes or Bootstrap classes we've already used. The HTML below should be placed under the navigation bar, within the body tags:
+<div class="container">
+ <div>
+ <div class="user-profile-tabs-container">
+ <ul class="profile-pagination pagination tabs">
+ <li class='tab'>
+ <a href="#yourAccountTab">Your Account</a>
+ </li>
+ <li class='tab'>
+ <a href='#subscriptionTab'>Subscription</a>
+ </li>
+ </ul>
+ </div>
+ <!-- the markup for the user profile tab -->
+ <div class="user-account tab-pane" id="yourAccountTab">
+ <div class="page-header user-profile-header">Personal Information</div>
+ <div class="row">
+ <div class="col-md-3">
+ <div class="user-image-container"><img src="/images/user-upload-placeholder.png"/>
+ <div style="padding: 10px" class="div"></div>
+ <button class="default-button center-block">UPLOAD</button>
+ </div>
+ </div>
+ <div class="col-md-7">
+ <form role="form" class="default-form">
+ <div class="form-group col-md-12">
+ <label for="name">Name</label>
+ <input id="name" type="text" class="form-control"/>
+ </div>
+ <div class="form-group col-md-12">
+ <label for="email">Email</label>
+ <input name="email" type="email" class="form-control"/>
+ </div>
+ <div class="form-group col-md-6">
+ <label for="city">City</label>
+ <input name="city" type="text" class="form-control"/>
+ </div>
+ <div class="form-group col-md-6">
+ <label for="state">State</label>
+ <input name="state" type="text" class="form-control"/>
+ </div>
+ <div class="form-group col-md-12">
+ <label for="country">Country</label>
+ <input name="country" type="text" class="form-control"/>
+ </div>
+ <button class="default-button center-block">SAVE PROFILE</button>
+ </form>
+ <div style="padding: 20px" class="div"></div>
+ <form role="form" class="default-form">
+ <div class="form-group col-md-12">
+ <label for="password">Password</label>
+ <input name="password" type="password" class="form-control"/>
+ </div>
+ <div class="form-group col-md-12">
+ <label for="password">Password Confirmation</label>
+ <input name="password" type="password" class="form-control"/>
+ </div>
+ <button class="default-button center-block">CHANGE PASSWORD</button>
+ </form>
+ </div>
+ </div>
+ </div>
+ <!-- the markup for the subscription tab -->
+ <div class='user-subscription tab-pane' id="subscriptionTab" >
+ <div class="row text-center">
+ <img src="/images/subscription-placeholder.png"/><img src="/images/subscription-placeholder.png"/><img src="/images/subscription-placeholder.png"/>
+ </div>
+ <div style="padding:20px"></div>
+ <div class="row">
+ <div class="col-sm-6 col-sm-push-3">
+ <h3 class="subsection-label col-sm-12">Credit Cards</h3>
+ <div class="col-sm-12">
+ <table class="table credit-card-list">
+ <tr>
+ <td class="col-sm-2">VISA</td>
+ <td class="col-sm-9">XXXX-1234</td>
+ <td class="col-sm-1"><i class="fa fa-minus-circle"></i></td>
+ </tr>
+ <tr>
+ <td class="col-sm-2">VISA</td>
+ <td class="col-sm-9">XXXX-1234</td>
+ <td class="col-sm-1"><i class="fa fa-minus-circle"></i></td>
+ </tr>
+ </table>
+ <button class="default-button center-block">ADD CARD</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
Let's add two Sass files for the profile page. The first is _profile.scss
file, which will hold all of our profile-specific styles. The second is _buttons.scss
, which we'll use to style the buttons on the subscription page. We'll also use it as a place for all buttons styles. Creating stylesheets specifically for repeated element types is a common convention.
First create the new files:
$ pwd #=> should be in the vagrant/bloc-jams directory. If not, cd into it.
$ touch app/stylesheets/_profile.scss
$ touch app/stylesheets/_buttons.scss
We'll add the profile styles first:
+$hot-pink: #D2287B;
+.user-profile-header {
+ border: none;
+ font-size: 24px;
+ font-weight: 300;
+ margin-top: 20px;
+}
+.user-image-container {
+ display: inline-block;
+}+
+.tab-content.user-subscription {
+ .well {
+ background: transparent;
+ border: $hot-pink 2px solid;
+ }+
+ .subsection-label {
+ margin-bottom: 15px;
+ font-size: 24px;
+ color: white;
+ font-weight: 300;
+ }
+
+ .table.credit-card-list {
+ td {
+ border-top-color: rgba(white, .6);
+ font-weight: 300;
+ font-size: 16px;
+ }
+ }
+ .fa-minus-circle {
+ color: $hot-pink;
+ }
+
+}
+.user-profile-tabs-container {
+ margin: 42px auto 20px;
+ text-align: center;
+ .pagination.profile-pagination {
+ font-weight: 700;
+ border: $hot-pink 2px solid;
+ border-radius: 8px;
+
+ & > li > a {
+ border: none;
+ background-color: transparent;
+ color: white;
+ letter-spacing: 2px;
+ }
+
+ & > .active > a {
+ background-color: $hot-pink;
+ border-radius: 4px;
+ }
+ }
+}
+
+.default-form {
+ $default-input-color: #0A032B;
+ label {
+ font-weight: 300;
+ font-size: 16px;
+ }
+ input {
+ background-color: rgba(white, .6);
+ border-radius: 0;
+ border: none;
+ font-weight: 300;
+ font-size: 16px;
+ padding: 10px 12px 10px;
+ }
+}
+
+.no-left-gutter {
+ padding-left: 0px;
+}
+
+.no-right-gutter {
+ padding-right: 0px;
+}
Add the button styles:
+.default-button {
+ background-color: #D2287B;
+ color: white;
+ font-weight: 700;
+ border-radius: 5px;
+ border: none;
+ padding: 10px 20px;
+ font-size: 14px;
+ letter-spacing: 2px;
+ outline: none;
+
+ &:active {
+ background-color: #9B1D64;
+ outline: none;
+ }
+
+ &:disabled {
+ background-color: #B3B3B3;
+ cursor: not-allowed;
+ }
+}
We've added two pseudo-classes to buttons.scss
which manage different states of the buttons in the same way that :hover
does. :active
will set a style when the tab we've clicked is active (i.e. when the tab is showing, like in the example gif at the beginning of this checkpoint). The :disabled
style will apply to the tab that is not selected.
We need to add @import
statements to the app.scss
for the new stylesheet partials:
+ @import "buttons";
+ @import "profile";
You should see our two profile tabs stacked on top of one another when you open localhost:3000/profile.html
.
Create a profile.js
file in the app/scripts
folder. This is where we'll be writing the logic for our jQuery tab handler:
$ pwd #=> should be in the vagrant/bloc-jams directory. If not, cd into it.
$ touch app/scripts/profile.js
Let's define a function called selectTabHandler
that will take one argument (the event
) and will execute the tab toggle for our profile page.
+// holds the name of our tab button container for selection later in the function
+var tabsContainer = ".user-profile-tabs-container"
+var selectTabHandler = function(event) {
+};
We want to be able to click a tab button, remove the :active
pseudo-class from the active tab, and then apply the :active
state to the clicked tab. After the click is registered, we want to select the .tab-pane
that holds the active tab's content, then display it.
+var tabsContainer = ".user-profile-tabs-container"
+var selectTabHandler = function(event) {
+ $tab = $(this);
+ $(tabsContainer + " li").removeClass('active');
+ $tab.parent().addClass('active');
+ selectedTabName = $tab.attr('href');
+ console.log(selectedTabName);
+ $(".tab-pane").addClass('hidden');
+ $(selectedTabName).removeClass('hidden');
+ event.preventDefault();
+};
The hidden
class that we're adding is a Bootstrap class that sets the CSS display
property to none
. The event.preventDefault()
call is a function that prevents the default behavior of an event on a certain HTML element. In this case, we're preventing the default behavior of an <a>
tag with a href
attribute defined -- this is the element that we click to switch tabs.
Let's add the handler to the callback on a jQuery
click event so our final profile.js
looks like this:
+var tabsContainer = ".user-profile-tabs-container"
+var selectTabHandler = function(event) {
+ $tab = $(this);
+ $(tabsContainer + " li").removeClass('active');
+ $tab.parent().addClass('active');
+ selectedTabName = $tab.attr('href');
+ console.log(selectedTabName);
+ $(".tab-pane").addClass('hidden');
+ $(selectedTabName).removeClass('hidden');
+ event.preventDefault();
+};
+
+if (document.URL.match(/\/profile.html/)) {
+ $(document).ready(function() {
+ var $tabs = $(tabsContainer + " a");
+ $tabs.click(selectTabHandler);
+ $tabs[0].click();
+ });
+}
We also need to add a require
statement to app/scripts/app.js
to make sure profile.js
runs when we load the page.
require("./landing");
require("./collection");
require("./album");
+require("./profile");
Refresh the page and you should have working profile and subscription tabs.
Commit, merge, push and deploy your changes:
$ git add .
$ git commit -m "Added a profile and subscription page with jquery tabs"
$ git checkout master
$ git merge profile-subscription-page
$ git push origin master
$ git push heroku master
Further exploration:
- Review the pseudo-classes documentation and get a sense for potential use cases.
- Read this explanation about
event.preventDefault()
. - Go through the steps in this checkpoint again, only use
.hover()
instead of.click()
. What do you think offers the better user experience, and why? -- Speak with your mentor about your conclusions.