hy-push-state
Version:
Turn static web sites into dynamic web apps
212 lines (170 loc) • 6.55 kB
Markdown
# src / mixin / update.js
Copyright (c) 2018 Florian Klampfer <https://qwtel.com/>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
```js
import { fragmentFromString, isExternal } from "../common";
import { scriptMixin } from "./script-hack";
export const updateMixin = C =>
class extends scriptMixin(C) {
```
Extracts the title of the page
```js
getTitle(fragment) {
return (fragment.querySelector("title") || {}).textContent;
}
```
Extracts the elements to be replaced
```js
getReplaceElements(fragment) {
if (this.replaceIds.length > 0) {
return this.replaceIds.map(id => fragment.getElementById(id));
} else if (this.el.id) {
return [fragment.getElementById(this.el.id)];
} else {
const index = Array.from(document.getElementsByTagName(this.el.tagName)).indexOf(this.el);
return [fragment.querySelectorAll(this.el.tagName)[index]];
}
}
```
Takes the response string and turns it into document fragments
that can be inserted into the DOM.
```js
responseToContent(context) {
const { response } = context;
const documentFragment = fragmentFromString(response);
const title = this.getTitle(documentFragment);
const replaceEls = this.getReplaceElements(documentFragment);
if (replaceEls.some(x => x == null)) {
throw Object.assign(context, { replaceElMissing: true });
}
const scripts = this.scriptSelector ? this.tempRemoveScriptTags(replaceEls) : [];
return Object.assign(context, { documentFragment, title, replaceEls, scripts });
}
```
Replaces the old elments with the new one, one-by-one.
```js
replaceContentByIds(elements) {
this.replaceIds
.map(id => document.getElementById(id))
.forEach((oldElement, i) => oldElement.parentNode.replaceChild(elements[i], oldElement));
}
```
When no `relaceIds` are set, replace the entire content of the component (slow).
```js
replaceContentWholesale([el]) {
this.el.innerHTML = el.innerHTML;
}
```
TODO: doc
```js
replaceContent(replaceEls) {
if (this.replaceIds.length > 0) {
this.replaceContentByIds(replaceEls);
} else {
this.replaceContentWholesale(replaceEls);
}
}
```
TODO: doc
```js
updateDOM(context) {
try {
const { replaceEls } = context;
this.updateHistoryTitle(context);
if (isExternal(this)) this.rewriteURLs(replaceEls);
this.replaceContent(replaceEls);
} catch (error) {
throw Object.assign(context, { error });
}
}
```
When fetching documents from an external source,
relative URLs will be resolved relative to the current `window.location`.
We can rewrite URL to absolute urls
```js
rewriteURLs(replaceEls) {
replaceEls.forEach(el => {
/* console.time(); */
el.querySelectorAll("[href]").forEach(this.rewriteURL("href"));
el.querySelectorAll("[src]").forEach(this.rewriteURL("src"));
el.querySelectorAll("img[srcset]").forEach(this.rewriteURLSrcSet("srcset"));
el.querySelectorAll("blockquote[cite]").forEach(this.rewriteURL("cite"));
el.querySelectorAll("del[cite]").forEach(this.rewriteURL("cite"));
el.querySelectorAll("ins[cite]").forEach(this.rewriteURL("cite"));
el.querySelectorAll("q[cite]").forEach(this.rewriteURL("cite"));
el.querySelectorAll("img[longdesc]").forEach(this.rewriteURL("longdesc"));
el.querySelectorAll("frame[longdesc]").forEach(this.rewriteURL("longdesc"));
el.querySelectorAll("iframe[longdesc]").forEach(this.rewriteURL("longdesc"));
el.querySelectorAll("img[usemap]").forEach(this.rewriteURL("usemap"));
el.querySelectorAll("input[usemap]").forEach(this.rewriteURL("usemap"));
el.querySelectorAll("object[usemap]").forEach(this.rewriteURL("usemap"));
el.querySelectorAll("form[action]").forEach(this.rewriteURL("action"));
el.querySelectorAll("button[formaction]").forEach(this.rewriteURL("formaction"));
el.querySelectorAll("input[formaction]").forEach(this.rewriteURL("formaction"));
el.querySelectorAll("video[poster]").forEach(this.rewriteURL("poster"));
el.querySelectorAll("object[data]").forEach(this.rewriteURL("data"));
el.querySelectorAll("object[codebase]").forEach(this.rewriteURL("codebase"));
el.querySelectorAll("object[archive]").forEach(this.rewriteURLList("archive"));
/* console.timeEnd(); */
/* el.querySelectorAll("command[icon]").forEach(this.rewriteURL("icon")); */ // obsolte
});
}
rewriteURL(attr) {
return el => {
try {
el.setAttribute(attr, new URL(el.getAttribute(attr), this.href).href);
} catch (e) {
if (process.env.DEBUG)
console.warn(`Couldn't rewrite URL in attribute ${attr} on element`, el);
}
};
}
rewriteURLSrcSet(attr) {
return el => {
try {
el.setAttribute(
attr,
el
.getAttribute(attr)
.split(/\s*,\s*/)
.map(str => {
const pair = str.split(/\s+/);
pair[0] = new URL(pair[0], this.href).href;
return pair.join(" ");
})
.join(", ")
);
} catch (e) {
if (process.env.DEBUG)
console.warn(`Couldn't rewrite URLs in attribute ${attr} on element`, el);
}
};
}
rewriteURLList(attr) {
return el => {
try {
el.setAttribute(
attr,
el
.getAttribute(attr)
.split(/[\s,]+/)
.map(str => new URL(str, this.href).href)
.join(", ")
);
} catch (e) {
if (process.env.DEBUG)
console.warn(`Couldn't rewrite URLs in attribute ${attr} on element`, el);
}
};
}
};
```