2
0
mirror of https://github.com/esiur/iui.git synced 2026-04-04 06:58:22 +00:00

initial commit

This commit is contained in:
2021-02-22 11:39:50 +03:00
commit e82f4bc4cf
87 changed files with 14463 additions and 0 deletions

70
src/Router/Link.js Normal file
View File

@@ -0,0 +1,70 @@
import IUIElement from "../Core/IUIElement.js";
import { IUI } from "../Core/IUI.js";
export default IUI.module(class Link extends IUIElement
{
constructor()
{
//debugger;
super({ cssClass: 'link' });
// super({ cssClass: 'link' });
this._register("route");
this.addEventListener("click",
(e) => {
var r = this.getBoundingClientRect();
this.style.setProperty("--x", (e.x - r.x) + "px");
this.style.setProperty("--y", (e.y - r.y) + "px");
this.style.setProperty("--w", r.width + "px");
this.style.setProperty("--h", r.height + "px");
this.classList.remove(this.cssClass + "-clicked");
void this.offsetWidth;
this.classList.add(this.cssClass + "-clicked");
let url = this.getAttribute("href");
let ok = this._emit("route", { url, cancelable: true, query: this.query});
if (!ok)
return;
//if (url == "#")
// url = router.current.link;
// return;
let target = this.hasAttribute("target") ? document.getElementById(this.getAttribute("target")) : null;
if (url == ":back") {
window.router.back();
return;
}
if (this.query)// || this.hasAttribute(":data"))
window.router.navigate(url || router.current.url, this.query, target);
else if (url != null)
window.router.navigate(url, undefined, target);
}
);
//this._register("click");
}
get link() {
return this.getAttribute("href");
}
set link(value) {
this.setAttribute("href", value);
}
create()
{
}
});

159
src/Router/Route.js Normal file
View File

@@ -0,0 +1,159 @@
import IUIElement from "../Core/IUIElement.js";
import { IUI } from "../Core/IUI.js";
import Router from "./Router.js";
export default IUI.module(class Route extends IUIElement {
constructor() {
super();
this.routes = [];
this.refs = {};
this._register("show");
this._register("hide");
}
async setData(value) {
if (this.hasAttribute("debug"))
debugger;
return await super.setData(value);
}
_updateLinks() {
for (var i = 0; i < this.children.length; i++) {
if (this.children[i] instanceof Route) {
this.routes.push(this.children[i]);
window.router.add(this.children[i], this);
i--;
}
}
}
get link() {
var link = this.name;
var parent = this.parent;
while (parent != null) {
link = parent.name + "/" + link;
parent = parent.parent;
}
return link;
}
get name() {
return this.getAttribute("name");
}
get src() {
return this.getAttribute("src");
}
get dst() {
return this._dst || this.getAttribute("dst");
}
set dst(value){
this._dst = value;
}
get caption() {
return this.getAttribute("caption");
}
get private() {
return this.hasAttribute("private");
}
get icon() {
return this.getAttribute("icon");
}
_getParent() {
let e = null;//this.parentElement;
while (e = this.parentElement) {
if (e instanceof Route || e instanceof Router)
return e;
}
return null;
}
async create() {
//window.router.add(this);
this._updateLinks();
if (this.hasAttribute("src")) {
let src = this.getAttribute("src").replace(/^\/+|\/+$/g, '');
let x = await fetch(src);
if (x.status != 200)
return;
let t = await x.text();
this.innerHTML = t;
//let xeval = (code) => eval(code);
}
// call create for the new elements
var newElements = this.querySelectorAll("*");
for (var i = 0; i < newElements.length; i++) {
// set route for all elements
var el = newElements[i];
// newElements[i].route = this;
el.view = this;
el.route = this;
if (el.hasAttribute("ref")) {
this.refs[el.getAttribute("ref")] = el;
}
if (el instanceof HTMLScriptElement) {
// this because HTML parsers don't evaluate script tag
// xeval.call(el.parentElement, "//# sourceURL=iui://" + src + "\r\n" + el.text);
//let func = new Function("//# sourceURL=iui://" +
// src + "-" + Math.round(Math.random() * 10000) + "\r\n return " + el.text.trim());
let func = new Function("//# sourceURL=iui://" + this.link
+ "\r\n return " + el.text.trim());
let rt = func.call(el.parentElement);
if (typeof (rt) === "object") {
for (var k in rt)
el.parentElement[k] = rt[k];
}
}
}
}
created() {
//this.updateBindings();
}
set(value) {
if (value == this.visible)
return;
if (value) {
this.setAttribute("selected", "");
this._emit("show");
}
else
{
this.removeAttribute("selected");
this._emit("hide");
}
}
get visible() { return this.hasAttribute("selected"); }
set visible(value) { this.set(value); }
});

