diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 0e8373e..0000000 --- a/src/App.js +++ /dev/null @@ -1,21 +0,0 @@ -import {TurtleElement} from "./Element/Element.js" -export class TurtleApp { - constructor(element) { - if(element instanceof HTMLElement) element = new TurtleElement(element) - this.element = element - this.modules = [] - this.data = {} - } - - use(module) { - let m = new module(this).init(this) - this.modules.push(m) - return m - } - - render(html){ - this.element.HTML = html - } - - -} \ No newline at end of file diff --git a/src/Component/Component.js b/src/Component/Component.js deleted file mode 100644 index f5c6249..0000000 --- a/src/Component/Component.js +++ /dev/null @@ -1,118 +0,0 @@ -import {TurtleElement} from "../Element/Element.js" -import { generateKey ,measureTime} from "../utils.js" -import { processDOM } from "./dom.js" -import {update} from "./update.js" - -export class TurtleComponent extends HTMLElement { - constructor() { - super() - this.componentID = generateKey() - window.TURTLE.TURTLE_COMPONENTS[this.tagName] = window.TURTLE.TURTLE_COMPONENTS[this.tagName] ?? {} - window.TURTLE.TURTLE_COMPONENTS[this.tagName][this.componentID] = this - this.props = {} - this.data = {} - this.refs = {} - this.states = {} - this.updateDependents = null - this.shouldUpdate = true - this.isUpdate = false - this.component_data = {} - this.usingShadowDOM = false - this.wrapping = null - let t = document.createElement("template") - t.innerHTML = this.innerHTML - this.wrapping = t.content - this.template = document.createElement("template") - if(this.getAttribute("t-props")){ - this.props = window.TURTLE.TURTLE_PROPS[this.getAttribute("t-props")] - delete window.TURTLE.TURTLE_PROPS[this.getAttribute("t-props")] - this.removeAttribute("t-props") - } - } - - setState(name,value){ - this.states[name] = value - if(this.shouldUpdate){ - if(this.updateDependents == null || this.updateDependents.includes(name)) this.requestRender() - } - } - - onCreate() {} - onRemove() {} - onRendered() {} - onUpdate() {} - onRender() {} - onEffect(){} - start() {} - - async requestRender() { - if (!this.isUpdate) { - let result = processDOM(this.template.content.childNodes) - this.refs = result.refs - this.mem = result.mem - result.events.forEach(e => { - let events = window.TURTLE.TURTLE_EVENTS[e.key] ?? {} - Object.keys(events).forEach(ev => { - e.node.addEventListener(ev, events[ev]) - }) - }) - - requestAnimationFrame(()=>{ - update(this.mem,this) - this.isUpdate = true - this.onRendered() - this.onEffect() - }) - - let r = this.usingShadowDOM ? this.shadowRoot : this - r.textContent = "" - r.appendChild(this.template.content) - }else{ - requestAnimationFrame(() => { - - update(this.mem, this) - this.isUpdate = true - this.onUpdated() - this.onEffect() - }) - } - } - - async connectedCallback() { - await this.start() - await this.onCreate() - await this.requestRender() - } - - async disconnectedCallback() { - await this.onRemove() - } - -} - -export function generateComponent(element,name,props){ - let component = document.createElement(name) - if(component instanceof HTMLUnknownElement){ - throw "Invalid component name" - } - - if(!(component instanceof TurtleComponent)){ - throw "Invalid component " - } - - component.props = props ?? {} - if(element instanceof HTMLElement) element.appendChild(component) - else if(element instanceof TurtleElement) element.addChild(component) - else throw "Invalid element" - return component -} - -export function component(name, callback) { - let $Component = class extends TurtleComponent { - async start() { - this.template.innerHTML = await callback.bind(this)(this,this.props) - } - } - - window.customElements.define(name, $Component) -} \ No newline at end of file diff --git a/src/Component/DOMUpdate.js b/src/Component/DOMUpdate.js deleted file mode 100644 index 93e6a7d..0000000 --- a/src/Component/DOMUpdate.js +++ /dev/null @@ -1,48 +0,0 @@ -export function updateDOM(refs, context) { - refs.refTextNodes.forEach(function(ref) { - updateTextNode(ref.node, ref.content, context) - }) - - refs.refAttrs.forEach(function(ref) { - updateAttr(ref.node,ref.name,ref.value,context) - }) - -} - -function updateTextNode(node, template, data) { - node.textContent = template.replace(/{{(.*?)}}/g, (match) => { - let expr = match.split(/{{|}}/)[1] - try { - let value = (new Function(`return ${expr}`).apply(data)) - return value - } catch (err) { - if (window.TURTLE_DEV) { - document.body.innerHTML = ` -
Error when render content of component : ".. {{${expr}}}.."
-
${err.stack}
- ` - throw "err" - } - return "?" - } - }) - -} - -function updateAttr(node, attrName, template, data) { - node.setAttribute(attrName, template.replace(/{{(.*?)}}/g, (match) => { - let expr = match.split(/{{|}}/)[1] - try { - return (new Function(`return ${expr}`)).apply(data) - } catch (err) { - if (window.TURTLE_DEV) { - document.body.innerHTML = ` -
Error in expression : ".. {{${expr}}}.."
-
${err.stack}
- ` - throw "err" - } - return "?" - } - })) -} \ No newline at end of file diff --git a/src/Component/directives.js b/src/Component/directives.js deleted file mode 100644 index 918ecb0..0000000 --- a/src/Component/directives.js +++ /dev/null @@ -1,22 +0,0 @@ -import { generateKey } from "../utils.js" -export function props(p) { - let key = generateKey() - window.TURTLE.TURTLE_PROPS[key] = p - return `t-props="${key}"` -} - -export function events(e) { - let key = generateKey() - window.TURTLE.TURTLE_EVENTS[key] = e - return `t-event="${key}"` -} - -export function ref(name) { - return `t-ref="${name}"` -} - -export function attrs(d) { - let key = generateKey() - window.TURTLE.TURTLE_ATTRS[key] = d - return `t-attrs="${key}"` -} \ No newline at end of file diff --git a/src/Component/dom.js b/src/Component/dom.js deleted file mode 100644 index 68b60c6..0000000 --- a/src/Component/dom.js +++ /dev/null @@ -1,70 +0,0 @@ -import { TurtleElement } from "../Element/Element.js" - -function matches(content) { - return /{{(.*?)}}/g.test(content) -} - -export function processDOM(nodes) { - let mem = [] - let refs = {} - let events = [] - for (let i = 0; i < nodes.length; i++) { - let node = nodes[i] - if (node.nodeType == Node.TEXT_NODE) { - if (matches(node.textContent)) { - mem.push({ - node: node, - temp: node.textContent - }) - } - } else if (node.nodeType == Node.ELEMENT_NODE) { - Array.from(node.attributes).forEach((attr) => { - if (attr.localName == "t-ref") { - refs[attr.value] = new TurtleElement(node) - node.removeAttribute("t-ref") - return - } - - if (attr.localName == "t-attrs") { - let attrs = window.TURTLE.TURTLE_ATTRS[attr.value] - Object.keys(attrs).forEach(name => { - node[name] = attrs[name] - }) - node.removeAttribute("t-attrs") - return - } - - if (attr.localName == "t-event") { - events.push({ - node: node, - key: attr.value - }) - node.removeAttribute("t-event") - return - } - - if (matches(attr.localName)) { - mem.push({ - node: node, - attr: attr.localName, - temp: attr.value - }) - } - - - }) - - let child = processDOM(node.childNodes) - mem.push(...child.mem) - let r = child.refs - refs = { ...refs, ...r } - events.push(...child.events) - } - } - - return { - mem, - refs, - events - } -} \ No newline at end of file diff --git a/src/Component/update.js b/src/Component/update.js deleted file mode 100644 index 3f27446..0000000 --- a/src/Component/update.js +++ /dev/null @@ -1,29 +0,0 @@ -export function update(mem, context) { - for (let i = 0; i < mem.length; i++) { - let info = mem[i] - let template = info.temp - let node = info.node - let value = template.replace(/{{(.*?)}}/g, (match) => { - let expr = match.split(/{{|}}/)[1] - try { - let value = (new Function(`return ${expr}`).apply(context)) - return value - } catch (err) { - if (window.TURTLE.TURTLE_DEV) { - document.body.innerHTML = ` -
Error when render content of component : ".. {{${expr}}}.."
-
${err.stack}
- ` - throw "err" - } - return "?" - } - }) - - if (info.atrr) { - node.setAttribute(info.atrr, value) - } else { - node.textContent = value - } - } -} \ No newline at end of file diff --git a/src/Element/Element.js b/src/Element/Element.js deleted file mode 100644 index 78db192..0000000 --- a/src/Element/Element.js +++ /dev/null @@ -1,143 +0,0 @@ -import { TurtleListElement } from "./ListElement.js" -import { generateKey } from "../utils.js" -export class TurtleElement { - constructor(element) { - if (element instanceof HTMLElement) { - element.turtle = element.turtle ?? {} - if (!element.turtle.key) { - element.turtle.key = generateKey() - } - } else { - element = element.HTMLElement - } - this.HTMLElement = element - } - - get parent() { - return new TurtleElement(this.HTMLElement.parentElement) - } - - get firstChild() { - return new TurtleElement(this.HTMLElement.firstElementChild) - } - - get lastChild() { - return new TurtleElement(this.HTMLElement.lastElementChild) - } - - get previousSibling() { - return new TurtleElement(this.HTMLElement.previousElementSibling) - } - - get nextSibling() { - return new TurtleElement(this.HTMLElement.previousElementSibling) - } - - get id() { - return this.HTMLElement.id - } - - set id(ID) { - this.HTMLElement.id = ID - } - - get classList() { - return this.HTMLElement.classList - } - - set HTML(html) { - this.HTMLElement.innerHTML = html - } - - get HTML() { - return this.HTMLElement.innerHTML - } - - set text(t) { - this.HTMLElement.textContent = t - } - - get text() { - return this.HTMLElement.textContent - } - - set val(value) { - this.HTMLElement.value = value - } - - get val() { - return this.HTMLElement.value - } - - set checked(state) { - this.HTMLElement.checked = state - } - - get checked() { - return this.HTMLElement.checked - } - - set disabled(state) { - this.HTMLElement.disabled = state - } - - get disabled() { - return this.HTMLElement.disabled - } - - get attrs() { - return this.HTMLElement.attibutes - } - - get styles() { - return this.HTMLElement.style - } - - get childNodes() { - return this.HTMLElement.childNodes - } - - computedStyle(pseudoElement) { - return getComputedStyle(this.HTMLElement, pseudoElement) - } - - click() { - this.HTMLElement.click() - } - - focus() { - this.HTMLElement.focus() - } - - remove() { - this.HTMLElement.remove() - } - - addChild(child) { - if (child instanceof HTMLElement) { - child = new TurtleElement(child) - } - this.HTMLElement.appendChild(child.HTMLElement) - } - - select(query) { - let result = this.HTMLElement.querySelector(query) - if (!result) { - throw `Invaild HTMLElement by query : ${query}` - } - } - - selectAll(query) { - let result = this.HTMLElement.querySelectorAll(query) - return new TurtleListElement(this, result) - } - - on(event, callback) { - this.HTMLElement.addEventListener(event, callback) - } - - off(event, callback) { - this.HTMLElement.removeEventListener(event, callback) - } - -} \ No newline at end of file diff --git a/src/Element/ListElement.js b/src/Element/ListElement.js deleted file mode 100644 index dc4fbf9..0000000 --- a/src/Element/ListElement.js +++ /dev/null @@ -1,34 +0,0 @@ -import {TurtleElement} from "./Element.js" -export class TurtleListElement{ - constructor(parent,list){ - this.parent = parent - this.list = list - } - - get length (){ - return this.list.length - } - - each(callback, context){ - this.list.forEach(element=>{ - callback.bind(context)(new TurtleElement(element)) - }) - } - - remove (idx){ - try { - this.list[idx].remove() - } catch (err) { - throw `Cannot remove element in list by index :${idx}` - } - } - - get(idx) { - try { - return new TurtleElement(this.list[idx]) - } catch (err) { - throw `Cannot get element in list by index :${idx}` - } - } - -} \ No newline at end of file diff --git a/src/Module.js b/src/Module.js deleted file mode 100644 index d78a8e0..0000000 --- a/src/Module.js +++ /dev/null @@ -1,9 +0,0 @@ -import { generateKey } from "./utils.js" - -export class TurtleModule{ - constructor(app){ - this.app = app - this.data = {} - this.id = generateKey() - } -} \ No newline at end of file diff --git a/src/Modules/Event.js b/src/Modules/Event.js deleted file mode 100644 index 9c982ab..0000000 --- a/src/Modules/Event.js +++ /dev/null @@ -1,60 +0,0 @@ -import {TurtleModule} from "../Module.js" - -window.TURTLE_EVENTS = {} - -export class TurtleEvent { - constructor(name, data) { - this.name = name - this.data = data - } - - emit() { - let listeners = window.TURTLE_EVENTS[this.name] - if (!listeners) { - return - } - - for (var idx in listeners) { - listeners[idx].callback.bind(listeners[idx].context)(this.data) - } - } -} - -export class EventModule extends TurtleModule { - constructor(app) { - super(app) - } - - init(app) { - this.app.event = this - return this - } - - emit(name, data) { - return new TurtleEvent(name, data) - } - - on(name, callback, context = this) { - if (!window.TURTLE_EVENTS[name]) window.TURTLE_EVENTS[name] = [] - window.TURTLE_EVENTS[name].push({ - callback: callback, - context: context - }) - - } - - off(name, callback) { - if (!window.TURTLE_EVENTS[name]) window.TURTLE_EVENTS[name] = [] - let listeners = window.TURTLE_EVENTS[name] - for (var idx in listeners) { - if (listeners[idx].callback === callback) { - window.TURTLE_EVENTS[name].splice(idx, 1) - } - } - } - - deleteAllEventListener(name) { - window.TURTLE_EVENTS[name] = [] - } -} - diff --git a/src/Modules/Http.js b/src/Modules/Http.js deleted file mode 100644 index 30f2b32..0000000 --- a/src/Modules/Http.js +++ /dev/null @@ -1,171 +0,0 @@ -import {TurtleModule} from "../Module.js" - -export class TurtleRequest { - constructor(configs) { - this.xhr = new XMLHttpRequest() - this.URL = new URL(configs.url) - this.method = configs.method - this.data = configs.data - this.body = configs.body - this.timeout = configs.timeout - this.headers = configs.headers ?? {} - this.withCredentials = configs.withCredentials ?? false - this.responseType = configs.responseType - this.sended = false - this.events = { - abort: new Function(), - done: new Function(), - timeout:new Function(), - error:new Function() - } - if (configs.auth) { - this.auth = { - username: configs.auth.username, - password: configs.auth.password - } - } else { - this.auth = null - } - } - - setHeader(name, value) { - this.headers[name] = value - } - - getHeader(name){ - return this.headers[name] - } - - deleteHeader(name){ - delete this.headers[name] - } - - setParam(name, value) { - this.URL.searchParams.set(name, value) - } - - deleteParam(name) { - this.URL.searchParams.delete(name) - } - - getParam(name) { - this.URL.searchParams.get(name) - } - - cancel() { - if (!this.sended) { - throw "Cannot cancel request!" - } else { - this.xhr.abort() - this.events.abort() - } - } - - send() { - return new Promise((resolve, reject) => { - if (this.auth != null) { - this.xhr.open(this.method, this.URL.href, true, this.auth.username, this.auth.password) - } else { - this.xhr.open(this.method, this.URL.href, true) - } - this.xhr.timeout = this.timeout - if (this.headers) { - Object.keys(this.headers).forEach(header => { - this.xhr.setRequestHeader(header, this.headers[header]) - }) - } - - this.xhr.responseType = this.responseType - this.xhr.withCredentials = this.withCredentials - if (this.data) { - this.xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8") - this.xhr.send(JSON.stringify(this.data)) - } else { - this.xhr.send(this.body) - } - let ctx = this - this.xhr.onerror = function(err) { - ctx.events.error() - reject(err) - } - this.xhr.onabort = function() { - ctx.events.abort() - resolve({ - abort: true - }) - } - - this.xhr.ontimeout = function() { - ctx.events.timeout() - resolve({ - timeouted: true - }) - } - - this.xhr.onload = function() { - if (ctx.xhr.readyState == 4) { - console.log(ctx.xhr) - let res = new TurtleResponse(ctx) - ctx.events.done(res) - resolve({ - response: res, - timeouted: false - }) - } - }; - }) - } -} - - -export class TurtleResponse { - constructor(req) { - this.req = req - this.xhr = this.req.xhr - this.status = this.xhr.status - this.statusText = this.xhr.statusText - this.URL = this.xhr.responseURL - this.type = this.xhr.responseType - } - - getHeader(name) { - return this.xhr.getResponseHeader(name) - } - - getAllHeaders(){ - return this.xhr.getAllResponseHeaders() - } - - text() { - return this.xhr.responseText - } - - json() { - try { - return JSON.parse(this.xhr.response) - } catch (err) { - throw "Cannot parse JSON from response" - } - } - - raw() { - return this.xhr.response - } -} - -export class HttpModule extends TurtleModule{ - constructor(app){ - super(app) - } - - init(){ - this.app.http = this - return this - } - - send(configs){ - let req = new TurtleRequest(configs) - return req.send() - } - -} \ No newline at end of file diff --git a/src/Modules/Router.js b/src/Modules/Router.js deleted file mode 100644 index 719e085..0000000 --- a/src/Modules/Router.js +++ /dev/null @@ -1,197 +0,0 @@ -import { TurtleModule } from "../Module.js" - - -function matches(list = {}, route) { - let routes = Object.keys(list) - - for (var i = 0; i < routes.length; i++) { - let passed = 0 - let data = {} - let parsed = parsePath(routes[i]) - if (parsed.length != route.length) continue - for (var j = 0; j < parsed.length; j++) { - if (parsed[j][0] == ":") { - passed++ - let name = parsed[j].substr(1, parsed[j].length) - data[name] = route[j] - } else if (parsed[j] == "*" || parsed[j] == route[j]) { - passed++ - continue - } - } - if (passed == parsed.length) return [true, data, routes[i]] - } - return [false, {}] -} - -function emitEvent(router, name, data) { - if (!router.events[name]) router.events[name] = [] - router.events[name].forEach(callback => { - callback(data) - }) -} - -function parsePath(path) { - let tok = path.split("/") - tok = tok.filter(t => t != "") - return tok -} - -function resolveRoute(router, url = new URL("/", window.load)) { - let parsedURL = parsePath(url.pathname) - let [passed, data, matched] = matches(router.routes, parsedURL) - let info = { - matched: matched, - params: data, - query: url.searchParams, - path: url.pathname - } - if (passed) { - - let routeConfig = Object.assign(router.routes[matched]) - let component = routeConfig.component ?? null - router.info = info - if (routeConfig.protect) { - let passed = routeConfig.protect({ router, info }) - if (!passed) { - emitEvent(router, "notallow", { router, info }) - return - } - } - - if (routeConfig.resolver) { - let signal = routeConfig.resolver({ router, info }) - if (signal) { - if (signal.redirect) { - router.redirect(signal.redirect) - } - - if (signal.changeComponent) { - component = signal.changeComponent - } - - if (signal.abort) { - return - } - - if (signal.block) { - emitEvent(router, "notallow", { router, info }) - } - - } - } - - if (routeConfig.callback) routeConfig.callback({ router, info }) - if (routeConfig.title) document.title = routeConfig.title - if (component) { - if(router.currentComponent){ - //window.customElements.upgrade(component,class extends HTMLElement{},{}) - } - if (!router.alwayLoadContent) { - if (!window.TURTLE.TURTLE_COMPONENTS[component.toUpperCase()]) { - if (routeConfig.loader) routeConfig.loader({ router, info }) - } - } else { - if (routeConfig.loader) routeConfig.loader({ router, info }) - } - router.currentComponent = component - let $component = document.createElement(component) - $component.router = {} - $component.router = { - router, - info - } - router.element.textContent = "" - router.element.appendChild($component) - } - - } else { - emitEvent(router, "notfound", { router, info }) - } - - emitEvent(router, "routeloaded", { router, info }) -} - -export class RouterModule extends TurtleModule { - - constructor(app) { - super(app) - } - - init(app) { - this.app.router = this - return this - } - - define(config) { - if (config.element) { - this.element = document.querySelector(config.element) - } else { - throw "Invalid element !" - } - this.currentComponent = null - this.type = config.type || "hash" - this.info = {} - this.routes = config.routes ?? {} - this.events = { - onRouteMatched: [], - onRouteChange: [] - } - this.alwayLoadContent = config.alwayLoadContent ?? true - } - - redirect(path, replace = false) { - if (this.type == "hash") { - if (!replace) - window.location.hash = path - else { - window.history.replaceState(null, null, `#${path}`) - path = window.location.hash.slice(1) - let url = new URL(path, window.location.origin) - resolveRoute(this, url) - } - } else { - if (!replace) - window.history.pushState(null, null, `${path}`) - else - window.history.replaceState(null, null, `${path}`) - } - } - on(name, callback) { - if (!this.events[name]) this.events[name] = [] - this.events[name].push(callback) - } - start() { - let context = this - if (this.type == "hash") { - let path = window.location.hash.slice(1) - let url = new URL(path, window.location.origin) - resolveRoute(context, url) - } - - if (this.type == "history") { - resolveRoute(context, new URL("", window.location.href)) - } - - window.addEventListener("click", function(e) { - let target = e.target - if (target.dataset["tlink"]) { - e.preventDefault() - let link = target.dataset["tlink"] - context.redirect(link, target.dataset["treplace"] ? true : false) - } - }) - - if (this.type == "hash") { - window.addEventListener("hashchange", function(e) { - let path = window.location.hash.slice(1) - let url = new URL(path, window.location.origin) - resolveRoute(context, url) - }) - } else { - window.addEventListener("popstate", function(e) { - resolveRoute(context, new URL("", window.location.href)) - }) - } - } -} \ No newline at end of file diff --git a/src/Modules/States.js b/src/Modules/States.js deleted file mode 100644 index 1431a86..0000000 --- a/src/Modules/States.js +++ /dev/null @@ -1,84 +0,0 @@ -import { TurtleModule } from "../Module.js" -import { generateKey } from "../utils.js" - - - -export class TurtleStoreState { - constructor(store, value) { - this.store = store - this._value = value - this.bindings = [] - this.actions_fn = {} - this.actions = {} - } - - addAction(name, fn) { - this.actions_fn[nane] = fn - this.actions[name] = function(value) { - this.actions_fn[name](value) - }.bind(this) - this.actions[name].attrs = { - state: this - } - return this.actions[name] - } - - bind(component, state) { - this.bindings.push({ - component: component, - state: state - }) - } - - set value(val) { - this._value = val - let r = [] - this.bindings.forEach((b, idx) => { - try { - b.component.setState(b.state, val) - } catch (err) { - this.bindings.splice(idx, 1) - } - }) - - } - - get value() { - return this._value - } -} - -export class TurtleStore { - constructor(name) { - this.name = name - this.store_key = generateKey() - this.states = {} - window.TURTLE.TURTLE_STORES[name] = this - } - - setState(name, value) { - if (!this.states[name]) this.states[name] = new TurtleStoreState(this, value) - this.states[name].value = value - } - - getState(name) { - return this.states[name].value - } -} - - -export class StateModule extends TurtleModule { - constructor(app) { - super(app) - } - - init(app) { - this.app.store = this - return this - } - - create(name) { - return new TurtleStore(name) - } - -} \ No newline at end of file diff --git a/src/Selector.js b/src/Selector.js deleted file mode 100644 index c3dd7fb..0000000 --- a/src/Selector.js +++ /dev/null @@ -1,45 +0,0 @@ -import {TurtleElement} from "./Element/Element.js" - -function wrap(context,by,query,res){ - if (!res && context.usePlaceholderElement) { - return context.placeholderElement - }else if(res){ - return new TurtleElement(res) - }else{ - throw `Cannot find element by ${by} :'${query}' ! ` - } -} - -const placeholder = new TurtleElement(document.createElement("div")) -export class TurtleSelector{ - constructor(root = document,placeholderElement = placeholder){ - this.root = root - this.usePlaceholderElement = false - this.placeholderElement = placeholderElement - } - - byId(id){ - let result = document.getElementById(id) - return wrap(this,"id",id,result) - } - - byClassName(className,idx=0){ - let result = document.getElementsByClassName(className)[idx] - return wrap(this,"Class Name",className,result) - } - - byTag(tag,idx=0){ - let result = document.getElementsByTagName(tag)[idx] - return wrap(this,"Tag Name",className,result) - } - - byName(name, idx = 0) { - let result = document.getElementsByName(name)[idx] - return wrap(this, "Name", name, result) - } - - byQuery(query,idx=0){ - let result = document.querySelectorAll(query)[idx] - return wrap(this, "Query", query, result) - } -} \ No newline at end of file diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..c2c7417 --- /dev/null +++ b/src/app.js @@ -0,0 +1,35 @@ +import {parseContents,process} from './component/process.js'; +import {initComponent} from './component/component.js'; + +export class TurtleApp{ + constructor(root){ + this.root = root + this.data = {} + this.components = {} + this.modules = [] + } + + use(module,configs ={}){ + this.modules.push(module.init(this,configs)) + } + + render(contents ){ + contents = parseContents(contents) + process(contents,this.root,{ + app:this, + _memories:[] + }) + + } + + createComponent(name,fn){ + initComponent(name,fn,this) + } + + +} + +export function createApp(root) { + return new TurtleApp(root) +} + diff --git a/src/component/base.js b/src/component/base.js new file mode 100644 index 0000000..07472aa --- /dev/null +++ b/src/component/base.js @@ -0,0 +1,96 @@ +import { parseContents, process } from './process.js'; +import {TurtleComponentState} from './states.js'; + +function evalInScope(js, contextAsScope) { + return new Function(`with (this) { return (${js}); }`).call(contextAsScope); +} + + +class TurtleBaseComponent extends HTMLElement { + constructor() { + super() + this.states = {} + this.app = null + this._memories = [] + this.props = [] + this._refs = {} + this.template = "" + this.fn = new Function() + this.onRender = new Function() + this.onUpdate = new Function() + this.onCreate = new Function() + this.onDestroy = new Function() + } + + get refs(){ + return this._refs + } + + createState(value){ + let state = new TurtleComponentState(value,this) + return state + } + + requestRender() { + let contents = parseContents(this.template) + process(contents, this, this) + + for (let i = 0; i < this._memories.length; i++) { + let d = this._memories[i] + if (d.type == "attr") { + d.node.setAttribute( + d.attr, + evalInScope(d.expr, this) + ) + } + + if (d.type == "text") { + d.node.textContent = evalInScope(d.expr, this) + } + + if (d.type == "html") { + d.node.innerHTML = evalInScope(d.expr, this) + } + } + + this.onRender.bind(this)() + + } + + forceUpdate() { + for (let i = 0; i < this._memories.length; i++) { + let d = this._memories[i] + if (d.type == "attr") { + d.node.setAttribute( + d.attr, + evalInScope(d.expr, this) + ) + } + + if (d.type == "text") { + d.node.textContent = evalInScope(d.expr, this) + } + + if (d.type == "html") { + d.node.innerHTML = evalInScope(d.expr, this) + } + } + + this.onUpdate.bind(this)() + } + + connectedCallback() { + this.onCreate.bind(this)() + this.template = this.fn.bind(this)(this, ...this.props) + this.requestRender() + } + + disconnectedCallback(){ + this.onDestroy.bind(this)() + } + + + +} + +window.customElements.define("turtle-component", TurtleBaseComponent) \ No newline at end of file diff --git a/src/component/component.js b/src/component/component.js new file mode 100644 index 0000000..b3b616e --- /dev/null +++ b/src/component/component.js @@ -0,0 +1,5 @@ +export function initComponent(name,fn, context) { + context.components[name] ={ + fn:fn + } +} \ No newline at end of file diff --git a/src/component/derectives.js b/src/component/derectives.js new file mode 100644 index 0000000..bdb2493 --- /dev/null +++ b/src/component/derectives.js @@ -0,0 +1,12 @@ +window.TURTLE_DERECTIVES = {} + +function generateKey() { + return (Math.floor(Math.random() * 999999) * Date.now()).toString(16) +} + +export function props(...args) { + let key = generateKey() + window.TURTLE_DERECTIVES[key] = args + return ` t-props="${key}" ` +} + diff --git a/src/component/process.js b/src/component/process.js new file mode 100644 index 0000000..0d864da --- /dev/null +++ b/src/component/process.js @@ -0,0 +1,66 @@ +function evalInScope(js, contextAsScope) { + return new Function(`with (this) { return (${js}); }`).call(contextAsScope); +} + +export function parseContents(contents) { + let xmlDoc = new DOMParser().parseFromString( + `${contents}`, + "text/xml" + ) + + return xmlDoc.querySelector("root") +} + +export function processAttributes(node, root, context = {}) { + if (node && root) { + for (var i = 0; i < node.attributes.length; i++) { + let attribute = node.attributes[i]; + if (attribute.localName.indexOf('bind-') === 0) { + let attrName = attribute.localName.substring('bind-'.length); + context._memories.push({ type: "attr", attr: attrName, node: root, expr: attribute.value }) + root.setAttribute(attrName, attribute.value); + }else if (attribute.localName.indexOf('on-') === 0) { + let eventName = attribute.localName.substring('on-'.length); + root.addEventListener(eventName,evalInScope(attribute.value,context)) + } else if (attribute.localName == "t-text") { + context._memories.push({ type: "text", node: root, expr: attribute.value }) + } else if (attribute.localName == "t-ref") { + context._refs[attribute.value] = root + } else if (attribute.localName == "t-html") { + context._memories.push({ type: "html", node: root, expr: attribute.value }) + } else { + root.setAttribute(attribute.name, attribute.value); + } + } + } +} + + +export function process(contents, root, context) { + for (let i = 0; i < contents.childNodes.length; i++) { + let node = contents.childNodes[i] + if (node.nodeType === Node.ELEMENT_NODE) { + let element = document.createElement(node.localName) + if (element instanceof HTMLUnknownElement) { + if (context.app.components[node.localName]) { + let component = document.createElement("turtle-component") + component.setAttribute("name", node.localName) + component.app = context.app + component.fn = context.app.components[node.localName].fn + if (node.hasAttribute("t-props")) { + component.props = window.TURTLE_DERECTIVES[node.getAttribute("t-props")] + } + root.appendChild(component) + } + } else { + processAttributes(node, element, context) + process(node, element, context) + root.appendChild(element) + } + } + + if (node.nodeType === Node.TEXT_NODE) { + root.appendChild(document.createTextNode(node.textContent)) + } + } +} \ No newline at end of file diff --git a/src/component/states.js b/src/component/states.js new file mode 100644 index 0000000..e2685ce --- /dev/null +++ b/src/component/states.js @@ -0,0 +1,18 @@ +export class TurtleComponentState{ + constructor(value, component){ + this._value = value + this._component = component + this.reflection = true + } + + get value(){ + return this._value + } + + set value(val){ + this._value = val + if(this.reflection ){ + this._component.forceUpdate() + } + } +} \ No newline at end of file diff --git a/src/modules/router/base.js b/src/modules/router/base.js new file mode 100644 index 0000000..a7b089f --- /dev/null +++ b/src/modules/router/base.js @@ -0,0 +1,76 @@ +import { parseContents, process } from '../../component/process.js'; +import { matches } from './parser.js'; + +export class TurtleRouterModule { + constructor(app, configs) { + this.element = configs.element ?? document.createElement("div") + this.routes = {} + this.params = {} + this.events = { + pagenotfound:[], + notallow:[] + } + this.query = new URLSearchParams() + this.app = app + this.matched = null + app.router = this + } + + static init(app, configs) { + return new TurtleRouterModule(app, configs) + } + + async matches(path) { + Object.keys(this.routes).forEach(async pattern => { + let result = matches(pattern, path) + if (result.matched) { + let content_fn = new Function() + let route_info = this.routes[pattern] + if (route_info.beforeLoadContent) route_info.beforeLoadContent.bind(this)() + this.matched = pattern + this.params = result.params + this.url = path + if (route_info.protect) { + let res = await route_info.protect() + if(!res){ + this.triggerEvent("notallow") + return + } + } + if (route_info.loader) content_fn = await route_info.loader.bind(this) + if (route_info.content) content_fn = await route_info.content.bind(this) + if (route_info.onContentLoaded) route_info.onContentLoaded.bind(this)() + if (route_info.callback) { await route_info.callback.bind(this)() } + let template = content_fn(this, result) + template = parseContents(template) + process(template, this.element, { + app: this.app, + _memories: [] + }) + }else{ + this.triggerEvent("pagenotfound") + } + + }) + } + + on(name, callback ){ + this.events[name].push(callback) + } + + triggerEvent(name){ + this.events[name].forEach(callback=>{ + callback() + }) + } + + + start() { + let ctx = this + window.addEventListener("hashchange", function(e) { + let path = window.location.hash.slice(1) + ctx.matches(path) + }) + } + +} \ No newline at end of file diff --git a/src/modules/router/page.js b/src/modules/router/page.js new file mode 100644 index 0000000..e5bc521 --- /dev/null +++ b/src/modules/router/page.js @@ -0,0 +1,5 @@ +export function createPage(fn) { + return { + template: fn + } +} \ No newline at end of file diff --git a/src/modules/router/parser.js b/src/modules/router/parser.js new file mode 100644 index 0000000..0a9067d --- /dev/null +++ b/src/modules/router/parser.js @@ -0,0 +1,28 @@ + +export function matches(pattern, urlToMatch) { + const patternParts = pattern.split('/'); + const urlParts = urlToMatch.split('/'); + + if (patternParts.length !== urlParts.length) { + return { matched: false }; + } + + const params = {}; + + for (let i = 0; i < patternParts.length; i++) { + const patternPart = patternParts[i]; + const urlPart = urlParts[i]; + + if (patternPart.startsWith(':')) { + const paramName = patternPart.slice(1); + params[paramName] = urlPart; + } else if (patternPart === '*') { + continue + } else if (patternPart !== urlPart) { + return { matched: false }; + } + } + + return { matched: true, params }; +} + diff --git a/src/turtle.js b/src/turtle.js index 75594f0..f8fd07e 100644 --- a/src/turtle.js +++ b/src/turtle.js @@ -1,21 +1,5 @@ -window.TURTLE = { - TURTLE_APPS :{}, - TURTLE_DEV:true, - TURTLE_COMPONENTS :{}, - TURTLE_EVENTS :{}, - TURTLE_PROPS :{}, - TURTLE_CONTEXT:{}, - TURTLE_STORES:{}, - TURTLE_ATTRS:{}, -} - -export * from "./App.js" -export * from "./Selector.js" -export * from "./Element/Element.js" -export * from "./Element/ListElement.js" -export * from "./Component/Component.js" -export * from "./Component/directives.js" -export * from "./Modules/Router.js" -export * from "./Modules/Http.js" -export * from "./Modules/Event.js" -export * from "./Modules/States.js" +export * from "./app.js" +export * from "./component/base.js" +export * from "./component/component.js" +export * from "./component/derectives.js" +export * from "./modules/router/base.js" diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index 65940cd..0000000 --- a/src/utils.js +++ /dev/null @@ -1,11 +0,0 @@ -export function generateKey(){ - let key = ((Math.floor(Math.random()*99999))*Date.now()).toString(16) - return `_${key}` -} - -export function measureTime(func) { - let start = performance.now(); - func(); - let end = performance.now(); - return end-start; -} \ No newline at end of file