Skip to content

Commit a129aa8

Browse files
authored
Merge pull request #11 from Group-One-Technology/jr.fix/reactive-el-variables
Fix: Reactive El Variables; Refactor: Rehaul of State Dependencies; Feat: Parent El Variables
2 parents 2a0246a + 0008af8 commit a129aa8

File tree

9 files changed

+503
-208
lines changed

9 files changed

+503
-208
lines changed

demo/observer.html

+20-13
Original file line numberDiff line numberDiff line change
@@ -13,51 +13,58 @@
1313
<ol class="mt-8 list-decimal ml-4">
1414
<li>
1515
<button
16-
:mouseenter="isHovered = true"
17-
:mouseleave="isHovered = false"
18-
:class="isHovered ? 'bg-red-200' : ''"
16+
:load="el.isHovered = true"
1917
:click="this.parentNode.parentNode.removeChild(this.parentNode)"
18+
:mouseenter="el.isHovered = true"
19+
:mouseleave="el.isHovered = false"
20+
:class="el.isHovered ? 'bg-red-200' : ''"
2021
>
2122
list item (click to delete)
2223
</button>
2324
<button
2425
class="px-2 py-1 bg-gray-200 rounded-md text-base"
25-
:click="this.previousElementSibling.setAttribute(':class', 'isHovered ? \'bg-red-200\' : \'bg-gray-200\'')"
26+
:click="this.previousElementSibling.setAttribute(':class', 'el.isHovered ? \'bg-red-200\' : \'bg-gray-200\'')"
27+
:mouseenter="el.isHovered = true"
28+
:mouseleave="el.isHovered = false"
29+
:class="el.isHovered ? 'bg-red-200' : ''"
2630
>
2731
Change :class Attribute
2832
</button>
2933
</li>
3034
<li>
3135
<button
32-
:mouseenter="isHovered2 = true"
33-
:mouseleave="isHovered2 = false"
34-
:class="isHovered2 ? 'bg-red-200' : ''"
3536
:click="this.parentNode.parentNode.removeChild(this.parentNode)"
3637
:text="`my id is ${this.getAttribute('id')} (hover me to change) list item (click to delete)`"
38+
:mouseenter="el.isHovered = true"
39+
:mouseleave="el.isHovered = false"
40+
:class="el.isHovered ? 'bg-red-200' : ''"
3741
></button>
3842
<button
3943
class="px-2 py-1 bg-gray-200 rounded-md text-base"
4044
:click="this.previousElementSibling.setAttribute(':id', '`list2${Math.random()}`')"
45+
:mouseenter="el.isHovered = true"
46+
:mouseleave="el.isHovered = false"
47+
:class="el.isHovered ? 'bg-red-200' : ''"
4148
>
4249
Change :id Attribute
4350
</button>
4451
</li>
4552
<li>
4653
<button
47-
:mouseenter="isHovered3 = true"
48-
:mouseleave="isHovered3 = false"
49-
:class="isHovered3 ? 'bg-red-200' : ''"
5054
:click="this.parentNode.parentNode.removeChild(this.parentNode)"
55+
:mouseenter="el.isHovered = true"
56+
:mouseleave="el.isHovered = false"
57+
:class="el.isHovered ? 'bg-red-200' : ''"
5158
>
5259
list item (click to delete)
5360
</button>
5461
</li>
5562
<li>
5663
<button
57-
:mouseenter="isHovered4 = true"
58-
:mouseleave="isHovered4 = false"
59-
:class="isHovered4 ? 'bg-red-200' : ''"
6064
:click="this.parentNode.parentNode.removeChild(this.parentNode)"
65+
:mouseenter="el.isHovered = true"
66+
:mouseleave="el.isHovered = false"
67+
:class="el.isHovered ? 'bg-red-200' : ''"
6168
>
6269
list item (click to delete)
6370
</button>

index.html

