@@ -1996,11 +2002,11 @@
Tonic Dialog:
Open Modal
diff --git a/lib/entity.js b/lib/entity.js
index ff82aaa..48f05d8 100644
--- a/lib/entity.js
+++ b/lib/entity.js
@@ -2,6 +2,7 @@ import { Interpreter, ClassInterpreter } from './generators/interpreter'
import { Lexer } from './generators/lexer'
import { Events } from './entity/events'
import { Attributes } from './entity/attributes'
+import { State } from './state'
export default class Entity {
constructor(el) {
@@ -12,8 +13,11 @@ 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
+
+ this.attributes.evaluateParent()
}
setAsParent() {
@@ -25,6 +29,10 @@ export default class Entity {
return !!this.uuid
}
+ isExists() {
+ return document.documentElement.contains(this.element)
+ }
+
getVariables() {
this._getVariablesFromAttributes()
this._getVariablesFromEvents()
@@ -86,31 +94,44 @@ export default class Entity {
_initVariables() {
this.variables = [...new Set(this.variables)]
- MiniJS.variables = [...new Set(MiniJS.variables.concat(this.variables))]
this.variables.forEach((variable) => {
- if (variable.startsWith('el.')) {
+ if (State.isElState(variable)) {
this.setAsParent()
- if (!this.parent) this.parent = this.getParent()
+ if (window[this.id] == null) {
+ window[this.id] = MiniJS.state.create({}, this.id)
+ }
- const varName = variable.replace('el.', '')
+ MiniJS.state.addVariable(this.id, this.id)
- if (!window[this.uuid]) window[this.uuid] = {}
+ if (variable !== 'el') {
+ const [_, varName] = variable.split('.')
+ MiniJS.state.addEntityVariable(this.id, varName, this.id)
+ }
+ } else if (State.isParentState(variable)) {
+ if (!this.parent) this.parent = this.getParent()
- // ! FIXME: Any changes to el.varName isn't being watched
- window[this.uuid][varName] = MiniJS.tryFromLocal(
- variable.replace('el.', this.uuid + '.')
- )
+ if (window[this.parent.id] == null) {
+ window[this.parent.id] = MiniJS.state.create({}, this.parent.id)
+ }
+
+ MiniJS.state.addVariable(this.parent.id, this.id)
- if (!this.variables.includes(this.uuid)) this.variables.push(this.uuid)
+ if (variable !== 'parent') {
+ const [_, varName] = variable.split('.')
+ MiniJS.state.addEntityVariable(this.parent.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 {
- window[variable] = variable.startsWith('$')
- ? MiniJS.tryFromLocal(variable)
- : window[variable]
+ const [identifier] = variable.split('.')
+
+ window[identifier] = variable.startsWith('$')
+ ? MiniJS.tryFromLocal(identifier)
+ : window[identifier]
+
+ MiniJS.state.addVariable(identifier, this.id)
}
})
}
@@ -118,12 +139,15 @@ export default class Entity {
async _interpret(expr, options = {}) {
const Engine = options.isClass ? ClassInterpreter : Interpreter
const engine = new Engine(expr, options)
- const ids = { $: 'document.querySelector' }
+ const ids = {
+ $: 'document.querySelector',
+ el: `proxyWindow['${this.id}']`,
+ }
- if (this.parent?.uuid) ids.el = `proxyWindow['${this.parent.uuid}']`
+ if (this.parent) ids.parent = `proxyWindow['${this.parent.id}']`
this.variables.forEach((variable) => {
- if (variable.startsWith('el.') || variable === 'el') return
+ if (State.isElState(variable) || State.isParentState(variable)) return
ids[variable] = `proxyWindow-${variable}`
})
@@ -133,8 +157,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
@@ -145,15 +167,13 @@ export default class Entity {
currentElement = parentNode
parentNode = currentElement.parentNode
}
- const entity = MiniJS.elements.find(
- (e) => e.uuid == parentNode.dataset.uuid
- )
+ const entities = Array.from(MiniJS.state.entities.values())
+ const entity = entities.find((e) => e.uuid == parentNode.dataset.uuid)
return entity
}
}
generateEntityUUID() {
- // Suggestion: we can use crypto.randomUUID(). Tho crypto only works in secure contexts
return 'Entity' + Date.now() + Math.floor(Math.random() * 10000)
}
@@ -161,8 +181,6 @@ export default class Entity {
this.getVariables()
this.events.apply()
await this.attributes.evaluate()
-
- MiniJS.elements.push(this)
}
initChildren() {
@@ -183,47 +201,31 @@ export default class Entity {
dispose() {
const elements = [this.element, ...this.element.querySelectorAll('*')]
+ const entities = Array.from(MiniJS.state.entities.values())
const variables = []
// Remove event bindings
for (const element of elements) {
if (element.nodeType !== 1) continue
- const entity = MiniJS.elements.find(
- (entity) => entity.element === element
- )
+ const entity = MiniJS.state.getEntityByElement(element, entities)
+
if (!entity) continue
variables.push(...entity.variables)
entity.events.dispose()
+ MiniJS.state.removeEntity(entity)
}
- // Remove disposed elements
- MiniJS.elements = MiniJS.elements.filter(
- (entity) => !elements.includes(entity.element)
- )
-
// Clean up unused variables
- const usedVariables = MiniJS.elements.reduce(
- (acc, entity) => acc.concat(entity.variables),
- []
- )
+ const usedVariables = entities
+ .filter((entity) => !elements.includes(entity.element))
+ .reduce((acc, entity) => acc.concat(entity.variables), [])
const unusedVariables = variables.filter(
(variable) => !usedVariables.includes(variable)
)
- MiniJS.variables = MiniJS.variables.filter(
- (variable) => !unusedVariables.includes(variable)
- )
-
- unusedVariables.forEach((variable) => {
- if (variable.startsWith('el.')) {
- const varName = variable.replace('el.', '')
- if (window[this.uuid]?.[varName]) delete window[this.uuid]
- } else {
- delete window[variable]
- }
- })
+ MiniJS.state.disposeVariables(this.id, unusedVariables)
}
}
diff --git a/lib/entity/attributes.js b/lib/entity/attributes.js
index 6c8ead4..6ef9d13 100644
--- a/lib/entity/attributes.js
+++ b/lib/entity/attributes.js
@@ -2,15 +2,24 @@ import { Events } from './events'
import { escapeHTML } from '../helpers/sanitize'
export class Attributes {
- static CUSTOM_ATTRIBUTES = [':class', ':text', ':value', ':checked', ':each']
+ static CUSTOM_ATTRIBUTES = [
+ ':class',
+ ':text',
+ ':value',
+ ':checked',
+ ':each',
+ ':parent',
+ ]
+ 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
const [nativeAttr] = attribute.replace(':', '').split('.')
- if (element[nativeAttr] !== undefined) return false
+ if (element[nativeAttr] === undefined) return false
return true
}
@@ -50,7 +59,8 @@ export class Attributes {
async evaluateAttribute(attr) {
if (!Attributes.isValidAttribute(attr, this.base.element)) return
- if (attr === ':class') await this.evaluateClass()
+ if (attr === ':parent') this.evaluateParent()
+ else if (attr === ':class') await this.evaluateClass()
else if (attr === ':text') await this.evaluateText()
else if ([':value', ':checked'].includes(attr)) await this.evaluateValue()
else if (attr === ':each') await this.evaluateEach()
@@ -61,58 +71,84 @@ export class Attributes {
}
}
+ evaluateParent() {
+ if (!this.base.element.hasAttribute(':parent')) return
+ if (this.base.isParent()) return
+ this.base.setAsParent()
+ }
+
async evaluateClass() {
const expr = this.base.element.getAttribute(':class')
if (!expr) return
- const updatedClassNames = await this.base._interpret(expr, {
- base: this.initialState.classList,
- isClass: true,
- })
+ try {
+ const updatedClassNames = await this.base._interpret(expr, {
+ base: this.initialState.classList,
+ isClass: true,
+ })
- this.base.element.setAttribute('class', updatedClassNames)
+ 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
}
}
diff --git a/lib/entity/events.js b/lib/entity/events.js
index d5b6c73..10b4244 100644
--- a/lib/entity/events.js
+++ b/lib/entity/events.js
@@ -20,6 +20,7 @@ export class Events {
':change',
':clickout',
':press',
+ ':load',
...Events.CUSTOM_KEY_EVENTS,
]
@@ -36,7 +37,8 @@ export class Events {
Events.validEvents = [...events, ...Events.CUSTOM_EVENTS]
}
- static applyEvents(entities) {
+ static applyEvents() {
+ const entities = Array.from(MiniJS.state.entities.values())
entities.forEach((entity) => {
entity.events.apply()
})
@@ -70,6 +72,7 @@ export class Events {
apply() {
this.dispose()
+ this.evaluate(':load')
this.setChangeEvent()
this.setClickoutEvent()
@@ -79,6 +82,7 @@ export class Events {
// Other Event Bindings
Array.from(el.attributes).forEach((attr) => {
+ if (attr.name === ':load') return
if (!Events.isValidEvent(attr.name)) return
const isKeyEvent = Events.CUSTOM_KEY_EVENTS.some((keyType) =>
@@ -225,6 +229,17 @@ export class Events {
async evaluate(attr) {
const value = this.base.element.getAttribute(attr)
if (!value) return
+
+ if (attr === ':load') {
+ const elVariables = this.base.variables
+ .filter((v) => v.startsWith('el.') && v !== 'el')
+ .map((v) => v.replace('el.', ''))
+ const variables = this.base.variables.filter((v) => !v.startsWith('el.'))
+
+ MiniJS.state.attachVariableHelpers(variables)
+ MiniJS.state.attachVariableHelpers(elVariables, this.base.id)
+ }
+
await this.base._interpret(value)
}
diff --git a/lib/generators/lexer.js b/lib/generators/lexer.js
index a651ab4..a7582b6 100644
--- a/lib/generators/lexer.js
+++ b/lib/generators/lexer.js
@@ -31,6 +31,14 @@ function setMemberIdentifier(node, members = []) {
}
}
+/*
+ TODO
+ - being read as undefined.toLocalTimeString()
+ :click="const time = new Date().toLocaleTimeString())"
+- add support for:
+ :click="const [a, b] = [1, 2]"
+*/
+
export class Lexer {
static debug = false
static ID_TYPE = {
diff --git a/lib/main.js b/lib/main.js
index 508f769..e415396 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -1,39 +1,14 @@
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,16 +16,13 @@ const MiniJS = (() => {
let startTime = performance.now()
_setDebugMode()
- _setProxyWindow()
+ state.setProxyWindow()
Events.initValidEvents()
_findElements()
_initializeGlobalVariables()
- _addMethodsToVariables()
- Events.applyEvents(_elements)
- updateStates()
+ Events.applyEvents()
+ state.evaluate()
_listenToDOMChanges()
- // Temporarily commented out - to be reviewed
- // _evaluateLoadEvents();
const endTime = performance.now()
const executionTime = endTime - startTime
console.log(`MiniJS took ${executionTime}ms to run.`)
@@ -63,15 +35,14 @@ const MiniJS = (() => {
record.type === 'attributes' &&
record.attributeName.startsWith(':')
) {
- const entity = _elements.find(
- (entity) => entity.element === record.target
- )
+ const entity = state.getEntityByElement(record.target)
+ // TODO: Add support for dynamically inserted events like :click
entity?.attributes.evaluateAttribute(record.attributeName)
}
record.removedNodes.forEach((node) => {
if (node.nodeType !== 1) return
- const entity = _elements.find((entity) => entity.element === node)
+ const entity = state.getEntityByElement(node)
entity?.dispose()
})
@@ -85,30 +56,15 @@ 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() {
- _elements.forEach((entity, index) => {
+ const entities = Array.from(state.entities.values())
+ entities.forEach((entity, index) => {
entity.getVariables()
})
}
@@ -128,25 +84,6 @@ const MiniJS = (() => {
}
}
- function _evaluateLoadEvents() {
- _elements.forEach((entity) => {
- entity.events.evaluate(':load')
- })
- }
-
- 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('*')
@@ -154,8 +91,7 @@ const MiniJS = (() => {
const elem = elems[i]
if (elem.nodeType !== 1) continue
- const entity = new Entity(elem)
- _elements.push(entity)
+ new Entity(elem)
}
}
@@ -181,20 +117,11 @@ const MiniJS = (() => {
set debug(value) {
_debug = !!value
},
- get elements() {
- return _elements
- },
- set elements(newElements) {
- _elements = newElements
- },
- get variables() {
- return _variables
- },
- set variables(newVarList) {
- _variables = newVarList
- },
get window() {
- return proxyWindow
+ return state.window
+ },
+ get state() {
+ return state
},
tryFromLocal,
}
diff --git a/lib/state.js b/lib/state.js
new file mode 100644
index 0000000..ff88777
--- /dev/null
+++ b/lib/state.js
@@ -0,0 +1,190 @@
+import MiniArray from './helpers/array'
+
+export class State {
+ static isLocalState(variable) {
+ return variable[0] === '$'
+ }
+
+ static isElState(variable) {
+ return variable.startsWith('el.') || variable === 'el'
+ }
+
+ static isParentState(variable) {
+ return variable.startsWith('parent.') || variable === 'parent'
+ }
+
+ constructor() {
+ this.window = null
+
+ this.entities = new Map() // key: entityID, value: entity
+ this.variables = new Map() // key: variable, value: entityID
+ this.entityVariables = new Map() // key: entityID.variable, value: entityID
+ }
+
+ setProxyWindow() {
+ this.window = this.create(window)
+ }
+
+ getEntityByElement(el, entities = Array.from(this.entities.values())) {
+ return entities.find((entity) => entity.element === el)
+ }
+
+ addEntity(entity) {
+ this.entities.set(entity.id, entity)
+ }
+
+ removeEntity(entity) {
+ this.entities.delete(entity.id)
+
+ const variables = [...this.variables.entries()]
+ variables.forEach(([key, value]) => {
+ if (key === entity.id) this.variables.delete(key)
+ else if (value.includes(entity.id))
+ this.variables.set(
+ key,
+ value.filter((id) => id !== entity.id)
+ )
+ })
+
+ const entityVariables = [...this.entityVariables.entries()]
+ entityVariables.forEach(([key, value]) => {
+ const [entityID] = key.split('.')
+ if (entityID === entity.id) this.entityVariables.delete(key)
+ else if (value.includes(entity.id))
+ this.entityVariables.set(
+ key,
+ value.filter((id) => id !== entity.id)
+ )
+ })
+
+ delete window[entity.id]
+ }
+
+ hasDependency(variable) {
+ return this.variables.has(variable) || this.entityVariables.has(variable)
+ }
+
+ addVariable(variable, entityID) {
+ const variables = this.variables.get(variable) || []
+ this.variables.set(variable, [...new Set(variables), entityID])
+ }
+
+ addEntityVariable(parentEntityID, variable, entityID) {
+ const key = `${parentEntityID}.${variable}`
+ const variables = this.entityVariables.get(key) || []
+ this.entityVariables.set(key, [...new Set(variables), entityID])
+ }
+
+ create(object, entityID = null) {
+ const ctx = this
+
+ return new Proxy(object, {
+ set: function (target, property, value) {
+ if (entityID) ctx.setEntityState(target, property, value, entityID)
+ else 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.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])
+ this.attachVariableHelpers([property], entityID)
+ }
+
+ evaluate() {
+ Array.from(this.entities.values()).forEach((entity) => {
+ entity.attributes.evaluate()
+ })
+
+ this.attachVariableHelpers(Array.from(this.variables.keys()))
+ }
+
+ evaluateDependencies(variable) {
+ const variables = this.variables.get(variable) || []
+
+ variables.forEach((entityID) => {
+ const entity = this.entities.get(entityID)
+ // TODO: Only update relevant attributes that uses those variables
+ entity?.attributes.evaluate()
+ })
+
+ const entityVariables = this.entityVariables.get(variable) || []
+
+ entityVariables.forEach((entityID) => {
+ const entity = this.entities.get(entityID)
+ // TODO: Only update relevant attributes that uses those variables
+ entity?.attributes.evaluate()
+ })
+
+ this.attachVariableHelpers([variable])
+ }
+
+ attachVariableHelpers(variables, entityID = null) {
+ variables.forEach((variable) => {
+ const value =
+ entityID != null
+ ? this.window[entityID][variable]
+ : this.window[variable]
+
+ if (Array.isArray(value) && !(value instanceof MiniArray)) {
+ if (entityID != null)
+ this.window[entityID][variable] = new MiniArray(...value)
+ else this.window[variable] = new MiniArray(...value)
+ }
+ })
+ }
+
+ disposeVariables(entityID, variables) {
+ variables.forEach((variable) => {
+ if (State.isElState(variable)) {
+ delete window[entityID]
+ MiniJS.state.disposeVariable(entityID)
+
+ if (variable !== 'el') {
+ const varName = variable.replace('el.', '')
+ MiniJS.state.disposeEntityVariable(entityID, varName)
+ }
+ } else {
+ delete window[variable]
+ MiniJS.state.disposeVariable(variable)
+
+ if (State.isLocalState(variable)) localStorage.removeItem(variable)
+ }
+ })
+ }
+
+ disposeVariable(variable) {
+ this.variables.delete(variable)
+ }
+
+ disposeEntityVariable(parentEntityID, variable) {
+ const key = `${parentEntityID}.${variable}`
+ this.entityVariables.delete(key)
+ }
+}
diff --git a/readme.md b/readme.md
index 23f8ef8..edfe422 100644
--- a/readme.md
+++ b/readme.md
@@ -23,6 +23,8 @@ To setup MiniJS in your local machine, you can do the following:
`State` are variables that changes the UI or the DOM that uses it when they get updated.
+Note: Only non-nested objects are supported for reactive state.
+
### Setting Initial State
You can set the initial state of the variables using vanilla JS:
@@ -178,12 +180,29 @@ These are the events added in by MiniJS:
## Statements
-- `:each` - loop through an array and render a template for each item
+- `:each` (experimental!) - loop through an array and render a template for each item
+ - do not use in production
+
+## Variables
+
+### Variables saved in Local Storage
+
+Appending `$` to the variable name will save the variable in the local storage:
+
+```html
+
+
+
+```
-## Variable
+Note: Currently, this is only available for globally declared variables.
### Variable Scoping
+#### Global Variables
+
Whenever you create a variable, it will automatically be added to the global scope. This means that you can access it anywhere in your code.
```html
@@ -194,23 +213,108 @@ Whenever you create a variable, it will automatically be added to the global sco
```
-If you want to create a local variable, instead of using `const`, `var`, and `let` variable declarations, you need use `el.`:
+#### Local Variables
+
+To use variables only in a current event, you can create a local variable using `const`, and `let`:
+
+```html
+
+```
+
+### Element Variables
+
+If you want to use the variable across an element's attributes and events, you can use `el.`:
```html
```
+Like the example above, `:load` can be used to set the initial value of the variable.
+
+### Parent Element Variables
+
+Adding a `:parent` attribute to an element will allow you to access its variables from its children using `parent.` variables.
+
+A children's `parent.` variable is the same as the parent's `el.` variable.
+
+```html
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
+ eirmod.
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
+ eirmod.
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
+ eirmod.
+
+
+
+```
+
### Variable Methods
MiniJS added some commonly-used custom methods to variables.