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