334
src/Router/Router.js Normal file
View File

@@ -0,0 +1,334 @@
import IUIElement from "../Core/IUIElement.js";
import Route from "./Route.js"
import Target from "./Target.js";
import { IUI } from "../Core/IUI.js";
export default IUI.module(class Router extends Target
{
constructor()
{
super({routes: [], _states: new Map(), active: null, cssClass: "router"});
this._history = [];
//IUI._router = this;
//Object.defineProperty(window, "router", {
// get() {
// if (!IUI._router.isConnected)
// IUI._router = document.getElementsByTagName("i-router")[0];
// return IUI._router;
// }
//});
}
_getRouteParent(route) {
let e = null;
while (e = route.parentElement) {
if (e instanceof Route || e instanceof Router)
return e;
}
return null;
}
add(route, parent = null) {
if (parent == null) {
this.routes.push(route);
}
else {
route.parent = parent;
this.appendChild(route);
//parent.routes.push(route);
}
}
_routeInPath(name, routes)
{
for (var i = 0; i < routes.length; i++)
if (routes[i].name == name)
return routes[i];
return null;
}
getRoute(url, data) {
let p = url.split("/");
let searchRoutes = this.routes;
for (var i = 0; i < p.length; i++) {
var route = this._routeInPath(p[i], searchRoutes);
if (route == null)
return [null, null];
if (i == p.length - 1) {
// return [destination state route (link, icon,..etc) , actual route to view]
if (route.dst == null)
return [route, route];
else {
let dst = route.dst instanceof Function ? route.dst(data) : route.dst;
let url = dst.replace(/^[/]*(.*?)[/]*$/g, '$1').trim();
return [route, this.getRoute(url)[1]];
}
}
searchRoutes = route.routes;
}
}
back() {
//if (this._history.length > 1) {
// let last = this._history[this._history.length - 2];
// this.navigate(last.url, last.data, last.target);
//}
window.history.back();
}
_toQuery(o) {
let rt = [];
for (let i in o)
if (o[i] == undefined)
rt.push(i);
else
rt.push(i + "=" + encodeURI(o[i].toString().replace("&", "&&")));///encodeURIComponent(o[i]));
return rt.join("&");
}
_fromQuery(q) {
let kv = q.replace("&&", "\0").split('&');
let rt = {};
for (let i = 0; i < kv.length; i++) {
let d = kv[i].replace("\0", "&").split('=', 2);
let v = decodeURI(d[1] || ''); //decodeURIComponent(d[1] || '');
if (v != null && v.trim() != '' && !isNaN(v))
v = new Number(v);
rt[d[0]] = v;
}
return JSON.parse(JSON.stringify(rt));
}
async navigate(url, data, target, state)
{
let q = url.match(/^\/*(.*?)\?(.*)$|^\/*(.*)$/);
//debugger;
var path;
// do we have a query string ?
if (q[2] !== undefined) {
path = q[1];
data = this._fromQuery(q[2]);
url = path + "?" + q[2];
}
// do we have data ?
else if (data !== undefined) {
path = q[3];
url = path + "?" + this._toQuery(data);
}
else {
path = q[3];
url = path;
}
let [stateRoute, viewRoute] = this.getRoute(path, data);
if (stateRoute == null)
return;
let ok = this._emit("navigate", { url, stateRoute, viewRoute, base: path, data, cancelable: true });
if (!ok)
return;
// destination view not found
if (viewRoute == null) {
console.log(`Destination route not found ${stateRoute.dst}`);
viewRoute = stateRoute;
}
//let state = null;
//if (data !== undefined) {
// for (let [k, v] of this._states)
// if (v == data) {
// state = k;
// break;
// }
// if (state == null) {
// state = Math.random().toString(36).substr(2, 10);
// this._states.set(state, data);
// }
//}
if (!(target instanceof Target))
target = this;
if (state == null) {
let id = Math.random().toString(36).substr(2, 10);
state = { id, url, data, target, stateRoute, viewRoute };
this._states.set(id, state);
history.pushState(id, stateRoute.caption, this._hash ? "#" + url : "/" + url);
}
this._history.push(state.id);// { url, data, target, stateRoute, viewRoute });
target.show(viewRoute, this.active);
viewRoute.set(true);
this.active = viewRoute;
//{ url: "/", data: null, target: null };
this._emit("route", { route: stateRoute });
viewRoute.query = data || {};
stateRoute.query = viewRoute.query;
target.setLoading(true);
if (stateRoute.dataMap != null) {
// if map function failed to call setData, we will render without it
if (!(await stateRoute.dataMap.render(data || {})))
await stateRoute.render();
if (viewRoute != stateRoute)
await viewRoute.setData(stateRoute.data);
}
else //if (data !== undefined)
await viewRoute.setData(data);
target.setLoading(false);
}
hide() {
// do nothing, we're not here to hide.
}
refresh() {
let state = this.current;
this.navigate(state.url, state.data, state.target, state);
//this.current.render();
//this.current.data = this.current.data;
//if (updateAttributes)
// this.current.updateAttributes(true);
}
show(route, active) {
super.show(route, active);
}
get current() {
return this._states.get(history.state);//.viewRoute;
//return this._history[this._history.length - 1].viewRoute;
}
get previous() {
if (this._history.length > 2)
return this._states.get(this._history[this._history.length - 2]);//.viewRoute;
else
return null;
}
create() {
// save origin
this.origin = window.location.pathname + window.location.search;
}
destroy() {
console.log("Destroyed", this);
}
created()
{
if (this.hasAttribute("type") && this.getAttribute("type").toLowerCase() == "hash")
this._hash = true;
/// find all children
for (var i = 0; i < this.children.length; i++) {
let e = this.children[i];
if (e instanceof Route) {
this.add(e);
if (e.visible)
this.navigate(e.name);
}
}
this._emit("created");
//console.log("Router created", this);
}
connectedCallback() {
//console.log("New router", this);
window.router = this;
let self = this;
window.addEventListener("popstate", function (event) {
//console.log(event);
let stateId = event.state;
let path;
if (self._hash) {
path = window.location.hash;
if (path.length > 0)
path = path.substr(1);
}
else {
path = window.location.pathname;
}
if (stateId != null) {
if (stateId != self._history[self._history.length -1]) {
//this._lastStateId = stateId;
let state = self._states.get(stateId);
self.navigate(path, state.data, state.target, state);
}
else {
console.log("SAME");
}
}
else {
this._lastState = null;
self.navigate(path, undefined, undefined, {});
}
//alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
console.log(document.location.hash, event.state);
});
this._register("navigate");
this._register("route");
this._register("created");
}
});

60
src/Router/Target.js Normal file
View File

@@ -0,0 +1,60 @@
import IUIElement from "../Core/IUIElement.js";
import { IUI } from "../Core/IUI.js";
import Route from "./Route.js";
export default IUI.module(class Target extends IUIElement {
constructor(properties) {
super(IUI.extend(properties, { cssClass: 'target' }));
this._register("show");
this._register("hide");
}
setLoading(value)
{
if (value)
this.classList.add(this.cssClass + "-loading");
else
this.classList.remove(this.cssClass + "-loading");
}
create() {
}
show(route, previous) {
let previousTarget = previous?.target;
route.target = this;
for (var i = 0; i < this.children.length; i++)
if (this.children[i] instanceof Route && this.children[i] != route) {
this.children[i].set(false);
}
//if (previous != null && previous != route && previous.target == this) {
// previous.set(false);
//}
//else
if (previousTarget != null && previousTarget != this) {
previousTarget.hide(this.active);
}
if (route.parentElement != this)
this.appendChild(route);
this._emit("show", { route, previous});
}
hide(route) {
for (var i = 0; i < this.children.length; i++)
if (this.children[i] instanceof Route) {
this.children[i].set(false);
}
this._emit("hide", { route });
}
});