Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
alexpetros committed Jun 20, 2024
0 parents commit 19fe4f5
Show file tree
Hide file tree
Showing 11 changed files with 1,201 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
44 changes: 44 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<meta charset="UTF-8">
<title>Missing Piece Tests</title>
<script src="./missing-piece.js"></script>

<h1>Missing Piece Tests</h1>

<h2>Parsing</h2>

<button action="/test/responses/text.html" method=GET target="_this">Plain Text</button>
<button action="/test/responses/h3.html" method=GET target="_this">H3</button>
<button action="/test/responses/red-h3.html" method=GET target="_this">Red H3</button>
<button action="/test/responses/all-blue.html" method=GET target="_this">color: blue</button>

<h2>Forms</h2>

<form action="/test/responses/text" method=GET>
<input type=hidden name=key value=tst>
<button>Get</button>
</form>

<form action="/test/responses/text" method=GET target="_this">
<input type=hidden name=key value=tst>
<button>Get (self target)</button>
</form>

<form action="/test/responses/text" method=PUT>
<input type=hidden name=key value=tst>
<button>Put</button>
</form>

<form action="/test/responses/text" method=DELETE>
<input type=hidden name=key value=tst>
<button>Delete</button>
</form>


<h2>Buttons</h2>
<button action="/test/responses/text.html">Get (default)</button>
<button action="/test/responses/text.html" method=GET>Get</button>
<button action="/test/responses/text.html" method=POST>Post</button>
<button action="/test/responses/text.html" method=PUT>Put</button>
<button action="/test/responses/text.html" method=DELETE>Delete</button>

7 changes: 7 additions & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"target": "es2020",
"checkJs": true
},
"include": ["*"]
}
98 changes: 98 additions & 0 deletions missing-piece.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const ADDITIONAL_FORM_METHODS = ['PUT', 'PATCH', 'DELETE']
const EXISTING_TARGET_KEYWORDS = ['_self', '_blank', '_parent', '_top', '_unfencedTop']

/**
* @param {string} url
* @param {string} method
* @param {FormData} data
* @param {string} target
*/
function ajax(url, method, data, target) {
/** @param {Event} e */
return async (e) => {
e.preventDefault() // The new actions override old ones


let targetElement
if (target === '_this') {
targetElement = e.target
} else if (target) {
targetElement = document.querySelector('target')
if (!targetElement) {
console.error(`no element found for target ${target} - ignorning`)
return null
}
}

const opts = { method }
if (method !== 'GET' && method !== 'DELETE') {
opts.body = data
} // else convert to URL params

const res = await fetch(url, opts)
const responseText = await res.text()
if (targetElement) {
const template = document.createElement('template')
template.innerHTML = responseText
processNode(template)
console.log(template.content.children)

// @ts-ignore - all the targets are going to be Elements
targetElement.replaceWith(template.content)
} else {
// TODO check for html wrapper?
document.querySelector('html').innerHTML = responseText
processNode(document)
// TODO push to url and history
// Handle redirects
}
}
}

/**
* @param {Document | Element} node
*/
function processNode(node) {
// #1 forms can PUT, PATCH, and DELETE
const forms = node.querySelectorAll('form')
for (const form of forms) {
const method = form.getAttribute('method')
const target = form.getAttribute('target') || undefined
// Only process forms that a) have subtree targets or b) have new methods
// TODO move this into query selector?
if (target || ADDITIONAL_FORM_METHODS.includes(method)) {
const url = form.getAttribute('action')
const data = new FormData(form)
form.addEventListener('submit', ajax(url, method, data, target))
}
}

// #2 buttons can make requests on their own
const buttons = node.querySelectorAll('button[action]')
for (const button of buttons) {
const url = button.getAttribute('action')
const target = button.getAttribute('target')
const method = button.getAttribute('method') || 'GET'
// TODO add the name/value if appropriate
button.addEventListener('click', ajax(url, method, undefined, target))
}

// #3 Links, buttons and forms can target
const links = node.querySelectorAll('a[target]')
for (const link of links) {
const target = link.getAttribute('target')
const hasExistingBehavior = EXISTING_TARGET_KEYWORDS.includes(target) ||
document.querySelector(`iframe[name=${target}]`)

const url = link.getAttribute('href')
if (!hasExistingBehavior) {
link.addEventListener('click', ajax(url, 'GET', undefined, target))
}
}
}

if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => { processNode(document) });
} else {
processNode(document)
}
Loading

0 comments on commit 19fe4f5

Please sign in to comment.