NWFX is an HTMX-like library for use with NWF (Nintendo Web Framework) titles. The NWF is a development environment used by developers to make WiiU and 3DS titles using web applets, utilizing html, css, and JavaScript. Several games and system titles on the WiiU and 3DS are web applets, which this library targets
This software is still heavily in development. The only way to use it is to copy the prebuilt JavaScript from the dist
folder. Eventually I would like to get this put on a package CDN of some kind, such as unpkg, and remove the dist
folder from this repo as it's a build artifact of the src
TypeScript
This library will likely never be 1:1 compatible with the original HTMX api it is based on, though it tries to be as accurate as possible. This is due to several reasons, most notably:
- WiiU and 3DS browsers lack nearly every modern feature we make use of. They are WebKit based, and have support for features around the release of Chrome 4. This is not a hard rule, however, and they still lacks some features beyond Chrome 1
- Both consoles have abysmal rendering speeds and slow download times. Because of this, the library should remain as simple and small as possible to ensure the fastest download and execution times
- Since this targets specific platforms, not all trigger events will be supported as there's no real way to trigger them, or they simply do not exist on these platforms
While the feature set may not be ever 1:1, the goal is the as accurately implement the functionality which is supported to provide an API which works how you'd expect it to work with HTMX
Due to the above mentioned limitations, a basic form of element hydration is implemented when loading a page and new content from AJAX. Once a page loads, or new content is requested and inserted into the DOM, NWFX will query all un-hydrated elements in the modified area to add their trigger event listeners. Because of this, the load
event is triggered during the hydration step, not the insertion step. Once an element has it's events attached it is given the nwfx-hydrated
attribute, excluding it from future queries
A subset of the HTMX api is implemented and can be used simply by replacing attributes hx
with nwfx
in most simple usages. The current list of supported features is as follows
A subset of the HTMX events are fired during the execution on the trigger handlers. These events likely do not appear at the exact same times as they would in HTMX, as the libraries are designed very differently, but they should be close enough. Any non-standard HTMX events are marked as such
nwfx:xhr:loadstart
- Dispatched on the XHRloadstart
event. See MDN for detailsnwfx:xhr:loadend
- Dispatched on the XHRloadend
event. See MDN for detailsnwfx:xhr:progress
- Dispatched on the XHRprogress
event. See MDN for detailsnwfx:xhr:load
- Dispatched on the XHRload
event. See MDN for details. Non-standard HTMX eventnwfx:xhr:abort
- Dispatched on the XHRabort
event. See MDN for detailsnwfx:beforeRequest
- Dispatched right beforeXMLHttpRequest
is created and setupnwfx:beforeSend
- Dispatched right beforexhr.send
is callednwfx:afterOnLoad
- Dispatched at the end of of handling a successful responsenwfx:afterRequest
- Dispatched at the end of of the XHR request regardless of statusnwfx:responseError
- Dispatched when XHR gets a non 200-300 response. Contains non-standard event dataevent.detail.status
andevent.detail.responseText
document.addEventListener('nwfx:beforeSend', function() {
// Request is about to be sent
wiiuBrowser.showLoadingIcon(true);
});
document.addEventListener('nwfx:afterRequest', function() {
// Request finished, regardless of status
wiiuBrowser.showLoadingIcon(false);
});
document.addEventListener('nwfx:responseError', function(e) {
// Request was not 200-300
wiiuErrorViewer.openByCodeAndMessage(5984000, 'Error: Unable to handle request');
console.debug(e.detail.status);
console.debug(e.detail.responseText);
wiiuBrowser.showLoadingIcon(false);
});
document.addEventListener('nwfx:afterOnLoad', function() {
// Request was a success
wiiuBrowser.showLoadingIcon(false);
});
All elements MUST have one of the following attributes to be hydrated by NWFX. When a request is being made, the element which triggered the request is given the nwfx-request
class, and any additional triggers are rejected. Only one request per element may be in flight at a time
nwfx-get
Issues a GET requestnwfx-post
Issues a POST request (Does not currently send data)nwfx-put
Issues a PUT request (Does not currently send data)nwfx-patch
Issues a PATCH request (Does not currently send data)nwfx-delete
Issues a DELETE request
<div nwfx-get="/get-other-content">
Click me!
</div>
The following default headers are sent during requests. NWFX does not currently respect response headers
NWFX-Request
Always set to "true"NWFX-Current-URL
The currentwindow.location.href
NWFX-Trigger
Theid
attribute of the element triggering the request. Not sent if element has noid
setNWFX-Trigger-Name
Thename
attribute of the element triggering the request. Not sent if element has noname
setNWFX-Target
Theid
attribute of the element targeted for swapping. Not sent if element has noid
setNWFX-Prompt
Set when prompting is being used
Custom request headers can be set on the triggering element using the nwfx-headers
attribute, setting a JSON object of headers. Invalid JSON is ignored and no error is given
<button nwfx-headers='{"x-token": "some token"}' nwfx-get="/get">
Click me
</button>
<button nwfx-headers='invalid' nwfx-get="/get">
Click me
</button>
Both query strings and request bodies are built using elements name
and value
attributes. On GET
requests these are converted into the requests query string. On all other requests this is sent as a request body with the Content-Type
header set to application/x-www-form-urlencoded
By default, only the triggering element is checked. With <form>
elements, the <form>
element is not checked, and instead all it's children and inputs are used
Custom data may also be provided through the nwfx-vals
attribute. Values passed into nwfx-vals
are set AFTER the elements attributes and form fields, overwriting any same keys if they exist. Unlike HTMX this must ALWAYS be valid JSON, no values are ever computed for simplicity and security. The hx-vars
attribute is not implemented and never will be, for the same simplicity and security reasons as well as it being deprecated by HTMX anyway
<button name="button" value="some value" nwfx-get="/get">
Query string is "button=some%20value"
</button>
<form name="form" name="myform" value="this is ignored" nwfx-post="/post">
<input type="text" name="username">
<!-- POST body is username=YOUR_INPUT -->
<button type="submit">Submit</button>
</form>
<button nwfx-post="/post" nwfx-vals='{"hello": "world", "nwfx": "rocks"}'>
POST body is hello=world&nwfx=rocks
</button>
A basic nwfx-trigger
implementation is supported. Only the following events are supported
click
change
submit
load
(triggered during hydration)
If no trigger is set, the same defaults found in HTMX are used
change
for<input>
,<textarea>
, and<select>
elementssubmit
for<form>
elementsclick
for everything else
<div nwfx-get="/get-other-content" nwfx-trigger="load">
I run and get replaced when loaded
</div>
The following trigger modifiers are supported
once
Only allow an element to trigger it's event once. If using this modifer, once the event has been triggered, the element is given thenwfx-triggered
attribute and any future events are rejected
<div nwfx-get="/get-other-content" nwfx-trigger="click once">
I only run once
</div>
By default the target element is the element dispatching the event. Targeting a different element is supported through the nwfx-target
attribute. This attribute takes in a query selector
<div nwfx-get="/get-other-content" nwfx-target="#replace-me">
I replace someone else
</div>
<div id="replace-me">
My content gets replaced
</div>
NWFX supports all the basic swapping methods provided by HTMX
innerHTML
Default. Replaces the targetsinnerHTML
with the new contentouterHTML
Replaces the target with the new contentafterbegin
Prepends the content before the first child inside the targetbeforebegin
Prepends the content before the target in the targets parent elementbeforeend
Appends the content after the last child inside the targetafterend
Appends the content after the target in the targets parent elementdelete
Ignores response and deletes the targetnone
Do nothing and do not delete target (Out of band swaps and response headers are NOT yet implemented)
<div nwfx-get="/get-other-content" nwfx-swap="outerHTML">
My content does not get replaced, my whole self does!
</div>
Indicators are implemented in the same way as HTMX. Every element with the nwfx-indicator
class is hidden by default, and is shown again if the parent element has the nwfx-request
class. By default the following css is used
.nwfx-indicator {
opacity: 0;
transition: opacity 200ms ease-in;
}
.nwfx-request .nwfx-indicator {
opacity: 1;
}
.nwfx-request.indicator {
opacity: 1;
}
When an element is firing an AJAX request the nwfx-request
class is added to the element, showing all nwfx-indicator
elements. It is removed after the AJAX request finishes, hiding them again
<div nwfx-get="/get-other-content">
Click me!
<img class="nwfx-indicator" src="spinner.gif">
</div>
Elements with nwfx-boost
set to "true"
will have all of their children a
tags converted into AJAX requests targeting the body
element. Forms use their method
and action
attributes to determine the HTTP verb and URL. The URL bar does not update
<div nwfx-boost="true">
<a href="/html">Boosted</a>
<!--
This gets converted into
<a href="/html" nwfx-get="/html" nwfx-target="body" nwfx-hydrated="true">Boosted</a>
-->
</div>
<div nwfx-boost="true">
<form action="/get">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>
<!--
This gets converted into
<form action="/get" nwfx-get="/get" nwfx-target="body" nwfx-hydrated="true">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>
-->
</div>
Elements with the nwfx-confirm
attribute will ask the user to confirm the action before issuing the AJAX request. Unlike the original HTMX library, if the once
trigger modifier is used then the element is NOT marked as triggered even if confirmation is declined. I felt this was better user experience
<div nwfx-delete="/account" nwfx-confirm="Are you sure you want to delete your account?">
Delete account
</div>
Elements with the nwfx-prompt
set will ask the user to enter input before issuing the AJAX request. The input is sent in the NWFX-Prompt
header. Unlike the original HTMX library, if the once
trigger modifier is used then the element is NOT marked as triggered even if the prompt is declined. I felt this was better user experience
<div nwfx-post="/search" nwfx-prompt="Enter query">
Search
</div>