Back to "Whistle-stop -kiertue HTMX-laajennuksista ja HTMX:n käytöstä ASP.NET Corella"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET Core HTMX Javascript

Whistle-stop -kiertue HTMX-laajennuksista ja HTMX:n käytöstä ASP.NET Corella

Friday, 02 May 2025

Johdanto

HTMX on tehokas JavaScript-kirjasto, jonka avulla voit luoda dynaamisia verkkosovelluksia minimaalisella JavaScriptilla. Sen avulla voit tehdä AJAX-pyyntöjä, vaihtaa HTML-sisältöä ja käsitellä tapahtumia suoraan HTML-ominaisuuksissasi. Olen käyttänyt HTMX:ää noin kaksi vuotta tässä vaiheessa, ja jokaisen projektin myötä opin yhä enemmän sen kyvyistä, ja mikä tärkeintä, kyse on rajoituksista.

En kuitenkaan edelleenkään väitä, että minulla olisi asiantuntevaa tietoa siitä. Halusin vain kertoa asioista, joita olen oppinut matkan varrella.

Tapahtumat

flowchart LR subgraph Request Preparation A[htmx:configRequest] --> B[htmx:confirm] --> C[htmx:prompt] --> D[htmx:abort] end subgraph Request Lifecycle D --> E[htmx:beforeRequest] --> F[htmx:request] --> G[htmx:afterRequest] end subgraph Response Handling G --> H[htmx:beforeOnLoad] --> I[htmx:onLoad] I --> J[htmx:beforeSwap] --> K[htmx:swap] --> L[htmx:afterSwap] L --> M[htmx:afterSettle] --> N[htmx:afterOnLoad] end

Kuten näet, HTMX tarjoaa useita tapahtumia, joihin voit kytkeytyä muuttaaksesi pyyntöä tai vastausta. Käyttämällä jokaista näistä voit muokata HTMX:n vuorovaikutusta palvelimen / asiakkaan kanssa melko kattavasti.

Laajennukset

Yksi HTMX:n tehokkaimmista puolista on kyky luo laajennuksia laajentaakseen valmiuksiaan. Omassa tapauksessani yleensä koukkaan htmx:configRequest lisäparametrien lisääminen pyyntöön. Tämä on hyödyllistä, kun haluat siirtää lisätietoja palvelimelle tarvitsematta muuttaa HTML- tai JavaScript-koodia.

Muut laajennukset saattavat koukuttaa htmx:beforeRequest muuttaa pyyntöä ennen sen lähettämistä, mutta jälkeen eniten muita laajennuksia, jotka koukuttavat configRequest; kuten beforeRequest jutut kuten HX-Vals sekä HX-IncludeS on jo kiinni reuestissa (joko hyötykuormassa \ kyselystringissä). Voit jopa koukuttaa htmx:afterSwap Suorittaa toimintoja sen jälkeen, kun sisältö on vaihdettu. Yhdistettynä asiakaspuolen tempauskirjastoihin kuten Alppi.js tai Lit voit luoda voimakkaita dynaamisia sovelluksia, joissa on minimaalinen koodi.

HTMX tarjoaa joitakin sisäänrakennettuja laajennuksia, kuten hx-boost sekä hx-swap-oob joiden avulla voit parantaa HTMX:n toiminnallisuutta kirjoittamatta mitään mukautettua koodia. Joskus on kuitenkin tehtävä omat laajennukset vastaamaan tiettyjä vaatimuksia.

Voit esimerkiksi haluta lisätä omia otsikoita pyyntöihisi, muuttaa pyynnön hyötykuormaa tai käsitellä tiettyjä tapahtumia ainutlaatuisella tavalla.

Tämän HTMX:n avulla saat kätevät integraatiopisteet:


{
  /**
   * init(api)
   * Called once when the extension is initialized.
   * Use it to set up internal state, store references, or access HTMX utility functions via the api parameter.
   */
  init: function(api) {
    return null;
  },

  /**
   * getSelectors()
   * Returns additional CSS selectors that HTMX should monitor.
   * Useful if your extension needs to handle custom elements or dynamic behavior.
   */
  getSelectors: function() {
    return null;
  },

  /**
   * onEvent(name, evt)
   * Called on every HTMX event (e.g., htmx:beforeRequest, htmx:afterSwap).
   * Return false to cancel the event or stop propagation.
   */
  onEvent: function(name, evt) {
    return true;
  },

  /**
   * transformResponse(text, xhr, elt)
   * Modify the raw response text before it is parsed and swapped into the DOM.
   * Use this to sanitize or preprocess HTML.
   */
  transformResponse: function(text, xhr, elt) {
    return text;
  },

  /**
   * isInlineSwap(swapStyle)
   * Return true if your extension will handle this swap style manually.
   * This tells HTMX to skip default behavior.
   */
  isInlineSwap: function(swapStyle) {
    return false;
  },

  /**
   * handleSwap(swapStyle, target, fragment, settleInfo)
   * Perform custom DOM manipulation if you implement a custom swap style.
   * Return true to prevent HTMX's default swap.
   */
  handleSwap: function(swapStyle, target, fragment, settleInfo) {
    return false;
  },

  /**
   * encodeParameters(xhr, parameters, elt)
   * Modify or serialize request parameters before sending.
   * Return null to use default URL/form encoding.
   * Return a string to override with a custom payload (e.g., JSON).
   */
  encodeParameters: function(xhr, parameters, elt) {
    return null;
  }
}

HTMX tarjoaa useita valmiiksi rakennettuja laajennuksia, joita voit Lue aiheesta täältä.

Esimerkiksi kätevä sisäänrakennettu laajennus json-encode Voit lähettää JSON-tietoja pyyntöelimessä URL-koodattujen tietojen sijaan. Tämä on hyödyllistä, kun haluat lähettää monimutkaisia datarakenteita tai -sarjoja palvelimelle. Huomaat, että tämä liittyy kolmeen tapahtumaan

  • init - laajennuksen perustamiseen ja HTMX-rajapintaa koskevan viittauksen tallentamiseen
  • onEvent - asettaa Content-Type Otsikko application/json kun pyyntöä konfiguroidaan
  • encodeParameters - ohittaaksesi URL-koodatun oletusmuodon koodaamalla ja sarjaamalla muuttujat JSONiksi. Se palauttaa myös merkkijonon, joka estää HTMX:ää käyttämästä URL-koodattua oletusmuotoa.
(function() {
  let api
  htmx.defineExtension('json-enc', {
    init: function(apiRef) {
      api = apiRef
    },

    onEvent: function(name, evt) {
      if (name === 'htmx:configRequest') {
        evt.detail.headers['Content-Type'] = 'application/json'
      }
    },

    encodeParameters: function(xhr, parameters, elt) {
      xhr.overrideMimeType('text/json')

      const object = {}
      parameters.forEach(function(value, key) {
        if (Object.hasOwn(object, key)) {
          if (!Array.isArray(object[key])) {
            object[key] = [object[key]]
          }
          object[key].push(value)
        } else {
          object[key] = value
        }
      })

      const vals = api.getExpressionVars(elt)
      Object.keys(object).forEach(function(key) {
        // FormData encodes values as strings, restore hx-vals/hx-vars with their initial types
        object[key] = Object.hasOwn(vals, key) ? vals[key] : object[key]
      })

      return (JSON.stringify(object))
    }
  })
})()

Tai jopa yksinkertaisempi, mutta vielä enemmän käsialaa hx-debug Laajennus, jolla lisätään HX-Debug Kohti pyyntöä. Tämä on hyödyllistä vianetsintä- ja kirjaustarkoituksiin, koska sen avulla voit nähdä raa'an pyynnön ja vastaustiedot dev-konsolissa.

(function() {
  htmx.defineExtension('debug', {
    onEvent: function(name, evt) {
      if (console.debug) {
        console.debug(name, evt)
      } else if (console) {
        console.log('DEBUG:', name, evt)
      } else {
        throw new Error('NO CONSOLE SUPPORTED')
      }
    }
  })
})()

Niitä on paljon enemmän, mukaan lukien erittäin voimakas asiakaspuoli tempaisee laajennusta jonka avulla voit käyttää asiakaspuolen tempauskirjastoja muuttaaksesi palautetun JSON-datan HTML:ksi. Tästä on hyötyä dynaamisen käyttöliittymän luomisessa tarvitsematta luottaa palvelimen renderointiin.

Mukautettuja laajennuksia

Dynaamiset rivitunnukset

Esimerkiksi tuoreessa projektissa käytin HTMX OMB -swapeja taulukon useiden rivien päivittämiseen. Tätä varten halusin tietää, mitkä rivit ovat tällä hetkellä esillä pöydässä, joten päivitin vain näkyvät rivit.

Laajennus

