HTML5 Zone is brought to you in partnership with:

Andrew Trice is a Technical Evangelist with Adobe Systems. Andrew brings to the table over a decade of experience designing, implementing, and delivering rich applications for the web, desktop, and mobile devices. Andrew is an experienced architect, team leader, accomplished speaker, and published author, specializing in immersive experiences, mobile development, realtime data systems, and data visualization. Andrew is a DZone MVB and is not an employee of DZone and has posted 53 posts at DZone. You can read more from them at their website. View Full User Profile

Implementing the “Card” UI Pattern in PhoneGap/HTML5 Applications

12.02.2013
| 43854 views |
  • submit to reddit

“Cards,” in a user interface, seem to be the rage these days. Card-based user interface paradigms can be seen everywhere from Google Glass, to Pinterest, Google Plus, Spotify, Flipboard, and many, many more places too innumerable to mention.

Some proclaim the card user interface paradigm as the absolute future of web design, some see it as just another trend.  In either case, you might need to implement a card style user interface in your own applications.  If you happen to be building apps that target PhoneGap or in the browser, you’re in luck! 

In this article, we’ll cover some techniques for building card UI paradigms with web standards. We won’t be focusing much on PhoneGap itself since this pattern is implemented purely from the HTML/CSS side of things, with interactivity and dynamism added via JavaScript.  All source code for the examples discussed in this article can be accessed at: https://github.com/triceam/cards-ui

What’s a Card?

Before we talk about building “card” interfaces, it’s important to understand exactly what we’re talking about.  Essentially, a card is a container that logically encapsulates bits of information.  In most cases, a card is a rectangular area that contains a small amount of easily digestible information.  The border of this rectangle is used to convey encapsulation of the content, in other words, a separation of the content within the “card” from content elsewhere on the screen.  Often there are many cards placed on the screen in close proximity to each other, and the borders or coloring of the cards is used to separate information between each of the cards.  If you glance at the user interface for Pinterest or Google Plus, you’ll quickly see user interface cards in action.

Implementation in with PhoneGap, or in HTML, CSS, and JavaScript

With web technologies, it is very easy to create rectangular visual structures, and to arrange content within those structures.  Let’s start with the basics.  In the image below, we have two basic card structures, both implemented with HTML.

Both of these examples can be seen in the 01_simple_card.html file in the GitHub repository.

Before we talk about the cards, let’s look at the page structure containing the cards.  There is a header area, which would act as the header navigation in a PhoneGap area, a footer, and the main content area.  Each of these HTML structures has a CSS class that determines how the content is presented.  All of the cards that we will be adding will be inside the <div> with the “content” CSS class.

<body>
    <div class="content">
        <!--- All cards go here --->
    </div>
 
    <div class="header">
        Cards in an HTML UI
    </div>
    
    <div class="footer">
        All images copyright 2013 <a href="http://tricedesigns.com/" target="_blank">Andrew Trice</a>. 
    </div>
</body>

Each of these elements have CSS styles, which handle individual formatting.  If you view the source, you will see that each has a fixed position and styles, which determine the general presentation colors, borders, and the touch scroll interaction. 

Inside the content <div> is where we define the cards.  As I mentioned above, cards can be extremely simple.  We have given the content <div> a light gray background color, and each card with a white background and slightly darker border, so that each card is visually differentiated from both the background, and from each other (if multiple cards are in closer proximity).

For the first card, we simply have a <div> that contains a heading element and a paragraph element, as shown in the code below.

<div class="card">
    <h1>This is a card!</h1>
    <p>In essence, a card is just a rectangular region which contains content.  This content could be text, images, lists, etc... The card UI methaphor dictates the interaction and layout of these regions.</p>
</div>

To achieve the visual treatment, there are just a few simple CSS styles applied to the HTML.  The background and border colors have been defined, as well as the padding and overflow rules for the “card” container.

.card {
    background:#FFF;
    border:1px solid #AAA;
    border-bottom:3px solid #BBB;
    padding:0px;
    margin:15px;
    overflow:hidden;
}

For each heading and paragraph element, we’ve also defined margin and padding rules to govern the spacing of elements.  Again, you can see that it is not overly complex.

.card h1 {
    margin:0px;
    padding:10px;
    padding-bottom:0px;
}

.card p {
    margin:0px;
    padding:10px;
}

Now, let’s examine the second card, which contains both an image, a caption overlay, and a banner.  You’ll notice that the second example uses the exact same CSS class for the card layout and the paragraph formatting. However, there is also a <div> with an image style, a <div> for the banner, and a heading for the caption overlay.

<div class="card">
    <div class="card-image image2">
        <div class="banner"></div>
        <h2>Image, Banner, & HTML</h2>
    </div>
    <p>All standard HTML structures, styled with CSS.</p>
</div>

You’ll notice that the card does not contain an HTML <img> tag for the image.  Instead, the image is applied through the .card-image and .image1 CSS classes.  The .card CSS class defines the width, height, and background treatments.  The image1 CSS class actually defines the image that is used as the <div> element’s background.  In this case, the images are being applied as CSS backgrounds so that they are not stretched or distorted when the <div> elements are resized.

