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

Fix: Reactive El Variables; Refactor: Rehaul of State Dependencies; Feat: Parent El Variables #11

Merged
merged 29 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
412d379
refactor: relocate state related logic to state.js
jorenrui Feb 12, 2024
6664308
chore: add note to update states
jorenrui Feb 12, 2024
fcf16e5
refactor: update initial text of stored valeu
jorenrui Feb 12, 2024
a996de7
fix: exclude object properties in tracked variables
jorenrui Feb 12, 2024
a4e4cd7
feat: setup proxy object for el variables
jorenrui Feb 12, 2024
53e65aa
refactor: use local variables instead of el variables for modal
jorenrui Feb 12, 2024
c926d9b
feat: add dependency-based state update and add reactive entity varia…
jorenrui Feb 12, 2024
d2a02d1
fix: catch eval error for non-existing elements
jorenrui Feb 12, 2024
5c5a6b0
refactor: prevent usage of :innerHTML and :innerText
jorenrui Feb 12, 2024
bd5df01
style: missing button class
jorenrui Feb 12, 2024
cedeb01
fix: native dynamic attr not working
jorenrui Feb 12, 2024
28da332
chore: add todo for dynamically inserted events
jorenrui Feb 12, 2024
daa3b93
refactor: remove usage of variables
jorenrui Feb 12, 2024
a8ce4fc
fix: error in disposal of el variables
jorenrui Feb 12, 2024
a470e30
fix: update cleanup of entity dependencies on disposal
jorenrui Feb 12, 2024
d647faf
refactor: relocate disposal of variables logic to state
jorenrui Feb 12, 2024
ebe61f1
refactor: relocate add/remove of MiniJS.elements
jorenrui Feb 12, 2024
a18284e
refactor: rename dependencies to variables
jorenrui Feb 12, 2024
cb64600
refactor: remove MiniJS.elements
jorenrui Feb 12, 2024
fb95554
feat: add :load event for setting el variable default values
jorenrui Feb 12, 2024
8bec0b1
refactor: relocate evaluate load events
jorenrui Feb 12, 2024
14c0392
fix: make helpers work with load events
jorenrui Feb 12, 2024
aa1edd7
chore: add todo list in lexer
jorenrui Feb 12, 2024
fd84d4e
docs: update info for variables
jorenrui Feb 12, 2024
e35f0cf
feat: add :parent directive
jorenrui Feb 12, 2024
789f5c1
fix: cleanup entity variable
jorenrui Feb 14, 2024
d4ee575
fix: prevent re-evaluate of :load events
jorenrui Feb 14, 2024
b216062
docs: remove hanging text
jorenrui Feb 14, 2024
0008af8
feat: add evaluate :parent attribute
jorenrui Feb 14, 2024
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
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' : ''"
jorenrui marked this conversation as resolved.
Show resolved Hide resolved
>
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' : ''"
jorenrui marked this conversation as resolved.
Show resolved Hide resolved
></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...
jorenrui marked this conversation as resolved.
Show resolved Hide resolved
</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"
jorenrui marked this conversation as resolved.
Show resolved Hide resolved
: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;"
jorenrui marked this conversation as resolved.
Show resolved Hide resolved
>
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)
jorenrui marked this conversation as resolved.
Show resolved Hide resolved

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
Loading