diff --git a/Test/Pages/home.js b/Test/Pages/home.js new file mode 100644 index 0000000..c40873b --- /dev/null +++ b/Test/Pages/home.js @@ -0,0 +1,8 @@ +class Home extends Page { + results = window.test + + render = () => { + } +} + +export default Home \ No newline at end of file diff --git a/Test/shadow.test.js b/Test/shadow.test.js index a97881a..7301fe4 100644 --- a/Test/shadow.test.js +++ b/Test/shadow.test.js @@ -12,15 +12,109 @@ window.testSuites.push( class testShadow { window.register(File, "file-el") let form = {data: "asdf"} const el = window.File(form) - console.log(el, el.$form, el._form) if(!(el.form === form)) { return `State field does not match object passed in!` } } - testRegisterThrowsIfNoConstructorParams() { + testMultiParams() { class File2 extends Shadow { $form + $tag + + constructor(...params) { + super(...params) + } + } + + window.register(File2, "file2-el") + let form = {data: "asdf"} + const el = window.File2(form, "tag") + if(!(el.form === form)) { + return `Form field does not match object passed in!` + } + if(!(el.tag === "tag")) { + return `Tag field does not match object passed in!` + } + } + + onlyGetFieldsNotUsing$() { + class File5 extends Shadow { + $form + $tag + + constructor(...params) { + super(...params) + } + } + + window.register(File5, "file5-el") + let form = {data: "asdf"} + const el = window.File5(form, "tag") + if(el.$tag !== undefined) { + return "Got field the wrong way!" + } + } + + testChangeAttrChangesField() { + class File3 extends Shadow { + $form + $tag + + constructor(...params) { + super(...params) + } + } + + window.register(File3, "file3-el") + let form = {data: "asdf"} + const el = window.File3(form, "tag") + el.setAttribute("tag", "asdf") + if(el.tag !== "asdf") { + return "Field did not change!" + } + } + + testChangeFieldChangesAttr() { + class File4 extends Shadow { + $form + $tag + + constructor(...params) { + super(...params) + } + } + + window.register(File4, "file4-el") + let form = {data: "asdf"} + const el = window.File4(form, "tag") + el.tag = "asdf" + if(el.getAttribute("tag") !== "asdf") { + return "Attribute did not change!" + } + } + + testDefaultStateFieldWorks() { + class File6 extends Shadow { + $form = {data: "asdf"} + + constructor(...params) { + super(...params) + console.log(this.form) + } + } + + window.register(File6, "file6-el") + const el = window.File6() + console.log(el, el.$form, el._form) + if(el.form === undefined) { + return `Default value did not work` + } + } + + testRegisterThrowsIfNoConstructorParams() { + class File3 extends Shadow { + $form constructor() { super() @@ -28,7 +122,7 @@ window.testSuites.push( class testShadow { } try { - window.register(File2, "file2-el") + window.register(File3, "file3-el") } catch(e) {} return "Error not thrown!" diff --git a/Test/test.html b/Test/test.html index 14801c7..4b1f6ed 100644 --- a/Test/test.html +++ b/Test/test.html @@ -4,9 +4,15 @@ Quill - + diff --git a/Test/test.js b/Test/test.js index 96500ce..6b8226a 100644 --- a/Test/test.js +++ b/Test/test.js @@ -15,7 +15,7 @@ window.test = async function() { var start = new Date(); for(let j=0; j 0) { + for(let i=0; i < spaceNum; i++) { + spaces += " " + } + } + console.log(`%c ${fail}${spaces}`, 'border-bottom: 2px solid #e9c9a0; color: rgb(254, 62, 43); border-radius: 10px; padding: 10px'); } else { success++; - console.log(`%c ${testNum}. ${test}`, 'background: #222; color: #00FF00'); + console.log(`%c${testNum}. ${test}`, 'border-bottom: 2px solid #e9c9a0; background: #628c60; color: transparent; border-radius: 10px; padding: 0px 10px 0px 10px'); } } // Need to flush ws buffer since saving is disabled, and that is what usually does the flushing diff --git a/index.js b/index.js index 6c9ab3d..055f9b2 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,61 @@ +/* NAVIGATION */ + +let oldPushState = history.pushState; +history.pushState = function pushState() { + let ret = oldPushState.apply(this, arguments); + window.dispatchEvent(new Event('pushstate')); + window.dispatchEvent(new Event('locationchange')); + return ret; +}; + +window.addEventListener('popstate', () => { + window.dispatchEvent(new Event('locationchange')); +}); + +window.addEventListener('locationchange', locationChange); +let urlBeforeChange = window.location.href; + +window.navigateTo = function(url) { + window.history.pushState({}, '', url); +} + +Object.defineProperty(window, 'routes', { + configurable: true, + enumerable: true, + set: function(newValue) { + Object.defineProperty(window, 'routes', { + value: newValue, + writable: false, + configurable: false, + enumerable: true + }); + + locationChange(); + }, + get: function() { + return window.routes; + } +}); + +function locationChange() { + let URL = window.location.pathname.split("/").filter(d => (d !== 'Web') && (!d.includes('.html'))).join("/") + if(URL === "") URL = "/" + + console.log("Location change: ", URL) + + if(!window.routes[URL]) { + console.error("Quill: no URL for this route: ", URL) + return + } + + let page = new window.routes[URL]() + window.rendering.push(page) + page.render() + window.rendering.pop(page) + + urlBeforeChange = window.location.href; +} + /* $() */ HTMLElement.prototype.$ = function(selector) { @@ -14,6 +72,77 @@ window.$ = function(selector, el = document) { } } +/* CONSOLE */ + +console.red = function(message) { + this.log(`%c${message}`, "color: rgb(254, 79, 42);"); +}; + +console.green = function(message) { + this.log(`%c${message}`, "color: rgb(79, 254, 42);"); + +} + +/* STRING TRANSLATORS */ + +window.css = function css(cssString) { + let container = document.querySelector("style#quillStyles"); + if(!container) { + container = document.createElement('style'); + container.id = "quillStyles"; + document.head.appendChild(container); + } + + let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); + primarySelector = primarySelector.replace(/\*/g, "all"); + primarySelector = primarySelector.replace(/#/g, "id-"); + primarySelector = primarySelector.replace(/,/g, ""); + let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) + if(!stylesheet) { + stylesheet = document.createElement('style'); + stylesheet.id = primarySelector; + stylesheet.appendChild(document.createTextNode(cssString)); + container.appendChild(stylesheet); + } else { + stylesheet.innerText = cssString + } +} + +window.html = function html(htmlString) { + let container = document.createElement('div'); + container.innerHTML = htmlString; + + // If there's only one child, return it directly + if (container.children.length === 1) { + return container.children[0]; + } + + // If there are multiple children, use a DocumentFragment + let fragment = document.createDocumentFragment(); + while (container.firstChild) { + fragment.appendChild(container.firstChild); + } + + return fragment; +}; + +/* COMPATIBILITY */ + +function detectMobile() { + const mobileDeviceRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; + return mobileDeviceRegex.test(navigator.userAgent); +} + +function getSafariVersion() { + const userAgent = navigator.userAgent; + const isSafari = userAgent.includes("Safari") && !userAgent.includes("Chrome"); + if (isSafari) { + const safariVersionMatch = userAgent.match(/Version\/(\d+\.\d+)/); + const safariVersion = safariVersionMatch ? parseFloat(safariVersionMatch[1]) : null; + return safariVersion; + } +} + /* REGISTER */ window.ObservedObject = class ObservedObject { @@ -30,26 +159,21 @@ window.Page = class Page { } window.Shadow = class Shadow extends HTMLElement { - constructor(stateVariables, ...params) { + constructor(stateNames, ...params) { super() - console.log(Object.getOwnPropertyDescriptors(this)) - - if(!stateVariables) { - console.log("No state variables passed in, returning") - return - } - - // State -> Attributes - // set each state value as getter and setter - stateVariables.forEach(attrName => { - const backingFieldName = `_${attrName}`; // Construct the backing field name + // State -> Attributes: set each state value as getter and setter + stateNames.forEach(name => { + const backingFieldName = `_${name}`; // Construct the backing field name + if(this["$" + name] !== undefined) { // User provided a default value + console.log("user default: ", name) + } - Object.defineProperty(this, attrName, { + Object.defineProperty(this, name, { set: function(newValue) { - // console.log(`Setting attribute ${attrName} to `, newValue); + // console.log(`Setting attribute ${name} to `, newValue); this[backingFieldName] = newValue; // Use the backing field to store the value - this.setAttribute(attrName, typeof newValue === "object" ? "{..}" : newValue); // Synchronize with the attribute + this.setAttribute(name, typeof newValue === "object" ? "{..}" : newValue); // Synchronize with the attribute }, get: function() { // console.log("get: ", this[backingFieldName]) @@ -60,42 +184,31 @@ window.Shadow = class Shadow extends HTMLElement { }); }); - // if there are more than one, expect them to be named - if(stateVariables.length > 1) { - if(typeof params[0] !== "object") { - console.error(`Quill: elements with multiple state initializers must structure them like {color: "blue", type: 2}`) - return - } + // Match params to state names + switch(stateNames.length) { + case 0: + console.log("No state variables passed in, returning") + return + default: let i = -1 - for (let param in params[0]) { + for (let param of params) { i++ - let paramName = "$" + param - if(stateVariables[i] !== paramName) { - if(!stateVariables[i]) { - console.error(`${el.prototype.constructor.name}: state ${state} must be initialized`) - } else { - continue - } - console.error(`${el.prototype.constructor.name}: state initializer ${i} ${paramName} should be ${stateVariables[0]}`) + + if(i > stateNames.length) { + console.error(`${el.prototype.constructor.name}: too many parameters for state!`) return - } else { - this[paramName] = params[0][param] + } + + if(this[stateNames[i]] === undefined) { + this[stateNames[i]] = param } } - } else { - if(!params[0] && stateVariables[0]) { - console.log(params, stateVariables) - console.error(`${el.prototype.constructor.name}: state initializer $${params[0]} should be ${stateVariables[0]}`) - return - } - this[stateVariables[0]] = params[0] } - // Check if all state variables are set - for(let state of stateVariables) { - // console.log(elem[state]) - if(!this[state]) { - console.error(`Quill: state ${state} must be initialized`) + // Check if all state variables are set. If not set, check if it is a user-initted value + for(let state of stateNames) { + if(this[state] === undefined) { + console.error(`Quill: state "${state}" must be initialized`) } } } @@ -139,42 +252,31 @@ window.Registry = class Registry { } static render = (el, parent) => { - if(!(el.constructor.name === "Home")) { - window.rendering[window.rendering.length-1].appendChild(el) + let renderParent = window.rendering[window.rendering.length-1] + if(renderParent) { + renderParent.appendChild(el) } window.rendering.push(el) el.render() window.rendering.pop(el) } - static registerHome(el) { - // console.log(params, stateVariables) - let home = new el() - Registry.render(home) - return home - } - static register = (el, tagname) => { - if(el.prototype.constructor.name === "Home") { - Registry.registerHome(el) - return - } - - let stateVariables = this.parseClassFields(el).filter(field => field.startsWith('$')); - let stateVariablesWithout$ = stateVariables.map(str => str.substring(1)); + let stateVariables = this.parseClassFields(el).filter(field => field.startsWith('$')).map(str => str.substring(1)); // Observe attributes Object.defineProperty(el, 'observedAttributes', { get: function() { - return stateVariablesWithout$; + return stateVariables; } }); // Attributes -> State Object.defineProperty(el.prototype, 'attributeChangedCallback', { value: function(name, oldValue, newValue) { - const fieldName = `$${name}`; - if (fieldName in this && this[fieldName] !== newValue && newValue !== "[object Object]") { + const fieldName = `${name}`; + let blacklistedValues = ["[object Object]", "{..}", this[fieldName]] + if (stateVariables.includes(fieldName) && !blacklistedValues.includes(newValue)) { this[fieldName] = newValue; } }, @@ -186,18 +288,52 @@ window.Registry = class Registry { // Actual Constructor window[el.prototype.constructor.name] = function (...params) { - // console.log(params, stateVariables) - let elIncarnate = new el(stateVariablesWithout$, ...params) + let elIncarnate = new el(stateVariables, ...params) + // detect default variables, give em the treatment + // consider going back to this as the method, since i can then maybe fix the cosntructor issue as well + // user can define non-initted and non-outside variables in the constructor Registry.render(elIncarnate) return elIncarnate } } } -window.register = Registry.register -window.rendering = [] + +/* DEFAULT WRAPPERS */ + +window.a = function a({ href, name=href } = {}) { + let link = document.createElement("a") + link.setAttribute('href', href); + link.innerText = name + return link +} + +window.img = function img({width="", height="", src=""}) { + let image = new Image() + if(width) image.style.width = width + if(height) image.style.height = height + if(src) image.src = src + return image +} + +window.p = function p(innerText) { + let para = document.createElement("p") + para.innerText = innerText + return para +} + +window.div = function (innerText) { + let div = document.createElement("div") + div.innerText = innerText + return div +} + +window.span = function (innerText) { + let span = document.createElement("span") + span.innerText = innerText + return span +} /* PROTOTYPE FUNCTIONS */ - HTMLElement.prototype.addAttribute = function(name) { this.setAttribute(name, "") } @@ -340,108 +476,5 @@ HTMLElement.prototype.onClick = function(func) { return this } -/* DEFAULT WRAPPERS */ - -window.a = function a({ href, name=href } = {}) { - let link = document.createElement("a") - link.setAttribute('href', href); - link.innerText = name - return link -} - -window.img = function img({width="", height="", src=""}) { - let image = new Image() - if(width) image.style.width = width - if(height) image.style.height = height - if(src) image.src = src - return image -} - -window.p = function p(innerText) { - let para = document.createElement("p") - para.innerText = innerText - return para -} - -window.div = function (innerText) { - let div = document.createElement("div") - div.innerText = innerText - return div -} - -window.span = function (innerText) { - let span = document.createElement("span") - span.innerText = innerText - return span -} - -/* STRING TRANSLATORS */ - -window.css = function css(cssString) { - let container = document.querySelector("style#quillStyles"); - if(!container) { - container = document.createElement('style'); - container.id = "quillStyles"; - document.head.appendChild(container); - } - - let primarySelector = cssString.substring(0, cssString.indexOf("{")).trim(); - primarySelector = primarySelector.replace(/\*/g, "all"); - primarySelector = primarySelector.replace(/#/g, "id-"); - primarySelector = primarySelector.replace(/,/g, ""); - let stylesheet = container.querySelector(`:scope > style[id='${primarySelector}']`) - if(!stylesheet) { - stylesheet = document.createElement('style'); - stylesheet.id = primarySelector; - stylesheet.appendChild(document.createTextNode(cssString)); - container.appendChild(stylesheet); - } else { - stylesheet.innerText = cssString - } -} - -window.html = function html(htmlString) { - let container = document.createElement('div'); - container.innerHTML = htmlString; - - // If there's only one child, return it directly - if (container.children.length === 1) { - return container.children[0]; - } - - // If there are multiple children, use a DocumentFragment - let fragment = document.createDocumentFragment(); - while (container.firstChild) { - fragment.appendChild(container.firstChild); - } - - return fragment; -}; - -/* COMPATIBILITY */ - -function detectMobile() { - const mobileDeviceRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; - return mobileDeviceRegex.test(navigator.userAgent); -} - -function getSafariVersion() { - const userAgent = navigator.userAgent; - const isSafari = userAgent.includes("Safari") && !userAgent.includes("Chrome"); - if (isSafari) { - const safariVersionMatch = userAgent.match(/Version\/(\d+\.\d+)/); - const safariVersion = safariVersionMatch ? parseFloat(safariVersionMatch[1]) : null; - return safariVersion; - } -} - -/* CONSOLE */ - -console.red = function(message) { - this.log(`%c${message}`, "color: rgb(254, 79, 42);"); -}; - -console.green = function(message) { - this.log(`%c${message}`, "color: rgb(79, 254, 42);"); - -} \ No newline at end of file +window.register = Registry.register +window.rendering = [] \ No newline at end of file diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..e41f8b5 --- /dev/null +++ b/todo.txt @@ -0,0 +1,11 @@ +Constructor Parameters + + In swift, can choose to pass in parameters from outside or not + This works because of labels and ability to override based on parameters + + In JS, how would I init state from within but pass in another parameter? + +Refresh in Subpages + + The hosting service needs to redirect all pages to the main index.html + This is the problem with Forum - you navigate, it's fine, refresh, goes away