From 412d3791a4a0e07d5e896c5570b9376f646caf4f Mon Sep 17 00:00:00 2001
From: Joeylene <23741509+jorenrui@users.noreply.github.com>
Date: Mon, 12 Feb 2024 14:06:05 +0800
Subject: [PATCH 01/29] refactor: relocate state related logic to state.js
---
lib/main.js | 77 ++++++++--------------------------------------------
lib/state.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 81 insertions(+), 65 deletions(-)
create mode 100644 lib/state.js
diff --git a/lib/main.js b/lib/main.js
index 508f769..9377453 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -1,39 +1,15 @@
import Entity from './entity'
-import MiniArray from './helpers/array'
import { Lexer } from './generators/lexer'
import { observeDOM } from './generators/observer'
+import { State } from './state'
import { Events } from './entity/events'
let nativeProps = Object.getOwnPropertyNames(window)
const MiniJS = (() => {
- window.proxyWindow = null
-
let _debug = false
let _elements = []
- let _variables = []
-
- const watchHandler = {
- set: function (target, property, value) {
- // Set variable to new value
- target[property] = value
-
- // Store to localstorage
- if (property[0] === '$') {
- localStorage.setItem(property, JSON.stringify(value))
- }
-
- if (_variables.includes(property)) {
- updateStates(property)
- _addMethodsToVariables([property])
- }
-
- return true
- },
- get: function (target, property) {
- return target[property]
- },
- }
+ const state = new State()
async function init() {
// Automatically initialize when the script is loaded
@@ -41,13 +17,13 @@ const MiniJS = (() => {
let startTime = performance.now()
_setDebugMode()
- _setProxyWindow()
+ state.setProxyWindow()
Events.initValidEvents()
_findElements()
_initializeGlobalVariables()
- _addMethodsToVariables()
+ state.attachVariableHelpers()
Events.applyEvents(_elements)
- updateStates()
+ state.triggerDOMUpdate()
_listenToDOMChanges()
// Temporarily commented out - to be reviewed
// _evaluateLoadEvents();
@@ -85,26 +61,10 @@ const MiniJS = (() => {
})
}
- function _addMethodsToVariables(variables = _variables) {
- variables.forEach((variable) => {
- if (
- Array.isArray(proxyWindow[variable]) &&
- !(proxyWindow[variable] instanceof MiniArray)
- ) {
- proxyWindow[variable] = new MiniArray(...proxyWindow[variable])
- }
- })
- }
-
- function _setProxyWindow() {
- proxyWindow = new Proxy(window, watchHandler)
- }
-
function _setDebugMode() {
- if (_debug) {
- console.log('MiniJS Debug Mode Enabled')
- Lexer.debug = true
- }
+ if (!_debug) return
+ console.log('MiniJS Debug Mode Enabled')
+ Lexer.debug = true
}
function _initializeGlobalVariables() {
@@ -134,19 +94,6 @@ const MiniJS = (() => {
})
}
- async function updateStates(property = null) {
- for (const entity of _elements) {
- if (
- entity.variables.includes(property) ||
- property == null ||
- entity.uuid == property ||
- entity.parent?.uuid == property
- ) {
- await entity.attributes.evaluate()
- }
- }
- }
-
function _findElements() {
const elems = document.body.getElementsByTagName('*')
@@ -188,13 +135,13 @@ const MiniJS = (() => {
_elements = newElements
},
get variables() {
- return _variables
+ return state.variables
},
- set variables(newVarList) {
- _variables = newVarList
+ set variables(variables) {
+ state.variables = variables
},
get window() {
- return proxyWindow
+ return state.window
},
tryFromLocal,
}
diff --git a/lib/state.js b/lib/state.js
new file mode 100644
index 0000000..4fcc0bb
--- /dev/null
+++ b/lib/state.js
@@ -0,0 +1,69 @@
+import MiniArray from './helpers/array'
+
+export class State {
+ static isLocalState(variable) {
+ return variable[0] === '$'
+ }
+
+ constructor() {
+ this.window = null
+ this.variables = []
+ }
+
+ setProxyWindow() {
+ this.window = this.create(window)
+ }
+
+ create(object) {
+ const ctx = this
+
+ return new Proxy(object, {
+ set: function (target, property, value) {
+ if (State.isLocalState(property))
+ ctx.setLocalState(target, property, value)
+ else ctx.setState(target, property, value)
+
+ return true
+ },
+ get: function (target, property) {
+ return target[property]
+ },
+ })
+ }
+
+ setLocalState(target, property, value) {
+ localStorage.setItem(property, JSON.stringify(value))
+ this.setState(target, property, value)
+ }
+
+ setState(target, property, value) {
+ target[property] = value
+
+ if (!this.variables.includes(property)) return
+ this.triggerDOMUpdate(property)
+ this.attachVariableHelpers()
+ }
+
+ triggerDOMUpdate(state) {
+ for (const entity of MiniJS.elements) {
+ const shouldUpdate =
+ entity.variables.includes(state) ||
+ state == null ||
+ entity.uuid == state ||
+ entity.parent?.uuid == state
+
+ if (shouldUpdate) entity.attributes.evaluate()
+ }
+ }
+
+ attachVariableHelpers(variables = this.variables) {
+ variables.forEach((variable) => {
+ if (
+ Array.isArray(this.window[variable]) &&
+ !(this.window[variable] instanceof MiniArray)
+ ) {
+ this.window[variable] = new MiniArray(...this.window[variable])
+ }
+ })
+ }
+}
From 666430810fc82a5ab1982af46c5a076eb632c049 Mon Sep 17 00:00:00 2001
From: Joeylene <23741509+jorenrui@users.noreply.github.com>
Date: Mon, 12 Feb 2024 15:55:58 +0800
Subject: [PATCH 02/29] chore: add note to update states
---
lib/state.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/state.js b/lib/state.js
index 4fcc0bb..c546e32 100644
--- a/lib/state.js
+++ b/lib/state.js
@@ -52,6 +52,7 @@ export class State {
entity.uuid == state ||
entity.parent?.uuid == state
+ // TODO: Only update relevant attributes that uses those variables
if (shouldUpdate) entity.attributes.evaluate()
}
}
From fcf16e5d651797a35745902a0e1116c5b9d0ab6d Mon Sep 17 00:00:00 2001
From: Joeylene <23741509+jorenrui@users.noreply.github.com>
Date: Mon, 12 Feb 2024 15:56:09 +0800
Subject: [PATCH 03/29] refactor: update initial text of stored valeu
---
index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/index.html b/index.html
index ebe09e5..fdff595 100644
--- a/index.html
+++ b/index.html
@@ -1304,7 +1304,7 @@
Storing to Local Storage:
class="ml-3 mt-2 text-lg font-medium font-mono"
:text="$storedValue"
>
- Update and Reload
+ Loading...
From a996de7ed68c13a0452418c808b88bc285d76ce6 Mon Sep 17 00:00:00 2001
From: Joeylene <23741509+jorenrui@users.noreply.github.com>
Date: Mon, 12 Feb 2024 16:05:46 +0800
Subject: [PATCH 04/29] fix: exclude object properties in tracked variables
---
lib/entity.js | 26 ++++++++++++--------------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/lib/entity.js b/lib/entity.js
index ff82aaa..00cdb73 100644
--- a/lib/entity.js
+++ b/lib/entity.js
@@ -86,33 +86,31 @@ export default class Entity {
_initVariables() {
this.variables = [...new Set(this.variables)]
- MiniJS.variables = [...new Set(MiniJS.variables.concat(this.variables))]
+
+ const trackedVariables = []
this.variables.forEach((variable) => {
- if (variable.startsWith('el.')) {
+ if (variable.startsWith('el.') || variable === 'el') {
this.setAsParent()
if (!this.parent) this.parent = this.getParent()
- const varName = variable.replace('el.', '')
-
if (!window[this.uuid]) window[this.uuid] = {}
-
- // ! FIXME: Any changes to el.varName isn't being watched
- window[this.uuid][varName] = MiniJS.tryFromLocal(
- variable.replace('el.', this.uuid + '.')
- )
-
- if (!this.variables.includes(this.uuid)) this.variables.push(this.uuid)
} else if (typeof window[variable] === 'function') {
this.variables.splice(this.variables.indexOf(variable), 1)
MiniJS.variables.splice(MiniJS.variables.indexOf(variable), 1)
} else {
- window[variable] = variable.startsWith('$')
- ? MiniJS.tryFromLocal(variable)
- : window[variable]
+ const [identifier] = variable.split('.')
+
+ window[identifier] = variable.startsWith('$')
+ ? MiniJS.tryFromLocal(identifier)
+ : window[identifier]
+
+ trackedVariables.push(identifier)
}
})
+
+ MiniJS.variables = [...new Set(MiniJS.variables.concat(trackedVariables))]
}
async _interpret(expr, options = {}) {
From a4e4cd70f14cb6e61ac04a8e70989e5dadf3edff Mon Sep 17 00:00:00 2001
From: Joeylene <23741509+jorenrui@users.noreply.github.com>
Date: Mon, 12 Feb 2024 17:12:58 +0800
Subject: [PATCH 05/29] feat: setup proxy object for el variables
---
lib/entity.js | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/lib/entity.js b/lib/entity.js
index 00cdb73..3cfb76b 100644
--- a/lib/entity.js
+++ b/lib/entity.js
@@ -91,11 +91,15 @@ export default class Entity {
this.variables.forEach((variable) => {
if (variable.startsWith('el.') || variable === 'el') {
- this.setAsParent()
+ if (this.uuid == null) {
+ this.setAsParent()
- if (!this.parent) this.parent = this.getParent()
+ if (!this.parent) this.parent = this.getParent()
- if (!window[this.uuid]) window[this.uuid] = {}
+ window[this.uuid] = MiniJS.state.create({}, this.uuid)
+ }
+
+ trackedVariables.push(this.uuid)
} else if (typeof window[variable] === 'function') {
this.variables.splice(this.variables.indexOf(variable), 1)
MiniJS.variables.splice(MiniJS.variables.indexOf(variable), 1)
From 53e65aa5fb5cc82764f47d18d379bd351ea38115 Mon Sep 17 00:00:00 2001
From: Joeylene <23741509+jorenrui@users.noreply.github.com>
Date: Mon, 12 Feb 2024 17:13:23 +0800
Subject: [PATCH 06/29] refactor: use local variables instead of el variables
for modal
---
index.html | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/index.html b/index.html
index fdff595..8e87b61 100644
--- a/index.html
+++ b/index.html
@@ -1996,11 +1996,11 @@ Tonic Dialog:
Open Modal
From c926d9b1500dddf3e16e48d84ed0f5d9442e8320 Mon Sep 17 00:00:00 2001
From: Joeylene <23741509+jorenrui@users.noreply.github.com>
Date: Mon, 12 Feb 2024 19:20:44 +0800
Subject: [PATCH 07/29] feat: add dependency-based state update and add
reactive entity variables
---
demo/observer.html | 33 +++++++------
lib/entity.js | 30 +++++++-----
lib/main.js | 11 ++---
lib/state.js | 118 +++++++++++++++++++++++++++++++++++++++------
4 files changed, 146 insertions(+), 46 deletions(-)
diff --git a/demo/observer.html b/demo/observer.html
index 69254ae..d7bc15a 100644
--- a/demo/observer.html
+++ b/demo/observer.html
@@ -13,51 +13,56 @@
-
-
-
-
diff --git a/lib/entity.js b/lib/entity.js
index 3cfb76b..24411c0 100644
--- a/lib/entity.js
+++ b/lib/entity.js
@@ -12,6 +12,7 @@ export default class Entity {
this.events = new Events(this)
this.attributes = new Attributes(this)
+ MiniJS.state.addEntity(this)
if (MiniJS.debug) this.element.dataset.entityId = this.id
}
@@ -86,7 +87,6 @@ export default class Entity {
_initVariables() {
this.variables = [...new Set(this.variables)]
-
const trackedVariables = []
this.variables.forEach((variable) => {
@@ -96,13 +96,17 @@ export default class Entity {
if (!this.parent) this.parent = this.getParent()
- window[this.uuid] = MiniJS.state.create({}, this.uuid)
+ window[this.id] = MiniJS.state.create({}, this.id)
}
- trackedVariables.push(this.uuid)
+ MiniJS.state.addDependency(this.id, this.id)
+
+ if (variable !== 'el') {
+ const [_, varName] = variable.split('.')
+ MiniJS.state.addEntityDependency(this.id, varName, this.id)
+ }
} else if (typeof window[variable] === 'function') {
this.variables.splice(this.variables.indexOf(variable), 1)
- MiniJS.variables.splice(MiniJS.variables.indexOf(variable), 1)
} else {
const [identifier] = variable.split('.')
@@ -110,11 +114,14 @@ export default class Entity {
? MiniJS.tryFromLocal(identifier)
: window[identifier]
+ MiniJS.state.addDependency(identifier, this.id)
trackedVariables.push(identifier)
}
})
- MiniJS.variables = [...new Set(MiniJS.variables.concat(trackedVariables))]
+ MiniJS.state.variables = [
+ ...new Set(MiniJS.state.variables.concat(trackedVariables)),
+ ]
}
async _interpret(expr, options = {}) {
@@ -135,8 +142,6 @@ export default class Entity {
return await engine.interpret(this)
}
- /* Note: I don't this getParent() is needed,
- since el. variables should use the current element's uuid instead. */
getParent() {
if (this.isParent()) {
return this
@@ -155,7 +160,6 @@ export default class Entity {
}
generateEntityUUID() {
- // Suggestion: we can use crypto.randomUUID(). Tho crypto only works in secure contexts
return 'Entity' + Date.now() + Math.floor(Math.random() * 10000)
}
@@ -197,6 +201,7 @@ export default class Entity {
if (!entity) continue
variables.push(...entity.variables)
+ MiniJS.state.removeEntity(entity)
entity.events.dispose()
}
@@ -215,16 +220,19 @@ export default class Entity {
(variable) => !usedVariables.includes(variable)
)
- MiniJS.variables = MiniJS.variables.filter(
+ MiniJS.state.variables = MiniJS.state.variables.filter(
(variable) => !unusedVariables.includes(variable)
)
unusedVariables.forEach((variable) => {
- if (variable.startsWith('el.')) {
+ if (variable.startsWith('el.') || variable === 'el') {
+ delete window[this.id]
const varName = variable.replace('el.', '')
- if (window[this.uuid]?.[varName]) delete window[this.uuid]
+ MiniJS.state.disposeDependency(this.id)
+ MiniJS.state.disposeEntityDependency(this.id, varName)
} else {
delete window[variable]
+ MiniJS.state.disposeDependency(variable)
}
})
}
diff --git a/lib/main.js b/lib/main.js
index 9377453..30de2ab 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -23,7 +23,7 @@ const MiniJS = (() => {
_initializeGlobalVariables()
state.attachVariableHelpers()
Events.applyEvents(_elements)
- state.triggerDOMUpdate()
+ state.evaluate()
_listenToDOMChanges()
// Temporarily commented out - to be reviewed
// _evaluateLoadEvents();
@@ -134,15 +134,12 @@ const MiniJS = (() => {
set elements(newElements) {
_elements = newElements
},
- get variables() {
- return state.variables
- },
- set variables(variables) {
- state.variables = variables
- },
get window() {
return state.window
},
+ get state() {
+ return state
+ },
tryFromLocal,
}
})()
diff --git a/lib/state.js b/lib/state.js
index c546e32..736b3a0 100644
--- a/lib/state.js
+++ b/lib/state.js
@@ -8,18 +8,83 @@ export class State {
constructor() {
this.window = null
this.variables = []
+
+ this.entities = new Map() // key: entityID, value: entity
+ this.dependencies = new Map() // key: variable, value: entityID
+ this.entityDependencies = new Map() // key: entityID.variable, value: entityID
}
setProxyWindow() {
this.window = this.create(window)
}
- create(object) {
+ addEntity(entity) {
+ this.entities.set(entity.id, entity)
+ }
+
+ removeEntity(entity) {
+ this.entities.delete(entity.id)
+
+ this.dependencies.forEach((_, entityID) => {
+ if (entityID === entity.id) this.dependencies.delete(entity.id)
+ })
+
+ this.entityDependencies.forEach((_, variable) => {
+ const [entityID] = variable.split('.')
+ if (entityID === entity.id) this.entityDependencies.delete(variable)
+ })
+ }
+
+ hasDependency(variable) {
+ return (
+ this.dependencies.has(variable) || this.entityDependencies.has(variable)
+ )
+ }
+
+ addDependency(variable, entityID) {
+ const dependencies = this.dependencies.get(variable) || []
+ this.dependencies.set(variable, [...new Set(dependencies), entityID])
+ }
+
+ removeDependency(variable, entityID) {
+ const dependencies = (this.dependencies.get(variable) || []).filter(
+ (dep) => dep === entityID
+ )
+
+ this.dependencies.set(variable, dependencies)
+ }
+
+ disposeDependency(variable) {
+ this.dependencies.delete(variable)
+ }
+
+ addEntityDependency(parentEntityID, variable, entityID) {
+ const key = `${parentEntityID}.${variable}`
+ const dependencies = this.entityDependencies.get(key) || []
+ this.entityDependencies.set(key, [...new Set(dependencies), entityID])
+ }
+
+ removeEntityDependency(parentEntityID, variable, entityID = null) {
+ const key = `${parentEntityID}.${variable}`
+ const dependencies = (this.entityDependencies.get(key) || []).filter(
+ (dep) => dep === entityID
+ )
+
+ this.entityDependencies.set(key, dependencies)
+ }
+
+ disposeEntityDependency(parentEntityID, variable) {
+ const key = `${parentEntityID}.${variable}`
+ this.entityDependencies.delete(key)
+ }
+
+ create(object, entityID = null) {
const ctx = this
return new Proxy(object, {
set: function (target, property, value) {
- if (State.isLocalState(property))
+ if (entityID) ctx.setEntityState(target, property, value, entityID)
+ else if (State.isLocalState(property))
ctx.setLocalState(target, property, value)
else ctx.setState(target, property, value)
@@ -39,22 +104,47 @@ export class State {
setState(target, property, value) {
target[property] = value
- if (!this.variables.includes(property)) return
- this.triggerDOMUpdate(property)
- this.attachVariableHelpers()
+ if (!this.hasDependency(property)) return
+ this.evaluateDependencies(property)
+ this.attachVariableHelpers([property])
+ }
+
+ setEntityState(target, property, value, entityID) {
+ target[property] = value
+
+ if (!this.hasDependency(entityID)) return
+
+ const variable = `${entityID}.${property}`
+ this.evaluateDependencies(variable)
+ this.attachVariableHelpers([entityID])
}
- triggerDOMUpdate(state) {
- for (const entity of MiniJS.elements) {
- const shouldUpdate =
- entity.variables.includes(state) ||
- state == null ||
- entity.uuid == state ||
- entity.parent?.uuid == state
+ evaluate() {
+ Array.from(this.entities.values()).forEach((entity) => {
+ entity.attributes.evaluate()
+ })
+
+ this.attachVariableHelpers(Array.from(this.dependencies.keys()))
+ }
+
+ evaluateDependencies(variable) {
+ const dependencies = this.dependencies.get(variable) || []
+
+ dependencies.forEach((entityID) => {
+ const entity = this.entities.get(entityID)
+ // TODO: Only update relevant attributes that uses those variables
+ entity?.attributes.evaluate()
+ })
+
+ const entityDependencies = this.entityDependencies.get(variable) || []
+ entityDependencies.forEach((entityID) => {
+ const entity = this.entities.get(entityID)
// TODO: Only update relevant attributes that uses those variables
- if (shouldUpdate) entity.attributes.evaluate()
- }
+ entity?.attributes.evaluate()
+ })
+
+ this.attachVariableHelpers([variable])
}
attachVariableHelpers(variables = this.variables) {
From d2a02d108b47582b5129e44e4ef2c7b109d7b28b Mon Sep 17 00:00:00 2001
From: Joeylene <23741509+jorenrui@users.noreply.github.com>
Date: Mon, 12 Feb 2024 19:23:02 +0800
Subject: [PATCH 08/29] fix: catch eval error for non-existing elements
---
index.html | 9 +++--
lib/entity.js | 29 ++++++++-------
lib/entity/attributes.js | 78 +++++++++++++++++++++++++---------------
3 files changed, 72 insertions(+), 44 deletions(-)
diff --git a/index.html b/index.html
index 8e87b61..85e6775 100644
--- a/index.html
+++ b/index.html
@@ -1400,9 +1400,12 @@
Multi Select:
:click="selectedTags = selectedTags.add(tag);
filteredTags = filteredTags.remove(selectedTag);
selectedTag = filteredTags.first"
- :mouseover="selectedTag = tag"
- :class="selectedTag == tag ? 'bg-blue-100 text-blue-700' : 'text-gray-700'"
- class="font-mono font-semibold py-2 px-3 rounded cursor-pointer hover:bg-blue-100 hover:text-blue-700"
+ :mouseover="el.isHovering = true"
+ :mouseout="el.isHovering = false"
+ :class="(selectedTag == tag ? 'text-blue-700' : 'text-gray-700')
+ (selectedTag == tag ? 'bg-blue-100' : '')
+ (el.isHovering && selectedTag != tag ? 'bg-gray-50' : '')"
+ class="font-mono font-semibold py-2 px-3 rounded cursor-pointer"
:text="tag"
>
diff --git a/lib/entity.js b/lib/entity.js
index 24411c0..849651c 100644
--- a/lib/entity.js
+++ b/lib/entity.js
@@ -26,6 +26,10 @@ export default class Entity {
return !!this.uuid
}
+ isExists() {
+ return document.documentElement.contains(this.element)
+ }
+
getVariables() {
this._getVariablesFromAttributes()
this._getVariablesFromEvents()
@@ -91,19 +95,19 @@ export default class Entity {
this.variables.forEach((variable) => {
if (variable.startsWith('el.') || variable === 'el') {
- if (this.uuid == null) {
- this.setAsParent()
+ this.setAsParent()
- if (!this.parent) this.parent = this.getParent()
+ if (!this.parent) this.parent = this.getParent()
- window[this.id] = MiniJS.state.create({}, this.id)
+ if (window[this.uuid] == null) {
+ window[this.uuid] = MiniJS.state.create({}, this.id)
}
- MiniJS.state.addDependency(this.id, this.id)
+ MiniJS.state.addDependency(this.uuid, this.id)
if (variable !== 'el') {
const [_, varName] = variable.split('.')
- MiniJS.state.addEntityDependency(this.id, varName, this.id)
+ MiniJS.state.addEntityDependency(this.uuid, varName, this.id)
}
} else if (typeof window[variable] === 'function') {
this.variables.splice(this.variables.indexOf(variable), 1)
@@ -127,9 +131,10 @@ export default class Entity {
async _interpret(expr, options = {}) {
const Engine = options.isClass ? ClassInterpreter : Interpreter
const engine = new Engine(expr, options)
- const ids = { $: 'document.querySelector' }
-
- if (this.parent?.uuid) ids.el = `proxyWindow['${this.parent.uuid}']`
+ const ids = {
+ $: 'document.querySelector',
+ el: `proxyWindow['${this.uuid}']`,
+ }
this.variables.forEach((variable) => {
if (variable.startsWith('el.') || variable === 'el') return
@@ -226,10 +231,10 @@ export default class Entity {
unusedVariables.forEach((variable) => {
if (variable.startsWith('el.') || variable === 'el') {
- delete window[this.id]
+ delete window[this.uuid]
const varName = variable.replace('el.', '')
- MiniJS.state.disposeDependency(this.id)
- MiniJS.state.disposeEntityDependency(this.id, varName)
+ MiniJS.state.disposeDependency(this.uuid)
+ MiniJS.state.disposeEntityDependency(this.uuid, varName)
} else {
delete window[variable]
MiniJS.state.disposeDependency(variable)
diff --git a/lib/entity/attributes.js b/lib/entity/attributes.js
index 6c8ead4..3d4c426 100644
--- a/lib/entity/attributes.js
+++ b/lib/entity/attributes.js
@@ -65,54 +65,74 @@ export class Attributes {
const expr = this.base.element.getAttribute(':class')
if (!expr) return
- const updatedClassNames = await this.base._interpret(expr, {
- base: this.initialState.classList,
- isClass: true,
- })
-
- this.base.element.setAttribute('class', updatedClassNames)
+ try {
+ const updatedClassNames = await this.base._interpret(expr, {
+ base: this.initialState.classList,
+ isClass: true,
+ })
+
+ this.base.element.setAttribute('class', updatedClassNames)
+ } catch (error) {
+ if (!this.base.isExists()) return
+ throw error
+ }
}
async evaluateText() {
const textExpr = this.base.element.getAttribute(':text')
if (!textExpr) return
- const newText = await this.base._interpret(textExpr)
+ try {
+ const newText = await this.base._interpret(textExpr)
- if (newText || newText == '') this.base.element.innerText = newText
+ if (newText || newText == '') this.base.element.innerText = newText
+ } catch (error) {
+ if (!this.base.isExists()) return
+ throw error
+ }
}
async evaluateValue() {
- const valueExpr = this.base.element.getAttribute(':value')
+ try {
+ const valueExpr = this.base.element.getAttribute(':value')
- if (valueExpr) {
- const newValue = await this.base._interpret(valueExpr)
+ if (valueExpr) {
+ const newValue = await this.base._interpret(valueExpr)
- if (this.base.element.value !== newValue && newValue != null)
- this.base.element.value = newValue
- }
+ if (this.base.element.value !== newValue && newValue != null)
+ this.base.element.value = newValue
+ }
- const checkedExpr = this.base.element.getAttribute(':checked')
+ const checkedExpr = this.base.element.getAttribute(':checked')
- if (checkedExpr) {
- const newValue = await this.base._interpret(checkedExpr)
+ if (checkedExpr) {
+ const newValue = await this.base._interpret(checkedExpr)
- if (newValue) this.base.element.checked = newValue
+ if (newValue) this.base.element.checked = newValue
+ }
+ } catch (error) {
+ if (!this.base.isExists()) return
+ throw error
}
}
async evaluateOtherAttributes() {
- for (const attr of this.dynamicAttributes) {
- if (Attributes.CUSTOM_ATTRIBUTES.includes(attr)) continue
-
- const expr = this.base.element.getAttribute(attr)
- if (!expr) return
-
- const newValue = await this.base._interpret(expr)
- const nativeAttr = attr.slice(1)
-
- if (this.base.element[nativeAttr] !== newValue && newValue != null)
- this.base.element[nativeAttr] = newValue
+ try {
+ for (const attr of this.dynamicAttributes) {
+ if (Attributes.CUSTOM_ATTRIBUTES.includes(attr)) continue
+
+ const expr = this.base.element.getAttribute(attr)
+ if (!expr) return
+
+ const newValue = await this.base._interpret(expr)
+ const nativeAttr = attr.slice(1)
+
+ if (this.base.element[nativeAttr] !== newValue && newValue != null)
+ this.base.element[nativeAttr] = newValue
+ }
+ } catch (error) {
+ if (!this.base.isExists()) return
+ throw error
}
}
From 5c5a6b0fb6af1a2f470ad498e98c9f95a38f0975 Mon Sep 17 00:00:00 2001
From: Joeylene <23741509+jorenrui@users.noreply.github.com>
Date: Tue, 13 Feb 2024 00:02:29 +0800
Subject: [PATCH 09/29] refactor: prevent usage of :innerHTML and :innerText
---
lib/entity/attributes.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lib/entity/attributes.js b/lib/entity/attributes.js
index 3d4c426..9ec104a 100644
--- a/lib/entity/attributes.js
+++ b/lib/entity/attributes.js
@@ -3,9 +3,11 @@ import { escapeHTML } from '../helpers/sanitize'
export class Attributes {
static CUSTOM_ATTRIBUTES = [':class', ':text', ':value', ':checked', ':each']
+ static FORBIDDEN_ATTRIBUTES = [':innerHTML', ':innerText']
static isValidAttribute(attribute, element) {
if (!attribute.startsWith(':')) return false
+ if (Attributes.FORBIDDEN_ATTRIBUTES.includes(attribute)) return false
if (Events.isValidEvent(attribute)) return false
if (Attributes.CUSTOM_ATTRIBUTES.includes(attribute)) return true
From bd5df017ddd33c28201877ae0786bc9c7ebe305c Mon Sep 17 00:00:00 2001
From: Joeylene <23741509+jorenrui@users.noreply.github.com>
Date: Tue, 13 Feb 2024 00:04:19 +0800
Subject: [PATCH 10/29] style: missing button class
---
demo/observer.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/demo/observer.html b/demo/observer.html
index d7bc15a..e324950 100644
--- a/demo/observer.html
+++ b/demo/observer.html
@@ -39,6 +39,7 @@
:class="el.isHovered ? 'bg-red-200' : ''"
>