- WIP on lean-live, working version
This commit is contained in:
parent
0deeeac3ac
commit
ab335bf919
File diff suppressed because one or more lines are too long
@ -1,58 +1,131 @@
|
|||||||
export class Events {
|
export class Events {
|
||||||
#object;
|
#object;
|
||||||
|
#mutationObserver;
|
||||||
|
#events = [];
|
||||||
|
#uids = [];
|
||||||
|
|
||||||
constructor(object, swapping) {
|
constructor(object, swapping) {
|
||||||
this.#object = object;
|
this.#object = object;
|
||||||
|
|
||||||
if ( object.url || null === this.object.parentElement.closest('ui-live') ) {
|
console.log(this.object.parentElement.closest('ui-live'))
|
||||||
this.attachLinks();
|
|
||||||
this.attachForms();
|
if ( (this.object.url && this.object.action === 'replace') || null === this.object.parentElement.closest('ui-live') ) {
|
||||||
this.attachDataLiveUi();
|
this.registerEvents(this.object);
|
||||||
|
|
||||||
|
this.#mutationObserver = new MutationObserver((mutationsList, observer) => {
|
||||||
|
for (const mutation of mutationsList) {
|
||||||
|
if (mutation.type === 'childList') {
|
||||||
|
mutation.addedNodes.forEach(node => {
|
||||||
|
if (node instanceof Element) {
|
||||||
|
this.registerEvents(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for removed nodes
|
||||||
|
mutation.removedNodes.forEach(node => {
|
||||||
|
if (node.hasAttribute ? node.hasAttribute('data-ui-live-event') : false) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#mutationObserver.observe(this.#object, {
|
||||||
|
childList:true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! swapping) {
|
if (! swapping) {
|
||||||
this.autoloadUrl();
|
switch(object.loading) {
|
||||||
}
|
case "eager":
|
||||||
}
|
this.autoloadUrl();
|
||||||
|
|
||||||
autoloadUrl() {
|
break;
|
||||||
this.object.url && this.object.emit("live:autoload", {
|
|
||||||
element: this.object,
|
|
||||||
url: this.object.url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
attachLinks() {
|
|
||||||
const links = this.object.querySelectorAll("a[href]");
|
|
||||||
|
|
||||||
for (const link of links) {
|
case "lazy":
|
||||||
if (link.dataset.uiLive === undefined) {
|
const observer = new IntersectionObserver(
|
||||||
const href = link.getAttribute('href');
|
(entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
this.autoloadUrl();
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threshold: 0.1,
|
||||||
|
rootMargin: '100px'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (href.length && href.substring(0, 1) !== '#') {
|
observer.observe(object);
|
||||||
link.addEventListener("click", (ev) => {
|
break;
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
this.object.emit("live:navigate", {
|
|
||||||
element: this.object,
|
|
||||||
url: href,
|
|
||||||
link: link,
|
|
||||||
sourceEvent: ev
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attachForms() {
|
registerEvents(parentNode) {
|
||||||
const forms = this.object.querySelectorAll("form");
|
parentNode = parentNode ?? document;
|
||||||
|
|
||||||
|
this.attachLinks(parentNode);
|
||||||
|
this.attachForms(parentNode);
|
||||||
|
this.attachDataLiveUi(parentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterEvents() {
|
||||||
|
if (this.#mutationObserver) {
|
||||||
|
this.#mutationObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#uids.forEach((uid) => {
|
||||||
|
console.debug("Unregistering "+ uid);
|
||||||
|
|
||||||
|
this.#events[uid]();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
autoloadUrl() {
|
||||||
|
if (this.object.url) {
|
||||||
|
|
||||||
|
this.object.emit("live:autoload", {
|
||||||
|
element: this.object,
|
||||||
|
url: this.object.url,
|
||||||
|
vars: this.object.variables,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attachLinks(parentNode) {
|
||||||
|
const links = parentNode.querySelectorAll("a[href]:not([data-ui-live-event]):not([data-ui-live]):not([target='_blank'])");
|
||||||
|
|
||||||
|
for (const link of links) {
|
||||||
|
const href = link.getAttribute('href');
|
||||||
|
|
||||||
|
if (href.length && href.substring(0, 1) !== '#') {
|
||||||
|
this.registerEventFrom(link, 'click', (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
this.object.emit("live:navigate", {
|
||||||
|
element: this.object,
|
||||||
|
url: href,
|
||||||
|
link: link,
|
||||||
|
sourceEvent: ev
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attachForms(parentNode) {
|
||||||
|
const forms = parentNode.querySelectorAll("form:not([data-ui-live-event]):not([target='blank'])");
|
||||||
|
|
||||||
for (const form of forms) {
|
for (const form of forms) {
|
||||||
const method = form.hasAttribute('method') ? form.getAttribute('method').toUpperCase() : "GET";
|
const method = form.hasAttribute('method') ? form.getAttribute('method').toUpperCase() : "GET";
|
||||||
|
|
||||||
form.addEventListener("submit", (ev) => {
|
this.registerEventFrom(form, 'submit', (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
const name = ev.submitter.getAttribute('name'),
|
const name = ev.submitter.getAttribute('name'),
|
||||||
@ -67,28 +140,28 @@ export class Events {
|
|||||||
|
|
||||||
if (method === "GET") {
|
if (method === "GET") {
|
||||||
form.querySelectorAll("input,select").forEach((ele) => {
|
form.querySelectorAll("input,select").forEach((ele) => {
|
||||||
ele.addEventListener("change", (ev) => form.requestSubmit());
|
this.registerEventFrom(ele, 'change', (ev) => form.requestSubmit());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attachDataLiveUi() {
|
attachDataLiveUi(parentNode) {
|
||||||
const elements = this.object.querySelectorAll("[data-ui-live]");
|
const elements = parentNode.querySelectorAll("[data-ui-live]");
|
||||||
|
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
switch(element.tagName.toUpperCase()) {
|
switch(element.tagName.toUpperCase()) {
|
||||||
case "TEXTAREA":
|
case "TEXTAREA":
|
||||||
case "SELECT":
|
case "SELECT":
|
||||||
case "INPUT":
|
case "INPUT":
|
||||||
element.addEventListener("change", (ev) =>
|
this.registerEventFrom(element, 'change', (ev) =>
|
||||||
this.object.emit("live:input-change", { element: element, source: this.object, submitter: element, sourceEvent: ev })
|
this.object.emit("live:input-change", { element: element, source: this.object, submitter: element, sourceEvent: ev })
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
element.addEventListener("click", (ev) => {
|
this.registerEventFrom(element, 'click', (ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
@ -100,6 +173,27 @@ export class Events {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerEventFrom(element, eventName, callback, reset) {
|
||||||
|
if (reset ?? false) {
|
||||||
|
const currentUid = element.getAttribute('data-ui-live-event');
|
||||||
|
|
||||||
|
if (currentUid) {
|
||||||
|
this.#events[currentUid]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uid = eventName + ":" + Math.random().toString(16).slice(2);
|
||||||
|
|
||||||
|
element.addEventListener(eventName, callback);
|
||||||
|
element.setAttribute('data-ui-live-event', uid);
|
||||||
|
this.#uids[uid] = eventName;
|
||||||
|
|
||||||
|
this.#events[uid] = () => {
|
||||||
|
element.removeEventListener(eventName, callback);
|
||||||
|
element.removeAttribute('data-ui-live-event' + eventName)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
get object() {
|
get object() {
|
||||||
return this.#object;
|
return this.#object;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,15 +8,15 @@ export class UiLive extends HTMLElement {
|
|||||||
fetch : true
|
fetch : true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#intervalId;
|
|
||||||
|
|
||||||
#connected = false;
|
#connected = false;
|
||||||
|
#eventMapper;
|
||||||
|
#intervalId;
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
this.intervalId && clearTimeout(this.intervalId);
|
this.intervalId && clearTimeout(this.intervalId);
|
||||||
|
|
||||||
documentManipulatorObject.unregisterLiveElement(this);
|
documentManipulatorObject.unregisterLiveElement(this);
|
||||||
|
this.#eventMapper.unregisterEvents();
|
||||||
|
|
||||||
console.debug("component disconnected: ", this);
|
console.debug("component disconnected: ", this);
|
||||||
}
|
}
|
||||||
@ -24,9 +24,8 @@ export class UiLive extends HTMLElement {
|
|||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
documentManipulatorObject.registerLiveElement(this);
|
documentManipulatorObject.registerLiveElement(this);
|
||||||
|
|
||||||
this.on("live:swap", () => new Events(this, true));
|
this.on("live:swap", () => this.mapEvents(true));
|
||||||
|
this.mapEvents(false);
|
||||||
new Events(this, false);
|
|
||||||
|
|
||||||
if (this.pool) {
|
if (this.pool) {
|
||||||
const self = {};
|
const self = {};
|
||||||
@ -40,6 +39,14 @@ export class UiLive extends HTMLElement {
|
|||||||
console.debug("component connected: ", this);
|
console.debug("component connected: ", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mapEvents(swapping) {
|
||||||
|
if (this.#eventMapper) {
|
||||||
|
this.#eventMapper.unregisterEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#eventMapper = new Events(this, swapping);
|
||||||
|
}
|
||||||
|
|
||||||
adoptedCallback() {
|
adoptedCallback() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,11 +72,45 @@ export class UiLive extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
swapHtml(html) {
|
swapHtml(html) {
|
||||||
this.innerHTML = html;
|
switch(this.action) {
|
||||||
|
case "prepend":
|
||||||
|
case "append":
|
||||||
|
let target = this.parentElement.querySelector(`ui-live[data-name="${this.name}"]:last-child`);
|
||||||
|
|
||||||
|
let elem = document.createElement("p");
|
||||||
|
target.insertAdjacentElement('afterend', elem);
|
||||||
|
elem.outerHTML = html;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "replace":
|
||||||
|
this.innerHTML = html;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(this.querySelectorAll("script"))
|
||||||
|
.forEach( oldScriptEl => {
|
||||||
|
const newScriptEl = document.createElement("script");
|
||||||
|
|
||||||
|
Array.from(oldScriptEl.attributes).forEach( attr => {
|
||||||
|
newScriptEl.setAttribute(attr.name, attr.value)
|
||||||
|
});
|
||||||
|
|
||||||
|
const scriptText = document.createTextNode(oldScriptEl.innerHTML);
|
||||||
|
newScriptEl.appendChild(scriptText);
|
||||||
|
|
||||||
|
oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl);
|
||||||
|
});
|
||||||
|
|
||||||
this.emit("live:swap");
|
this.emit("live:swap");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return string */
|
||||||
|
get variables() {
|
||||||
|
return this.dataset.vars ? new URLSearchParams(JSON.parse(this.dataset.vars)).toString() : "";
|
||||||
|
}
|
||||||
|
|
||||||
/** @return string */
|
/** @return string */
|
||||||
get url() {
|
get url() {
|
||||||
return this.dataset.src;
|
return this.dataset.src;
|
||||||
@ -80,6 +121,21 @@ export class UiLive extends HTMLElement {
|
|||||||
return this.dataset.src = value;
|
return this.dataset.src = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return string */
|
||||||
|
get action() {
|
||||||
|
return this.dataset.action ?? "replace";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return string */
|
||||||
|
set action(value) {
|
||||||
|
return this.dataset.action = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return string */
|
||||||
|
get loading() {
|
||||||
|
return this.dataset.loading ?? "eager";
|
||||||
|
}
|
||||||
|
|
||||||
/** @return string */
|
/** @return string */
|
||||||
get selector() {
|
get selector() {
|
||||||
return this.dataset.selector;
|
return this.dataset.selector;
|
||||||
|
|||||||
20
src/Live.php
20
src/Live.php
@ -23,7 +23,7 @@ class Live
|
|||||||
$this->params = json_decode($encodedHeader, true);
|
$this->params = json_decode($encodedHeader, true);
|
||||||
|
|
||||||
foreach($this->params['vars'] ?? [] as $variables) {
|
foreach($this->params['vars'] ?? [] as $variables) {
|
||||||
parse_str(implode('&', $variables['values']), $vars);
|
parse_str(is_array($variables['values']) ? implode('&', $variables['values']) : $variables['values'], $vars);
|
||||||
|
|
||||||
$this->variablesObjects[$variables['source']] = new Variables($variables['source'], $vars, true);
|
$this->variablesObjects[$variables['source']] = new Variables($variables['source'], $vars, true);
|
||||||
}
|
}
|
||||||
@ -49,7 +49,7 @@ class Live
|
|||||||
$node = $dom->querySelector("ui-live[data-name=\"{$name}\"]");
|
$node = $dom->querySelector("ui-live[data-name=\"{$name}\"]");
|
||||||
|
|
||||||
if ($node && !$this->isAlreadyRendering($node, $list)) {
|
if ($node && !$this->isAlreadyRendering($node, $list)) {
|
||||||
$elements[$name] = $this->renderElement($node);
|
$elements[$name] = $this->renderElement($node, $this->params['outerHtml'] ?? false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,8 +82,20 @@ class Live
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function renderElement(\DOM\Element $element) : string
|
protected function renderElement(\DOM\Element $element, bool $outer = false) : string
|
||||||
{
|
{
|
||||||
return $element->innerHTML;
|
return $outer ? $this->outerHtml($element) : $element->innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function outerHtml(\DOM\Element $element): string {
|
||||||
|
$dom = \DOM\HTMLDocument::createEmpty();
|
||||||
|
|
||||||
|
$cloned = $dom->importNode($element, true);
|
||||||
|
|
||||||
|
$dom->appendChild($cloned);
|
||||||
|
|
||||||
|
$html = $dom->saveHTML();
|
||||||
|
|
||||||
|
return preg_replace('/<\?xml[^?]*\?>/', '', $html);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user