+22-16
Original file line numberDiff line numberDiff line change
@@ -1304,7 +1304,7 @@ <h3 class="font-bold font-mono">Storing to Local Storage:</h3>
13041304
class="ml-3 mt-2 text-lg font-medium font-mono"
13051305
:text="$storedValue"
13061306
>
1307-
Update and Reload
1307+
Loading...
13081308
</h2>
13091309
</div>
13101310

@@ -1400,9 +1400,12 @@ <h3 class="font-bold font-mono">Multi Select:</h3>
14001400
:click="selectedTags = selectedTags.add(tag);
14011401
filteredTags = filteredTags.remove(selectedTag);
14021402
selectedTag = filteredTags.first"
1403-
:mouseover="selectedTag = tag"
1404-
:class="selectedTag == tag ? 'bg-blue-100 text-blue-700' : 'text-gray-700'"
1405-
class="font-mono font-semibold py-2 px-3 rounded cursor-pointer hover:bg-blue-100 hover:text-blue-700"
1403+
:mouseover="el.isHovering = true"
1404+
:mouseout="el.isHovering = false"
1405+
:class="(selectedTag == tag ? 'text-blue-700' : 'text-gray-700')
1406+
(selectedTag == tag ? 'bg-blue-100' : '')
1407+
(el.isHovering && selectedTag != tag ? 'bg-gray-50' : '')"
1408+
class="font-mono font-semibold py-2 px-3 rounded cursor-pointer"
14061409
:text="tag"
14071410
></li>
14081411
</ul>
@@ -1502,13 +1505,13 @@ <h3 class="font-bold font-mono">Multi Select:</h3>
15021505
grid-template-rows: 0fr 1fr;
15031506
}
15041507
</style>
1505-
<div class="accordion">
1508+
<div class="accordion" :parent>
15061509
<section
15071510
class="grid transition-all border-gray-300 border border-b-0 rounded hover:bg-gray-100"
1508-
:class="activeSection =='about' ? 'active' : ''"
1511+
:class="parent.activeSection =='about' ? 'active' : ''"
15091512
>
15101513
<a
1511-
:click="activeSection='about'"
1514+
:click="parent.activeSection='about'"
15121515
class="cursor-pointer font-bold p-4"
15131516
>
15141517
About Us
@@ -1523,10 +1526,10 @@ <h3 class="font-bold font-mono">Multi Select:</h3>
15231526

15241527
<section
15251528
class="grid transition-all border-gray-300 border border-b-0 rounded hover:bg-gray-100"
1526-
:class="activeSection =='contact' ? 'active' : ''"
1529+
:class="parent.activeSection =='contact' ? 'active' : ''"
15271530
>
15281531
<a
1529-
:click="activeSection='contact'"
1532+
:click="parent.activeSection='contact'"
15301533
class="cursor-pointer font-bold p-4"
15311534
>
15321535
Contact Us
@@ -1541,9 +1544,12 @@ <h3 class="font-bold font-mono">Multi Select:</h3>
15411544

15421545
<section
15431546
class="grid transition-all border-gray-300 border rounded hover:bg-gray-100"
1544-
:class="activeSection =='team' ? 'active' : ''"
1547+
:class="parent.activeSection =='team' ? 'active' : ''"
15451548
>
1546-
<a :click="activeSection='team'" class="cursor-pointer font-bold p-4">
1549+
<a
1550+
:click="parent.activeSection='team'"
1551+
class="cursor-pointer font-bold p-4"
1552+
>
15471553
Team 3
15481554
</a>
15491555
<div class="overflow-hidden">
@@ -1996,11 +2002,11 @@ <h3 class="font-bold font-mono">Tonic Dialog:</h3>
19962002
<a
19972003
class="bg-indigo-600 hover:bg-indigo-800 transition-all text-white rounded px-3 py-2 cursor-pointer"
19982004
:click="isModalOpen = true;
1999-
el.loader = document.createElement('div');
2000-
el.loader.className = 'shimmer h-32 w-full';
2001-
el.loader.style.minWidth = '300px';
2002-
el.modalContent = $('#modal-container-content');
2003-
el.modalContent.innerHTML = el.loader.outerHTML;"
2005+
const loader = document.createElement('div');
2006+
loader.className = 'shimmer h-32 w-full';
2007+
loader.style.minWidth = '300px';
2008+
const modalContent = $('#modal-container-content');
2009+
modalContent.innerHTML = loader.outerHTML;"
20042010
>
20052011
Open Modal
20062012
</a>

