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/reactivityLike I said, it’s justpetite-vuePreview 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-forMust 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

above translates to 500 ms on entry and 250 ms on departure.

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

<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-bindPackaging 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.

usescriptThe 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.