aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2023-08-11 20:42:02 +0530
committerAkshay Nair <phenax5@gmail.com>2023-08-11 20:42:02 +0530
commit695365fa359fb98f14d06c205159739077f67fed (patch)
tree6b99fa9a2f18c30ab784f6f9890a409337c9dbf0
parente7d02fc16d4b814c44752a49bca5e7854fed719f (diff)
downloadcss-everything-695365fa359fb98f14d06c205159739077f67fed.tar.gz
css-everything-695365fa359fb98f14d06c205159739077f67fed.zip
feat: complets form submit example
-rw-r--r--TODO.norg4
-rw-r--r--examples/api/content.css26
-rw-r--r--examples/api/signup.css77
-rw-r--r--examples/api/style.css32
-rw-r--r--src/eval.ts27
-rw-r--r--src/index.ts41
-rw-r--r--tests/eval.spec.ts1
7 files changed, 155 insertions, 53 deletions
diff --git a/TODO.norg b/TODO.norg
index f85a80f..31a0413 100644
--- a/TODO.norg
+++ b/TODO.norg
@@ -4,8 +4,8 @@
- (x) `get-variable`
- (x) `update-variable`
- (x) Use css units for `delay` function
- - ( ) Specify node type - `button(id)` or `button#id`
- - ( ) `--cssx-attr-*` for attributes
+ - (x) Specify node type - `button(id)` or `button#id`
+ - (x) attributes
- ( ) `--cssx-text` (and maybe `--cssx-html`?)
- ( ) Improve error messages
- ( ) Evaluate `calc`?
diff --git a/examples/api/content.css b/examples/api/content.css
deleted file mode 100644
index 5463ba2..0000000
--- a/examples/api/content.css
+++ /dev/null
@@ -1,26 +0,0 @@
-
-#output-container-content {
- padding: 0.5rem;
- margin-top: 1rem;
- border: 1px solid #888;
-
- --cssx-children: counter-text btn-increment;
- --count: '0';
-}
-#output-container-content::after {
- content: "Loaded from external stylesheet";
-}
-
-#counter-text {}
-#counter-text::after { content: var(--count) }
-
-#btn-increment {
- display: inline-block;
- border: 1px solid gray;
- padding: 0.3rem 0.6rem;
- font-size: 0.9rem;
- cursor: pointer;
-
- --cssx-on-click: update(output-container-content, --count, '99');
-}
-#btn-increment::after { content: "Increment" }
diff --git a/examples/api/signup.css b/examples/api/signup.css
new file mode 100644
index 0000000..c5970c7
--- /dev/null
+++ b/examples/api/signup.css
@@ -0,0 +1,77 @@
+
+#signup-page-content {
+ border: 1px solid #888;
+ padding: 1rem;
+ max-width: 700px;
+ margin: 1rem auto;
+
+ --cssx-children: form#form;
+ --count: '0';
+}
+
+#signup-page-content::before {
+ content: "Sign-Up";
+ display: block;
+ font-size: 2rem;
+ border-bottom: 1px solid gray;
+}
+
+#form {
+ display: block;
+
+ --cssx-on-submit:
+ prevent-default()
+ add-class(form, 'submitting')
+ request('https://httpbin.org/post', POST)
+ remove-class(form, 'submitting')
+ add-class(form, 'submitted')
+ ;
+
+ --cssx-children: input#input-email input#input-password actions #message;
+}
+#form.submitted #message::after {
+ display: block;
+ content: "Form submitted successfully";
+}
+#form.submitting #submit-btn {
+ pointer-events: none;
+ opacity: .5;
+}
+
+#form input {
+ display: block;
+ width: 100%;
+ padding: 0.4rem 0.8rem;
+ margin-top: 1rem;
+}
+
+#input-email {
+ --cssx-on-mount:
+ set-attr('type', 'email')
+ set-attr('name', 'email')
+ set-attr('required', 'true')
+ set-attr('placeholder', 'Email. Eg:- mail@postbox.com')
+ ;
+}
+
+#input-password {
+ --cssx-on-mount:
+ set-attr('type', 'password')
+ set-attr('name', 'password')
+ set-attr('required', 'true')
+ set-attr('placeholder', 'Password. Eg:- password, password1, password2, password123')
+ ;
+}
+
+#actions {
+ text-align: right;
+ padding-top: 1rem;
+ --cssx-children: button#submit-btn;
+}
+
+#submit-btn {
+ padding: 0.4rem 0.7rem;
+ --cssx-on-mount: set-attr('type', 'submit');
+}
+#submit-btn::after { content: "Sign-Up"; }
+
diff --git a/examples/api/style.css b/examples/api/style.css
index cdf4559..b79f265 100644
--- a/examples/api/style.css
+++ b/examples/api/style.css
@@ -1,34 +1,38 @@
body {
- --cssx-children: button#load-btn output-container;
+ --cssx-children: button#signup-btn signup-page;
+}
+body * {
+ box-sizing: border-box;
}
-#load-btn {
+#signup-btn {
display: inline-block;
background: #5180e9;
color: #000;
padding: 0.5rem 1rem;
cursor: pointer;
+ --cssx-on-mount: set-attr('type', 'button');
+
--cssx-on-click:
- add-class(output-container, 'loading')
- add-class(load-btn, 'loading')
- load-cssx(output-container-content, './content.css')
- remove-class(output-container, 'loading')
- remove-class(load-btn, 'loading')
- delay(0.7s)
- js-eval('alert("Loaded page")')
+ add-class(signup-page, 'loading')
+ add-class(signup-btn, 'loading')
+ delay(0.5s)
+ load-cssx(signup-page-content, './signup.css')
+ remove-class(signup-page, 'loading')
+ remove-class(signup-btn, 'loading')
;
}
-#load-btn::after { content: "Click me for magic"; }
-#load-btn.loading {
+#signup-btn::after { content: "Register now to start your free trail for $99"; }
+#signup-btn.loading {
pointer-events: none;
opacity: 0.4;
}
-#output-container {
- --cssx-children: output-container-content;
+#signup-page {
+ --cssx-children: signup-page-content;
}
-#output-container.loading::after {
+#signup-page.loading::after {
content: "Loading...";
}
diff --git a/src/eval.ts b/src/eval.ts
index 466cafd..191a76b 100644
--- a/src/eval.ts
+++ b/src/eval.ts
@@ -9,6 +9,10 @@ export type EvalActions = {
loadCssx(id: string, url: string): Promise<string>
getVariable(name: string): Promise<string | undefined>
updateVariable(id: string, varName: string, value: string): Promise<void>
+ setAttribute(name: string, value: string): Promise<void>
+ withEvent(fn: (e: any) => void): Promise<void>
+ getFormData(): Promise<FormData | undefined>
+ sendRequest(_: { method: string, url: string, data: FormData | undefined }): Promise<void>
// calculate ??
}
@@ -47,6 +51,7 @@ const getFunctions = (name: string, args: Expr[], actions: EvalActions) =>
await actions.removeClass(id, classes)
}
},
+
delay: async () => {
const num = await evalExpr(args[0], actions)
console.log(num)
@@ -56,6 +61,7 @@ const getFunctions = (name: string, args: Expr[], actions: EvalActions) =>
const js = await evalExpr(args[0], actions)
js && (await actions.jsEval(js))
},
+
'load-cssx': async () => {
const id = await evalExpr(args[0], actions)
const url = await evalExpr(args[1], actions)
@@ -63,6 +69,7 @@ const getFunctions = (name: string, args: Expr[], actions: EvalActions) =>
await actions.loadCssx(id, url)
}
},
+
var: async () => {
const varName = await evalExpr(args[0], actions)
const defaultValue = await evalExpr(args[1], actions)
@@ -76,5 +83,25 @@ const getFunctions = (name: string, args: Expr[], actions: EvalActions) =>
actions.updateVariable(id, varName, value)
}
},
+
+ 'set-attr': async () => {
+ const name = await evalExpr(args[0], actions)
+ const value = await evalExpr(args[1], actions)
+ if (name && value) {
+ actions.setAttribute(name, value)
+ }
+ },
+ 'prevent-default': async () => actions.withEvent(e => e.preventDefault()),
+
+ 'request': async () => {
+ const url = await evalExpr(args[0], actions)
+ const method = args[1] ? (await evalExpr(args[1], actions) ?? 'post') : 'post'
+
+ if (url) {
+ const data = await actions.getFormData()
+ await actions.sendRequest({ method, url, data })
+ }
+ },
+
_: () => Promise.reject(new Error('not supposed to be here')),
})
diff --git a/src/index.ts b/src/index.ts
index 7e73cf5..88347df 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -5,6 +5,8 @@ const UNSET_PROPERTY_VALUE = '<unset>'
const EVENT_HANDLERS = {
click: '--cssx-on-click',
load: '--cssx-on-load',
+ mount: '--cssx-on-mount',
+ submit: '--cssx-on-submit',
}
const injectStyles = () => {
@@ -33,7 +35,7 @@ const getChildrenIds = ($element: Element) => {
return value.split(/(\s*,\s*)|\s+/g).filter(Boolean)
}
-const getEvalActions = ($element: Element): EvalActions => ({
+const getEvalActions = ($element: Element, event: any): EvalActions => ({
addClass: async (id, cls) => document.getElementById(id)?.classList.add(cls),
removeClass: async (id, cls) =>
document.getElementById(id)?.classList.remove(cls),
@@ -65,29 +67,44 @@ const getEvalActions = ($element: Element): EvalActions => ({
$el.style.setProperty(varName, JSON.stringify(value))
}
},
+ setAttribute: async (name, value) => {
+ $element.setAttribute(name, value)
+ },
+ withEvent: async (fn) => fn(event),
+ getFormData: async () => $element.nodeName === 'FORM' ? new FormData($element as HTMLFormElement) : undefined,
+ sendRequest: async ({ url, method, data }) => {
+ await fetch(url, { method, body: data })
+ // TODO: Handle response?
+ },
})
-const handleEvents = async ($element: Element) => {
- for (const [event, property] of Object.entries(EVENT_HANDLERS)) {
+const handleEvents = async ($element: Element, isNewElement: boolean = false) => {
+ for (const [eventType, property] of Object.entries(EVENT_HANDLERS)) {
const handlerExpr = getPropertyValue($element, property)
if (handlerExpr) {
- ;($element as any)[`on${event}`] = async () => {
- console.log(`Triggered event: ${event}`)
+ const eventHandler = async (event: any) => {
+ console.log(`Triggered event: ${eventType}`)
const exprs = parse(handlerExpr)
for (const expr of exprs) {
- await evalExpr(expr, getEvalActions($element))
+ await evalExpr(expr, getEvalActions($element, event))
}
}
+
+ if (eventType === 'mount') {
+ if (isNewElement) setTimeout(eventHandler)
+ } else {
+ ;($element as any)[`on${eventType}`] = eventHandler
+ }
}
}
}
let nodeCount = 0
-const manageElement = async ($element: Element) => {
+const manageElement = async ($element: Element, isNewElement: boolean = false) => {
if (nodeCount++ > 100) return // NOTE: Temporary. To prevent infinite rec
- await handleEvents($element)
+ await handleEvents($element, isNewElement)
const childrenIds = getChildrenIds($element)
if (childrenIds.length > 0) {
@@ -98,12 +115,14 @@ const manageElement = async ($element: Element) => {
$element.appendChild($childrenRoot)
for (const childId of childrenIds) {
- const [tag, id] = childId.split('#')
+ let isNewElement = false;
+ const selector = childId.split('#')
+ const [tag, id] = selector.length >= 2 ? selector : ['div', ...selector]
const $child =
$childrenRoot.querySelector(`:scope > #${id}`) ??
- Object.assign(document.createElement(tag || 'div'), { id })
+ (isNewElement = true, Object.assign(document.createElement(tag || 'div'), { id }))
$childrenRoot.appendChild($child)
- await manageElement($child)
+ await manageElement($child, isNewElement)
}
}
}
diff --git a/tests/eval.spec.ts b/tests/eval.spec.ts
index 8b7424f..49ef4c4 100644
--- a/tests/eval.spec.ts
+++ b/tests/eval.spec.ts
@@ -10,6 +10,7 @@ describe('eval', () => {
loadCssx: jest.fn(),
getVariable: jest.fn(),
updateVariable: jest.fn(),
+ setAttribute: jest.fn(),
}
it('should add classes', async () => {