lib/entity.js

+51-49
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Interpreter, ClassInterpreter } from './generators/interpreter'
22
import { Lexer } from './generators/lexer'
33
import { Events } from './entity/events'
44
import { Attributes } from './entity/attributes'
5+
import { State } from './state'
56

67
export default class Entity {
78
constructor(el) {
@@ -12,8 +13,11 @@ export default class Entity {
1213

1314
this.events = new Events(this)
1415
this.attributes = new Attributes(this)
16+
MiniJS.state.addEntity(this)
1517

1618
if (MiniJS.debug) this.element.dataset.entityId = this.id
19+
20+
this.attributes.evaluateParent()
1721
}
1822

1923
setAsParent() {
@@ -25,6 +29,10 @@ export default class Entity {
2529
return !!this.uuid
2630
}
2731

32+
isExists() {
33+
return document.documentElement.contains(this.element)
34+
}
35+
2836
getVariables() {
2937
this._getVariablesFromAttributes()
3038
this._getVariablesFromEvents()
@@ -86,44 +94,60 @@ export default class Entity {
8694

8795
_initVariables() {
8896
this.variables = [...new Set(this.variables)]
89-
MiniJS.variables = [...new Set(MiniJS.variables.concat(this.variables))]
9097

9198
this.variables.forEach((variable) => {
92-
if (variable.startsWith('el.')) {
99+
if (State.isElState(variable)) {
93100
this.setAsParent()
94101

95-
if (!this.parent) this.parent = this.getParent()
102+
if (window[this.id] == null) {
103+
window[this.id] = MiniJS.state.create({}, this.id)
104+
}
96105

97-
const varName = variable.replace('el.', '')
106+
MiniJS.state.addVariable(this.id, this.id)
98107

99-
if (!window[this.uuid]) window[this.uuid] = {}
108+
if (variable !== 'el') {
109+
const [_, varName] = variable.split('.')
110+
MiniJS.state.addEntityVariable(this.id, varName, this.id)
111+
}
112+
} else if (State.isParentState(variable)) {
113+
if (!this.parent) this.parent = this.getParent()
100114

101-
// ! FIXME: Any changes to el.varName isn't being watched
102-
window[this.uuid][varName] = MiniJS.tryFromLocal(
103-
variable.replace('el.', this.uuid + '.')
104-
)
115+
if (window[this.parent.id] == null) {
116+
window[this.parent.id] = MiniJS.state.create({}, this.parent.id)
117+
}
118+
119+
MiniJS.state.addVariable(this.parent.id, this.id)
105120

106-
if (!this.variables.includes(this.uuid)) this.variables.push(this.uuid)
121+
if (variable !== 'parent') {
122+
const [_, varName] = variable.split('.')
123+
MiniJS.state.addEntityVariable(this.parent.id, varName, this.id)
124+
}
107125
} else if (typeof window[variable] === 'function') {
108126
this.variables.splice(this.variables.indexOf(variable), 1)
109-
MiniJS.variables.splice(MiniJS.variables.indexOf(variable), 1)
110127
} else {
111-
window[variable] = variable.startsWith('$')
112-
? MiniJS.tryFromLocal(variable)
113-
: window[variable]
128+
const [identifier] = variable.split('.')
129+
130+
window[identifier] = variable.startsWith('$')
131+
? MiniJS.tryFromLocal(identifier)
132+
: window[identifier]
133+
134+
MiniJS.state.addVariable(identifier, this.id)
114135
}
115136
})
116137
}
117138

118139
async _interpret(expr, options = {}) {
119140
const Engine = options.isClass ? ClassInterpreter : Interpreter
120141
const engine = new Engine(expr, options)
121-
const ids = { $: 'document.querySelector' }
142+
const ids = {
143+
$: 'document.querySelector',
144+
el: `proxyWindow['${this.id}']`,
145+
}
122146

123-
if (this.parent?.uuid) ids.el = `proxyWindow['${this.parent.uuid}']`
147+
if (this.parent) ids.parent = `proxyWindow['${this.parent.id}']`
124148

125149
this.variables.forEach((variable) => {
126-
if (variable.startsWith('el.') || variable === 'el') return
150+
if (State.isElState(variable) || State.isParentState(variable)) return
127151

128152
ids[variable] = `proxyWindow-${variable}`
129153
})
@@ -133,8 +157,6 @@ export default class Entity {
133157
return await engine.interpret(this)
134158
}
135159

136-
/* Note: I don't this getParent() is needed,
137-
since el. variables should use the current element's uuid instead. */
138160
getParent() {
139161
if (this.isParent()) {
140162
return this
@@ -145,24 +167,20 @@ export default class Entity {
145167
currentElement = parentNode
146168
parentNode = currentElement.parentNode
147169
}
148-
const entity = MiniJS.elements.find(
149-
(e) => e.uuid == parentNode.dataset.uuid
150-
)
170+
const entities = Array.from(MiniJS.state.entities.values())
171+
const entity = entities.find((e) => e.uuid == parentNode.dataset.uuid)
151172
return entity
152173
}
153174
}
154175

155176
generateEntityUUID() {
156-
// Suggestion: we can use crypto.randomUUID(). Tho crypto only works in secure contexts
157177
return 'Entity' + Date.now() + Math.floor(Math.random() * 10000)
158178
}
159179

160180
async init() {
161181
this.getVariables()
162182
this.events.apply()
163183
await this.attributes.evaluate()
164-
165-
MiniJS.elements.push(this)
166184
}
167185

168186
initChildren() {
@@ -183,47 +201,31 @@ export default class Entity {
183201

184202
dispose() {
185203
const elements = [this.element, ...this.element.querySelectorAll('*')]
204+
const entities = Array.from(MiniJS.state.entities.values())
186205
const variables = []
187206

188207
// Remove event bindings
189208
for (const element of elements) {
190209
if (element.nodeType !== 1) continue
191210

192-
const entity = MiniJS.elements.find(
193-
(entity) => entity.element === element
194-
)
211+
const entity = MiniJS.state.getEntityByElement(element, entities)
212+
195213
if (!entity) continue
196214

197215
variables.push(...entity.variables)
198216
entity.events.dispose()
217+
MiniJS.state.removeEntity(entity)
199218
}
200219

201-
// Remove disposed elements
202-
MiniJS.elements = MiniJS.elements.filter(
203-
(entity) => !elements.includes(entity.element)
204-
)
205-
206220
// Clean up unused variables
207-
const usedVariables = MiniJS.elements.reduce(
208-
(acc, entity) => acc.concat(entity.variables),
209-
[]
210-
)
221+
const usedVariables = entities
222+
.filter((entity) => !elements.includes(entity.element))
223+
.reduce((acc, entity) => acc.concat(entity.variables), [])
211224

212225
const unusedVariables = variables.filter(
213226
(variable) => !usedVariables.includes(variable)
214227
)
215228

216-
MiniJS.variables = MiniJS.variables.filter(
217-
(variable) => !unusedVariables.includes(variable)
218-
)
219-
220-
unusedVariables.forEach((variable) => {
221-
if (variable.startsWith('el.')) {
222-
const varName = variable.replace('el.', '')
223-
if (window[this.uuid]?.[varName]) delete window[this.uuid]
224-
} else {
225-
delete window[variable]
226-
}
227-
})
229+
MiniJS.state.disposeVariables(this.id, unusedVariables)
228230
}
229231
}

0 commit comments

Comments
 (0)