export default {
    encodeParameters: function (xhr, parameters, elt) {
        const ext = elt.getAttribute('hx-ext') || '';
        if (!ext.split(',').map(e => e.trim()).includes('dynamic-rowids')) {
            return null; // Use default behavior
        }

        const id = elt.dataset.id;
        const approve = elt.dataset.approve === 'true';
        const minimal = elt.dataset.minimal === 'true';
        const single = elt.dataset.single === 'true';

        const target = elt.dataset.target;
        const payload = { id, approve, minimal, single };

        if (approve && target) {
            const table = document.querySelector(target);
            if (table) {
                const rowIds = Array.from(table.querySelectorAll('tr[id^="row-"]'))
                    .map(row => row.id.replace('row-', ''));
                payload.rowIds = rowIds;
            }
        }

        // Merge payload into the parameters object
        Object.assign(parameters, payload);
        return null; // Return null to continue with default URL-encoded form encoding
    }
}

Sen käyttö

Käyttääksemme sitä meidän täytyy lisätä laajennus HTMX-kokoonpanoomme. Joten syöttökohdassa js-tiedostossa (olettaen, että käytät moduuleja; sinun pitäisi olla) voit tehdä jotain tällaista:

import dynamicRowIds from "./dynamicRowIds"; // Load the file

htmx.defineExtension("dynamic-rowids", dynamicRowIds); // Register the extension

Sitten mihin tahansa elementtiin haluat käyttää sitä voit lisätä hx-ext Ominaisuus arvolla dynamic-rowids.

                <button
                    hx-ext="dynamic-rowids"
                    data-target="#my-table"
                    data-id="@Model.Id"
                    data-param1="true"
                    data-param2="false"
                    data-param3="@Model.Whatever"
                    hx-post
                    hx-controller="Contoller"
                    hx-action="Action"
                >
                    <i class='bx bx-check text-xl text-white'></i>
                </button>

Säilytä paramit

