Skip to content

Instantly share code, notes, and snippets.

@joelip
Created June 20, 2014 02:12
Show Gist options
  • Save joelip/ae1df793ae80a6dcfe68 to your computer and use it in GitHub Desktop.
Save joelip/ae1df793ae80a6dcfe68 to your computer and use it in GitHub Desktop.

Creating the User Profile and Subscription Page

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.

Switching between tabs

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

Adding the profile HTML

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>

Styling the Profile Page

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";

Adding JavaScript

You should see our two profile tabs stacked on top of one another when you open localhost:3000/profile.html.

Tabs stacked

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.

Assignment

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment