Alpine ha una codebase aperta che permette di estendere la libreria attraverso le sue API. Il modo in cui queste API vengono consumate dipende dal metodo di importazione di Alpine, ed è diverso a seconda che si usi un tag script, come abbiamo proposto in questa guida, oppure un modulo NPM. La documentazione avverte che, quando si estendono le funzionalità della libreria, le API devono essere registrate dopo che Alpine è stato scaricato ed è disponibile sulla pagina, ma prima che abbia inizializzato la pagina stessa.
Rinviamo alla documentazione online per la soluzione del modulo NPM; qui ci focalizzeremo sulla soluzione che adotta il tag script.
Direttive personalizzate in Alpine
Le estensioni vanno registrate all'interno dell'event listener alpine:init
utilizzando il metodo Alpine.directive()
. Vediamo un esempio:
<script>
document.addEventListener('alpine:init', () => {
Alpine.directive('foo', ...)
})
</script>
Se si vuole spostare il codice in un file .js
esterno, questo deve essere importato prima di Alpine:
<script src="/js/foo.js" defer></script>
<script src="/js/alpine.js" defer></script>
Le direttive create con Alpine.directive()
possono essere utili quando si desidera aggiungere una logica custom che vada oltre le finalità specifiche delle direttiva native, come x-data
, x-on
o x-bind
.
Firma del metodo
La firma del metodo è la seguente:
Alpine.directive(name, (el, { value, modifiers, expression }, { Alpine, effect, cleanup }))
name
è una stringa che definisce il nome della direttiva. Ad esempio,foo
perx-foo
.- Il secondo parametro è una callback che definisce il comportamento della direttiva. Questa accetta tre parametri:
-
el
è l'elemento del DOM su cui è applicata la direttiva.{ value, modifiers, expression }
è un oggetto con tre proprietà che forniscono i dettagli sull'utilizzo della direttiva.{ Alpine, effect, cleanup }
è un oggetto di utilità con tre proprietà che permettono di interagire con il framework.
Una volta definita una direttiva, questa diventa disponibile con il prefisso x-
seguito dal nome specificato. Ogni volta che Alpine.js incontra la direttiva su un elemento durante l'inizializzazione del DOM, esegue la callback.
Copiare il contenuto di un elemento al clic su un pulsante
Nell'esempio che segue, creiamo una direttiva x-copy
che permette di copiare il contenuto di un elemento specifico al clic su un pulsante. In questo caso si tratta di elementi pre
e code
. Ecco il codice completo da copiare e incollare nel proprio editor:
<html>
<head>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.5/dist/cdn.min.js"></script>
<style>
.copied {
color: green;
font-style: italic;
margin-left: 10px;
}
pre {
color: #eee;
background: #000;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
max-width: 300px;
}
button {
margin-top: 10px;
}
</style>
<script>
document.addEventListener('alpine:init', () => {
// Registra la direttiva x-copy
Alpine.directive('copy', (el, { expression }, { cleanup }) => {
// Crea un elemento per visualizzare il feedback
const feedback = document.createElement('span');
feedback.classList.add('copied');
el.appendChild(feedback);
// Usa expression come ID per costruire il selettore, con fallback a 'pre code'
const selector = expression ? `#${expression} code` : 'pre code';
const codeElement = document.querySelector(selector);
// Funzione per copiare il testo
const copyCode = async () => {
// Se non trova l'elemento, genera un messaggio di errore
// e interrompe l'esecuzione
if (!codeElement) {
feedback.textContent = 'Codice non trovato!';
setTimeout(() => feedback.textContent = '', 2000);
return;
}
try {
// Copia il testo contenuto nell'elemento
const textToCopy = codeElement.textContent.trim();
await navigator.clipboard.writeText(textToCopy);
feedback.textContent = 'Copiato!';
setTimeout(() => {
feedback.textContent = '';
}, 2000);
} catch (err) {
console.error('Errore nella copia:', err);
feedback.textContent = 'Errore!';
setTimeout(() => {
feedback.textContent = '';
}, 2000);
}
};
// Aggiunge l'evento di clic
el.addEventListener('click', copyCode);
// Pulizia
cleanup(() => {
el.removeEventListener('click', copyCode);
});
});
});
</script>
</head>
<body>
<div x-data>
<p>Codice di esempio:</p>
<pre id="code-block"><code>function saluta() {
console.log("Ciao, mondo!");
}</code></pre>
<button x-copy="code-block">Copia il codice</button>
</div>
</body>
</html>
- Con
Alpine.directive()
definiamo la direttiva personalizzatax-copy
che assegniamo al pulsante;el
è il pulsante a cui assegniamo la direttiva. - La funzione
copyCode
verifica se esistecodeElement
(il container del testo da copiare): se non esiste, mostra un errore. Quindi estrae il testo dacodeElement.textContent.trim()
, lo copia connavigator.clipboard.writeText()
, infine mostra il testo"Copiato!"
o il messaggio di errore per 2 secondi. - Abbiamo quindi aggiunto un event listener che ascolta il clic sul pulsante e il metodo
cleanup
per rimuovere il listener. - Infine, aggiungiamo il codice HTML del componente.
Quando l'utente fa clic sul pulsante, il codice contenuto nel blocco <pre><code>
viene copiato negli appunti ed è pronto per essere incollato nel proprio editor.
Integrazione con Alpine.magic
Alpine permette anche di definire proprietà o metodi magici, ossia funzioni globali che possono essere invocate nel codice dell'applicazione col prefisso $
(es. $miaFunzione()
). Per registrare una magic property o un magic method, Alpine fornisce il metodo Alpine.magic()
.
Metodi e proprietà magici sono disponibili in tutti i componenti della pagina e sono reattivi in quanto possono interagire con lo scope di x-data
dell’elemento in cui vengono utilizzati.
Quella che segue è la firma del metodo Alpine.magic()
:
Alpine.magic('name', (el, { Alpine }) => {})
name
è il nome della proprietàel
è l'elemento del DOM da cui è attivato il metodoAlpine
è l'oggetto globaleAlpine
Utilizzo pratico di un magic method in Apine
Nell'esempio che segue, sostituiamo la direttiva x-copy
che abbiamo creato nell'esempio precedente con il magic method $copy
. Un magic method è più flessibile rispetto alla direttiva, perché permette di utilizzare la logica della funzione in diversi contesti senza doverla legare ad un unico elemento con una direttiva specifica. Modifichiamo, quindi, lo script precedente come segue:
<script>
document.addEventListener('alpine:init', () => {
// Registra il magic method 'copy'
Alpine.magic('copy', (el) => {
return async (elementId) => {
// Usa un selettore combinato per trovare direttamente l'elemento <code>
const codeElement = document.querySelector(`#${elementId} code`);
// Crea un elemento per visualizzare il feedback
const feedback = document.createElement('span');
feedback.classList.add('copied');
el.appendChild(feedback);
try {
// Se non trova l'elemento, genera un messaggio di errore
// e interrompe l'esecuzione
if (!codeElement) {
feedback.textContent = 'Codice non trovato!';
setTimeout(() => feedback.textContent = '', 2000);
return;
}
// Copia il testo contenuto nell'elemento
const textToCopy = codeElement.textContent.trim();
await navigator.clipboard.writeText(textToCopy);
feedback.textContent = 'Copiato!';
setTimeout(() => feedback.textContent = '', 2000);
} catch (err) {
// In caso di errore, genera un messaggio di errore generico
console.error('Errore nella copia:', err);
feedback.textContent = 'Errore!';
setTimeout(() => feedback.textContent = '', 2000);
}
};
});
});
</script>
Quindi modifichiamo il markup del componente:
<div x-data>
<p>Codice di esempio:</p>
<pre id="code-block"><code>function saluta() {
console.log("Ciao, mondo!");
}</code></pre>
<button @click="$copy('code-block')">Copia il codice</button>
</div>
In questi esempi abbiamo utilizzato l'ID #code-block
per costruire il selettore dell'elemento. In questo modo si perde flessibilità nella selezione dell'elemento. Ma si guadagna in precisione. Data la semplicità di questo esempio, potremmo utilizzare anche il più generico selettore 'pre code'
.
Per un'analisi più dettagliata delle API disponibili, si rinvia alla documentazione online.