Tämä on toinen yksinkertainen HTMX-laajennus, tällä kertaa kytkettynä htmx:configRequest Kun muokkaamme URL-osoitetta ennen pyynnön lähettämistä. Tämä laajennus on kätevä, jos käytät querystring-pohjaista suodatusta jne. Ja haluat joidenkin pyyntöjen säilyttävän olemassa olevat suodattimet, kun taas toiset eivät (esim. "nimi" ja "aloituspäivä", mutta eivät "sivu" tai "sort".

Tämä on SIMILAR, mutta ei aivan sama kuin nykyinen HTMX-laajennus push-paramit

Laajennus

Huomaat, että me koukkaamme onEvent Kuunnellaan htmx:configRequest tapahtuma.

Sitten me:

  • Hanki elementti, joka laukaisi tapahtuman
  • Hae se. preserve-params-exclude Määritä elementti (jos se on olemassa) ja jaa se erinäisiin avaimiin suljettavaksi (joten emme lisää niitä pyyntöön)
  • Hae nykyiset URL-parametrit ikkunan sijainnista
  • Hae uudet parametrit pyynnöstä URL-osoitteesta
  • Katkaise nykyiset parametrit ja tarkista, ovatko ne poissulkemisluettelossa eivätkä jo uusissa parametreissa
  • Jos ne eivät ole, lisäämme ne uusiin muuttujiin
  • Lopuksi asetamme uudet parametrit pyyntöön URL-osoitteeseen ja palaamme todenmukaisesti, jotta voimme jatkaa pyyntöä.
export default {
    onEvent: function (name, evt) {
        if (name !== 'htmx:configRequest') return;
        const el = evt.detail.elt;
        const excludeStr = el.getAttribute('preserve-params-exclude') || '';
        const exclude = excludeStr.split(',').map(s => s.trim());

        const currentParams = new URLSearchParams(window.location.search);
        const newParams = new URLSearchParams(evt.detail.path.split('?')[1] || '');

        currentParams.forEach((value, key) => {
            if (!exclude.includes(key) && !newParams.has(key)) {
                newParams.set(key, value);
            }
        });

        evt.detail.path = evt.detail.path.split('?')[0] + '?' + newParams.toString();

        return true;
    }
};

Tässä käytän olennaista HTMX.Net sen lapuille auttajille. Kaltaiset hx-controller sekä hx-action ovat tunnisteiden auttajia, jotka luovat sinulle oikeat HTMX-ominaisuudet. Kuten myös hx-route-<x> jotta arvot kulkevat reitillä. Tämä on todella hyödyllistä, koska sen avulla voit käyttää C#-koodia luodaksesi oikeat arvot attribuuteille sen sijaan, että joutuisit koodaamaan ne HTML:ssäsi.

Sen käyttö

Laajennuksena sitä on todella helppo käyttää:

Ensin meidän on lisättävä laajennus HTMX-kokoonpanoomme.


import preserveParams from './preserveParams.js';
htmx.defineExtension('preserve-params', preserveParams);

HUOMAUTUS: Huomaat, että HTMX-laajennukset lataavat laajennuksen automaattisesti.

// Autoloading the extension and registering it
(function() {
  htmx.defineExtension('debug', {
}

Tämä on hyvä tapa tehdä se, jos käytät HTMX:ää muussa kuin moduuliympäristössä. Jos kuitenkin käytät moduuleja (mikä sinun pitäisi olla) on parempi käyttää import Ilmoitus laajennuksen lataamisesta ja sen jälkeen nimenomaisesti rekisteröi se omaasi vastaan htmx inspiraatiota. Näin voit hyödyntää puiden ravistelua ja ladata vain tarvitsemasi laajennukset.

Sitten oman elementin voit lisätä hx-ext Ominaisuus arvolla preserve-params ja preserve-params-exclude Attribuutti, jossa on pilkkueroteltu luettelo muuttujista, jotka voidaan jättää pyynnön ulkopuolelle.


<a class="btn-outline-icon"
   hx-controller="MyController"
   hx-action="MyAction"
   hx-route-myparam="@MyParam"
   hx-push-url="true"
   hx-ext="preserve-params"
   preserve-params-exclude="page,sort"
   hx-target="#page-content"
   hx-swap="innerHTML show:top"
   hx-indicator>
    <i class="bx bx-search"></i>
</a>

Tässä tapauksessa syynä on event.detail.path uuden saaminen myparam Arvolla se korvaa sen uudella arvollamme, mutta säilyttää kaikki muut (paitsi page sekä sort). Voimme siis edelleen siirtää URL-osoitteeseen asettamamme suodattimet palvelimelle ilman, että meidän tarvitsee olla huolissamme niiden katoamisesta, kun teemme uuden pyynnön.

ASP.NET-ydin

Yksi HTMX:n hienoimmista puolista on se, että sen vuorovaikutus palvelimen kanssa tapahtuu HTTP-otsikoiden kautta. Nämä otsikot tarjoavat palvelimelle runsaan kontekstin siitä, mikä laukaisi pyynnön, jolloin voit vastata sopivasti ASP.NET-päätepisteistäsi tai Razor-näkymistäsi.

Jälleen yksi keskeinen tekijä tässä on HTMX.Net...................................................................................................................................... Monet sen tarjoamat tuotteet ovat näppäriä Request laajennukset HTMX-pyyntöjen havaitsemiseksi. Tämä on hyödyllistä sen määrittämiseksi, onko pyynnön tehnyt HTMX vai ei, ja sen mukaan.

Sillä on myös oma mekanisminsa laukaisimien lähettämiseksi


Response.Htmx(h => {
    h.WithTrigger("yes")
     .WithTrigger("cool", timing: HtmxTriggerTiming.AfterSettle)
     .WithTrigger("neat", new { valueForFrontEnd= 42, status= "Done!" }, timing: HtmxTriggerTiming.AfterSwap);
});

Työnnä Urls ym.... jne. Khalid teki loistavaa työtä luodessaan laajennuksia, joiden avulla HTMX:n kanssa on helppo työskennellä ASP.NET Coressa.

Se on avaintyökalu työkalupakissani, kun työskentelen HTMX:n ja ASP.NET Coren kanssa. Katso tätä!

Yleiset HTMX-pyyntöotsikot

Tässä HTMX:n kannattavimpien otsikoiden erittely jokaisen pyynnön yhteydessä:

Otsikko Kuvaus Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko Otsikko |----------------------------|------------------------------------------------------------------------------------------------------------------| HX-Pyynnöt aina HTMX-pyyntöön. Hyvä havaitsemaan HTMX-puheluita keskiohjelmistoissa tai ohjaimissa. ......................................................................... HX-Target DOM:n kohdeelementin tunniste, johon vastaus vaihdetaan. ......................................................................... HX-Trigger Pyynnön aiheuttaneen tekijän henkilöllisyys (esim. nappi). ......................................................................... HX-Trigger-Nimi Laukaisimen nimi (täytteenä lomakkeet). ......................................................................... HX-Prompt sisältää käyttäjäsyötteitä HX-promptista. ......................................................................... HX-Current-URL-selaimen URL-osoite, kun pyyntö esitettiin. Hyödyllistä puunkorjuussa ja kontekstissa. ......................................................................... HX-History-Restory-Pyynnöksen lähettäminen on totta, jos pyyntö on osa navigoinnin jälkeistä historian restaurointia (esim. takapainike). .........................................................................

Pyydä laajennuksia

Käytän näitä aika laajasti ASP.NET Core -sovelluksissani. Yhdistettynä HTMX-verkkoihin, kuten Request.IsHtmx() , Request.IsHtmxBoosted() sekä Request.IsHtmxNonBoosted() HTMX-pyynnöt voi helposti havaita ja niihin voi vastata sen mukaan.

Minulla on esimerkiksi todella yksinkertainen laajennus pyynnölle, jonka avulla voin havaita, jos pyyntö kohdistuu pääosaani #page-content div. Jos on, niin tiedän, että minun pitäisi lähettää osittainen takaisin. HUOMAUTUS: Monet eivät ymmärrä, että "täysi sivu" voidaan määritellä osittaiseksi, sitten se vain ohittaa layoutin.

        if (Request.PageContentTarget())
        {   
            Response.PushUrl(Request);
            return PartialView("List", vm);
        }
        return View("List", vm);
        
        public static class RequestExtensions
{
        
        public static bool PageContentTarget(this HttpRequest request)
    {
        bool isPageContentTarget = request.Headers.TryGetValue("hx-target", out var pageContentHeader) 
                                   && pageContentHeader == "page-content";
        
        return isPageContentTarget;
    }
    }

Vastelaajennukset

Pyydä-laajennusten lisäksi voit myös luoda Response-laajennuksia lähettääksesi tapahtumia takaisin asiakkaalle. Tästä on hyötyä asiakassivutapahtumien käynnistämisessä.

SweetAlert-esimerkki

Esimerkiksi SweetAlert2-integraatiossani Sallin ikkunan sulkemisen palvelimen asettamalla liipaisimella.

    document.body.addEventListener('sweetalert:close', closeSweetAlertLoader);

Tämä laukeaa palvelimelta HTMX-laukaisimena.


    public static void CloseSweetAlert(this HttpResponse response)
    {
        response.Headers.Append("HX-Trigger" , JsonSerializer.Serialize(new
        {
            sweetalert = "close"
        }));

    }

Tämä laukaisee sweetalert:close Tapahtuma asiakaspuolella, jolloin voit sulkea dialogin. Voit myös siirtää dataa takaisin asiakkaalle käyttämällä HX-Trigger Otsikko. Tämä on hyödyllistä tietojen siirtämiseksi palvelimelta asiakkaalle ilman HTML- tai JavaScript-koodin muuttamista.

Kuten näet, näitä tapahtumia on helppo kuunnella vain lisäämällä tapahtuman kuuntelija kehoon. Käytän JSONia pääasiassa siten, että se koodaa aina oikein.

Näytä toast

Kirjoitin paahtomenetelmästäni aiemmin täälläMutta se kannattaa mainita tässäkin. Jotta palvelin voisi yksinkertaisesti käynnistää paahtopuheilmoituksen asiakaspuolella. Asetin liipaisimen tähän Response-laajennukseen.

    public static void ShowToast(this HttpResponse response, string message, bool success = true)
    {
        response.Headers.Append("HX-Trigger", JsonSerializer.Serialize(new
        {
            showToast = new
            {
                toast = message,
                issuccess =success
            }
        }));

    }

Sitten liityn tapahtuman asiakaspuoleen ja soitan showToast Funktio.

import { showToast, showHTMXToast } from './toast';

window.showHTMXToast = showHTMXToast;

document.body.addEventListener("showToast", showHTMXToast);

Tämä tulee sitten minun luokseni. showToast Toimi ja hyvin, osoittaa paahtoleipää; katso siitä lisää artikkelissa .



export function showHTMXToast(event) {
    const xhr = event?.detail?.xhr;
    let type = 'success';
    let message = xhr?.responseText || 'Done!';

    try {
        const data = xhr ? JSON.parse(xhr.responseText) : event.detail;

        if (data.toast) message = data.toast;
        if ('issuccess' in data) {
            type = data.issuccess === false ? 'error' : 'success';
        } else if (xhr?.status >= 400) {
            type = 'error';
        } else if (xhr?.status >= 300) {
            type = 'warning';
        }

    } catch {
        if (xhr?.status >= 400) type = 'error';
        else if (xhr?.status >= 300) type = 'warning';
    }

    showToast(message, 3000, type);
}

Johtopäätöksenä

Siinä se, pyörremyrskykiertue HTMX:stä ja ASP.NET Coresta. Toivottavasti pidit sitä hyödyllisenä ja informatiivisena. Jos sinulla on kysyttävää tai kommentteja, kommentoi alla.

logo

©2024 Scott Galloway