Skip to content

Adapting HTML CSS JS project

ok-at-github edited this page Aug 28, 2017 · 5 revisions

The origin version of this document was written by Harbs. Thanks for contributing this awesome stuff!

CodePen live demo of the origin JavaScript project

Table of contents

Introduction
Step 1: Understanding the Structure
Step 2: Deciding what components we need
Step 3: Creating the Wrapper Component
Step 4: Adding Code
Step 5: Adding CSS
Step 6: Using the Component
Conclusion

Introduction

I needed a tabbed interface for an app and FlexJS does not yet have a built-in component which does this. I searched for some nicely formatted tabbed UIs and I found one I liked which was created by Lewi Hussey. Download it as zip or run the origin live demo via CodePen.
If you open the project, you will see there’s a specific hierarchy of HTML, CSS and JS that’s all needed for it to work correctly. I thought this would be a great opportunity to explain how to incorporate something like this into a project.

Step 1: Understanding the Structure

I stripped the content down to its core. This left me with the following HTML structure:

<section class="wrapper">
    <ul class="tabs">
        <li class="active">Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
    </ul>
    <ul class="tab__content">
        <li class="active">
            <div class="content__wrapper">
                <h2>Section 1</h2>
                <p>Section 1 text</p>
            </div>
        </li>
        <li>
            <div class="content__wrapper">
                <h2>Section 2</h2>
                <p>Section 2 text</p>
            </div>
        </li>
        <li>
            <div class="content__wrapper">
                <h2>Section 3</h2>
                <p>Section 3 text</p>
            </div>
        </li>
    </ul>
</section>

The significant part of this is that we need the following pieces:

  • A “wrapper” around everything
  • A <ul> element for the tabs
  • A <ul> element for the content
  • A <li> element for each of the tabs
  • A <li> element for each of the content sections.

Step 2: Deciding what components we need

Technically we do not need any custom elements to create this setup. We can use a <js:Div> component for the wrapper, and <js:Ul> and <js:Li> components for the rest. However, it is easier if we create at least one component to contain the ActionScript version of the JavaScript code which does its thing. We can create the component either in ActionScript or MXML. The advantage of MXML is that we can declare both ActionScript code and the CSS in the same file. If we would use an ActionScript version, the JavaScript (ActionScript) can be included, but the CSS must go elsewhere.

The simplest component which can be used as a base for an MXML file is <js:Group>, so I am going to use that.

Step 3: Creating the Wrapper Component

On the simplest level, adding a new MXML <js:Group> component looks like this:

<?xml version="1.0" encoding="utf-8"?>
<js:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
 	    xmlns:js="library://ns.apache.org/flexjs/basic">

    <fx:Style>
    </fx:Style>

    <fx:Script>
        <![CDATA[
        ]]>
    </fx:Script>

</js:Group>

All CSS styling will go within the <fx:Style> section, and all code will go within the <fx:Script> section. The actual content does not need to be declared inside the component because this component will be used inside another MXML file which will declare the sub-content there. The only thing we might want to do would be to declare some class name so we could apply consistent styling to all tab wrappers in our app. We do that like this:

<js:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:js="library://ns.apache.org/flexjs/basic"
     typeNames="TabContainer">

The reason I’m using <typeNames> rather than <className> is because <className> is added to the list of <typeNames> defined in the component. This allows me to set specific <classNames> to instances of the component and add class-based styling to instances while preserving the global styling.

Step 4: Adding Code

I stripped out the JavaScript code which is not component-specific and the remaining original code looks like this:

$(document).ready(function(){
    // Variables
    var clickedTab = $(".tabs > .active");
    var tabWrapper = $(".tab__content");
    var activeTab = tabWrapper.find(".active");
    var activeTabHeight = activeTab.outerHeight();
    // Show tab on page load
    activeTab.show();
    // Set height of wrapper on page load
    tabWrapper.height(activeTabHeight);
    $(".tabs > li").on("click", function() {
        // Remove class from active tab
        $(".tabs > li").removeClass("active");
        // Add class active to clicked tab
        $(this).addClass("active");
        // Update clickedTab variable
        clickedTab = $(".tabs .active");
        // fade out active tab
        activeTab.fadeOut(250, function() {
            // Remove active class all tabs
            $(".tab__content > li").removeClass("active");
            // Get index of clicked tab
            var clickedTabIndex = clickedTab.index();
            // Add class active to corresponding tab
            $(".tab__content > li").eq(clickedTabIndex).addClass("active");
            // update new active tab
            activeTab = $(".tab__content > .active");
            // Update variable
            activeTabHeight = activeTab.outerHeight();
            // Animate height of wrapper to new tab height
            tabWrapper.stop().delay(50).animate({
                height: activeTabHeight
            }, 500, function() {
                // Fade in active tab
                activeTab.delay(50).fadeIn(250);
            });
        });
    });
});

To convert this to ActionScript code, we could try and rewrite this to use actual references to FlexJS components. Ultimately this would probably be more efficient and allow hooks into FlexJS functionality, but for my use case, I don’t really care that much about that. I just want to get the functionality of this code as quickly and easily as possible. I’m already using jquery elsewhere in my app, so there’s no extra code loading for doing so.

FlexJS already has type definitions for jquery, so we can actually assign types to the jquery code and get code intelligence in an IDE (I am using Visual Studio Code).

