如何使用Alpine.js创建可重用组件并显示它?例如,也许我想定义一个通用的Alpine.js按钮组件,从参数中更改文本和颜色,然后让我的Alpine.js导航栏组件使用按钮组件来显示登录按钮。
我可以在纯客户端代码中做到这一点,而不依赖于服务器在按钮组件使用的任何地方模板化所有按钮HTML吗?
我可以在纯客户端代码中做到这一点,而不依赖于服务器模板吗?
可以。
Alpine.js总是试图说服您使用服务器端模板引擎。
但就像你一样,我不会让自己被说服:
<template x-component="dropdown">
<div x-data="{ ...dropdown(), ...$el.parentElement.data() }">
<button x-on:click="open">Open</button>
<div x-show="isOpen()" x-on:click.away="close" x-text="content"></div>
</div>
</template>
<x-dropdown content="Content for my first dropdown"></x-dropdown>
<div> Random stuff... </div>
<x-dropdown content="Content for my second dropdown"></x-dropdown>
<x-dropdown></x-dropdown>
<script>
function dropdown() {
return {
show: false,
open() { this.show = true },
close() { this.show = false },
isOpen() { return this.show === true },
content: 'Default content'
}
}
// The pure client-side code
document.querySelectorAll('[x-component]').forEach(component => {
const componentName = `x-${component.getAttribute('x-component')}`
class Component extends HTMLElement {
connectedCallback() {
this.append(component.content.cloneNode(true))
}
data() {
const attributes = this.getAttributeNames()
const data = {}
attributes.forEach(attribute => {
data[attribute] = this.getAttribute(attribute)
})
return data
}
}
customElements.define(componentName, Component)
})
</script>
Alpine.js贡献者@ryangjchandler指出可重用模板超出了Alpine.js:的范围
提议的[Alpine.js版本3]x-component指令与组件的模板或标记无关。相反,它将提供一种编写更直接可重复使用的数据集的方法&函数,同时减少需要在标记中定义的指令数量。
如果您需要可重复使用的模板,我会考虑使用服务器端模板引擎或更为单一的前端框架,如Vue或React。(链接(
和
您正在寻找的功能远远超出Alpine的范围。它的设计目的是与服务器或静态文件中的现有标记一起使用,而不是替换/组件化标记。(链接(
使用alpinejs-component
同一页由cdn:
<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">
<!-- use the person template to find the <template id="person"> element. -->
<x-component-wrapper x-component template="person" x-data="{ item: person }"></x-component-wrapper>
</template>
</ul>
</div>
<template id="person">
<li class="user-card">
<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>
<script src="https://unpkg.com/alpinejs-component@1.x.x/dist/component.min.js"></script>
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
使用url导入html模板:
<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-wrapper x-component url="/public/person.html" x-data="{ item: person }"></x-component-wrapper>
</template>
</ul>
</div>
<script src="https://unpkg.com/alpinejs-component@1.x.x/dist/component.min.js"></script>
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
person.html
:
<li class="user-card">
<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>
npm:安装
npm i -D alpinejs-component
yarn add -D alpinejs-component
注册插件:
import Alpine from "alpinejs";
import component from "alpinejs-component";
Alpine.plugin(component);
window.Alpine = Alpine;
Alpine.start();
或使用浏览器中的模块:
<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>
工作良好。
更多信息alpinejs组件
使用Alpine.js v3和Global Alpine Components,您可以使用Alpine.component((来封装此功能。
https://github.com/markmead/alpinejs-component
<div x-data="dropdown">
...
</div>
<script>
Alpine.component('dropdown', () => ({
open: false,
toggle() { this.open = !this.open }
}))
</script>
您可以使用Alpine.data
和用x-bind
封装指令的文档化方法来实现这一点。诀窍是绑定x-html
指令。在你的HTML中这样做:
<div x-data="dropdown" x-bind="bind"></div>
在您的Javascript中:
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
show: false,
bind: {
['x-html']() { return `
<button @click="show = !show">Click me!</button>
<div x-show="show">Hello World</div>
`},
},
}));
})
JSFiddle在这里。
这有点麻烦,因为您将所有嵌套内容封装在绑定在x-html
指令中的多行HTML字符串中(尽管可能并不比到处克隆模板更麻烦(。请确保您没有在内容中使用反勾字符。尽管如此,内容可以嵌套得任意深,并且可以包含Alpine.js指令。您可以通过声明参数并将参数传递到Alpine.data
来初始化组件。您还可以绑定x-modelable
以将组件的任何属性作为输出公开。
如果您更喜欢使用模板,也许是因为当标记没有嵌入字符串中时,编辑器可以更好地突出显示语法,所以您可以将这种方法与模板相结合。下面是一个示例,演示了x-modelable
以及模板的使用。实际上,Alpine为您进行模板克隆。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js"></script>
</head>
<body>
<div x-data="{clicked: false}">
<div>Clicked is <span x-text="clicked"></span></div>
<div x-data="dropdown" x-bind="bind" x-model="clicked"></div>
</div>
<template id="dropdown">
<button @click="show = !show">Click me!</button>
<div x-show="show">Hello World</div>
</template>
</body>
<script type="text/javascript">
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
show: false,
bind: {
['x-modelable']: 'show',
['x-html']() { return document.querySelector('#dropdown').innerHTML},
},
}));
})
</script>
</html>
JSFiddle在这里。
Vimesh UI中带有本地自定义元素的x组件(https://github.com/vimeshjs/vimesh-ui)是一个更完整的可重用组件实现:
<head>
<script src="https://unpkg.com/@vimesh/style" defer></script>
<script src="https://unpkg.com/@vimesh/ui"></script>
<script src="https://unpkg.com/alpinejs" defer></script>
</head>
<body x-cloak class="p-2" x-data="{name: 'Counter to rename', winner: 'Jacky'}">
Rename the 2nd counter : <input type="text" x-model="name" class="rounded-md border-2 border-blue-500">
<vui-counter x-data="{step: 1}" :primary="true" title="First" x-init="console.log('This is the first one')" owner-name="Tom"></vui-counter>
<vui-counter x-data="{step: 5}" :title="name + ' @ ' + $prop('owner-name')" owner-name="Frank"></vui-counter>
<vui-counter x-data="{step: 10, value: 1000}" :owner-name="winner">
<vui-counter-trigger></vui-counter-trigger>
</vui-counter>
<template x-component.unwrap="counter" :class="$prop('primary') ? 'text-red-500' : 'text-blue-500'"
x-data="{ step : 1, value: 0}" x-init="$api.init && $api.init()" title="Counter" owner-name="nobody">
<div>
<span x-text="$prop('title')"></span><br>
Owner: <span x-text="$prop('owner-name')"></span><br>
Step: <span x-text="step"></span><br>
Value : <span x-text="value"></span><br>
<button @click="$api.increase()"
class="inline-block rounded-lg bg-indigo-600 px-4 py-1.5 text-white shadow ring-1 ring-indigo-600 hover:bg-indigo-700 hover:ring-indigo-700">
Increase
</button>
<slot></slot>
</div>
<script>
return {
init() {
console.log(`Value : ${this.value} , Step : ${this.step}`)
},
increase() {
this.value += this.step
}
}
</script>
</template>
<template x-component="counter-trigger">
<button @click="$api.of('counter').increase()"
class="inline-block rounded-lg mt-2 bg-green-600 px-4 py-1.5 text-white shadow ring-1 ring-green-600 hover:bg-green-700 hover:ring-green-700">
Tigger from child element</button>
</template>
</body>