Skip to content

Commit

Permalink
add o-model, o-if and other cool stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
sebkolind committed Nov 16, 2023
1 parent b7e7186 commit cf234bd
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 101 deletions.
59 changes: 37 additions & 22 deletions example/app.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,47 @@
import { O } from '../dist/one'
import { One } from '../dist/one'
import { getItems } from './services/get-items'

const Component = new O('[hello-world]', function () {
this.setState({
const Component = new One('[hello-world]', async function () {
this.state = {
name: 'John Doe',
count: 0,
text: 'Click me',
items: [
{ title: 'Item 1' },
{ title: 'Item 2' },
{ title: 'Item 3' }
]
})
})
bool: false,
selected: 'two',
buttonText: 'Click me',
items: [],
checked: false
}

const Button = new O('button', function () {
this.setScope(Component)
const items = await getItems()

this.on('click', function (data) {
const count = data.count + 1
this.state = {
items,
subtitle: getSubtitle(items.length)
}

this.register([Button])
})

this.setState({
const Button = new One('button', function () {
this.on('click', function ({ state }) {
const count = state.count + 1
const items = state.items.filter(i => i.id === 1)

this.state = {
buttonText: `Clicked ${count} times`,
count,
text: `Clicked ${count}`,
items: [
{ title: 'Item 1' }
]
})
bool: !state.bool,
items,
subtitle: getSubtitle(items.length)
}
})
})

console.log('Component', Component, 'Button', Button)
Component.mount()

function getSubtitle (count) {
const start = count < 2 ? 'is' : 'are'
const end = count > 1 ? 'items' : 'item'

return `There ${start} ${count} ${end}`
}
22 changes: 17 additions & 5 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,25 @@
<a href="./robots.txt" aria-label="robots.txt"></a>
<div hello-world>
<p>one.js 💛</p>
<div o-if="items.length">
<p>It's true!</p>
</div>
<p o-text="subtitle">There are 0 items</p>
<ul o-for="items">
<li o-text="title"></li>
<li o-text="title">Loading...</li>
</ul>
<span o-text="name"></span>
<button o-text="text"></button>
<div>
Clicked <span o-text="count">0</span> times
<span o-text="name">Jane Doe</span>
<button o-text="buttonText">Click me</button>
<div>Clicked <span o-text="count">0</span> times</div>
<input o-model="name" type="text" />
<div>The selected value: <span o-text="selected"></span></div>
<select o-model="selected">
<option value="one">One</option>
<option value="two">Two</option>
</select>
<input type="checkbox" o-model="checked" />
<div o-if="checked">
<p>Checked</p>
</div>
</div>
<script type="module" src="app.js"></script>
Expand Down
6 changes: 3 additions & 3 deletions example/services/get-items.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ export async function getItems () {
resolve([
{
id: 1,
name: 'Grocery shopping',
title: 'Grocery shopping',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
done: false
},
{
id: 2,
name: 'Clean the house',
title: 'Clean the house',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
done: false
},
{
id: 3,
name: 'Go to the gym',
title: 'Go to the gym',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
done: false
}
Expand Down
158 changes: 87 additions & 71 deletions lib/one.js
Original file line number Diff line number Diff line change
@@ -1,115 +1,131 @@
const TEXT = 'o-text'
const FOR = 'o-for'
const KEY = 'key'
import { Render } from './render'

class O {
class One {
#el = null
#scope = null
#state = {}

#setup = null

constructor (el, setup) {
this.#el = typeof el === 'string' ? document.querySelectorAll(el) : el

setup.bind(this)()

if (!this.#el) {
throw new Error('Element not found')
}

if (this.#scope) {
this.#state = this.#scope.getState()
}
this.#setup = setup

this.models()
}

get el () {
return this.#el
}

setup (fn) {
fn.bind(this)()
set el (el) {
this.#el = el

return this
}

setScope (scope) {
this.#scope = scope
get scope () {
return this.#scope
}

on (event, fn) {
const iterator = this.#el.length ? this.#el : [this.#el]
for (const el of iterator) {
el[`on${event}`] = () => {
fn.bind(this)(this.#state)
}
}
set scope (scope) {
this.#scope = scope

return this
}

setState (state, override = false) {
this.#state = override ? state : { ...this.#state, ...state }
this.render(this.#scope?.el ?? this.#el)
get state () {
return this.#state
}

getState (key) {
if (key) {
return this.#state[key]
set state (state) {
this.#state = this.#scope?.state
? { ...this.#scope.state, ...state }
: { ...this.state, ...state }

if (this.#scope) {
this.#scope.state = this.state

return this
}

return this.#state
new Render().render(
this.el,
this.state
)

return this
}

render (target, state = this.#state) {
const iterator = target.length ? target : [target]
models () {
this.#el.forEach(el => {
const models = el.querySelectorAll('[o-model]')

for (const el of iterator) {
if (el.getAttribute(TEXT)) {
const text = el.getAttribute(TEXT)
const key = el.getAttribute(KEY)
const s = key ? state?.[key]?.[text] : state[text]

if (s != null && s !== el.textContent) {
el.textContent = s
}
}

if (el.getAttribute(FOR)) {
const template = el.children[0].cloneNode(true)
const rendered = template.getAttribute(KEY)
const array = state[el.getAttribute(FOR)]
if (models.length) {
models.forEach(model => {
const key = model.getAttribute('o-model')

// Remove all children that are not in the array
if (rendered) {
const children = Array.from(el.children)
model[this.getModelEvent(el)] = (e) => {
const value = e.target.type === 'checkbox'
? e.target.checked
: e.target.value

let i = 0
for (const c of children) {
if (!array[i]) {
el.removeChild(c)
this.state = {
[key]: value
}
i++
}
}

// Only remove first child if it is the template
if (!rendered) {
el.children[0].remove()
}

array.forEach((item, i) => {
// Don't append already existing children
if (!el.children[i]) {
template.setAttribute(KEY, i)
el.append(template.cloneNode(true))
}
})
}
})
}

this.render(el.children, array)
getModelEvent (el) {
if (el.tagName === 'INPUT') {
if (el.type === 'checkbox') {
return 'onchange'
}
return 'oninput'
}

if (el.children.length) {
for (const c of el.children) {
this.render(c)
}
if (el.tagName === 'SELECT') {
return 'onchange'
}

return 'oninput'
}

register (components) {
components.forEach(component => {
component.scope = this
component.state = this.state
component.mount()
})
}

on (event, fn) {
const iterator = this.#el.length ? this.#el : [this.#el]
for (const el of iterator) {
el[`on${event}`] = () => {
fn.bind(this)({
state: this.#scope?.state ?? this.#state
})
}
}
}

mount (state) {
if (state) {
this.#state = state
}
this.#setup.bind(this)()

return this
}
}

export { O }
export { One }
Loading

0 comments on commit cf234bd

Please sign in to comment.