Where to Discuss?

Local Group

Preface

Goal: Moving Alpine.js’s attribute using x-spread.

Writing Alpine.js is like writing configuration, as opposed with writing logic in coding.

Article Steps

The alpine.js is divided into two parts.

  • Inline Alpine.js, then

  • Extracted Alpine.js.

The first one has already discussed in previous article. Now all we need is, to extract from the element.

Examples

For tutorial purpose we need two layouts:

  1. Simple layout, to explain jQuery.

  2. Enhance layout, as real live example.

Preview

Tabs Component: Enhanced Layout: Alpine


Simple Tabs : Structure

Document: HTML Head

We can reuse previous tailwind stylesheet.

  <title>Simple Tabs - Alpine.js (extracted)</title>
  <link rel="stylesheet" type="text/css" 
        href="../css/03-tailwind-simple.css">
  <script src="../js/vendors/alpine.min.js"></script>
  <script src="../js/custom/alpine-simple.js"></script>

Yes, we are going to move stuff to an external javascript.

Document: HTML Body: Tab Headers

Now it is as simple as:

  <main class="tabs" x-data="tabs()">
    <div class="tab-headers">
      <div x-spread="header_home">Home</div>
      <div x-spread="header_team">Team</div>
      <div x-spread="header_news">News</div>
      <div x-spread="header_about">About</div>
    </div>
      …
    </div>

    …
  </main>

It looks tidy right 🙂?

How Does It Works?

We are going to initialize stuff with x-data=tabs(). All the configuration, bundled in this tabs()function.

function tabs() {
  return {
    tab: 'news',
    // … more configuration here
  }
}

Javascript: Handling Headers Event

The x-spread manage all the attributes.

      <div x-spread="header_home">Home</div>

So we can have the details somewhere else, such as below code.

function tabs() {
  return {
    tab: 'news',
    header_home: {
      ['@click']()      { this.tab = 'home' },
      [':class']()      { 
        return (this.tab == 'home') ? 'active bg-blue-500' : 'bg-gray-700';
      }
    },
    // … more configuration here
  }
}

The Issue with Alpine

We still have to write down the declaration, for each element such as below:

function tabs() {
  return {
    tab: 'news',
    header_home: {
      ['@click']()      { this.tab = 'home' },
      [':class']()      { 
        return (this.tab == 'home') ? 'active bg-blue-500' : 'bg-gray-700';
      }
    },
    header_team: {
      ['@click']()      { this.tab = 'team' },
      [':class']()      { 
        return (this.tab == 'team') ? 'active bg-teal-500' : 'bg-gray-700';
      }
    },
    header_news: {
      ['@click']()      { this.tab = 'news' },
      [':class']()      { 
        return (this.tab == 'news') ? 'active bg-red-500' : 'bg-gray-700';
      }
    },
    header_about: {
      ['@click']()      { this.tab = 'about' },
      [':class']()      { 
        return (this.tab == 'about') ? 'active bg-orange-500' : 'bg-gray-700';
      }
    },
    // … more configuration here
  }
}

Or maybe it is just me. I just still don’t know how to do it yet.

Document: HTML Body: Tab Content

The same writing style, also applied the tab contents.

    <div class="tab-contents">
      <div x-spread="content_home">
        <h3>Home</h3>
        <p>Lorem ipsum dolor sit amet,
           consectetur adipiscing elit.
           Quisque in faucibus magna.</p>
      </div>
      <div x-spread="content_team">
        <h3>Team</h3>
        <p>Nulla luctus nisl in venenatis vestibulum.
           Nam consectetur blandit consectetur.</p>
      </div>
      <div x-spread="content_news">
        <h3>News</h3>
        <p>Phasellus vel tempus mauris,
           quis mattis leo.
           Mauris quis velit enim.</p>
      </div>
      <div x-spread="content_about">
        <h3>About</h3>
        <p>Interdum et malesuada fames ac ante
           ipsum primis in faucibus.
           Nulla vulputate tortor turpis,
           at eleifend eros bibendum quis.</p>
      </div>
    </div>

Javascript: Handling Headers Event to Tab Content

The x-spread manage all the attributes. Consider this example below, for home tab:

  <main class="tabs" x-data="tabs()">
    <div class="tab-headers">
      <div x-spread="header_home">Home</div>
      …
    </div>

    <div class="tab-spacer"></div>

    <div class="tab-contents">
      <div x-spread="content_home">
        <h3>Home</h3>
        <p>Lorem ipsum dolor sit amet,
           consectetur adipiscing elit.
           Quisque in faucibus magna.</p>
      </div>
      …
    </div>
  </main>

The same extracted attributes, also applied to the tab contents.

function tabs() {
  return {
    tab: 'news',
    header_home: {
      ['@click']()      { this.tab = 'home' },
      [':class']()      { 
        return (this.tab == 'home') ? 'active bg-blue-500' : 'bg-gray-700';
      }
    },
    // … more configuration here
    content_home: {
      ['x-show']() { return (this.tab == 'home'); },
      [':class']() { return (this.tab == 'home') ? 'bg-blue-500' : ''; }
    },
    // … more configuration here
  }
}

Again, I know it is long. But that is all.

Your code should be working by now.

Preview

The same as previous example.

The Riddle: Issue with Alpine!

Can we make this simpler?

Unfortunately I failed to combine the x-spread with x-for. Perhaps I’m asking too much. Or maybe I just do not know how to do it yet.