.card-image {
    width:100%;
    height:200px;
    padding:0px;
    margin:0px;
    background-position:center;
    background-repeat:no-repeat;
    position:relative;
    overflow:hidden;
}


.image1 {
    background-image:url('http://farm6.staticflickr.com/5323/9902848784_cbd10ba3ca_c.jpg');
}

In these examples, there are multiple different CSS classes defined for different images, but all use the .card-image CSS class so that they have the same display rules.

The yellow “NEW” banner is also actually a <div> element, where the banner is actually an image applied through CSS, and placed in the top left corner inside of the image’s <div> element.

.card-image .banner {
    height:50px;
    width:50px;
    top:0px;
    right:0px;
    background-position:top right;
    background-repeat:no-repeat;
    background-image:url('../images/new.png');
    position:absolute;
}

The caption is simply a heading element that is positioned inside of the image <div> at the bottom, with a semi-transparent background and white text to overlay the actual image content.

.card-image h1, 
.card-image h2, 
.card-image h3, 
.card-image h4, 
.card-image h5, 
.card-image h6 {
    position:absolute;
    bottom:0px;
    width:100%;
    color:white;
    background:rgba(0,0,0,0.65);
    margin:0px;
    padding:6px;
    border:none;
}

Everything that you are seeing so far is straightforward HTML and CSS, and is fairly simple.  Things start to get slightly more complicated when we start putting multiple cards next to each other, however it doesn’t have to be overly complex. 

Let’s take a look at the next example, which shows multiple cards in close proximity to each other.

This example can be viewed in the 02_multiple_cards_float.html file in the GitHub repository.

In this example, we have multiple cards which have been defined using the exact HTML and CSS structures described above.  However, in this case we’ve added a CSS class to the content element so that it defines a float-left behavior for each of the cards.  You can see the CSS implementation below.

.float-left .card {
    float:left;
    width:300px;  
    height:270px;
}

With the float left behavior defined for each card, the cards will be laid out to the right of the previous card if there is enough space in the horizontal area.  If there is not enough space, the next card will drop to the next vertical line break.  You’ll also notice that each card <div> has a fixed height, and a fixed-width of 300px.  The fixed sizes guarantee that all of the content will be arranged by the browser without overlapping, and wrapping breakpoints are determined by the browser (the browser would determine if there would be 2 cards per horizontal line in portrait orientation versus 3 cards per horizontal line in landscape). 

This works great if all of your cards are exactly the same size.  However, that is seldom the case in the real world.  This approach doesn’t allow for variable height or variable/responsive width content.  So, let’s take a look at another example.

In this case, we have two columns of cards.  Each card has a variable height, based upon it’s content, and the width of the cards are variable, based upon the width of each column.



You can view this example in the 03_multiple_cards_columns.html file in the GitHub repository.

In order to support variable height content, you need to “stack” the cards in separate vertical containers.  If you tried to use CSS columns on a single div element, then you could run into issues with cards breaking mid-card when wrapping into separate columns.

To achieve this, I created two div containers, and placed the HTML for the cards inside of each. 

<div class="content ">
    <div class="leftColumn"> 
        <!--- cards here ---> 
    </div>
    <div class="rightColumn"> 
        <!--- cards here --->        
    </div> 
</div>

The CSS for each of these columns dictates that each will be 49% of the width of the viewport, and each card will take up 100% of this space.  This allows for content to scale with the viewport size, and since each card is in a separate container, there is no impact on variable-height content.  Since each “column” <div> element has the display property “inline-block”, the web view (or browser) will render these elements next to each other.

.rightColumn,
.leftColumn {
    display:inline-block;
    width:49%;
    vertical-align:top;
}

So, this is a step in the right direction, right? 

We now have variable height cards being displayed side by side next to each other.  However, this still isn’t an ideal solution.  If you were to view this on a phone form factor, you’d have 2 tiny columns of squished content.  If you were to view this on a tablet in landscape orientation, you would have two columns of stretched content , or, at least, inefficient use of whitespace.

So far, we’ve only talked about the actual HTML structure, and the CSS styles used to display the content.  In a real world application, you need things to be dynamic and responsive. 

If you are building a PhoneGap application, or are leveraging client-side dynamic rendering, then you will need to add JavaScript to conditionally layout the html elements based upon the viewport of the device that is being used to consume the content.

So, let’s go ahead add some JavaScript to define adaptive behaviors in this experience.




You can view this example in the 04_programmatic_cards.html file in the GitHub repository.

This example is completely dynamic, leveraging Zepto.js for quick DOM manipulation and Mustache.js for HTML template generation. In this example, the number of columns is determined programmatically based on the width of the web view.  Then, the HTML is generated for each card based upon the defined data and the HTML template, and placed into the appropriate column.

If you haven’t used HTML templates before, here’s a quick introduction… HTML templates allow you to dynamically generate HTML strings based upon data.  This allows for easy separation of your data model from your presentation layer (HTML structure).  You define a template structure with markup that determines where data should be injected.  The template library takes this template, injects data, and outputs a full HTML string that is dynamic based upon whatever data is passed in.

