Code Monkey home page Code Monkey logo

alpinejs-component's Introduction

Alpine JS Component

Reusable HTML components powered by Alpine JS reactivity 🛸

Install

With a CDN

<script
  defer
  src="https://unpkg.com/alpinejs-component@latest/dist/component.min.js"
></script>

<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>

With a Package Manager

npm install -D alpinejs-component

yarn add -D alpinejs-component
import Alpine from 'alpinejs'
import component from 'alpinejs-component'

Alpine.plugin(component)

Alpine.start()

Example

On Page Components

You can render on page components by using a <template> with an id that matches the template attribute on the component.

Here we are rendering the component HTML found in <template id="person"> element.

<div
  x-data="{
    people: [
      { name: 'John', age: '25', skills: ['JavaScript', 'CSS'] },
      { name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] }
    ]
  }"
>
  <ul>
    <template x-for="person in people">
      <x-component template="person" x-data="{ item: person }"></x-component>
    </template>
  </ul>
</div>

<template id="person">
  <li>
    <h2 x-text="item.name"></h2>

    <p x-text="item.age"></p>

    <ul>
      <template x-for="skill in item.skills">
        <li x-text="skill"></li>
      </template>
    </ul>
  </li>
</template>

Global Components

If you don't want on page components you can use the url attribute which accepts a path to the HTML component.

Here we are telling Alpine JS to fetch the HTML from /public/person.html within the codebase.

<div
  x-data="{
    people: [
      { name: 'John', age: '25', skills: ['JavaScript', 'CSS'] },
      { name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] }
    ]
  }"
>
  <ul>
    <template x-for="person in people">
      <x-component
        url="/public/person.html"
        x-data="{ item: person }"
      ></x-component>
    </template>
  </ul>
</div>

Then we'd have a file /public/person.html which could look like this.

<li>
  <h2 x-text="item.name"></h2>

  <p x-text="item.age"></p>

  <ul>
    <template x-for="skill in item.skills">
      <li x-text="skill"></li>
    </template>
  </ul>
</li>

Dynamic Templates

You can pass template or url as a dynamic value, here's an example.

<div
  x-data="{
    components: [
      {
        template: '/public/person.html',
        data: { name: 'John', age: '25', skills: ['JavaScript', 'CSS'] }
      },
      {
        template: '/public/person.html',
        data: { name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] }
      },
    ]
  }"
>
  <ul>
    <template x-for="component in components">
      <x-component
        :template="component.template"
        x-data="{ item: component.data }"
      ></x-component>

      // Or

      <x-component
        :url="component.template"
        x-data="{ item: component.data }"
      ></x-component>
    </template>
  </ul>
</div>

Styling Components

Including Stylesheets

You can use styles attribute to specify which stylesheets to include.

<style title="person">
  /* ... */
</style>

<x-component
  template="person"
  styles="person"
  x-data="{ item: person }"
></x-component>

You can also include multiple stylesheets by separating them with a comma.

<style title="person">
  /* ... */
</style>

<style title="general">
  /* ... */
</style>

<x-component
  template="person"
  styles="person,general"
  x-data="{ item: person }"
></x-component>

Or, if you want to include all stylesheets you can use styles="global"

Inline Stylesheet

You can add a <style> element with the components CSS to the component itself.

<div>
  <style>
    .example {
      background: #00f;
    }
  </style>

  <p class="example" x-text="message"> </p>
</div>

Renaming Component

If you need to change the name x-component, you can do so by setting the global xComponent object. This is necessary because blade components start with x-, which can cause conflicts.

window.xComponent = {
  name: 'a-component',
}

You will then call components like this:

<div
  x-data="{
    people: [
      { name: 'John', age: '25', skills: ['JavaScript', 'CSS'] },
      { name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] }
    ]
  }"
>
  <ul>
    <template x-for="person in people">
      <a-component
        url="/public/person.html"
        x-data="{ item: person }"
      ></a-component>
    </template>
  </ul>
</div>

Stats

alpinejs-component's People

Contributors

lowlighter avatar luanhimmlisch avatar markmead avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

alpinejs-component's Issues

Templating and `x-component` fail to refresh on array manipulation

taking the code given in the README and adding just one button that removes the first record in the array people and a button to dump the array to the console:

<div
  x-data="{
    people: [
      { name: 'John', age: '25', skills: ['JavaScript', 'CSS'] },
      { name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] }
    ]
  }"
>
  <ul>
    <template x-for="person in people">
      <x-component template="person" x-data="{ item: person }"></x-component>
    </template>
  </ul>

  <!-- addition -->
  <p><button @click="people.splice(0,1)">Remove John</button></p>
  <p><button @click="console.dir(people)">Dump Array to Console</button></p>
  <!-- end of addition -->
