diff --git a/README.md b/README.md
index aa6ad86..58c7e91 100644
--- a/README.md
+++ b/README.md
@@ -340,30 +340,26 @@ If you want to use the variable across an element's attributes and events, you c
Like the example above, `:load` can be used to set the initial value of the variable.
-### Parent Element Variables
+### Group 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.
+Adding a `:group` attribute to an element will allow you to access its variables from its children using `group.` variables.
```html
-
-
-
+
+
-
About Us
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod.
@@ -374,14 +370,14 @@ A children's `parent.` variable is the same as the parent's `el.` variable.
class="grid transition-all border-gray-300 border border-b-0 rounded hover:bg-gray-100"
>
Contact Us
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod.
@@ -390,17 +386,17 @@ A children's `parent.` variable is the same as the parent's `el.` variable.
Team 3
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod.
@@ -409,6 +405,14 @@ A children's `parent.` variable is the same as the parent's `el.` variable.
```
+You can set the default value of the group variables in the `:group` directive:
+
+```html
+
+
+
+```
+
### Variable Methods
MiniJS added some commonly-used custom methods to variables.
diff --git a/index.html b/index.html
index cb26273..d1bf800 100644
--- a/index.html
+++ b/index.html
@@ -1471,14 +1471,15 @@ Multi Select:
grid-template-rows: 0fr 1fr;
}
-
+
About Us
@@ -1491,12 +1492,13 @@ Multi Select:
Team 3
diff --git a/lib/entity.js b/lib/entity.js
index bb770f7..87236b7 100644
--- a/lib/entity.js
+++ b/lib/entity.js
@@ -14,6 +14,7 @@ export class Entity {
this.dynamicScripts = dynamicScripts
this.variables = []
+ this.groupVariables = []
this.id = this.generateEntityUUID()
this.state = {}
@@ -23,15 +24,18 @@ export class Entity {
if (this.base.debug) this.element.dataset.entityId = this.id
- this.attributes.evaluateParent()
+ this.setAsGroup()
}
- setAsParent() {
+ setAsGroup() {
+ if (!this.element.hasAttribute(':group')) return
+ if (this.isGroup()) return
+
this.uuid = this.id
this.element.dataset['mini.uuid'] = this.uuid
}
- isParent() {
+ isGroup() {
return !!this.uuid
}
@@ -117,7 +121,8 @@ export class Entity {
return !(variable in this.state)
})
- this.variables.push(...identifiers)
+ if (name === ':group') this.groupVariables.push(...identifiers)
+ else this.variables.push(...identifiers)
})
}
@@ -143,7 +148,7 @@ export class Entity {
this.variables.forEach((variable) => {
if (State.isElState(variable)) {
- this.setAsParent()
+ this.setAsGroup()
if (window[this.id] == null) {
window[this.id] = this.base.state.create({}, this.id)
@@ -151,26 +156,26 @@ export class Entity {
this.base.state.addVariable(this.id, this.id)
- if (variable !== 'el') {
+ if (variable !== State.EL_STATE) {
const [_, varName] = variable.split('.')
this.base.state.addEntityVariable(this.id, varName, this.id)
}
- } else if (State.isParentState(variable)) {
- if (!this.parent) this.parent = this.getParent()
+ } else if (State.isGroupState(variable)) {
+ if (!this.group) this.group = this.getGroup()
- // Cases where parent is not found:
- // - an each item with a :parent directive being removed due to re-evaluation of :each attribute
- if (this.parent == null) return
+ // Cases where group is not found:
+ // - an each item with a :group directive being removed due to re-evaluation of :each attribute
+ if (this.group == null) return
- if (window[this.parent.id] == null) {
- window[this.parent.id] = this.base.state.create({}, this.parent.id)
+ if (window[this.group.id] == null) {
+ window[this.group.id] = this.base.state.create({}, this.group.id)
}
- this.base.state.addVariable(this.parent.id, this.id)
+ this.base.state.addVariable(this.group.id, this.id)
- if (variable !== 'parent') {
+ if (variable !== State.GROUP_STATE) {
const [_, varName] = variable.split('.')
- this.base.state.addEntityVariable(this.parent.id, varName, this.id)
+ this.base.state.addEntityVariable(this.group.id, varName, this.id)
}
} else if (typeof window[variable] === 'function') {
this.variables.splice(this.variables.indexOf(variable), 1)
@@ -186,15 +191,14 @@ export class Entity {
})
}
- getParent() {
- let currentElement = this.element
- let parentNode = this.getClosestEl('data-mini.uuid')
+ getGroup() {
+ const groupNode = this.getClosestEl('data-mini.uuid')
- if (parentNode == null) return { id: 'EntityDocument' }
+ if (groupNode == null) return { id: 'EntityDocument' }
const entities = Array.from(this.base.state.entities.values())
const entity = entities.find(
- (e) => e.uuid == parentNode.dataset['mini.uuid']
+ (e) => e.uuid == groupNode.dataset['mini.uuid']
)
return entity
diff --git a/lib/entity/attributes.js b/lib/entity/attributes.js
index 13bef3e..bee14b6 100644
--- a/lib/entity/attributes.js
+++ b/lib/entity/attributes.js
@@ -11,7 +11,7 @@ export class Attributes {
':checked',
':each',
':each.item',
- ':parent',
+ ':group',
]
static FORBIDDEN_ATTRIBUTES = [':innerHTML', ':innerText']
@@ -64,12 +64,15 @@ export class Attributes {
const ids = {
$: 'document-querySelector',
el: `proxyWindow['${this.base.id}${State.DISABLE_RE_RENDER_KEY}']`,
+ group: this.base.group
+ ? `proxyWindow['${this.base.group.id}${
+ !options.isGroup ? State.DISABLE_RE_RENDER_KEY : ''
+ }']`
+ : undefined,
+ ...(options.ids || {}),
// "this" is set under the interpreter as bind context
}
- if (this.base.parent)
- ids.parent = `proxyWindow['${this.base.parent.id}${State.DISABLE_RE_RENDER_KEY}']`
-
engine.replace(ids)
// window variables are used instead of proxy window
@@ -93,7 +96,7 @@ export class Attributes {
async evaluateAttribute(attr) {
if (!Attributes.isValidAttribute(attr, this.base.element)) return
- if (attr === ':parent') this.evaluateParent()
+ if (attr === ':group') await this.evaluateGroup()
else if (attr === ':class') await this.evaluateClass()
else if (attr === ':text') await this.evaluateText()
else if (attr === ':value') await this.evaluateValue()
@@ -131,10 +134,31 @@ export class Attributes {
})
}
- evaluateParent() {
- if (!this.base.element.hasAttribute(':parent')) return
- if (this.base.isParent()) return
- this.base.setAsParent()
+ /*
+ :group is a special attribute that acts as an :load event
+ when it has a given expr. Unlike other attributes, state updates
+ inside :group will trigger re-renders.
+
+ NOTE: This should NOT be used in this.evaluate() because it will
+ trigger an infinite loop.
+ */
+ async evaluateGroup() {
+ if (!this.base.isGroup()) return
+
+ const expr = this.base.element.getAttribute(':group')
+ if (!expr) return
+
+ const ids = {}
+
+ this.base.groupVariables.forEach((variable) => {
+ ids[variable] = `proxyWindow['${this.base.id}'].${variable}`
+ })
+
+ try {
+ await this._interpret(expr, { isGroup: true, ids })
+ } catch (error) {
+ this._handleError(':group', expr, error)
+ }
}
async evaluateClass() {
diff --git a/lib/entity/events.js b/lib/entity/events.js
index 80d3e67..5823f00 100644
--- a/lib/entity/events.js
+++ b/lib/entity/events.js
@@ -425,19 +425,34 @@ export class Events {
const expr = entity.element.getAttribute(attr)
if (!expr) return
+ const EL_PREFIX = State.EL_STATE + '.'
const elVariables = entity.variables
- .filter((v) => v.startsWith('el.') && v !== 'el')
- .map((v) => v.replace('el.', ''))
- const variables = entity.variables.filter((v) => !v.startsWith('el.'))
+ .filter((v) => v.startsWith(EL_PREFIX) && v !== State.EL_STATE)
+ .map((v) => v.replace(EL_PREFIX, ''))
+
+ const GROUP_PREFIX = State.EL_STATE + '.'
+ const groupVariables = entity.variables
+ .filter((v) => v.startsWith(GROUP_PREFIX) && v !== State.GROUP_STATE)
+ .map((v) => v.replace(GROUP_PREFIX, ''))
+
+ const variables = entity.variables.filter(
+ (v) => !v.startsWith(EL_PREFIX) && !v.startsWith(GROUP_PREFIX)
+ )
mini.state.attachVariableHelpers(variables)
mini.state.attachVariableHelpers(elVariables, entity.id)
+ if (entity.group)
+ mini.state.attachVariableHelpers(groupVariables, entity.group.id)
+
try {
await this._interpret(expr)
mini.state.attachVariableHelpers(variables)
mini.state.attachVariableHelpers(elVariables, entity.id)
+
+ if (entity.group)
+ mini.state.attachVariableHelpers(groupVariables, entity.group.id)
} catch (error) {
if (!entity.isExists()) return
console.error(
@@ -455,10 +470,10 @@ export class Events {
// "this" is set under the interpreter as bind context
}
- if (this.base.parent) ids.parent = `proxyWindow['${this.base.parent.id}']`
+ if (this.base.group) ids.group = `proxyWindow['${this.base.group.id}']`
this.base.variables.forEach((variable) => {
- if (State.isElState(variable) || State.isParentState(variable)) return
+ if (State.isElState(variable) || State.isGroupState(variable)) return
ids[variable] = `proxyWindow-${variable}`
})
diff --git a/lib/generators/lexer.js b/lib/generators/lexer.js
index 409827f..26f0ace 100644
--- a/lib/generators/lexer.js
+++ b/lib/generators/lexer.js
@@ -1,6 +1,7 @@
import { Parser } from 'acorn'
import * as walk from 'acorn-walk'
import escodegen from 'escodegen'
+import { State } from '../state'
const FUNCTION_NODE_TYPES = [
'FunctionDeclaration',
@@ -113,7 +114,7 @@ function getVariables(node) {
export class Lexer {
static debug = false
static IGNORED_KEYS = ['event', 'window', 'document', 'console', 'Math']
- static ENTITY_KEYS = ['el', 'parent']
+ static ENTITY_KEYS = [State.EL_STATE, State.GROUP_STATE]
constructor(code) {
this._code = code
diff --git a/lib/state.js b/lib/state.js
index 36192c6..9aae05c 100644
--- a/lib/state.js
+++ b/lib/state.js
@@ -4,7 +4,7 @@ import { Mini } from './main'
export class State {
static DISABLE_RE_RENDER_KEY = '_.'
static EL_STATE = 'el'
- static PARENT_STATE = 'parent'
+ static GROUP_STATE = 'group'
static isLocalState(variable) {
return variable[0] === '$'
@@ -16,10 +16,10 @@ export class State {
)
}
- static isParentState(variable) {
+ static isGroupState(variable) {
return (
- variable.startsWith(State.PARENT_STATE + '.') ||
- variable === State.PARENT_STATE
+ variable.startsWith(State.GROUP_STATE + '.') ||
+ variable === State.GROUP_STATE
)
}
@@ -82,8 +82,8 @@ export class State {
this.variables.set(variable, [...new Set(variables), entityID])
}
- addEntityVariable(parentEntityID, variable, entityID) {
- const key = `${parentEntityID}.${variable}`
+ addEntityVariable(groupID, variable, entityID) {
+ const key = `${groupID}.${variable}`
const variables = this.entityVariables.get(key) || []
this.entityVariables.set(key, [...new Set(variables), entityID])
}
@@ -196,6 +196,7 @@ export class State {
evaluate() {
Array.from(this.entities.values()).forEach((entity) => {
+ entity.attributes.evaluateAttribute(':group')
entity.attributes.evaluate()
})
@@ -261,8 +262,8 @@ export class State {
this.variables.delete(variable)
}
- disposeEntityVariable(parentEntityID, variable) {
- const key = `${parentEntityID}.${variable}`
+ disposeEntityVariable(entityID, variable) {
+ const key = `${entityID}.${variable}`
this.entityVariables.delete(key)
}
}