This is my third article about getting started
introduce
Your new, lightweight, JavaScript framework.
Your new lightweight JavaScript framework.
- simple
- lightweight
- Strong as hell
Alpine is a rugged, small tool for combining behavior directly in markup. Think of it as the jQuery of the modern web. Enter a script label and start.
Alpine is a collection of 15 directives, six attributes, and two methods.
The best way to learn what Alpine is and what it does is to see it for yourself!
I prefer to think of Alpine. Js as a peripheral ecosystem of Vue because it is so similar to Vue and is used in the latest version@vue/reactivity
Like I said, it’s justpetite-vue
Preview version, but it provides a good idea, at least for me is inspired, that after I roughly read its source code, I think learning a library, or a single wheel, we should not be simple to learn and use them, we should study the thought of it, implementation, should change to do the same, So in this installment, I’ll start with an introduction to Alpine (much like Vue, but still informative) and share my experience reading the source code.
Learn to use Alpine. Js from scratch
The life cycle
Alpine.initializing
Alpine before loading
document.addEventListener('alpine:init'.() = > {
Alpine.data(...)
})
Copy the code
Alpine.initialized
After Alpine loading
document.addEventListener('alpine:initialized'.() = > {
//
})
Copy the code
15 instructions
x-data
In Alpine, it all starts with X-Data.
X-data defines HTML blocks as components and provides them with responsive data.
<div x-data="{ open: false }"></div>
Copy the code
scope
Like JS, data defined in Alpine has its own scope, and attributes defined in the X-data directive are available to all of their child elements.
Because X-Data is evaluated as JavaScript objects, you can define methods and even getters in it.
<div x-data="{ foo: 'bar' }">
<span x-text="foo"><! -- Will output: "bar" --></span>
<div x-data="{ bar: 'baz' }">
<span x-text="foo"><! -- Will output: "bar" --></span>
<div x-data="{ foo: 'bob' }">
<span x-text="foo"><! -- Will output: "bob" --></span>
</div>
</div>
</div>
Copy the code
No data component
Sometimes you want to create a Alpine component, but you don’t need any data, in which case you can always pass in an empty object and not even pass it:
<div x-data="{}".
<div x-data...
Copy the code
Data reuse
As shown in the Alpine. The data ().
x-init
X-init is a function that is executed before component DOM initialization. Example 1 below prints I’m being Initialized! Of course, we can also define some common initialization operations in x-init, such as requesting data from the back end and assigning values:
<div x-init="console.log('I\'m being initialized! ')"></div>
<div
x-data="{ posts: [] }"
x-init="posts = await (await fetch('/posts')).json()"
>.</div>
Copy the code
$nextTick
Of course, sometimes we want x-init to be executed after rendering, as in React useEffect(… , []) or mount in Vue. Use Alpine’s $nextTick as well:
<div x-init="$nextTick(() => { ... })"></div>
Copy the code
independentx-init
You can add X-init to any element outside the component registered with X-Data.
<div x-data>
<span x-init="console.log('I can initialize')"></span>
</div>
<span x-init="console.log('I can initialize too')"></span>
Copy the code
self-executinginit()
methods
If you need to use both x-data and x-init, you can define the init() function in x-data to have the same effect:
<div
x-data="{ init() { console.log('I am called automatically') } }"
>.</div>
Copy the code
The same is true for components registered with Alpine.data() syntax.
Alpine.data('dropdown'.() = > ({
init() {
console.log('I will get evaluated when initializing each "dropdown" component.')}}))Copy the code
x-show
X-show is one of Alpine’s most useful and powerful directives. It provides an expression to show and hide DOM elements.
<div x-data="{ open: false }">
<button x-on:click="open = ! open">Toggle Dropdown</button>
<div x-show="open">
Dropdown Contents...
</div>
</div>
Copy the code
You can use X-cloak to avoid initialization flicker, see X-cloak.
You can activate the animation effect with X-Transition, see X-Transition.
x-bind
X-bind allows you to set the HTML attributes of an element based on the result of a JavaScript expression and abbreviates:.
<div x-data="{ placeholder: 'Type here...' }">
<input type="text" x-bind:placeholder="placeholder">
</div>
<input type="text" :placeholder="placeholder">
Copy the code
It is often used to bind a class or style, usually written as either a string or an object. Unlike other attributes, classes are merged rather than overwritten.
x-on
X-on allows for easier scheduling of events, or you can use the abbreviation @ :
<button x-on:click="alert('Hello World! ')">Say Hi</button>
<button @click="alert('Hello World! ')">Say Hi</button>
Copy the code
$event
If you want to access JavaScript event objects from expressions, you can use Alpine’s $event property.
In addition, Alpine passes event objects to any method referenced.
<button @click="alert($event.target.getAttribute('message'))" message="Hello World">Say Hi</button>
<button @click="handleClick">.</button>
<script>
function handleClick(e) {
// Now you can access the event object (e) directly
}
</script>
Copy the code
Keyboard events
Alpine makes it easy to listen for keyDown and KeyUp events on specific keys.
<input type="text" @keyup.enter="alert('Submitted! ')">
Copy the code
This is a listener that runs when the Shift key is held down and Enter is pressed, rather than when Enter is pressed alone.
<input type="text" @keyup.shift.enter="alert('Submitted! ')">
Copy the code
You can use them directly as modifiers by converting any valid key names exposed by keyboardevent.key to keybab-case.
<input type="text" @keyup.page-down="alert('Submitted! ')">
Copy the code
For reference purposes, here’s a list of common keys that you might want to listen for.
Modifier | Keyboard Key |
---|---|
.shift |
Shift |
.enter |
Enter |
.space |
Space |
.ctrl |
Ctrl |
.cmd |
Cmd |
.meta |
Cmd on Mac, Ctrl on Windows |
.alt |
Alt |
.up .down .left .right |
Up / Down / Left / Right arrows |
.esc |
Escape |
.tab |
Tab |
.caps-lock |
Caps Lock |
Custom events
The Alpine event listener is a wrapper around the native DOM event listener. Therefore, they can listen for any DOM event, including custom events.
Here is an example of a component that dispatches a custom DOM event and listens for it.
<div x-data @foo="alert('Button Was Clicked! ')">
<button @click="$event.target.dispatchEvent(new CustomEvent('foo', { bubbles: true }))">.</button>
</div>
Copy the code
The modifier
.prevent
.prevent is equivalent to calling.preventdefault () in the listener of the browser event object
.stop
Similar to.prevent,.stop is equivalent to calling.stopPropagation () inside the listener of the browser event object to prevent event propagation from bubbling.
.outside
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle</button>
<div x-show="open" @click.outside="open = false">Contents...</div>
</div>
Copy the code
In the example above, after you display the contents of the drop-down list by clicking the “Toggle” button, you can close it by clicking anywhere but the contents of the page.
This is because.outside is listening for clicks from elements not registered with it.
It is worth noting that the.outside expression is evaluated only if the element it registers is visible on the page.
.window
When the.window modifier appears, Alpine registers an event listener on the window object of the page rather than on the element itself.
<div @keyup.escape.window="...">.</div>
Copy the code
.document
.document works like.window in that it registers listeners only in the document global, not the Window global.
.once
Causes the handler to be called only once
.debounce
Image stabilization
.throttle
The throttle
.self
By adding.self to the event listener, you ensure that the event originates from the element that declares it and not from the child element.
<button @click.self="handleClick">
Click Me
<img src="..." />
</button>
Copy the code
In the example above, we have an tag in the
.camel
Sometimes you might want to listen for camel case events, such as the customEvent in our example. Because camelCase is not supported in HTML attributes, Alpine needs to add the.camel modifier internally to use the camelCase event name. By adding.camel, Alpine now listens on customEven t instead of custom-Event.
<div @custom-event.camel="handleCustomEvent">.</div>
Copy the code
.passive
The browser optimizes scrolling on the page to make it fast and smooth, even when executing JavaScript on the page. However, improperly implemented touch and scroll listeners can prevent this optimization and result in poor site performance. If you are listening for touch events, you must add.passive to the listener to avoid blocking scrolling.
<div @touchstart.passive="...">.</div>
Copy the code
x-text
X-text sets the text content of the element to the result of the given expression.
<div x-data="{ username: 'calebporzio' }">
Username: <strong x-text="username"></strong>
</div>
Copy the code
x-html
X-html sets the innerHTML element to the result of a given expression.
⚠️ is used only for trusted content and never for user-provided content. ⚠ ️
⚠️ Rendering HTML dynamically from third parties can easily lead to XSS vulnerabilities. ⚠ ️
<div x-data="{ username: '<strong>calebporzio</strong>' }">
Username: <span x-html="username"></span>
</div>
Copy the code
x-model
Two-way binding
The X-Model allows you to bind the values of the input elements to Alpine data.
<div x-data="{ message: '' }">
<input type="text" x-model="message">
<span x-text="message">
</div>
Copy the code
The modifier
.lazy
For text input, by default, x-Model updates its properties with each keystroke. By adding the.lazy modifier, you can force the X-Model input to update attributes only when the user is not paying attention to the input element.
This is handy for things like real-time form validation, where you might not want to show input validation errors until the user “tabs” leave the field.
<input type="text" x-model.lazy="username">
<span x-show="username.length > 20">The username is too long.</span>
Copy the code
.number
By default, any data stored in an attribute through the X-Model is stored as a string. To force Alpine to store values as JavaScript numbers, add the.number modifier.
<input type="text" x-model.number="age">
<span x-text="typeof age"></span>
Copy the code
.debounce
& .throttle
Anti-shake and throttling
x-for
Alpine’s X-for directive allows you to create DOM elements by iterating through lists. Here is a simple example of creating a list of colors based on an array.
<ul x-data="{ colors: ['Red', 'Orange', 'Yellow'] }">
<template x-for="(color, index) in colors">
<li>
<span x-text="index + ': '"></span>
<span x-text="color"></span>
</li>
</template>
</ul>
Copy the code
There are two rules worth noting about X-for:
x-for
Must be in<template>
Declaration on element- namely
<template>
The elementMust beThere is only one root element
keys
If you are reordering items, it is important to specify key values for each X-for iteration. Without dynamic keys, Alpine might have a hard time keeping track of reordered content and could cause strange side effects.
<ul
x-data="{ colors: [ { id: 1, label: 'Red' }, { id: 2, label: 'Orange' }, { id: 3, label: 'Yellow' }, ]}"
>
<template x-for="color in colors" :key="color.id">
<li x-text="color.label"></li>
</template>
</ul>
Copy the code
Iterate over a range
If you need to simply loop n times instead of iterating through a number group, Alpine provides a short syntax.
<ul>
<template x-for="i in 10">
<li x-text="i"></li>
</template>
</ul>
Copy the code
x-transition
Alpine provides the X-Transition instruction to create smooth transitions between display and hide elements.
<div x-data="{ username: '<strong>calebporzio</strong>' }">
Username: <span x-html="username"></span>
</div>
Copy the code
Transition helper
Here is a simple example:
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle</button>
<span x-show="open" x-transition>
Hello 👋
</span>
</div>
Copy the code
.duration
You can use the. Duration modifier to configure the desired duration for a transformation:
<div . x-transition.duration.500ms>
Copy the code
The
If you want to specify display and disappear times separately:
<div .
x-transition:enter.duration.500ms
x-transition:leave.duration.400ms
>
Copy the code
.delay
Set the delay with the.delay modifier:
<div . x-transition.delay.50ms>
Copy the code
.opacity
& .scale
X-transtion defaults to using zoom and opacity for the same function. You can set a single function using.opacity &.scale:
<! -- Only turn on transparency effect -->
<div . x-transition.opacity>
<! -- Only enable zoom effect -->
<div . x-transition.scale>
Copy the code
.scale can also be configured with the properties of its scale value:
<div . x-transition.scale.80>
Copy the code
The code snippet above will scale the element by 80%.
You can of course set the zoom values to show and disappear separately:
<div .
x-transition:enter.scale.80
x-transition:leave.scale.90
>
Copy the code
.origin
To customize the origin of the scaling transformation, you can use the.origin modifier:
<div . x-transition.scale.origin.top | bottom | left | right >
Copy the code
You can also freely combine.origin. Top. Right.
x-effect
X-effect is used to trigger the evaluation of an expression when one of its dependencies changes. You can think of it as a monitor, but instead of specifying the properties to monitor, it will monitor all the properties used in it.
<div x-data="{ label: 'Hello' }" x-effect="console.log(label)">
<button @click="label += ' World! '">Change Message</button>
</div>
Copy the code
x-ignore
By default, Alpine grabs the entire DOM tree of an element marked x-data or X-init, but for some reason you don’t want to do this, so you can mark the element x-ignore:
<div x-data="{ label: 'From Alpine' }">
<div x-ignore>
<span x-text="label"></span>
</div>
</div>
Copy the code
The From Alpine text will not appear in the tag.
x-ref
X-ref and $refs are a very useful tool for easy direct access to DOM elements. It is most useful as an alternative to apis like getElementById and querySelector.
<button @click="$refs.text.remove()">Remove Text</button>
<span x-ref="text">Hello 👋</span>
Copy the code
x-cloak
X-cloak solves the initialization display problem by hiding the elements it attaches until Alpine is fully loaded on the page.
However, for X-Cloak to work, you must add the following CSS to the page.
[x-cloak] { display: none ! important; }
Copy the code
For now, the following example hides the tag until Alpine sets its text content to the Message property.
<span x-cloak x-text="message"></span>
Copy the code
x-if
X-if is used to toggle elements on the page, similar to x-show, but it adds or removes elements entirely, rather than setting hidden on the CSS display property. Because of this difference in behavior, x-if should not be applied directly to elements, but to
, which contains them. That way, Alpine can keep its records once the element is removed from the page.
<template x-if="open">
<div>Contents...</div>
</template>
Copy the code
Unlike X-Show, X-if does not support switching using X-Transition.
Six attributes
$el
$EL is a magic property that can be used to retrieve the current DOM node.
<button @click="$el.innerHTML = 'Hello World! '">Replace me with "Hello World!"</button>
Copy the code
$refs
$refs is used to retrieve the X-ref tag inside the COMPONENT’s DOM element. This is useful when you want to manually manipulate DOM elements, which is often used as a succinct local alternative to document.querySelector.
<button @click="$refs.text.remove()">Remove Text</button>
<span x-ref="text">Hello 👋</span>
Copy the code
$store
See Alpine.data() for details.
$watch
Monitor component property changes
<div x-data="{ open: false }" x-init="$watch('open', value => console.log(value))">
<button @click="open = ! open">Toggle Open</button>
</div>
Copy the code
Gets the old replaced value
$watch tracks the previous value of the property being monitored, which you can access using the optional second argument to the callback, as follows:
<div x-data="{ open: false }" x-init="$watch('open', (value, oldValue) => console.log(value, oldValue))">
<button @click="open = ! open">Toggle Open</button>
</div>
Copy the code
$dispatch
$dispatch is a shortcut to schedule browser events.
<div @notify="alert('Hello World! ')">
<button @click="$dispatch('notify')">
Notify
</button>
</div>
Copy the code
Underlying $dispatch is a lengthy API, element.dispatchEvent(new CustomEvent(…)). )
Notes on the dissemination of events
Note that because of event bubbling, you need to use the.window modifier when you need to capture events dispatched by nodes in the same nested hierarchy:
<! -- 🚫 Won't work -->
<div x-data>
<span @notify="..."></span>
<button @click="$dispatch('notify')">
<div>
<!-- ✅ Will work (because of .window) -->
<div x-data>
<span @notify.window="..."></span>
<button @click="$dispatch('notify')">
<div>
Copy the code
The first example doesn’t work because when a custom event is scheduled, it will propagate to its common ancestor, div, rather than its sibling, SPAN. The second example works because the sibling node listens for notify at the window level, where custom events will eventually jump to.
Schedule events for other components
Using the above.window, you can schedule other component events
<div
x-data="{ title: 'Hello' }"
@set-title.window="title = $event.detail"
>
<h1 x-text="title"></h1>
</div>
<div x-data>
<button @click="$dispatch('set-title', 'Hello World! ')">.</button>
</di
<! -- When clicked, the content of the h1 will set to "Hello World!" . -- -- >
Copy the code
schedulingx-modal
You can also use $dispatch() to trigger data updates for the X-Model data binding. Such as:
<div x-data="{ title: 'Hello' }">
<span x-model="title">
<button @click="$dispatch('input', 'Hello World! ')">
<! -- After the buttons pressed, `x-model` will catch the bubbling "input" event, and update title. -->
<! When the button is pressed, 'X-model' captures the bubbling 'input' event and updates the title. -->
</span>
</div>
Copy the code
This makes it easy to customize input components whose values can be set by x-Model.
$nextTick
$nextTick allows you to execute a given expression in Alpine after a responsive DOM update. This is useful when you want to interact with the DOM after any data updates.
<div x-data="{ title: 'Hello' }">
<button
@click=" title = 'Hello World! '; $nextTick(() => { console.log($el.innerText) }); "
x-text="title"
></button>
</div>
Copy the code
Two methods
Alpine.data()
Alpine.data() provides a solution for reusing X-data in your application
<div x-data="dropdown">
<button @click="toggle">.</button>
<div x-show="open">.</div>
</div>
<script>
document.addEventListener('alpine:init'.() = > {
Alpine.data('dropdown'.() = > ({
open: false.toggle() {
this.open = !this.open
}
}))
})
</script>
Copy the code
We refer attributes defined directly in X-data to the Alpine component object.
Register from the package
If you choose to build your Alpine code, you can follow these steps to register the component:
import Alpine from `alpinejs`
import dropdown from './dropdown.js'
Alpine.data('dropdown', dropdown)
Alpine.start()
Copy the code
// dropdown.js
export default function () = > ({
open: false,
toggle() {
this.open = ! this.open
}
})
Copy the code
Use magic stats
If you access magic methods or properties in a component object, you can use this context:
Alpine.data('dropdown'.() = > ({
open: false.init() {
this.$watch('open'.() = >{... }}})))Copy the code
withx-bind
Packaging instructions
<div x-data="dropdown">
<button x-bind="trigger"></button>
<div x-bind="dialogue"></div>
</div>
Copy the code
Alpine.data('dropdown'.() = > ({
open: false.trigger: {['@click'] () {this.open = ! this.open
},
},
dialogue: {['x-show'] () {return this.open
},
},
}))
Copy the code
Alpine.store()
Alpine provides the Alpine. Store () API for managing state globally.
usescript
The label
<script>
document.addEventListener('alpine:init'.() = > {
Alpine.store('darkMode', {
on: false.toggle() {
this.on = ! this.on
}
})
})
</script>
Copy the code
Using package Management
import Alpine from 'alpinejs'
Alpine.store('darkMode', {
on: false.toggle() {
this.on = !this.on
}
})
Alpine.start()
Copy the code
Access to stores
Alpine provides $store to get global objects:
<div x-data :class="$store.darkMode.on && 'bg-black'">.</div>
<button x-data @click="$store.darkMode.toggle()">Toggle Dark Mode</button>
Copy the code
The advanced
responsive
Alpine is “responsive,” in the sense that when you change a piece of data, everything that depends on that data automatically “reacts.” Every bit of reaction that happens in Alpine is because Alpine has two very important reaction functions at its core: alpine.reactive () and alpine.effect ().
Alpine.reactive()
Let’s start with Alpin.reactive (). This function takes a JavaScript object as its argument and returns a “response” version of that object. Such as:
let data = { count: 1 }
let reactiveData = Alpine.reactive(data)
Copy the code
Alpine.effect()
Alpine. Effect accepts a single callback function. Once Alpine. Effect is called, it runs the provided function but looks for any interaction with the response data. If it detects an interaction (a GET or set from the agent mentioned earlier), it will keep track of it and ensure that the callback is re-run in the future if the data changes. Such as:
let data = Alpine.reactive({ count: 1 })
Alpine.effect(() = > {
console.log(data.count)
})
Copy the code
inheritance
Custom instruction
Alpine lets you register your own custom directives using the Alpine. Directive () API.
The method signature
Alpine.directive('[name]'.(el, { value, modifiers, expression }, { Alpine, effect, cleanup }) = > {})
Copy the code
parameter | explaination |
---|---|
name | The name of the directive. The name “foo” for example would be consumed as x-foo |
el | The DOM element the directive is added to |
value | If provided, the part of the directive after a colon. Ex: 'bar' in x-foo:bar |
modifiers | An array of dot-separated trailing additions to the directive. Ex: ['baz' .'lob' ] from x-foo.baz.lob |
expression | The attribute value portion of the directive. Ex: law from x-foo="law" |
Alpine | The Alpine global object |
effect | A function to create reactive effects that will auto-cleanup after this directive is removed from the DOM |
cleanup | A function you can pass bespoke callbacks to that will run when this directive is removed from the DOM |
Simple example
Here’s an example of a simple instruction we’ll create called X-upperCase:
Alpine.directive('uppercase'.el= > {
el.textContent = el.textContent.toUpperCase()
})
Copy the code
<div x-data>
<span x-uppercase>Hello World!</span>
</div>
Copy the code
Computed expression
When registering a custom directive, you may need to evaluate a user-supplied JavaScript expression:
<div x-data="{ message: 'Hello World!' }">
<div x-log="message"></div>
</div>
Copy the code
You need to retrieve the actual value of the message by evaluating it as a JavaScript expression using the X-Data range. Fortunately, Alpine exposes an evaluate() API that evaluates JavaScript expressions.
Alpine.directive('log'.(el, { expression }, { evaluate }) = > {
// expression === 'message'
console.log(
evaluate(expression)
)
})
Copy the code
Lead-in response
Building on the previous X-log example, let’s assume that we want the X-log to log the value of message and also log it if the value changes.
We can tweak the implementation of X-LO G and introduce two new apis to do this: evaluateLater() and Effect ().
Alpine.directive('log'.(el, { expression }, { evaluateLater, effect }) = > {
let getThingToLog = evaluateLater(expression)
effect(() = > {
getThingToLog(thingToLog= > {
console.log(thingToLog)
})
})
})
Copy the code
Instead of immediately evaluating the message and retrieving the results, we convert the string expression Message into an actual JavaScript function that can be run at any time. If a JavaScript expression is evaluated multiple times, it is strongly recommended to first generate a JavaScript function and use it instead of calling evaluate() directly. The reason is that the process of interpreting ordinary strings as JavaScript functions is expensive and should be avoided when unnecessary.
Cleaning Up
If for some reason you need to do something when a custom instruction is removed from an element, Alpine also provides a cleanup() method for you to use:
Alpine.directive('... '.(el, {}, { cleanup }) = > {
let handler = () = > {}
window.addEventListener('click', handler)
cleanup(() = > {
window.removeEventListener('click', handler)
})
})
Copy the code
Custom magics
Alpine allows you to register custom “magics” (properties or methods) using Alpine. Magic (). Any magic you register can be used in Alipine code prefixed with $.
The method signature
Alpine.magic('[name]'.(el, { Alpine }) = > {})
Copy the code
parameter | explaination |
---|---|
name | The name of the magic. The name “foo” for example would be consumed as $foo |
el | The DOM element the magic was triggered from |
Alpine | The Alpine global object |
Magic properties
Alpine.magic('now'.() = > {
return (new Date).toLocaleTimeString()
})
Copy the code
<span x-text="$now"></span>
Copy the code
Magic methods
Alpine.magic('clipboard'.() = > {
return subject= > navigator.clipboard.writeText(subject)
})
Copy the code
<button @click="$clipboard('hello world')">Copy "Hello World"</button>
Copy the code
Write and share plug-ins
You can get a quick start using Alpine’s official “plugin-Blueprint” package. Copy the repository and run NPM install && NPM run build to get the plug-in.
Otherwise, let’s manually create a Alpine plug-in called Foo that contains a directive (x-foo) and a magic attribute ($Foo).
Script Include
<html>
<script src="/js/foo.js" defer></script>
<script src="/js/alpine.js" defer></script>
<div x-data x-init="$foo()">
<span x-foo="'hello world'">
</div>
</html>
Copy the code
Note that our script was introduced before Alpine. This is important, because otherwise Alpine would have been initialized when our plug-in loaded.
// /js/foo.js
document.addEventListener('alpine:init'.() = > {
window.Alpine.directive('foo',...).window.Alpine.magic('foo', ...)
})
Copy the code
Bundle module
import Alpine from 'alpinejs'
import foo from 'foo'
Alpine.plugin(foo)
window.Alpine = Alpine
window.Alpine.start()
Copy the code
You’ll notice a new API: Alpine.plugin(). This is a handy method that Alpine exposes.
// foo
export default function (Alpine) {
Alpine.directive('foo', ...)
Alpine.magic('foo', ...)
}
Copy the code
Async
Alpine is built where most normal functions support asynchronous functions.
For example, suppose you have a simple function called getLabel() that is used as input to an X-text instruction:
function getLabel() {
return 'Hello World! '
}
Copy the code
<span x-text="getLabel()"></span>
Copy the code
Because getLabel is synchronized, everything works as expected. Now suppose that getLabel makes a network request to retrieve the label, but cannot return one immediately (asynchronously). By making getLabel an asynchronous function, you can call it from Alpine using JavaScript’s await syntax.
async function getLabel() {
let response = await fetch('/api/label')
return await response.text()
}
Copy the code
<span x-text="await getLabel()"></span>
Copy the code
Also, if you prefer to call methods in Alpine without trailing parentheses, you can omit them, and Alpine will detect that the provided function is asynchronous and handle it accordingly. Such as:
<span x-text="getLabel"></span>
Copy the code
CSP (Content-Security Policy)
To enable Alpine to execute pure strings from HTML attributes as JavaScript expressions, such as X-ON :click=”console.log()”, it relies on content security policies that violate the “unsafe Eval “policy.
Alpine doesn’t actually use eval() itself because it’s slow and problematic. Instead, it uses better function declarations, but still violates “broadening Eval”.
To accommodate the need for this CSP environment, Alpine offers an alternative build that does not violate “unsafe-eval”, but has a stricter syntax.
The installation
The Script tag
<html>
<script src="alpinejs/alpinejs-csp/cdn.js" defer></script>
</html>
Copy the code
Module import
import Alpine from '@alpinejs/csp'
window.Alpine = Alpine
window.Alpine.start()
Copy the code
limit
Because Alpine can no longer interpret strings as plain JavaScript, it must parse and construct JavaScript functions manually. Because of this limitation, you must use Alpine. Data to register the X-data object, and you must reference the properties and methods in it only by key.
For example, an inline component like this will not work.
<! -- Bad -->
<div x-data="{ count: 1 }">
<button @click="count++">Increment</button>
<span x-text="count"></span>
</div>
Copy the code
However, if the expression is decomposed into an external API, the following is valid for CSP builds:
<! -- Good -->
<div x-data="counter">
<button @click="increment">Increment</button>
<span x-text="count"></span>
</div>
Copy the code
Alpine.data('counter'.() = > ({
count: 1.increment() {
this.count++
}
}))
Copy the code
Make a new mimicry calculator
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
<div id="app" x-data="data" x-cloak>
<div class="calculator">
<div class="result" style="grid-area: result" x-text="equation"></div>
<button style="grid-area: ac" @click="clear">AC</button>
<button style="grid-area: plus-minus" @click="calculateToggle">Plus or minus</button>
<button style="grid-area: percent" @click="calculatePercentage">%</button>
<button style="grid-area: add" @click="append('+')">+</button>
<button style="grid-area: subtract" @click="append('-')">-</button>
<button style="grid-area: multiply" @click="The append (' * ')">x</button>
<button style="grid-area: divide" @click="The append (' present ')">present</button>
<button style="grid-area: equal" @click="calculate">=</button>
<button style="grid-area: number-1" @click="append(1)">1</button>
<button style="grid-area: number-2" @click="append(2)">2</button>
<button style="grid-area: number-3" @click="append(3)">3</button>
<button style="grid-area: number-4" @click="append(4)">4</button>
<button style="grid-area: number-5" @click="append(5)">5</button>
<button style="grid-area: number-6" @click="append(6)">6</button>
<button style="grid-area: number-7" @click="append(7)">7</button>
<button style="grid-area: number-8" @click="append(8)">8</button>
<button style="grid-area: number-9" @click="append(9)">9</button>
<button style="grid-area: number-0" @click="append(0)">0</button>
<button style="grid-area: dot" @click="append('.')">.</button>
</div>
</div>
Copy the code
[x-cloak] {
display: none ! important;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #eee;
}
.calculator{-button-width: 80px;
--button-height: 80px;
display: grid;
grid-template-areas:
'result result result result'
'ac plus-minus percent divide'
'number-7 number-8 number-9 multiply'
'number-4 number-5 number-6 subtract'
'number-1 number-2 number-3 add'
'number-0 number-0 dot equal';
grid-template-columns: repeat(4.var(--button-width));
grid-template-rows: repeat(6.var(--button-height));
box-shadow: -8px -8px 16px -10px rgba(255.255.255.1),
8px 8px 16px -10px rgba(0.0.0.0.15);
padding: 24px;
border-radius: 20px;
}
.calculator button {
margin: 8px;
padding: 0;
border: 0;
display: block;
outline: none;
border-radius: calc(var(--button-height) / 2);
font-size: 24px;
font-family: Helvetica;
font-weight: normal;
color: # 999;
background: linear-gradient(
135deg.rgba(230.230.230.1) 0%.rgba(246.246.246.1) 100%
);
box-shadow: -4px -4px 10px -8px rgba(255.255.255.1),
4px 4px 10px -8px rgba(0.0.0.0.3);
}
.calculator button:active {
box-shadow: -4px -4px 10px -8px rgba(255.255.255.1) inset,
4px 4px 10px -8px rgba(0.0.0.0.3) inset;
}
.result {
text-align: right;
line-height: var(--button-height);
font-size: 48px;
font-family: Helvetica;
padding: 0 20px;
color: # 666;
}
Copy the code
document.addEventListener('alpine:init'.() = > {
Alpine.data('data'.() = > ({
equation: '0'.isDecimalAdded: false.isOperatorAdded: false.isStarted: false.isOperator(character) {
return ['+'.The '-'.The '*'.'present'].indexOf(character) > -1
},
append(character) {
if (this.equation === '0'&&!this.isOperator(character)) {
if (character === '. ') {
this.equation += ' ' + character
this.isDecimalAdded = true
} else {
this.equation = ' ' + character
}
this.isStarted = true
return
}
if (!this.isOperator(character)) {
if (character === '. ' && this.isDecimalAdded) {
return
}
if (character === '. ') {
this.isDecimalAdded = true
this.isOperatorAdded = true
} else {
this.isOperatorAdded = false
}
this.equation += ' ' + character
}
if (this.isOperator(character) && !this.isOperatorAdded) {
this.equation += ' ' + character
this.isDecimalAdded = false
this.isOperatorAdded = true}},calculate() {
let result = this.equation
.replace(new RegExp(The '*'.'g'), The '*')
.replace(new RegExp('present'.'g'), '/')
this.equation = parseFloat(eval(result).toFixed(9)).toString()
this.isDecimalAdded = false
this.isOperatorAdded = false
},
calculateToggle() {
if (this.isOperatorAdded || !this.isStarted) {
return
}
this.equation = this.equation + '* 1'
this.calculate()
},
calculatePercentage() {
if (this.isOperatorAdded || !this.isStarted) {
return
}
this.equation = this.equation + '* 0.01'
this.calculate()
},
clear() {
this.equation = '0'
this.isDecimalAdded = false
this.isOperatorAdded = false
this.isStarted = false}}})))Copy the code
New mimicry learning from CodingStartup
Write in the last
Source code sharing, please look forward to.