</div>

<template id="person">
  <li>
    <h2 x-text="item.name"></h2>

    <p x-text="item.age"></p>

    <ul>
      <template x-for="skill in item.skills">
        <li x-text="skill"></li>
      </template>
    </ul>
  </li>
</template>

If you click the Remove button, the array is indeed dropping John and keeping Jane (verify by clicking the 2nd button), but the presentation keep John and removes Jane.

[THOUGHT] Shadow DOM

Everything was great till I read this technique will not support global CSS. Its such a shame! I was hoping every issue was getting solved right here! Its a pain to see how Global CSS is not supported by Shadow DOM! I wish there was some workaround.

One question: If I have linked a CSS file in main page, and component also links to the same file will it get redownloaded by the browser or will it be taken from the cache?

[BUG] Issue Building with Vite

I have installed the plugin with a package manager and created a basic component :

<div x-data="tabs">
  <link rel="stylesheet" href="/assets/classless.css">
  <nav>
    <ul>
      <img alt="app_logo"
       src="../assets/logo.svg" />
      <span style="font-size: 1.5em ">rlog</span>
      <template x-for="tab in tabs">
        <li x-text="tab"></li>
      </template>
      <img class="float-right" alt="RFC logo" src="../assets/Logo_RFC_Couleur_Blanc.png"  />
    </ul>
  </nav>
</div>

I use it inside my index.html

<x-component-wrapper 
      x-component
      url="../components/Navbar.html"
></x-component-wrapper>

When I use npm run dev (vite --host 0.0.0.0 --port 3000) it work fine but when I use npm run build (vite build) it break because the url "../components/Navbar.html" doesn't exist anymore.

I try fixing that by adding the component to the static assets with assetsInclude: ['/components/*.html'] in vite.config.js but it didn't work for several reasons :

  • A hash is added to the file name
  • The file is then stored in the assets folder, not 'components/'
  • The links inside are not process so there are broken

Is there a way to fix that so I can steel use Vite to build?

My vite.config.js

import { resolve } from 'path'
import { defineConfig } from 'vite'

export default defineConfig({ 
  root : resolve(__dirname, 'app/'),
  build: {
    outDir : resolve(__dirname, 'dist/'),
    emptyOutDir: true,
  }
})

Thank you!

Styles are not working as expected with Bootstrap

I was toying around with alpinejs-component and some simple bootstrap styles and I'm running into the issues, that inside alpinejs components, the styles and their overwrites are not working as expected if styles="global" is used.

Check this simple Codepen that demos the problem:
https://codepen.io/theomega/pen/JjVXjxG

Screenshot 2024-03-09 at 21 00 51

The code tries to wrap a simple bootstrap button with the following classes inside a component:

<button type="button" class="btn btn-primary">Click Me</button>

while using styles="global" to get the Bootstrap styles copied over.

Notice, that in this case the background color of the button is lost somehow. Funnily, everything else still works, including the mouse over hover (with the correct color).

One workaround that I found is not using any styles on the x-component but rather copy the bootstrap css stylesheet link a second time and into the template of the component. Not idea, but at least works. I'm also opening this issue to document this.

Not sure where the issue lies here...

Override the `x-component` directive

Hello

Because our toolchain depends on more and more packages it could be nice to permits the overriding of the custom element name x-component. An example need is quoted in #21 ;-)

I found this change do the job:

 ...
  let tagName = Alpine.store('alpinejs-component.tagName');
  if( ! tagName )
    tagName = 'y-component';
  if (window.customElements.get(tagName)) {
    return
  }

  customElements.define(tagName, ComponentWrapper)
 ...

then before loading the plugin we can set another tag name like "z-component" with

Alpine.store('alpinejs-component.tagName', 'z-component');
Alpine.plugin(component)

I can purpose a PR if you agree with this proposal ;-)

Thanks & cheers

Is x-component-wrapper necessary?

This plugin works great for me, but the need for both x-component-wrapper and x-component hurts my eyes.

Is there a reason why we can't bind custom fields on divs or sections?

https://stackoverflow.com/a/67276585 works in a similar fashion. But uses the querySelector to bind the HTMLElement element. I'm thinking could we not uses a similar approach to late-bind the HTMLElement on found tags? That way we could just do and it'll bind HTMLElement to the x-my-component?

Thoughts?

Support recursive components

Nice work on this and thanks for it!

