1

In tinybind.js (which seems to be a dead project) setting: var view = tinybind.bind(app, data) results in the UI updating upon directly setting a global variable, with no special requirements. Is there an equivalent in Alpine.js (which seems a more popular maintained project)

I know you get use globalData, but I'm keen to remain as close to standard JS as possible.

Other frameworks and 'no require build' libraries can do this, it seems a shame Alpine can only really react inside its own component.

<!DOCTYPE html>
<html>
<head>
    <title>Tabbed Layout</title>
    <style>
        body {
            display: flex;
            flex-direction: column;
            height: 100vh;
            margin: 0;
            align-items: center;
        }

        #app {
            height: 100vh;
            width: 600px;
            border: 2px solid #ccc;
            position: relative;
        }

        .tab-container {
            display: flex;
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            background-color: #fff;
            display: flex;
        }

        .tab {
            flex: 1;
            text-align: center;
            text-transform: uppercase;
            padding: 10px;
            cursor: pointer;
            background-color: #ddd;
            border: 1px solid #ccc;
            transition: background-color 0.3s ease;
        }

        .tab:hover {
            background-color: #bbb;
        }

        .tab.active {
            background-color: #aaa;
        }

        .tab-content {
            display: none;
            position: absolute;
            width: 100%;
            text-align: center;
        } 

        .tab-content[x-show] {
            display: block;
        }
    </style>
</script>
</head>
<body x-data="data" :class="`${mode} ${theme}`">
    <div id="app">
        <div>
            <div id="content_tab1" class="tab-content" x-show="activeTab === 'tab1'">
                <div>Content for Tab 1</div>
            </div>
            <div id="content_tab2" class="tab-content" x-show="activeTab === 'tab2'">
                <div>Content for Tab 2</div>
            </div>
            <div id="content_tab3" class="tab-content" x-show="activeTab === 'tab3'">
                <div>Content for Tab 3</div>
            </div>
            <div id="content_tab4" class="tab-content" x-show="activeTab === 'tab4'">
                <div>Content for Tab 4</div>
            </div>
            <div class="tab-container">
                <div @click="activeTab = 'tab1'" class="tab" :class="{ active: activeTab === 'tab1' }">
                    <span>tab1</span>
                </div>
                <div @click="activeTab = 'tab2'" class="tab" :class="{ active: activeTab === 'tab2' }">
                    <span>tab2</span>
                </div>
                <div @click="activeTab = 'tab3'" class="tab" :class="{ active: activeTab === 'tab3' }">
                    <span>tab3</span>
                </div>
                <div @click="activeTab = 'tab4'" class="tab" :class="{ active: activeTab === 'tab4' }">
                    <span>tab4</span>
                </div>
            </div>
        </div>
     </div>
</body>
    <script>
        let data = {
            activeTab: 'tab1',
            mode: 'light',
            theme: 'blue',
        };
    </script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
    <script>
        setTimeout(() => {
            data.activeTab = 'tab4'
        }, 500);
    </script>
</html>

I'm expecting to set something like the tinybind.js var view = tinybind.bind(app, data) and the UI update automatically when a the global var data is changed in vanilla JS.

2 Answers 2

1

The idiomatic way is to use the Alpine.data function to make your data reactive (if it's not defined inline with the x-data element.)

Here is an example of how you could make this work:

document.addEventListener("alpine:init", () => {
  Alpine.data('tabState', () => ({
    init() {
      setTimeout(() => {
        this.activeTab = 'tab4'
      }, 500);
    },

    activeTab: 'tab1',
    mode: 'light',
    theme: 'blue',
  }));
});
body {
  display: flex;
  flex-direction: column;
  height: 100vh;
  margin: 0;
  align-items: center;
}

#app {
  height: 100vh;
  width: 600px;
  border: 2px solid #ccc;
  position: relative;
}

.tab-container {
  display: flex;
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: #fff;
  display: flex;
}

.tab {
  flex: 1;
  text-align: center;
  text-transform: uppercase;
  padding: 10px;
  cursor: pointer;
  background-color: #ddd;
  border: 1px solid #ccc;
  transition: background-color 0.3s ease;
}

.tab:hover {
  background-color: #bbb;
}

.tab.active {
  background-color: #aaa;
}

.tab-content {
  display: none;
  position: absolute;
  width: 100%;
  text-align: center;
}

.tab-content[x-show] {
  display: block;
}
<!DOCTYPE html>
<html>
<head>
    <title>Tabbed Layout</title>
    <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
</head>
<body :class="`${mode} ${theme}`">
    <div id="app" x-data="tabState">
        <div>
            <div id="content_tab1" class="tab-content" x-show="activeTab === 'tab1'">
                <div>Content for Tab 1</div>
            </div>
            <div id="content_tab2" class="tab-content" x-show="activeTab === 'tab2'">
                <div>Content for Tab 2</div>
            </div>
            <div id="content_tab3" class="tab-content" x-show="activeTab === 'tab3'">
                <div>Content for Tab 3</div>
            </div>
            <div id="content_tab4" class="tab-content" x-show="activeTab === 'tab4'">
                <div>Content for Tab 4</div>
            </div>
            <div class="tab-container">
                <div @click="activeTab = 'tab1'" class="tab" :class="{ active: activeTab === 'tab1' }">
                    <span>tab1</span>
                </div>
                <div @click="activeTab = 'tab2'" class="tab" :class="{ active: activeTab === 'tab2' }">
                    <span>tab2</span>
                </div>
                <div @click="activeTab = 'tab3'" class="tab" :class="{ active: activeTab === 'tab3' }">
                    <span>tab3</span>
                </div>
                <div @click="activeTab = 'tab4'" class="tab" :class="{ active: activeTab === 'tab4' }">
                    <span>tab4</span>
                </div>
            </div>
        </div>
     </div>
</body>
</html>

If you really want to use a plain JavaScript object, you can call Alpine.reactive instead.

document.addEventListener("alpine:init", () => {
  window.tabState = Alpine.reactive({
    activeTab: 'tab1',
    mode: 'light',
    theme: 'blue',
  });

  setTimeout(() => {
    tabState.activeTab = 'tab4'
  }, 500);
});

If you need to break out of the component space, the more idiomatic way is to use a global store instead.

Sign up to request clarification or add additional context in comments.

2 Comments

That's cracked it - I think.
I'd upvote but I'm under threshold - sorry!
0

Updated Calvin's code to this:

<script type="text/javascript">

    let tabState = {
        activeTab: 'tab1',
        mode: 'light',
        theme: 'blue',
    };

    // set tabState to be reactive
    document.addEventListener("alpine:init", () => {
        tabState = Alpine.reactive(tabState);
    })

    // test changing tabs
    let tabs = ['tab1', 'tab2', 'tab3', 'tab4'];
    setInterval(() => {
        let tab_index = tabs.indexOf(tabState.activeTab) + 1;
        if (tab_index > 3) tab_index = 0;
        tabState.activeTab = tabs[tab_index];
    }, 1000);
</script>

Looks to be working perfectly

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.