Enhanced Tabs: Structure

How about real live component? Let’s the fun begin.

Remember that we only add these two feature:

  • hover and,

  • bg-white.

Document: HTML Head

We still can reuse previous stylesheet.

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Tabs - Alpine.js (extracted)</title>
  <link rel="stylesheet" type="text/css" href="../css/04-tailwind-enhanced.css">
  <link rel="stylesheet" type="text/css" href="../css/02-border-radius.css">
  <script src="../js/vendors/alpine.min.js"></script>
  <script src="../js/custom/alpine-enhanced.js"></script>

Document: HTML Body: Initialization

  <main class="tabs" x-data="tabs()">
    /* .. elements here … */
  </main>
  • .

How Does It Works?

The same as the simple example above. But with hover variable. We are going to initialize stuff with x-data=tabs(). All the configuration, bundled in this tabs()function.

function tabs() {
  return {
    tab: 'news',    
    hover: false,
    // … more configuration here
  }
}

Document: HTML Body: Tab Headers

We have the third level div.

    <div class="tab-headers">
    <div class="tab-headers">
      <div x-spread="header_home">
        <div x-spread="header_inner_home">Home</div>
      </div>
      <div x-spread="header_team">
        <div x-spread="header_inner_team">Team</div>
      </div>
      <div x-spread="header_news">
        <div <div x-spread="header_inner_news">News</div>
      </div>
      <div x-spread="header_about">
        <div <div x-spread="header_inner_about">About</div>
      </div>
    </div>
      …
    </div>

Now I feel good. The html source code looks tidy.

Javascript: Handling Headers Event

The x-spread manage all the attributes.

    <div class="tab-contents">
      <div x-spread="content_home">
        <div class="tab-content" x-spread="content_inner_home">
          <h3>Home</h3>
          …

So we can have the details somewhere else, such as below code, along with the x-on:mouseenter and x-on:mouseleave event.

function tabs() {
  return {
    tab: 'news',
    hover: false,
    header_home: {
      ['@click']()      { this.tab = 'home'; this.hover = this.tab; },
      ['@mouseenter']() { this.hover = (this.tab == 'home') ? this.tab : false; },
      ['@mouseleave']() { this.hover = false },
      [':class']()      { 
        return (this.tab == 'home') ? 'active bg-blue-500' : 'bg-gray-700';
      }
    },
    // … more configuration here
  }
}

Document: HTML Body: Tab Content

We also have the third level div here. The same writing style, also applied the tab contents.

    <div class="tab-contents">
      <div x-spread="content_home">
        <div class="tab-content" x-spread="content_inner_home">
          <h3>Home</h3>
          <p>Lorem ipsum dolor sit amet,
             consectetur adipiscing elit.
             Quisque in faucibus magna.</p>
        </div>
      </div>
      …
    </div>

Javascript: Handling Headers Event to Tab Content

The x-spread manage all the attributes. Consider this example below, for home tab:

  <main class="tabs" x-data="tabs()">
    <div class="tab-headers">
      <div x-spread="header_home">
        <div x-spread="header_inner_home">Home</div>
      </div>
      …
    </div>

    <div class="tab-spacer"></div>

    <div class="tab-contents">
      <div x-spread="content_home">
        <div class="tab-content" x-spread="content_inner_home">
          <h3>Home</h3>
          <p>Lorem ipsum dolor sit amet,
             consectetur adipiscing elit.
             Quisque in faucibus magna.</p>
        </div>
      </div>
    </div>
  </main>

The same extracted attributes, also applied to the tab contents.

function tabs() {
  return {
    tab: 'news',
    hover: false,
    header_home: {
      ['@click']()      { this.tab = 'home'; this.hover = this.tab; },
      ['@mouseenter']() { this.hover = (this.tab == 'home') ? this.tab : false; },
      ['@mouseleave']() { this.hover = false },
      [':class']()      { 
        return (this.tab == 'home') ? 'active bg-blue-500' : 'bg-gray-700';
      }
    },
    // … more configuration here
    header_inner_home: {
      [':class']() { return (this.tab == 'home') ? 'bg-white' : ''; }
    },
    // … more configuration here
    content_home: {
      ['x-show']() { return (this.tab == 'home'); },
      [':class']() { return (this.tab == 'home') ? 'bg-blue-500' : ''; }
    },
    // … more configuration here
    content_inner_home: {
      [':class']() { return (this.hover == 'home') ? 'is-hovered' : ''; }
    },
    // … more configuration here
  }
}

You can examine yourself, how synching hover implemented here:

function tabs() {
  return {
    tab: 'news',
    hover: false,
    header_home: {
      ['@click']()      { this.tab = 'home'; this.hover = this.tab; },
      ['@mouseenter']() { this.hover = (this.tab == 'home') ? this.tab : false; },
      ['@mouseleave']() { this.hover = false }
      }
    },
    // … more configuration here
    content_inner_home: {
      [':class']() { return (this.hover == 'home') ? 'is-hovered' : ''; }
    },
    // … more configuration here
  }
}

I think that’s all.

Preview

The same as previous example.

Complete

If you want, I also provide a complete version with FontAwesome.

But, this won’t be my final journey. I still want to explore modern javascript framework as well.


What’s Next?

This lightweight alpine.js can be leveraged to vue.js.

Consider continue reading [ Tabs - JS - Vue.js (component) ].