In this example, we’re using the exact same CSS styles that we used in the previous example.  We’re just generating all of the HTML structure at runtime.  If the viewport width can have 1 column, it will be rendered with one column.  If the viewport can have 2, it will have 2 columns, and so forth. In this example, we’ve also added an event listener for the window’s resize event, so we can dynamically switch from 2 to 3 columns if the user switches from portrait to landscape orientation.  With this approach, we can now account for both variable height content, and variable width viewports.

Now, let’s take a look at the template used to generate each card.  This uses the Mustache.js library’s syntax to markup both conditional elements, and data injection into the HTML string generation.

<script id="card-template" type="text/template">
    <div class="card">
        {{#image}}
            <div class="card-image {{ image }}">
                {{#banner}} <div class="banner"></div> {{/banner}} 
                {{#caption}} <h2>{{caption}}</h2> {{/caption}}
            </div>
        {{/image}}
        {{#title}} <h1>{{title}}</h1> {{/title}}
        {{#message}} <p>{{{message}}}</p> {{/message}}
    </div>
</script>

Here’s the script that brings this example to life.  Once the page loads, the appropriate event resize event handler is added, and the initial layout is generated based upon the window/viewport size.

<script>
    var content, columns, compiledCardTemplate = undefined;
    var MIN_COL_WIDTH = 300;
    
    //data used to render the HTML templates
    var cards_data = [
        {   title:"This is a card!", 
            message:"In essence, a card is just a rectangular region which contains content. This content is just HTML.  This could be <b>text</b>, <i>images</i>, <u>lists</u>, etc... The card UI methaphor dictates the interaction and layout of these regions."  },
        {   message:"Yep, just some simple content ecapsulated in this card.",
            image:"image1"},
        {   image:"image2",
            banner:true, 
            caption:"Image, Banner & HTML",
            message:"All standard HTML structures, styled with CSS."},
        {   title:"This is another card!", 
            image:"image4",
            message:"Here, you can see a more complex card.  IT is all just layout of HTML structures.",
            caption:"Look, it's Vegas!",  },
        {   message:"Yep, just some simple content ecapsulated in this card.",
            image:"image5",
            banner:true, },
        {   image:"image6",
            caption:"It's a college!",
            message:"With HTML in the content.<ul><li>Bullet 1</li><li>Bullet 2</li><li>Bullet 3</li></ul>"},
        {   image:"image1",
            caption:"San Francisco City Hall",
            message:"All of these photos were captured with a quadcopter and GoPro! Check out my blog <a href='http://tricedesigns.com'>http://tricedesigns.com</a> to learn more!"},
    ];
      
    //page load initialization
    Zepto(function($){
        content = $(".content");
        compiledCardTemplate = Mustache.compile( $("#card-template").html() );
        layoutColumns();
        $(window).resize(onResize);
    })
        
    //resize event handler
    function onResize() {
        var targetColumns = Math.floor( $(document).width()/MIN_COL_WIDTH );
        if ( columns != targetColumns ) {
            layoutColumns();   
        }
    }
    
    //function to layout the columns
    function layoutColumns() {
        content.detach();
        content.empty();
        
        columns = Math.floor( $(document).width()/MIN_COL_WIDTH );
        
        var columns_dom = [];
        for ( var x = 0; x < columns; x++ ) {
            var col = $('<div class="column">');
            col.css( "width", Math.floor(100/columns)+"%" );
            columns_dom.push( col );   
            content.append(col);
        }
        
        for ( var x = 0; x < cards_data.length; x++ ) {
            var html = compiledCardTemplate( cards_data[x] );
            
            var targetColumn = x % columns_dom.length;
            columns_dom[targetColumn].append( $(html) );    
        }
        $("body").prepend (content);
    }
</script>

Be sure to check this out in the live HTML to get the best idea how it responds.  If you are in the desktop browser, you can resize your window and see the columns change dynamically.  If you are on a tablet, you can rotate your device and watch the user interface change from two to three columns.

In Conclusion

That’s pretty much the basics for implementing a card-based user interface inside of a PhoneGap application.  If you’re wondering how to get started building PhoneGap apps, just head over to PhoneGap.com and download the tools to get started. If you want to add mobile-themed user interface elements like buttons or lists to your apps (regardless of whether they implement the card layout), be sure to check out Topcoat, an open source framework for user interface elements.  If you want to add other kinds of effects and interactivity, be sure to check out the Effekt library of CSS transitions and animations.  If you want to add any additional interactivity, or make things dynamic based on data, then you’ll need to leverage JavaScript to programmatically generate new HTML structures at runtime, without reloading the HTML 5file.  You can download all of the source code for these examples at: https://github.com/triceam/cards-ui, and also, be sure to check out my blog at tricedesigns.com for any updates!

This article was commissioned by Intel and the Tizen Mobile Web Development team.

Published at DZone with permission of Andrew Trice, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Juan Chong replied on Tue, 2014/03/11 - 4:13pm

this tutorial its great! 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.