Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: Group Variables (rename of Parent Variables) #20

Merged
merged 4 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<div id="accordion" class="accordion" :parent>
<!-- Parent Element -->

<!-- Group Element -->
<div id="accordion" class="accordion" :group>
<!-- Children Elements -->
<!-- parent.variable == #accordion's el.variable -->
<section
class="grid transition-all border-gray-300 border border-b-0 rounded hover:bg-gray-100"
>
<button
:click="parent.activeSection = 'about'"
:click="group.activeSection = 'about'"
class="cursor-pointer font-bold p-4"
>
About Us
</button>
<div
class="p-4 pt-2 overflow-hidden hidden"
:class="parent.activeSection =='about' ? 'block' : 'hidden'"
:class="group.activeSection =='about' ? 'block' : 'hidden'"
>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod.
Expand All @@ -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"
>
<button
:click="parent.activeSection = 'contact'"
:click="group.activeSection = 'contact'"
class="cursor-pointer font-bold p-4"
>
Contact Us
</button>
<div
class="p-4 pt-2 overflow-hidden"
:class="parent.activeSection =='contact' ? 'block' : 'hidden'"
:class="group.activeSection =='contact' ? 'block' : 'hidden'"
>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod.
Expand All @@ -390,17 +386,17 @@ A children's `parent.` variable is the same as the parent's `el.` variable.

<section
class="grid transition-all border-gray-300 border rounded hover:bg-gray-100"
:class="parent.activeSection =='team' ? 'active' : ''"
:class="group.activeSection =='team' ? 'active' : ''"
>
<button
:click="parent.activeSection = 'team'"
:click="group.activeSection = 'team'"
class="cursor-pointer font-bold p-4"
>
Team 3
</button>
<div
class="p-4 pt-2 overflow-hidden"
:class="parent.activeSection =='team' ? 'block' : 'hidden'"
:class="group.activeSection =='team' ? 'block' : 'hidden'"
>
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod.
Expand All @@ -409,6 +405,14 @@ A children's `parent.` variable is the same as the parent's `el.` variable.
</div>
```

You can set the default value of the group variables in the `:group` directive:

```html
<div id="accordion" class="accordion" :group="activeSection = 'about'">
<!-- ... -->
</div>
```

### Variable Methods

MiniJS added some commonly-used custom methods to variables.
Expand Down
14 changes: 7 additions & 7 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1471,13 +1471,13 @@ <h3 class="font-bold font-mono">Multi Select:</h3>
grid-template-rows: 0fr 1fr;
}
</style>
<div class="accordion" :parent>
<div class="accordion" :group="activeSection = 'about'">
<section
class="grid transition-all border-gray-300 border border-b-0 rounded hover:bg-gray-100"
:class="parent.activeSection == 'about' ? 'active' : ''"
:class="group.activeSection == 'about' ? 'active' : ''"
>
<a
:click="parent.activeSection='about'"
:click="group.activeSection = 'about'"
class="cursor-pointer font-bold p-4"
>
About Us
jorenrui marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -1492,10 +1492,10 @@ <h3 class="font-bold font-mono">Multi Select:</h3>

<section
class="grid transition-all border-gray-300 border border-b-0 rounded hover:bg-gray-100"
:class="parent.activeSection =='contact' ? 'active' : ''"
:class="group.activeSection =='contact' ? 'active' : ''"
>
<a
:click="parent.activeSection='contact'"
:click="group.activeSection = 'contact'"
class="cursor-pointer font-bold p-4"
>
Contact Us
Expand All @@ -1510,10 +1510,10 @@ <h3 class="font-bold font-mono">Multi Select:</h3>

<section
class="grid transition-all border-gray-300 border rounded hover:bg-gray-100"
:class="parent.activeSection =='team' ? 'active' : ''"
:class="group.activeSection =='team' ? 'active' : ''"
>
<a
:click="parent.activeSection='team'"
:click="group.activeSection = 'team'"
class="cursor-pointer font-bold p-4"
>
Team 3
Expand Down
46 changes: 25 additions & 21 deletions lib/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class Entity {
this.dynamicScripts = dynamicScripts

this.variables = []
this.groupVariables = []
this.id = this.generateEntityUUID()

this.state = {}
Expand All @@ -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
}

Expand Down Expand Up @@ -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)
})
}

Expand All @@ -143,34 +148,34 @@ 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)
}

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)
Expand All @@ -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
Expand Down
42 changes: 33 additions & 9 deletions lib/entity/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class Attributes {
':checked',
':each',
':each.item',
':parent',
':group',
]
static FORBIDDEN_ATTRIBUTES = [':innerHTML', ':innerText']

Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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() {
Expand Down
25 changes: 20 additions & 5 deletions lib/entity/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, ''))

jorenrui marked this conversation as resolved.
Show resolved Hide resolved
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(
Expand All @@ -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}`
})
Expand Down
3 changes: 2 additions & 1 deletion lib/generators/lexer.js
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading