This repo ships two libraries that implement the same ahm protocol — pick whichever fits your page:
| library | file | dependency | size (min) |
|---|---|---|---|
| jquery-ahm | jquery-ahm.js |
jQuery 1.5+ | ~2 KB |
| vanilla-ahm | vanilla-ahm.js |
none | ~5.5 KB |
Both expose the same response protocol and the same set of features (ahm, ahm_exec, ahm_form, ahm_form_modal, ahm_load, auto-binding of a.ahm / form.ahm / span.ahm[data-url] / etc.). The only differences are the namespace ($.* vs AHM.*) and a handful of implementation-specific behaviors documented further down.
ahm (ajax html modification) reduces ajax client code by letting the server response itself describe the callbacks. One request → server returns a JSON map of {selector/action: payload} pairs → AHM applies all of them in one pass. The server stays the source of truth for both layout and validation; the client is a thin executor.
- Super light-weight
- Drop-in replacement for
$.ajax()/fetch() - jQuery version works with all jQuery methods + plugins + custom callbacks
- No repetitive javascript code for every ajax request
- Load HTML & Javascript on demand (
ahm_load)
jQuery 1.5+ (jQuery version) / no dependencies (vanilla version)
Use AHM for small server-driven UI updates:
- replace HTML fragments
- update input values
- call simple global page callbacks
- submit forms without a full page reload
- popup forms whose HTML & Javascript is lazy-loaded on demand
- server-side form validation (server re-paints all error divs in one response)
Avoid AHM for high-frequency interactions (slider drags, live typing). Each AHM call is a server round-trip — fine for submits and clicks, wrong tool for oninput.
The server returns an object where each key describes one action:
{
"#message/html": "Saved",
"#email/val": "user@example.com",
"/App.reload": {"force": true},
"/alert": "Hello"
}Each key is split by /:
- left side: CSS selector, or empty string for a global callback
- right side: action / callback name
- default action:
html
| action | jQuery version | vanilla version |
|---|---|---|
html |
jQuery's .html() (does not re-execute injected <script> tags) |
AHM.innerHTML(el, html) — replaces innerHTML AND re-creates <script> nodes so injected scripts execute |
val |
jQuery's .val() |
sets element.value |
| any other | tries jQuery method/plugin; if none, assigns as a property | silent no-op (the switch only handles html and val) |
Global callbacks (empty selector):
"/alert": "text"→window.alert("text")"/Namespace.method": params→window.Namespace.method(params)
If params is an array, AHM calls the callback with apply().
Dot notation supports only one level:
Foo.Bar.bazresolves towindow.Foo['Bar']— the rest is dropped.
Empty popup shell on the page; click a trigger, AHM fetches the form HTML, server returns it as one html action:
<a class="ahm" href="/contact-form?listing=123">Contact</a>
<div id="popup"></div>// server response when the link is clicked
{
"#popup/html": "<form class=\"ahm\" action=\"/contact-submit\" method=\"post\">…</form>"
}The fetched form is itself class="ahm", so submit is intercepted automatically — same response protocol applies.
On invalid input the server returns one response that paints all field errors at once + keeps the popup open:
{
"#email-error/html": "Invalid email",
"#phone-error/html": "",
"#popup-status/val": "draft"
}On valid input the same response protocol does the success flow — close popup, update page, show toast — no separate success code path on the client:
{
"#popup/html": "",
"#listing-counter/html": "27 inquiries",
"/Toast.show": "Message sent"
}$.ahm_load('/js/dist/heavy-feature.js'); // jQuery: synchronous, deduped by URL
AHM.ahm_load('/js/dist/heavy-feature.js', () => HeavyFeature.init()); // vanilla: async <script> append, optional onloadFiles: jquery-ahm.js, jquery-ahm.min.js
Version: 1.6.0
$.ahm(url, options) // $.ajax replacement; runs response through $.ahm_exec
$.ahm_exec(response) // execute a response object directly
$.ahm_form(form, options) // submit a form via FormData/XHR, run response
$.ahm_form_modal(form, options) // same, intended for modal forms
$.ahm_load(url) // load a script once per URL (deduped)
$.ahm_loaded // array of already-loaded URLsTypical calls:
$.ahm('/url');
$.ahm('/url', {data: {a: 1}});
$.ahm('/url', {type: 'POST', data: {a: 1}, context: this});
$.ahm_exec({'#message/html': 'Saved'});
$.ahm_form(document.getElementById('form-id'));| element | behavior |
|---|---|
a.ahm |
href copied to ahm-href, href set to javascript:void(0); click → $.ahm(href, {context: this}) |
span.ahm[data-url] |
request sent on page ready; data-delay delays it |
span.ahm (any) |
click → $.ahm(href, {context: this}) |
div.ahm[data-url] |
request sent on page ready |
form.ahm |
submit → $.ahm_form(this) |
form.ahm_modal |
submit → $.ahm_form_modal(this) |
Uses raw XMLHttpRequest (not $.ajax):
- serializes with
FormData(form[0]) - sets
X-Requested-With: XMLHttpRequest - disables
:submit; re-enables it after ~500 ms - on HTTP 200 → runs
$.ahm_exec(response) - non-200 is silently ignored (no
elsebranch)
$.ahm() delegates to $.ajax(settings). dataType defaults to 'json'. GET/POST encoding follows jQuery's $.param().
options.contextReferenceError. Inside the innerexec()of$.ahm_exec,optionsis not in scope, yetoptions.contextis referenced whenever a value or selector equals the literal string"this". Triggering that path throws.- Unknown action on a
$(selector)namespace falls through tonamespace[callback] = params— assigns onto the jQuery object instead of doing anything visible. alert('ahm: undefined callback=...')is shown to end users when an empty-selector callback can't be resolved.$.ahm_execevals strings starting with"function"as JS (yui-compressor hack).htmlaction does not re-execute injected<script>tags — that's a vanilla-only feature viaAHM.innerHTML. Push scripts via$.ahm_loadinstead.
Files: vanilla-ahm.js, vanilla-ahm.min.js
A jQuery-free implementation of the same protocol. Drop in directly:
<script src="vanilla-ahm.min.js"></script>Exposes a global AHM object.
AHM.ahm(url, options) // raw-XHR request; runs response through AHM.ahm_exec
AHM.ahm_exec(response)
AHM.exec(selector, callback, params) // single action
AHM.ahm_form(form, options) // submit a form
AHM.ahm_form_modal(form, options)
AHM.ahm_load(url, onload) // dedupes by URL; appends <script>, optional onload
AHM.ahm_loaded // array of already-loaded URLs
// plain text XHR helpers (do NOT use the AHM protocol)
AHM.get(url, callback)
AHM.post(url, callback)
AHM.load(url, element) // → element.innerHTML
// utilities
AHM.serialize_form_data(form) // GET-style form serialization (skips disabled/file/submit/button/reset)
AHM.appendArray(formData, values, name)// PHP-style nested FormData keys
AHM.innerHTML(element, html) // replaces innerHTML and re-creates <script> nodesSame selectors as jQuery (a.ahm, span.ahm[data-url], form.ahm, form.ahm_modal). Click dispatch is one document-level listener.
Additional vanilla behaviors:
- Every
form.ahm/form.ahm_modalpresent at DOMContentLoaded has its.submitmethod overridden to callAHM.ahm_form(this). Dynamically-injected forms don't get this override (the submit event still works through the document-level delegate). a.ahmwith bothahm-hrefANDhrefalready set logsconsole.error("double attribute ahm-href and href").
- serializes with
FormData(form) - submit element:
form.querySelector(".form-submitter") || form.querySelector("[type='submit']")—.form-submitterwins if present - sets
data-submit-status="ahm"on submit; only flips to"done"after the 500 ms re-enable timer - Duplicate-submit guard: if
data-submit-statusis set and not"done", submit returns early after loggingUZBAGOYSYA!to the console - Submit button supports
data-loading-text: itsvalueis swapped to that text during submission and restored on completion - GET method: appends
AHM.serialize_form_data(form)to the URL - HTTP 500 path is an empty branch — server errors are silently swallowed
- GET: appends
options.datato query string viaencodeURIComponent(flat — no nested support) - POST: sends
FormData; nested objects encoded with PHP-style keys viaAHM.appendArray() - Sets
X-Requested-With: XMLHttpRequest options.dataTypedefaults to'json'and is set asXMLHttpRequest.responseTypeoptions.error(xhr)fires from insideonreadystatechange's else branch — may fire multiple times per request (once per non-200 readyState transition)
AHM = {...}is declared withoutvar/let/const.- The action switch on DOM elements only handles
htmlandval. Any other action against a selector is a silent no-op. - GET URL-concat bug: when both the URL has an existing query string AND
options.datais provided, the implementation doesnew URLSearchParams(url)on the whole URL (not the query portion). For a base URL with a single?key=valthis misclassifies and produces a second?, yielding/foo?a=1?b=2. Avoid GET withoptions.datawhen the URL already has a query string. - The
data-submit-statusguard is sticky: a form that never transitions to"done"stays permanently un-submittable.
options.contextReferenceError when value or selector equals"this"—optionsis out of scope inside the innerexec().- Dot-notation single level —
Foo.Bar.bazonly resolves towindow.Foo['Bar']. alert('ahm: undefined callback=...')when an empty-selector callback can't be resolved.- Strings starting with
"function"in params areeval'd as JS.
MIT