Additionally, there’s no reason to call $(document).ready() in FlexJS. I use initComplete instead and my component becomes:

<?xml version="1.0" encoding="utf-8"?>
<js:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:js="library://ns.apache.org/flexjs/basic"
        typeNames="TabContainer" initComplete="init()">

    <fx:Style>
    </fx:Style>

    <fx:Script>
        <![CDATA[
            private function init():void {

                // Variables
                var clickedTab:jQuery = $(".tabs > .active");
                var tabWrapper:jQuery = $(".tab__content");
                var activeTab:jQuery = tabWrapper.find(".active");
                var activeTabHeight:Number = activeTab.outerHeight();

                // Show tab on page load
                activeTab.show();

                // Set height of wrapper on page load
                tabWrapper.height(activeTabHeight);

                $(".tabs > li").on("click", function():void {

                    // Remove class from active tab
                    $(".tabs > li").removeClass("active");

                    // Add class active to clicked tab
                    $(this).addClass("active");

                    // Update clickedTab variable
                    clickedTab = $(".tabs .active");

                    // fade out active tab
                    activeTab.fadeOut(250, function():void {

                        // Remove active class all tabs
                        $(".tab__content > li").removeClass("active");

                        // Get index of clicked tab
                        var clickedTabIndex:Number = clickedTab.index();

                        // Add class active to corresponding tab
                        $(".tab__content > li").eq(clickedTabIndex).addClass("active");

                        // update new active tab
                        activeTab = $(".tab__content > .active");

                        // Update variable
                        activeTabHeight = activeTab.outerHeight();
                        
                        // Animate height of wrapper to new tab height
                        tabWrapper.stop().delay(50).animate({
                            "height": activeTabHeight
                        }, 500, function():void {
                            // Fade in active tab
                            activeTab.delay(50).fadeIn(250);
                        });
                    });
                });
           }
        ]]>
    </fx:Script>

</js:Group>

One thing worth noting: in the animate() function, I changed the literal object notation to use property names in quotes. The reason I did this is to prevent Google Closure Compiler from minimizing the height property. That’s all we need to do to migrate the JS code for FlexJS.

Step 5: Adding CSS

To add the CSS, it should be as simple as copying and paste the CSS into the style element:

<fx:Style>
    @namespace "http://www.w3.org/1999/xhtml";
    .tabs {
        display: table;
        table-layout: fixed;
        width: 100%;
        transform: translateY(5px);
    }
    .tabs > li {
        transition-duration: .25s;
        display: table-cell;
        list-style: none;
        text-align: center;
        padding: 20px 20px 25px 20px;
        position: relative;
        overflow: hidden;
        cursor: pointer;
        color: white;
    }
    .tabs > li:before {
        z-index: -1;
        position: absolute;
        content: "";
        width: 100%;
        height: 120%;
        top: 0;
        left: 0;
        background-color: rgba(255, 255, 255, 0.3);
        transform: translateY(100%);
        transition-duration: .25s;
        border-radius: 5px 5px 0 0;
    }
    .tabs > li:hover:before {
        transform: translateY(70%);
    }
    .tabs > li.active {
        color: #50555a;
    }
    .tabs > li.active:before {
        transition-duration: .5s;
        background-color: white;
        transform: translateY(0);
    }

    .tab__content {
        background-color: white;
        position: relative;
        width: 100%;
        border-radius: 5px;
    }
    .tab__content > li {
        width: 100%;
        position: absolute;
        top: 0;
        left: 0;
        display: none;
        list-style: none;
    }
    .tab__content > li .content__wrapper {
        text-align: center;
        border-radius: 5px;
        width: 100%;
        padding: 45px 40px 40px 40px;
        background-color: white;
    }

    .content__wrapper h2 {
        width: 100%;
        text-align: center;
        padding-bottom: 20px;
        font-weight: 300;
    }
    .content__wrapper img {
        width: 100%;
        height: auto;
        border-radius: 5px;
    }
</fx:Style>

To handle native HTML selectors like <li> it’s necessary to add the default namespace @namespace "http://www.w3.org/1999/xhtml";

That allows the compiler to understand unmodified CSS.

Step 6: Using the Component

Using the component is very straight-forward. We just follow the markup pattern in the original HTML using FlexJS markup syntax:

<palette:TabContainer>
    <js:Ul className="tabs" id="tabList">
        <js:Li className="active">
            <js:TextNode text="{tabTitle1}"/>
        </js:Li>
        <js:Li>
            <js:TextNode text="{tabTitle2}"/>
        </js:Li>
    </js:Ul>
    <js:Ul className="tab__content" id="contentList">
        <js:Li className="active">
            <js:Div className="content__wrapper">
                <palette:TabContent1/>
            </js:Div>
        </js:Li>
        <js:Li>
            <js:Div className="content__wrapper">
                <palette:TabContent2/>
            </js:Div>
        </js:Li>
    </js:Ul>
</palette:TabContainer>

The actual content is declared elsewhere. I added ids to the <ul> for the tabs and content to make it possible to set the correct classNames programmatically in addition to the automatic changes done by jquery.

Conclusion

That’s it. I’ve now incorporated some random HTML, JS and CSS into my project.

Of course, if I wanted to implement an actual cross-platform FlexJS component, it would be much more work (and ultimately more flexible), but I think this “hacking together” of random HTML found on the web is a pretty common need and I hope this tutorial is helpful.

Clone this wiki locally