I'm trying to create a nested tree in my UI with parents, children, and grandchildren, etc. I can't get this to work for that use case. Something like this:

 <template id="node-component">
  <div>
    <h1 x-text="node.title"></h1>
    <template x-for="child in node.children">
      <x-component-wrapper
        x-component
        template="node-component"
        x-data="{ node: child }"
      ></x-component-wrapper>
    </template>
  </div>
</template>

In my case the parent's h1 renders but nothing else.

[QUESTION] Reactivity with X-MODEL

Hi!

Thanks for this plugin, this gets me so-close to client-side templating without any frameworks or libraries like Vue or React.

However, this might have to do with my setup, but it appears that this won't work fine with x-model directive. Here's what I'm trying:

My HTML file includes:

<html
  x-data = "alpine">
  <body>
    <x-component-wrapper
      x-component
      url = "/components/input/">
    </x-component-wrapper>
  </body>
</html>

My template file has the following:

<div>
  <link
    href = "/css/bundle.css"
    rel = "stylesheet"/>
  <input
    x-model = "input"/>
</div>

I've my Alpine data model in my JavaScript bundle:

import Alpine from 'alpinejs'
import Component from 'alpinejs-component'
Alpine.data('alpine', () => ({
  input: null
}))
Alpine.plugin(Component)
Alpine.start()

Now, I'm not sure how to bind the x-model to the <input>. Directly using x-model = "input" in my component works, however I need to be able to change the x-model = "<value>" for each of my <input> element. Any guidance would be super helpful! Also, I hope this is possible with the plugin because this would make developer experience so much better for my website made with Hugo. Currently, I've to copy-paste my input element several times and it's getting difficult to manage as I need to update all of those for a single change.

But if it's not possible, I totally understand. The plugin seems to be great at what it's doing and I appreciate the work that's put into this.

How to style with Tailwind CSS

Hi @markmead, nice library! Makes Alpine components nice and simple!

I had a look at the docs on styling, but I am struggling to style the components with a utility lib like Tailwind.ccs, or UnoCSS (in my case). Any idea how I could apply styles from the utility lib in my components?

Failed to fetch external template URL

Hey, congrats for this plugin. It's just fantastic!

I am having some trouble trying to load an external HTML like this:

<x-component
        url="https://cdn.jsdelivr.net/npm/@invopop/[email protected]/image-picker.html"
        x-data="{ url: 'https://placehold.co/40x40' }"
></x-component>

But I am getting an error Uncaught (in promise) TypeError: Failed to fetch

I am sure I am doing something wrong because the code below works:

fetch('https://cdn.jsdelivr.net/npm/@invopop/[email protected]/image-picker.html')
          .then(response => response.text())
          .then(html => {
            console.log(html)
          })
          .catch(error => console.error('Error loading HTML:', error));

Thanks!

[BUG] Template Name as Variable

How to keep template name as variable

<template x-for="c in components"> <x-component-wrapper x-component x-template="c.name" x-data="{ item: c }"></x-component-wrapper> </template>

[BUG] Broken Components

I found out that using <template> to wrap the component and then to refer/render it using <x-component-wrapper> doesn't not put the component in display. Basically they are attached as document fragments but doesn't show up on the page.

[QUESTION] How to Style?

-->
<head>
  <style>
    .dropdown-toggle {
      width: 100px;
      height: 100px;
      background-color: #f5f5f5;
    }
  </style>
</head>

<x-component-wrapper x-component template="dropdown" x-data="dropdown"></x-component-wrapper>
<x-component-wrapper x-component template="dropdown" x-data="dropdown"></x-component-wrapper>

<template id="dropdown">
  <div @click="close" class="dropdown-toggle">
    <button x-on:click="open">Open</button>
    <div x-show="show" x-text="content"></div>
  </div>
</template>

<script type="module">
  import { default as Alpine } from 'https://cdn.skypack.dev/alpinejs'
  import alpinejsComponent from 'https://cdn.skypack.dev/alpinejs-component'
  function dropdown() {
    return {
      show: false,
      open() {
        console.log('open')
        this.show = true
        console.log(this.show)
      },
      close(event) {
        const button = this.$el.querySelector('button')
        const target = event.target
        if (this.$el.contains(target) && !button.contains(target)) {
          this.show = false
        }
      },
      get isOpen() {
        return this.show === true
      },
      content: 'Default content',
      init() {
        console.log(this.$el.parentElement)
        console.log('dropdown --- init')
      },
    }
  }
  Alpine.data('dropdown', dropdown) // 注册数据
  Alpine.plugin(alpinejsComponent) // 注册组件
  Alpine.start() // 启动组件
</script>

style does not work

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.