Skip to content

Commit

Permalink
Merge pull request #11 from Group-One-Technology/jr.fix/reactive-el-v…
Browse files Browse the repository at this point in the history
…ariables

Fix: Reactive El Variables; Refactor: Rehaul of State Dependencies; Feat: Parent El Variables
  • Loading branch information
jorenrui authored Feb 14, 2024
2 parents 2a0246a + 0008af8 commit a129aa8
Show file tree
Hide file tree
Showing 9 changed files with 503 additions and 208 deletions.
33 changes: 20 additions & 13 deletions demo/observer.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,51 +13,58 @@
<ol class="mt-8 list-decimal ml-4">
<li>
<button
:mouseenter="isHovered = true"
:mouseleave="isHovered = false"
:class="isHovered ? 'bg-red-200' : ''"
:load="el.isHovered = true"
:click="this.parentNode.parentNode.removeChild(this.parentNode)"
:mouseenter="el.isHovered = true"
:mouseleave="el.isHovered = false"
:class="el.isHovered ? 'bg-red-200' : ''"
>
list item (click to delete)
</button>
<button
class="px-2 py-1 bg-gray-200 rounded-md text-base"
:click="this.previousElementSibling.setAttribute(':class', 'isHovered ? \'bg-red-200\' : \'bg-gray-200\'')"
:click="this.previousElementSibling.setAttribute(':class', 'el.isHovered ? \'bg-red-200\' : \'bg-gray-200\'')"
:mouseenter="el.isHovered = true"
:mouseleave="el.isHovered = false"
:class="el.isHovered ? 'bg-red-200' : ''"
>
Change :class Attribute
</button>
</li>
<li>
<button
:mouseenter="isHovered2 = true"
:mouseleave="isHovered2 = false"
:class="isHovered2 ? 'bg-red-200' : ''"
:click="this.parentNode.parentNode.removeChild(this.parentNode)"
:text="`my id is ${this.getAttribute('id')} (hover me to change) list item (click to delete)`"
:mouseenter="el.isHovered = true"
:mouseleave="el.isHovered = false"
:class="el.isHovered ? 'bg-red-200' : ''"
></button>
<button
class="px-2 py-1 bg-gray-200 rounded-md text-base"
:click="this.previousElementSibling.setAttribute(':id', '`list2${Math.random()}`')"
:mouseenter="el.isHovered = true"
:mouseleave="el.isHovered = false"
:class="el.isHovered ? 'bg-red-200' : ''"
>
Change :id Attribute
</button>
</li>
<li>
<button
:mouseenter="isHovered3 = true"
:mouseleave="isHovered3 = false"
:class="isHovered3 ? 'bg-red-200' : ''"
:click="this.parentNode.parentNode.removeChild(this.parentNode)"
:mouseenter="el.isHovered = true"
:mouseleave="el.isHovered = false"
:class="el.isHovered ? 'bg-red-200' : ''"
>
list item (click to delete)
</button>
</li>
<li>
<button
:mouseenter="isHovered4 = true"
:mouseleave="isHovered4 = false"
:class="isHovered4 ? 'bg-red-200' : ''"
:click="this.parentNode.parentNode.removeChild(this.parentNode)"
:mouseenter="el.isHovered = true"
:mouseleave="el.isHovered = false"
:class="el.isHovered ? 'bg-red-200' : ''"
>
list item (click to delete)
</button>
Expand Down
38 changes: 22 additions & 16 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1304,7 +1304,7 @@ <h3 class="font-bold font-mono">Storing to Local Storage:</h3>
class="ml-3 mt-2 text-lg font-medium font-mono"
:text="$storedValue"
>
Update and Reload
Loading...
</h2>
</div>

Expand Down Expand Up @@ -1400,9 +1400,12 @@ <h3 class="font-bold font-mono">Multi Select:</h3>
: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"
></li>
</ul>
Expand Down Expand Up @@ -1502,13 +1505,13 @@ <h3 class="font-bold font-mono">Multi Select:</h3>
grid-template-rows: 0fr 1fr;
}
</style>
<div class="accordion">
<div class="accordion" :parent>
<section
class="grid transition-all border-gray-300 border border-b-0 rounded hover:bg-gray-100"
:class="activeSection =='about' ? 'active' : ''"
:class="parent.activeSection =='about' ? 'active' : ''"
>
<a
:click="activeSection='about'"
:click="parent.activeSection='about'"
class="cursor-pointer font-bold p-4"
>
About Us
Expand All @@ -1523,10 +1526,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="activeSection =='contact' ? 'active' : ''"
:class="parent.activeSection =='contact' ? 'active' : ''"
>
<a
:click="activeSection='contact'"
:click="parent.activeSection='contact'"
class="cursor-pointer font-bold p-4"
>
Contact Us
Expand All @@ -1541,9 +1544,12 @@ <h3 class="font-bold font-mono">Multi Select:</h3>

<section
class="grid transition-all border-gray-300 border rounded hover:bg-gray-100"
:class="activeSection =='team' ? 'active' : ''"
:class="parent.activeSection =='team' ? 'active' : ''"
>
<a :click="activeSection='team'" class="cursor-pointer font-bold p-4">
<a
:click="parent.activeSection='team'"
class="cursor-pointer font-bold p-4"
>
Team 3
</a>
<div class="overflow-hidden">
Expand Down Expand Up @@ -1996,11 +2002,11 @@ <h3 class="font-bold font-mono">Tonic Dialog:</h3>
<a
class="bg-indigo-600 hover:bg-indigo-800 transition-all text-white rounded px-3 py-2 cursor-pointer"
:click="isModalOpen = true;
el.loader = document.createElement('div');
el.loader.className = 'shimmer h-32 w-full';
el.loader.style.minWidth = '300px';
el.modalContent = $('#modal-container-content');
el.modalContent.innerHTML = el.loader.outerHTML;"
const loader = document.createElement('div');
loader.className = 'shimmer h-32 w-full';
loader.style.minWidth = '300px';
const modalContent = $('#modal-container-content');
modalContent.innerHTML = loader.outerHTML;"
>
Open Modal
</a>
Expand Down
100 changes: 51 additions & 49 deletions lib/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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() {
Expand All @@ -25,6 +29,10 @@ export default class Entity {
return !!this.uuid
}

isExists() {
return document.documentElement.contains(this.element)
}

getVariables() {
this._getVariablesFromAttributes()
this._getVariablesFromEvents()
Expand Down Expand Up @@ -86,44 +94,60 @@ 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)
}
})
}

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}`
})
Expand All @@ -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
Expand All @@ -145,24 +167,20 @@ 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)
}

async init() {
this.getVariables()
this.events.apply()
await this.attributes.evaluate()

MiniJS.elements.push(this)
}

initChildren() {
Expand All @@ -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)
}
}
Loading

0 comments on commit a129aa8

Please sign in to comment.