@digicreon/mujs 1.0.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +177 -9
- package/dist/mu.min.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,9 +9,12 @@ No build step required. No dependencies. No framework. Just a single `<script>`
|
|
|
9
9
|
Inspired by [pjax](https://github.com/defunkt/jquery-pjax), [Turbo](https://turbo.hotwired.dev/) and [HTMX](https://htmx.org/), µJS aims to be simpler and lighter while covering the most common use cases.
|
|
10
10
|
|
|
11
11
|
- 🚀 **Fast** — Prefetch on hover, no full page reload, progress bar
|
|
12
|
-
- 🪶 **Lightweight** — Single file, ~
|
|
12
|
+
- 🪶 **Lightweight** — Single file, ~10 KB gzipped, zero dependencies
|
|
13
13
|
- 🔌 **Drop-in** — Works with any backend (PHP, Python, Ruby, Go…), no server-side changes needed
|
|
14
14
|
- 🧩 **Patch mode** — Update multiple page fragments in a single request
|
|
15
|
+
- 🎯 **Triggers** — Any element, any event: live search, polling, focus actions
|
|
16
|
+
- 🔄 **HTTP verbs** — GET, POST, PUT, PATCH, DELETE on links, buttons, and forms
|
|
17
|
+
- 📡 **SSE** — Real-time updates via Server-Sent Events
|
|
15
18
|
- ✨ **Modern** — View Transitions, DOM morphing (via idiomorph), `fetch` API, event delegation
|
|
16
19
|
|
|
17
20
|
|
|
@@ -22,6 +25,9 @@ Inspired by [pjax](https://github.com/defunkt/jquery-pjax), [Turbo](https://turb
|
|
|
22
25
|
- [Modes](#modes)
|
|
23
26
|
- [Patch mode](#patch-mode)
|
|
24
27
|
- [Forms](#forms)
|
|
28
|
+
- [HTTP methods](#http-methods)
|
|
29
|
+
- [Triggers](#triggers)
|
|
30
|
+
- [Server-Sent Events (SSE)](#server-sent-events-sse)
|
|
25
31
|
- [Ghost mode](#ghost-mode)
|
|
26
32
|
- [Scroll restoration](#scroll-restoration)
|
|
27
33
|
- [Prefetch](#prefetch)
|
|
@@ -40,25 +46,32 @@ Inspired by [pjax](https://github.com/defunkt/jquery-pjax), [Turbo](https://turb
|
|
|
40
46
|
|
|
41
47
|
### Via `<script>` tag (recommended)
|
|
42
48
|
|
|
49
|
+
Place the scripts at the end of `<body>`, after all your HTML content. This ensures the DOM is ready when µJS initializes.
|
|
50
|
+
|
|
43
51
|
```html
|
|
44
|
-
<
|
|
45
|
-
|
|
52
|
+
<body>
|
|
53
|
+
<!-- your content -->
|
|
54
|
+
<script src="/path/to/mu.min.js"></script>
|
|
55
|
+
<script>mu.init();</script>
|
|
56
|
+
</body>
|
|
46
57
|
```
|
|
47
58
|
|
|
48
59
|
### Via CDN
|
|
49
60
|
|
|
50
61
|
```html
|
|
51
62
|
<!-- unpkg -->
|
|
52
|
-
<script src="https://unpkg.com/mujs/dist/mu.min.js"></script>
|
|
63
|
+
<script src="https://unpkg.com/@digicreon/mujs/dist/mu.min.js"></script>
|
|
53
64
|
|
|
54
65
|
<!-- jsDelivr -->
|
|
55
|
-
<script src="https://cdn.jsdelivr.net/npm/mujs/dist/mu.min.js"></script>
|
|
66
|
+
<script src="https://cdn.jsdelivr.net/npm/@digicreon/mujs/dist/mu.min.js"></script>
|
|
67
|
+
|
|
68
|
+
<script>mu.init();</script>
|
|
56
69
|
```
|
|
57
70
|
|
|
58
71
|
### Via npm
|
|
59
72
|
|
|
60
73
|
```bash
|
|
61
|
-
npm install mujs
|
|
74
|
+
npm install @digicreon/mujs
|
|
62
75
|
```
|
|
63
76
|
|
|
64
77
|
|
|
@@ -71,8 +84,6 @@ After calling `mu.init()`, all internal links (URLs starting with `/`) are autom
|
|
|
71
84
|
<html>
|
|
72
85
|
<head>
|
|
73
86
|
<title>My site</title>
|
|
74
|
-
<script src="/path/to/mu.min.js"></script>
|
|
75
|
-
<script>mu.init();</script>
|
|
76
87
|
</head>
|
|
77
88
|
<body>
|
|
78
89
|
<!-- These links are automatically handled by µJS -->
|
|
@@ -91,6 +102,9 @@ After calling `mu.init()`, all internal links (URLs starting with `/`) are autom
|
|
|
91
102
|
|
|
92
103
|
<!-- This link is NOT handled (explicitly disabled) -->
|
|
93
104
|
<a href="/file.pdf" mu-disabled>Download PDF</a>
|
|
105
|
+
|
|
106
|
+
<script src="/path/to/mu.min.js"></script>
|
|
107
|
+
<script>mu.init();</script>
|
|
94
108
|
</body>
|
|
95
109
|
</html>
|
|
96
110
|
```
|
|
@@ -204,6 +218,23 @@ Data is sent as `FormData`. Ghost mode is enabled by default (POST responses sho
|
|
|
204
218
|
</form>
|
|
205
219
|
```
|
|
206
220
|
|
|
221
|
+
### PUT / PATCH / DELETE forms
|
|
222
|
+
|
|
223
|
+
Use `mu-method` to override the HTTP method. The form data is sent as `FormData`, like POST.
|
|
224
|
+
|
|
225
|
+
```html
|
|
226
|
+
<!-- PUT form -->
|
|
227
|
+
<form action="/api/user/1" mu-method="put">
|
|
228
|
+
<input type="text" name="name">
|
|
229
|
+
<button type="submit">Update</button>
|
|
230
|
+
</form>
|
|
231
|
+
|
|
232
|
+
<!-- DELETE form (no data needed) -->
|
|
233
|
+
<form action="/api/user/1" mu-method="delete">
|
|
234
|
+
<button type="submit">Delete</button>
|
|
235
|
+
</form>
|
|
236
|
+
```
|
|
237
|
+
|
|
207
238
|
### POST form with patch response
|
|
208
239
|
|
|
209
240
|
```html
|
|
@@ -251,6 +282,139 @@ Add `mu-confirm-quit` to a form. If any input is modified, the user is prompted
|
|
|
251
282
|
```
|
|
252
283
|
|
|
253
284
|
|
|
285
|
+
## HTTP methods
|
|
286
|
+
|
|
287
|
+
By default, links use GET and forms use their `method` attribute. The `mu-method` attribute overrides the HTTP method for any element.
|
|
288
|
+
|
|
289
|
+
Supported values: `get`, `post`, `put`, `patch`, `delete`, `sse`.
|
|
290
|
+
|
|
291
|
+
```html
|
|
292
|
+
<!-- DELETE button -->
|
|
293
|
+
<button mu-url="/api/item/42" mu-method="delete" mu-mode="remove" mu-target="#item-42">
|
|
294
|
+
Delete
|
|
295
|
+
</button>
|
|
296
|
+
|
|
297
|
+
<!-- PUT link -->
|
|
298
|
+
<a href="/api/publish/5" mu-method="put" mu-mode="none">Publish</a>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Non-GET requests send an `X-Mu-Method` header with the HTTP method, allowing the server to distinguish between standard and µJS-initiated requests.
|
|
302
|
+
|
|
303
|
+
> **Note:** `mu-post` is deprecated. Use `mu-method="post"` instead.
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
## Triggers
|
|
307
|
+
|
|
308
|
+
µJS supports custom event triggers via the `mu-trigger` attribute. This allows any element with a `mu-url` to initiate a fetch on events other than click or submit.
|
|
309
|
+
|
|
310
|
+
### Default triggers
|
|
311
|
+
|
|
312
|
+
When `mu-trigger` is absent, the trigger depends on the element type:
|
|
313
|
+
|
|
314
|
+
| Element | Default trigger |
|
|
315
|
+
|---|---|
|
|
316
|
+
| `<a>` | `click` |
|
|
317
|
+
| `<form>` | `submit` |
|
|
318
|
+
| `<input>`, `<textarea>`, `<select>` | `change` |
|
|
319
|
+
| Any other element | `click` |
|
|
320
|
+
|
|
321
|
+
### Available triggers
|
|
322
|
+
|
|
323
|
+
| Trigger | Browser event(s) | Typical elements |
|
|
324
|
+
|---|---|---|
|
|
325
|
+
| `click` | `click` | Any element (default for `<a>`, `<button>`, `<div>`...) |
|
|
326
|
+
| `submit` | `submit` | `<form>` |
|
|
327
|
+
| `change` | `input` | `<input>`, `<textarea>`, `<select>` |
|
|
328
|
+
| `blur` | `change` + `blur` (deduplicated) | `<input>`, `<textarea>`, `<select>` |
|
|
329
|
+
| `focus` | `focus` | `<input>`, `<textarea>`, `<select>` |
|
|
330
|
+
| `load` | *(fires immediately when rendered)* | Any element |
|
|
331
|
+
|
|
332
|
+
### Examples
|
|
333
|
+
|
|
334
|
+
**Live search with debounce:**
|
|
335
|
+
|
|
336
|
+
```html
|
|
337
|
+
<input type="text" name="q"
|
|
338
|
+
mu-trigger="change" mu-debounce="500"
|
|
339
|
+
mu-url="/search" mu-target="#results" mu-source="#results"
|
|
340
|
+
mu-mode="update">
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Action on focus (e.g. load suggestions):**
|
|
344
|
+
|
|
345
|
+
```html
|
|
346
|
+
<input type="text" mu-trigger="focus"
|
|
347
|
+
mu-url="/suggestions" mu-target="#suggestions" mu-mode="update">
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Action on blur (save on field exit):**
|
|
351
|
+
|
|
352
|
+
```html
|
|
353
|
+
<input type="text" name="title" mu-trigger="blur"
|
|
354
|
+
mu-url="/api/save" mu-method="put" mu-target="#status" mu-mode="update">
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Load content immediately:**
|
|
358
|
+
|
|
359
|
+
```html
|
|
360
|
+
<div mu-trigger="load"
|
|
361
|
+
mu-url="/sidebar" mu-target="#sidebar" mu-mode="update">
|
|
362
|
+
</div>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Polling
|
|
366
|
+
|
|
367
|
+
Combine `mu-trigger="load"` with `mu-repeat` to poll a URL at regular intervals:
|
|
368
|
+
|
|
369
|
+
```html
|
|
370
|
+
<div mu-trigger="load" mu-repeat="5000"
|
|
371
|
+
mu-url="/notifications" mu-target="#notifs" mu-mode="update">
|
|
372
|
+
</div>
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
The first fetch fires immediately, then every 5 seconds. Polling intervals are automatically cleaned up when the element is removed from the DOM.
|
|
376
|
+
|
|
377
|
+
### Debounce
|
|
378
|
+
|
|
379
|
+
Use `mu-debounce` to delay the fetch until the user stops interacting:
|
|
380
|
+
|
|
381
|
+
```html
|
|
382
|
+
<input type="text" name="q" mu-debounce="300"
|
|
383
|
+
mu-url="/search" mu-target="#results" mu-mode="update">
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
> **Note:** Triggers other than `click` and `submit` default to ghost mode (no browser history entry).
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
## Server-Sent Events (SSE)
|
|
390
|
+
|
|
391
|
+
µJS supports real-time updates via Server-Sent Events. Set `mu-method="sse"` to open an `EventSource` connection instead of a one-shot fetch.
|
|
392
|
+
|
|
393
|
+
```html
|
|
394
|
+
<div mu-trigger="load" mu-url="/chat/stream"
|
|
395
|
+
mu-mode="patch" mu-method="sse">
|
|
396
|
+
</div>
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Each incoming SSE message is treated as HTML and rendered according to the element's `mu-mode`. In patch mode, the server sends HTML fragments with `mu-patch-target` attributes, just like a regular patch response.
|
|
400
|
+
|
|
401
|
+
### Server-side example
|
|
402
|
+
|
|
403
|
+
```
|
|
404
|
+
event: message
|
|
405
|
+
data: <div mu-patch-target="#messages" mu-patch-mode="append"><p>New message!</p></div>
|
|
406
|
+
|
|
407
|
+
event: message
|
|
408
|
+
data: <span mu-patch-target="#online-count">42</span>
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Limitations
|
|
412
|
+
|
|
413
|
+
- **No custom headers**: `EventSource` does not support custom HTTP headers. Use query parameters for authentication (e.g. `mu-url="/stream?token=abc"`).
|
|
414
|
+
- **Connection limit**: Browsers allow ~6 SSE connections per domain in HTTP/1.1. Use HTTP/2 to avoid this limit.
|
|
415
|
+
- **Automatic cleanup**: SSE connections are closed when the element is removed from the DOM (e.g. when the page changes).
|
|
416
|
+
|
|
417
|
+
|
|
254
418
|
## Ghost mode
|
|
255
419
|
|
|
256
420
|
Ghost mode prevents a navigation from being added to browser history and disables automatic scroll-to-top.
|
|
@@ -424,7 +588,11 @@ All attributes support both `mu-*` and `data-mu-*` syntax.
|
|
|
424
588
|
| `mu-morph` | Disable morphing on this element (`false`). |
|
|
425
589
|
| `mu-transition` | Disable view transitions on this element (`false`). |
|
|
426
590
|
| `mu-prefetch` | Disable prefetch on hover for this link (`false`). |
|
|
427
|
-
| `mu-
|
|
591
|
+
| `mu-method` | HTTP method: `get`, `post`, `put`, `patch`, `delete`, or `sse`. |
|
|
592
|
+
| `mu-trigger` | Event trigger: `click`, `submit`, `change`, `blur`, `focus`, `load`. |
|
|
593
|
+
| `mu-debounce` | Debounce delay in milliseconds (e.g. `"500"`). |
|
|
594
|
+
| `mu-repeat` | Polling interval in milliseconds (e.g. `"5000"`). |
|
|
595
|
+
| `mu-post` | *(Deprecated)* Use `mu-method="post"` instead. |
|
|
428
596
|
| `mu-confirm` | Show a confirmation dialog before loading. |
|
|
429
597
|
| `mu-confirm-quit` | *(Forms)* Prompt before leaving if the form has been modified. |
|
|
430
598
|
| `mu-validate` | *(Forms)* Name of a JS validation function. Must return `true`/`false`. |
|
package/dist/mu.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/* µJS (muJS)
|
|
2
|
-
var mu=new function(){this._VERSION="1.0",this._defaults={processLinks:!0,processForms:!0,ghost:!1,ghostRedirect:!1,mode:"replace",target:"body",source:"body",title:"title",scrollToTop:null,urlPrefix:null,progress:!0,prefetch:!0,morph:!0,transition:!0,confirmQuitText:"Are you sure you want to leave this page?"},this._cfg={},this._lastUrl=null,this._previousUrl=null,this._abortCtrl=null,this._progressEl=null,this._prefetchCache=new Map,this._confirmQuit=!1,this._jsIncludes={},this._morph=null,this.init=function(t){if(mu._cfg=Object.assign({},mu._defaults,t||{}),mu._cfg._titleAttr=null,mu._cfg.title){var e=mu._cfg.title.lastIndexOf("/");-1!==e&&(mu._cfg._titleAttr=mu._cfg.title.substring(e+1),mu._cfg.title=mu._cfg.title.substring(0,e))}mu._morph||void 0===window.Idiomorph||"function"!=typeof window.Idiomorph.morph||(mu._morph=function(t,e,r){window.Idiomorph.morph(t,e,r)}),document.addEventListener("click",mu._onClick),document.addEventListener("submit",mu._onSubmit),document.addEventListener("mouseover",mu._onMouseOver),document.addEventListener("input",mu._onInput),window.addEventListener("popstate",mu._onPopState),window.addEventListener("beforeunload",mu._onBeforeUnload),window.history.replaceState({mu:!0,url:location.pathname+location.search},""),mu._emit("mu:init",{url:location.pathname+location.search})},this.load=function(t,e){var r=Object.assign({},mu._cfg,e||{});mu._loadExec(t,r)},this.getLastUrl=function(){return mu._lastUrl},this.getPreviousUrl=function(){return mu._previousUrl},this.setConfirmQuit=function(t){mu._confirmQuit=!!t},this.setMorph=function(t){mu._morph=t},this._attr=function(t,e){return t.hasAttribute("mu-"+e)?t.getAttribute("mu-"+e):t.hasAttribute("data-mu-"+e)?t.getAttribute("data-mu-"+e):null},this._attrBool=function(t,e,r){var o=mu._attr(t,e);return null===o?r:""===o||"true"===o||"false"!==o&&r},this._shouldProcess=function(t){if("true"===mu._attr(t,"disabled")||""===mu._attr(t,"disabled"))return!1;if("false"===t.getAttribute("mu")||"false"===t.getAttribute("data-mu"))return!1;if(t.hasAttribute("target"))return!1;if("A"===t.tagName&&t.hasAttribute("onclick"))return!1;if("FORM"===t.tagName&&t.hasAttribute("onsubmit"))return!1;if("true"===t.getAttribute("mu")||"true"===t.getAttribute("data-mu"))return!0;var e=t.getAttribute("href")||t.getAttribute("action")||"";return!(!e.startsWith("/")||e.startsWith("//"))},this._elemCfg=function(t){var e,r=Object.assign({},mu._cfg);if(null!==(e=mu._attr(t,"mode"))&&(r.mode=e),null!==(e=mu._attr(t,"target"))&&(r.target=e),null!==(e=mu._attr(t,"source"))&&(r.source=e),null!==(e=mu._attr(t,"title"))){r.title=e,r._titleAttr=null;var o=e.lastIndexOf("/");-1!==o&&(r._titleAttr=e.substring(o+1),r.title=e.substring(0,o))}return null!==(e=mu._attr(t,"url"))&&(r._url=e),null!==(e=mu._attr(t,"prefix"))&&(r.urlPrefix=e),r.ghost=mu._attrBool(t,"ghost",r.ghost),r.ghostRedirect=mu._attrBool(t,"ghost-redirect",r.ghostRedirect),r.scrollToTop=mu._attrBool(t,"scroll-to-top",r.scrollToTop),r.morph=mu._attrBool(t,"morph",r.morph),r.transition=mu._attrBool(t,"transition",r.transition),r.post=mu._attrBool(t,"post",!1),r.confirm=mu._attr(t,"confirm"),r.patchGhost=mu._attrBool(t,"patch-ghost",!0),r},this._onClick=function(t){var e=t.target.closest("a");if(e&&mu._cfg.processLinks&&mu._shouldProcess(e)&&!(t.ctrlKey||t.metaKey||t.shiftKey||t.altKey)){t.preventDefault();var r=mu._elemCfg(e),o=r._url||e.getAttribute("href");if(r._sourceElement=e,!r.confirm||window.confirm(r.confirm)){if(mu._confirmQuit){if(!window.confirm(r.confirmQuitText))return;mu.setConfirmQuit(!1)}mu._loadExec(o,r)}}},this._onSubmit=function(t){var e=t.target.closest("form");if(e&&mu._cfg.processForms&&mu._shouldProcess(e)&&e.reportValidity()){var r=mu._attr(e,"validate");if(!r||"function"!=typeof window[r]||window[r](e)){var o=(e.getAttribute("method")||"get").toLowerCase(),u=mu._elemCfg(e),i=u._url||e.getAttribute("action");if(u._sourceElement=e,"post"===o)t.preventDefault(),u.postData=new FormData(e),e.hasAttribute("mu-ghost")||e.hasAttribute("data-mu-ghost")||(u.ghost=!0),null===u.scrollToTop&&"patch"!==u.mode&&(u.scrollToTop=!0);else{t.preventDefault();var n=new FormData(e);i=i+"?"+new URLSearchParams(n).toString()}mu.setConfirmQuit(!1),mu._loadExec(i,u)}}},this._onInput=function(t){t.target.closest("form[mu-confirm-quit], form[data-mu-confirm-quit]")&&mu.setConfirmQuit(!0)},this._onMouseOver=function(t){if(mu._cfg.prefetch){var e=t.target.closest("a");if(e&&mu._shouldProcess(e)&&!1!==mu._attrBool(e,"prefetch",!0)){var r=mu._attr(e,"url")||e.getAttribute("href");if(r&&!mu._prefetchCache.has(r)){mu._prefetchCache.set(r,null);var o=mu._cfg.urlPrefix?mu._cfg.urlPrefix+r:r;fetch(o,{headers:{"X-Requested-With":"mujs","X-Mu-Prefetch":"1"},priority:"low"}).then(function(t){return t.ok?t.text():null}).then(function(t){t?mu._prefetchCache.set(r,t):mu._prefetchCache.delete(r)}).catch(function(){mu._prefetchCache.delete(r)})}}}},this._onPopState=function(t){var e=t.state;if(e&&e.mu){var r=Object.assign({},mu._cfg);r.ghost=!0,r.scrollToTop=!1,r._restoreScroll=void 0!==e.scrollX?{x:e.scrollX,y:e.scrollY}:null,mu._loadExec(e.url,r)}else window.location.href=document.location},this._onBeforeUnload=function(t){mu._confirmQuit&&(t.preventDefault(),t.returnValue=mu._cfg.confirmQuitText)},this._loadExec=async function(t,e){var r=e.urlPrefix?e.urlPrefix+t:t;if(mu._saveScroll(),mu._emit("mu:before-fetch",{url:t,fetchUrl:r,config:e,sourceElement:e._sourceElement||null})){mu._abortCtrl&&mu._abortCtrl.abort(),mu._abortCtrl=new AbortController,mu._showProgress();try{var o=null,u=null,i=t;if(mu._prefetchCache.has(t)&&null!==mu._prefetchCache.get(t))o=mu._prefetchCache.get(t),mu._prefetchCache.delete(t);else{var n={signal:mu._abortCtrl.signal,headers:{"X-Requested-With":"mujs","X-Mu-Mode":e.mode}};if(e.postData?(n.method="POST",n.body=e.postData):e.post&&(n.method="POST"),!(u=await fetch(r,n)).ok)return void mu._emit("mu:fetch-error",{url:t,fetchUrl:r,status:u.status,response:u});u.redirected&&(i=new URL(u.url).pathname+new URL(u.url).search),o=await u.text()}var s,a={url:t,finalUrl:i,html:o,config:e};if(!mu._emit("mu:before-render",a))return;if(s="patch"===e.mode?function(){mu._renderPatch(a.html,e)}:function(){mu._renderPage(a.html,e)},e.transition&&document.startViewTransition?document.startViewTransition(s):s(),"patch"===e.mode)e.patchGhost||(mu._previousUrl=mu._lastUrl,mu._lastUrl=i,window.history.pushState({mu:!0,url:i},"",i));else e.ghost||u&&u.redirected&&e.ghostRedirect||e.postData?u&&u.redirected&&!e.ghostRedirect?(mu._previousUrl=mu._lastUrl,mu._lastUrl=i,window.history.pushState({mu:!0,url:i},"",i)):(mu._previousUrl=mu._lastUrl,mu._lastUrl=i):(mu._previousUrl=mu._lastUrl,mu._lastUrl=i,window.history.pushState({mu:!0,url:i},"",i));if("patch"!==e.mode)if(e._restoreScroll)window.scrollTo(e._restoreScroll.x,e._restoreScroll.y);else{var l=e.ghost||u&&u.redirected&&e.ghostRedirect;if(null!==e.scrollToTop?e.scrollToTop:!l){var m=t.indexOf("#");if(-1!==m){var c=document.getElementById(t.substring(m+1));c&&c.scrollIntoView({behavior:"smooth"})}else window.scrollTo(0,0)}}mu.setConfirmQuit(!1),mu._emit("mu:after-render",{url:t,finalUrl:i,mode:e.mode})}catch(e){if("AbortError"===e.name)return;mu._emit("mu:fetch-error",{url:t,fetchUrl:r,error:e})}finally{mu._hideProgress()}}},this._renderPage=function(t,e){var r=(new DOMParser).parseFromString(t,"text/html"),o=null;e.source&&(o=r.querySelector(e.source)),o||(o=r.body);var u=document.querySelector(e.target);if(u){mu._applyMode(e.mode,u,o,e),mu._updateTitle(r,e),mu._mergeHead(r);var i=document.querySelector(e.target)||document.body;mu._runScripts(i)}else console.warn("[µJS] Target element '"+e.target+"' not found.")},this._renderPatch=function(t,e){for(var r=(new DOMParser).parseFromString(t,"text/html").querySelectorAll("[mu-patch-target], [data-mu-patch-target]"),o=0;o<r.length;o++){var u=r[o],i=u.getAttribute("mu-patch-target")||u.getAttribute("data-mu-patch-target"),n=u.getAttribute("mu-patch-mode")||u.getAttribute("data-mu-patch-mode")||"replace",s=document.querySelector(i);s?(mu._applyMode(n,s,u,e),"remove"!==n&&mu._runScripts(u)):console.warn("[µJS] Patch target '"+i+"' not found.")}},this._applyMode=function(t,e,r,o){var u=o.morph&&mu._morph;switch(t){case"update":u?mu._morph(e,r.innerHTML,{morphStyle:"innerHTML"}):e.innerHTML=r.innerHTML;break;case"prepend":e.prepend(r);break;case"append":e.append(r);break;case"before":e.before(r);break;case"after":e.after(r);break;case"remove":e.remove();break;case"none":break;default:"BODY"===e.tagName&&"BODY"===r.tagName?u?mu._morph(e,r.innerHTML,{morphStyle:"innerHTML"}):e.innerHTML=r.innerHTML:u?mu._morph(e,r.outerHTML,{morphStyle:"outerHTML"}):e.replaceWith(r)}},this._updateTitle=function(t,e){if(e.title){var r=t.querySelector(e.title);if(r){var o=e._titleAttr?r.getAttribute(e._titleAttr):r.textContent;o&&(document.title=o)}}},this._mergeHead=function(t){for(var e="link[rel='stylesheet'], style, meta[name], meta[property]",r=document.head.querySelectorAll(e),o=t.head.querySelectorAll(e),u=new Map,i=0;i<r.length;i++)u.set(mu._elKey(r[i]),r[i]);var n=new Set;for(i=0;i<o.length;i++){var s=mu._elKey(o[i]);n.add(s),u.has(s)||document.head.appendChild(o[i].cloneNode(!0))}u.forEach(function(t,e){n.has(e)||t.remove()})},this._elKey=function(t){return"LINK"===t.tagName?"link:"+t.getAttribute("href"):"STYLE"===t.tagName?"style:"+t.textContent.substring(0,100):"META"===t.tagName?"meta:"+(t.getAttribute("name")||t.getAttribute("property")):t.outerHTML},this._runScripts=function(t){for(var e=t.querySelectorAll("script"),r=0;r<e.length;r++){var o=e[r];if("true"!==mu._attr(o,"disabled")&&""!==mu._attr(o,"disabled")){if(o.hasAttribute("src")){var u=o.getAttribute("src");if(mu._jsIncludes[u])continue;mu._jsIncludes[u]=!0}for(var i=document.createElement("script"),n=0;n<o.attributes.length;n++)i.setAttribute(o.attributes[n].name,o.attributes[n].value);i.textContent=o.textContent,o.parentNode.replaceChild(i,o)}}},this._showProgress=function(){if(mu._cfg.progress){if(!mu._progressEl){mu._progressEl=document.createElement("div"),mu._progressEl.id="mu-progress";var t=mu._progressEl.style;t.position="fixed",t.top="0",t.left="0",t.height="3px",t.background="#29d",t.zIndex="99999",t.transition="width .3s ease",t.width="0"}document.body.appendChild(mu._progressEl),mu._progressEl.offsetWidth,mu._progressEl.style.width="70%"}},this._hideProgress=function(){mu._progressEl&&(mu._progressEl.style.width="100%",setTimeout(function(){mu._progressEl&&(mu._progressEl.style.transition="none",mu._progressEl.style.width="0",mu._progressEl.offsetWidth,mu._progressEl.style.transition="width .3s ease",mu._progressEl.remove())},200))},this._saveScroll=function(){var t=window.history.state;t&&t.mu&&(t.scrollX=window.scrollX,t.scrollY=window.scrollY,window.history.replaceState(t,""))},this._emit=function(t,e){(e=e||{}).lastUrl=mu._lastUrl,e.previousUrl=mu._previousUrl;var r=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:e});return document.dispatchEvent(r)}};
|
|
1
|
+
/* µJS (muJS) - mujs.org */
|
|
2
|
+
var mu=mu||new function(){this._VERSION="1.2.1",this._defaults={processLinks:!0,processForms:!0,ghost:!1,ghostRedirect:!1,mode:"replace",target:"body",source:"body",title:"title",scrollToTop:null,urlPrefix:null,progress:!0,prefetch:!0,prefetchTtl:3e3,morph:!0,transition:!0,confirmQuitText:"Are you sure you want to leave this page?"},this._cfg={},this._lastUrl=null,this._previousUrl=null,this._abortCtrl=null,this._progressEl=null,this._prefetchCache=new Map,this._confirmQuit=!1,this._jsIncludes={},this._morph=null,this._initialized=!1,this.init=function(t){if(mu._cfg=Object.assign({},mu._defaults,t||{}),mu._cfg._titleAttr=null,mu._cfg.title){var e=mu._cfg.title.lastIndexOf("/");-1!==e&&(mu._cfg._titleAttr=mu._cfg.title.substring(e+1),mu._cfg.title=mu._cfg.title.substring(0,e))}mu._morph||void 0===window.Idiomorph||"function"!=typeof window.Idiomorph.morph||(mu._morph=function(t,e,r){window.Idiomorph.morph(t,e,r)});for(var r=document.querySelectorAll("script[src]"),u=0;u<r.length;u++)mu._jsIncludes[r[u].getAttribute("src")]=!0;mu._initialized||(document.addEventListener("click",mu._onClick),document.addEventListener("submit",mu._onSubmit),document.addEventListener("mouseover",mu._onMouseOver),document.addEventListener("mouseout",mu._onMouseOut),document.addEventListener("input",mu._onInput),window.addEventListener("popstate",mu._onPopState),window.addEventListener("beforeunload",mu._onBeforeUnload),mu._initialized=!0),window.history.replaceState({mu:!0,url:location.pathname+location.search},""),document.body&&mu._initTriggers(document.body),mu._emit("mu:init",{url:location.pathname+location.search})},this.load=function(t,e){var r=Object.assign({},mu._cfg,e||{});mu._loadExec(t,r)},this.getLastUrl=function(){return mu._lastUrl},this.getPreviousUrl=function(){return mu._previousUrl},this.setConfirmQuit=function(t){mu._confirmQuit=!!t},this.setMorph=function(t){mu._morph=t},this._attr=function(t,e){return t.hasAttribute("mu-"+e)?t.getAttribute("mu-"+e):t.hasAttribute("data-mu-"+e)?t.getAttribute("data-mu-"+e):null},this._attrBool=function(t,e,r){var u=mu._attr(t,e);return null===u?r:""===u||"true"===u||"false"!==u&&r},this._shouldProcess=function(t){if("true"===mu._attr(t,"disabled")||""===mu._attr(t,"disabled"))return!1;if("false"===t.getAttribute("mu")||"false"===t.getAttribute("data-mu"))return!1;if(t.hasAttribute("target"))return!1;if("A"===t.tagName&&t.hasAttribute("onclick"))return!1;if("FORM"===t.tagName&&t.hasAttribute("onsubmit"))return!1;if("true"===t.getAttribute("mu")||"true"===t.getAttribute("data-mu"))return!0;var e=mu._attr(t,"url");if(null!==e)return!(!e.startsWith("/")||e.startsWith("//"));var r=t.getAttribute("href")||t.getAttribute("action")||"";return!(!r.startsWith("/")||r.startsWith("//"))},this._elemCfg=function(t){var e,r=Object.assign({},mu._cfg);if(null!==(e=mu._attr(t,"mode"))&&(r.mode=e),null!==(e=mu._attr(t,"target"))&&(r.target=e),null!==(e=mu._attr(t,"source"))&&(r.source=e),null!==(e=mu._attr(t,"title"))){r.title=e,r._titleAttr=null;var u=e.lastIndexOf("/");-1!==u&&(r._titleAttr=e.substring(u+1),r.title=e.substring(0,u))}return null!==(e=mu._attr(t,"url"))&&(r._url=e),null!==(e=mu._attr(t,"prefix"))&&(r.urlPrefix=e),r.ghost=mu._attrBool(t,"ghost",r.ghost),r.ghostRedirect=mu._attrBool(t,"ghost-redirect",r.ghostRedirect),r.scrollToTop=mu._attrBool(t,"scroll-to-top",r.scrollToTop),r.morph=mu._attrBool(t,"morph",r.morph),r.transition=mu._attrBool(t,"transition",r.transition),null!==(e=mu._attr(t,"method"))&&(r.method=e.toLowerCase()),!r.method&&mu._attrBool(t,"post",!1)&&(r.method="post"),r.confirm=mu._attr(t,"confirm"),r.patchGhost=mu._attrBool(t,"patch-ghost",!0),r},this._onClick=function(t){var e=t.target.closest("[mu-url], [data-mu-url], a");if(e&&mu._shouldProcess(e)&&"click"===mu._getTrigger(e)&&("A"!==e.tagName||mu._cfg.processLinks)&&!(t.ctrlKey||t.metaKey||t.shiftKey||t.altKey)){t.preventDefault();var r=mu._elemCfg(e),u=r._url||e.getAttribute("href");if(r._sourceElement=e,r.method||(r.method="get"),!r.confirm||window.confirm(r.confirm)){if(mu._confirmQuit){if(!window.confirm(r.confirmQuitText))return;mu.setConfirmQuit(!1)}"sse"!==r.method?mu._loadExec(u,r):mu._openSSE(u,e,r)}}},this._onSubmit=function(t){var e=t.target.closest("form");if(e&&mu._cfg.processForms&&mu._shouldProcess(e)&&"submit"===mu._getTrigger(e)&&e.reportValidity()){var r=mu._attr(e,"validate");if(!r||"function"!=typeof window[r]||window[r](e)){var u=mu._elemCfg(e),o=u.method||(e.getAttribute("method")||"get").toLowerCase();u.method=o;var i=u._url||e.getAttribute("action");if(u._sourceElement=e,"get"!==o)t.preventDefault(),u.postData=new FormData(e),e.hasAttribute("mu-ghost")||e.hasAttribute("data-mu-ghost")||(u.ghost=!0),null===u.scrollToTop&&"patch"!==u.mode&&(u.scrollToTop=!0);else{t.preventDefault();var n=new FormData(e);i=i+"?"+new URLSearchParams(n).toString()}mu.setConfirmQuit(!1),mu._loadExec(i,u)}}},this._onInput=function(t){t.target.closest("form[mu-confirm-quit], form[data-mu-confirm-quit]")&&mu.setConfirmQuit(!0)},this._onMouseOver=function(t){if(mu._cfg.prefetch){var e=t.target.closest("[mu-url], [data-mu-url], a");if(e&&mu._shouldProcess(e)&&"click"===mu._getTrigger(e)&&!1!==mu._attrBool(e,"prefetch",!0)){var r=mu._attr(e,"url")||e.getAttribute("href");if(r){var u=mu._prefetchCache.get(r);if(!(u&&Date.now()-u.ts<mu._cfg.prefetchTtl)&&r!==location.pathname+location.search){var o=mu._cfg.urlPrefix?mu._cfg.urlPrefix+r:r,i=fetch(o,{headers:{"X-Requested-With":"mujs","X-Mu-Prefetch":"1"},priority:"low"}).then(function(t){return t.ok?t.text():null}).catch(function(){return null});mu._prefetchCache.set(r,{promise:i,ts:Date.now()})}}}}},this._onMouseOut=function(t){},this._onPopState=function(t){var e=t.state;if(e&&e.mu){var r=Object.assign({},mu._cfg);r.ghost=!0,r.scrollToTop=!1,r._restoreScroll=void 0!==e.scrollX?{x:e.scrollX,y:e.scrollY}:null,mu._loadExec(e.url,r)}else window.location.href=document.location},this._onBeforeUnload=function(t){mu._confirmQuit&&(t.preventDefault(),t.returnValue=mu._cfg.confirmQuitText)},this._getTrigger=function(t){var e=mu._attr(t,"trigger");if(e)return e;var r=t.tagName;return"FORM"===r?"submit":"INPUT"===r||"TEXTAREA"===r||"SELECT"===r?"change":"click"},this._debounce=function(t,e){var r=null;return function(){clearTimeout(r),r=setTimeout(t,e)}},this._triggerAction=function(t){if(mu._shouldProcess(t)){var e=mu._elemCfg(t),r=e._url||t.getAttribute("href")||t.getAttribute("action");if(r){if(e._sourceElement=t,e._trigger=!0,e.transition=!1,e.method||(e.method="get"),t.hasAttribute("mu-ghost")||t.hasAttribute("data-mu-ghost")||(e.ghost=!0),"INPUT"===t.tagName||"TEXTAREA"===t.tagName||"SELECT"===t.tagName){var u=t.closest("form");if(u)if("get"===e.method){var o=new FormData(u),i=new URLSearchParams(o).toString();r=r.split("?")[0]+"?"+i}else e.postData=new FormData(u);else t.name&&"get"===e.method&&(r=r.split("?")[0]+"?"+encodeURIComponent(t.name)+"="+encodeURIComponent(t.value))}"sse"!==e.method?mu._loadExec(r,e):mu._openSSE(r,t,e)}}},this._initTriggers=function(t){for(var e=t.querySelectorAll("[mu-url], [data-mu-url], [mu-trigger], [data-mu-trigger]"),r=0;r<e.length;r++){var u=e[r];if(!u._mu_bound){var o=mu._getTrigger(u);if("click"!==o&&"submit"!==o)if(mu._attr(u,"url")||u.getAttribute("href")||u.getAttribute("action")){u._mu_bound=!0;var i=parseInt(mu._attr(u,"debounce"),10)||0,n=parseInt(mu._attr(u,"repeat"),10)||0,s=function(t){return function(){mu._triggerAction(t)}}(u);if(i>0&&(s=mu._debounce(s,i)),n>0&&(s=function(t,e,r){var u=!1;return function(){e(),u||(u=!0,t._mu_interval=setInterval(e,r))}}(u,s,n)),"change"===o)u.addEventListener("input",s);else if("blur"===o){var a=function(t){var e=0;return function(){var r=Date.now();r-e<50||(e=r,t())}}(s);u.addEventListener("change",a),u.addEventListener("blur",a)}else"focus"===o?u.addEventListener("focus",s):"load"===o&&s()}}}},this._cleanupTriggers=function(t){for(var e=t.querySelectorAll?t.querySelectorAll("*"):[],r=0;r<e.length;r++)e[r]._mu_interval&&(clearInterval(e[r]._mu_interval),e[r]._mu_interval=null),e[r]._mu_sse&&(e[r]._mu_sse.close(),e[r]._mu_sse=null);t._mu_interval&&(clearInterval(t._mu_interval),t._mu_interval=null),t._mu_sse&&(t._mu_sse.close(),t._mu_sse=null)},this._openSSE=function(t,e,r){e._mu_sse&&e._mu_sse.close();var u=r.urlPrefix?r.urlPrefix+t:t,o=new EventSource(u);e._mu_sse=o,o.onmessage=function(e){var u={url:t,html:e.data,config:r};mu._emit("mu:before-render",u)&&("patch"===r.mode?mu._renderPatch(u.html,r):mu._renderPage(u.html,r),mu._emit("mu:after-render",{url:t,finalUrl:t,mode:r.mode}))},o.onerror=function(){mu._emit("mu:fetch-error",{url:t,fetchUrl:u,error:new Error("SSE connection error")})}},this._loadExec=async function(t,e){var r=e.urlPrefix?e.urlPrefix+t:t;if(e._trigger||mu._saveScroll(),mu._emit("mu:before-fetch",{url:t,fetchUrl:r,config:e,sourceElement:e._sourceElement||null})){var u;e._trigger?u=new AbortController:(mu._abortCtrl&&mu._abortCtrl.abort(),u=mu._abortCtrl=new AbortController),e._trigger||mu._showProgress();try{var o=null,i=null,n=t,s=mu._prefetchCache.get(t);if(s&&s.promise&&Date.now()-s.ts<mu._cfg.prefetchTtl&&(o=await s.promise,u.signal.aborted))return;if(!o){var a={signal:u.signal,headers:{"X-Requested-With":"mujs","X-Mu-Mode":e.mode}},l=(e.method||"get").toUpperCase();if("GET"!==l&&(a.method=l,a.headers["X-Mu-Method"]=l,e.postData&&(a.body=e.postData)),!(i=await fetch(r,a)).ok)return void mu._emit("mu:fetch-error",{url:t,fetchUrl:r,status:i.status,response:i});i.redirected&&(n=new URL(i.url).pathname+new URL(i.url).search),o=await i.text()}e._trigger||mu._prefetchCache.set(t,{promise:Promise.resolve(o),ts:Date.now()});var m,c={url:t,finalUrl:n,html:o,config:e};if(!mu._emit("mu:before-render",c))return;if(m="patch"===e.mode?function(){mu._renderPatch(c.html,e)}:function(){mu._renderPage(c.html,e)},e.transition&&document.startViewTransition?document.startViewTransition(m):m(),"patch"===e.mode)e.patchGhost||(mu._previousUrl=mu._lastUrl,mu._lastUrl=n,window.history.pushState({mu:!0,url:n},"",n));else e.ghost||i&&i.redirected&&e.ghostRedirect||e.postData?i&&i.redirected&&!e.ghostRedirect?(mu._previousUrl=mu._lastUrl,mu._lastUrl=n,window.history.pushState({mu:!0,url:n},"",n)):(mu._previousUrl=mu._lastUrl,mu._lastUrl=n):(mu._previousUrl=mu._lastUrl,mu._lastUrl=n,window.history.pushState({mu:!0,url:n},"",n));if("patch"!==e.mode)if(e._restoreScroll)window.scrollTo(e._restoreScroll.x,e._restoreScroll.y);else{var h=e.ghost||i&&i.redirected&&e.ghostRedirect;if(null!==e.scrollToTop?e.scrollToTop:!h){var _=t.indexOf("#");if(-1!==_){var d=document.getElementById(t.substring(_+1));d&&d.scrollIntoView({behavior:"smooth"})}else window.scrollTo(0,0)}}mu.setConfirmQuit(!1),mu._emit("mu:after-render",{url:t,finalUrl:n,mode:e.mode})}catch(e){if("AbortError"===e.name)return;mu._emit("mu:fetch-error",{url:t,fetchUrl:r,error:e})}finally{e._trigger||mu._hideProgress()}}},this._renderPage=function(t,e){var r=(new DOMParser).parseFromString(t,"text/html"),u=null;e.source&&(u=r.querySelector(e.source)),u||(u=r.body);var o=document.querySelector(e.target);if(o){mu._cleanupTriggers(o),mu._applyMode(e.mode,o,u,e),e.ghost||mu._updateTitle(r,e),mu._mergeHead(r);var i=document.querySelector(e.target)||document.body;mu._runScripts(i),mu._initTriggers(i)}else console.warn("[µJS] Target element '"+e.target+"' not found.")},this._renderPatch=function(t,e){for(var r=(new DOMParser).parseFromString(t,"text/html").querySelectorAll("[mu-patch-target], [data-mu-patch-target]"),u=[],o=0;o<r.length;o++){var i=r[o],n=i.getAttribute("mu-patch-target")||i.getAttribute("data-mu-patch-target"),s=i.getAttribute("mu-patch-mode")||i.getAttribute("data-mu-patch-mode")||"replace",a=document.querySelector(n);a?(mu._cleanupTriggers(a),mu._applyMode(s,a,i,e),"remove"!==s&&(mu._runScripts(i),u.push(n))):console.warn("[µJS] Patch target '"+n+"' not found.")}for(var l=0;l<u.length;l++){var m=document.querySelector(u[l]);m&&mu._initTriggers(m)}},this._applyMode=function(t,e,r,u){var o=u.morph&&mu._morph;switch(t){case"update":o?mu._morph(e,r.innerHTML,{morphStyle:"innerHTML"}):e.innerHTML=r.innerHTML;break;case"prepend":e.prepend(r);break;case"append":e.append(r);break;case"before":e.before(r);break;case"after":e.after(r);break;case"remove":e.remove();break;case"none":break;default:"BODY"===e.tagName&&"BODY"===r.tagName?o?mu._morph(e,r.innerHTML,{morphStyle:"innerHTML"}):e.innerHTML=r.innerHTML:o?mu._morph(e,r.outerHTML,{morphStyle:"outerHTML"}):e.replaceWith(r)}},this._updateTitle=function(t,e){if(e.title){var r=t.querySelector(e.title);if(r){var u=e._titleAttr?r.getAttribute(e._titleAttr):r.textContent;u&&(document.title=u)}}},this._mergeHead=function(t){for(var e="link[rel='stylesheet'], style, script",r=document.head.querySelectorAll(e),u=t.head.querySelectorAll(e),o=new Set,i=0;i<r.length;i++)o.add(mu._elKey(r[i]));for(i=0;i<u.length;i++)if(!o.has(mu._elKey(u[i])))if("SCRIPT"===u[i].tagName){for(var n=document.createElement("script"),s=0;s<u[i].attributes.length;s++)n.setAttribute(u[i].attributes[s].name,u[i].attributes[s].value);n.textContent=u[i].textContent,n.hasAttribute("src")&&(mu._jsIncludes[n.getAttribute("src")]=!0),document.head.appendChild(n)}else document.head.appendChild(u[i].cloneNode(!0))},this._elKey=function(t){return"LINK"===t.tagName?"link:"+t.getAttribute("href"):"STYLE"===t.tagName?"style:"+t.textContent.substring(0,100):"SCRIPT"===t.tagName?"script:"+(t.getAttribute("src")||t.textContent.substring(0,100)):t.outerHTML},this._runScripts=function(t){for(var e=t.querySelectorAll("script"),r=0;r<e.length;r++){var u=e[r];if("true"!==mu._attr(u,"disabled")&&""!==mu._attr(u,"disabled")){if(u.hasAttribute("src")){var o=u.getAttribute("src");if(mu._jsIncludes[o])continue;mu._jsIncludes[o]=!0}for(var i=document.createElement("script"),n=0;n<u.attributes.length;n++)i.setAttribute(u.attributes[n].name,u.attributes[n].value);i.textContent=u.textContent,u.parentNode.replaceChild(i,u)}}},this._showProgress=function(){if(mu._cfg.progress){if(!mu._progressEl){mu._progressEl=document.createElement("div"),mu._progressEl.id="mu-progress";var t=mu._progressEl.style;t.position="fixed",t.top="0",t.left="0",t.height="3px",t.background="#29d",t.zIndex="99999",t.transition="width .3s ease",t.width="0"}document.body.appendChild(mu._progressEl),mu._progressEl.offsetWidth,mu._progressEl.style.width="70%"}},this._hideProgress=function(){mu._progressEl&&(mu._progressEl.style.width="100%",setTimeout(function(){mu._progressEl&&(mu._progressEl.style.transition="none",mu._progressEl.style.width="0",mu._progressEl.offsetWidth,mu._progressEl.style.transition="width .3s ease",mu._progressEl.remove())},200))},this._saveScroll=function(){var t=window.history.state;t&&t.mu&&(t.scrollX=window.scrollX,t.scrollY=window.scrollY,window.history.replaceState(t,""))},this._emit=function(t,e){(e=e||{}).lastUrl=mu._lastUrl,e.previousUrl=mu._previousUrl;var r=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:e});return document.dispatchEvent(r)}};
|