Skip to content

Commit

Permalink
added reactiveStore
Browse files Browse the repository at this point in the history
  • Loading branch information
dux committed Jul 14, 2024
1 parent 8497ce8 commit 3d19769
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 49 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,4 +463,17 @@ Finds first closest Fez node.
Fez('#icon-1').setColor('red')
```
* ### this.reactiveStore({})
Creates reactive store that updates this.html() on change. You can pass another reactivity function.
Used in default TODO example.
```js
this.reactiveStore({}, (o, k, v)=>{
window.requestAnimationFrame(()=>{
console.log(`key "${k}" changed to ${v}`)
this.html())
})
})
```
2 changes: 1 addition & 1 deletion demo/fez/todo.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Todo app demo, <a href="https://todomvc.com/" target="mvc">ToDo MVC candidate</a> (<a href="https://github.com/tastejs/todomvc/tree/master/examples/react/src">React</a>, <a href="https://github.com/tastejs/todomvc/tree/master/examples/vue/src">Vue</a>, <a href="https://github.com/tastejs/todomvc/tree/master/examples/angular/src">Angular</a>)
Todo app demo, features reactiveStore, fez-use, runtime scss. <a href="https://todomvc.com/" target="mvc">ToDo MVC candidate</a> (<a href="https://github.com/tastejs/todomvc/tree/master/examples/react/src">React</a>, <a href="https://github.com/tastejs/todomvc/tree/master/examples/vue/src">Vue</a>, <a href="https://github.com/tastejs/todomvc/tree/master/examples/angular/src">Angular</a>)

<ui-todo></ui-todo>
50 changes: 33 additions & 17 deletions demo/fez/todo.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
Fez('ui-todo', class extends FezBase {
// if you define static html, it will be converted tu function(fast), and you will be able to refresh state with this.html()
static html = `
<h3>Tasks</h3>
{{#if !@tasks[0]}}
{{#if !@data.tasks[0]}}
<p>No tasks found</p>
{{/if}}
{{#for task, index in @tasks}}
{{#if task.animate}}
{{#for task, index in @data.tasks}}
{{#if task.animate}} <!-- this is fine because this is string templating -->
<p fez-use="animate" style="display: none; height: 0px; opacity: 0;">
{{else}}
<p>
Expand All @@ -31,51 +32,66 @@ Fez('ui-todo', class extends FezBase {
&sdot;
<button onclick="$$.clearCompleted()">clear completed</button>
</p>
<pre class="code">{{ JSON.stringify(this.tasks, null, 2) }}</pre>
<pre class="code">{{ JSON.stringify(this.data.tasks, null, 2) }}</pre>
`

toggleComplete(index) {
const task = this.tasks[index]
const task = this.data.tasks[index]
task.done = !task.done
this.html()
}

clearCompleted() {
this.tasks = this.tasks.filter((t) => !t.done)
this.html()
this.data.tasks = this.data.tasks.filter((t) => !t.done)
}

removeTask(index) {
this.tasks = this.tasks.filter((_, i) => i !== index);
this.html()
this.data.tasks = this.data.tasks.filter((_, i) => i !== index);
}

setName(index, name) {
this.tasks[index].name = name
this.html() // refresh full component on every key stroke. done for render speed demo purposes
this.data.tasks[index].name = name
}

addTask() {
// no need to force update template, this is automatic because we are using reactiveStore()
this.counter ||= 0
this.tasks.push({name: `new task ${++this.counter}`, done: false, animate: true})
this.html()
this.data.tasks.push({
name: `new task ${++this.counter}`,
done: false,
animate: true
})
}

animate(node) {
// same as in svelte, uf you define fez-use="methodName", method will be called when node is added to dom.
// in this case, we animate show new node
$(node)
.css('display', 'block')
.animate({height: '33px', opacity: 1}, 200, () => {
delete this.tasks[this.tasks.length-1].animate
delete this.data.tasks[this.data.tasks.length-1].animate
$(node).css('height', 'auto')
this.html()
})
}

connect() {
this.tasks = [
// creates reactive store, that calls this.html() state refresh after every data set
// you can pass function as argument to change default reactive behaviour
this.data = this.reactiveStore({})

this.data.tasks = [
{name: 'First task', done: false},
{name: 'Second task', done: false},
{name: 'Third task', done: true },
]

for (const i in [1,2,3,4,5]) {
this.data.i = i
}

window.requestAnimationFrame(()=>{
for (const i in [1,2,3,4,5]) {
this.data.i = i
}
})
}
})
20 changes: 10 additions & 10 deletions dist/fez.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/fez.js.map

Large diffs are not rendered by default.

52 changes: 34 additions & 18 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ <h1>
<h2>
ui-todo
&sdot;
<small>Todo app demo, <a href="https://todomvc.com/" target="mvc">ToDo MVC candidate</a> (<a href="https://github.com/tastejs/todomvc/tree/master/examples/react/src">React</a>, <a href="https://github.com/tastejs/todomvc/tree/master/examples/vue/src">Vue</a>, <a href="https://github.com/tastejs/todomvc/tree/master/examples/angular/src">Angular</a>)</small>
<small>Todo app demo, features reactiveStore, fez-use, runtime scss. <a href="https://todomvc.com/" target="mvc">ToDo MVC candidate</a> (<a href="https://github.com/tastejs/todomvc/tree/master/examples/react/src">React</a>, <a href="https://github.com/tastejs/todomvc/tree/master/examples/vue/src">Vue</a>, <a href="https://github.com/tastejs/todomvc/tree/master/examples/angular/src">Angular</a>)</small>
</h2>

<div class="flex">
Expand All @@ -38,13 +38,14 @@ <h2>
</div>
<div><pre style="position: relative; top: -23px;"><code class="language-javascript">
Fez('ui-todo', class extends FezBase {
// if you define static html, it will be converted tu function(fast), and you will be able to refresh state with this.html()
static html = `
&lt;h3>Tasks&lt;/h3>
{{#if !@tasks[0]}}
{{#if !@data.tasks[0]}}
&lt;p>No tasks found&lt;/p>
{{/if}}
{{#for task, index in @tasks}}
{{#if task.animate}}
{{#for task, index in @data.tasks}}
{{#if task.animate}} &lt;!-- this is fine because this is string templating -->
&lt;p fez-use="animate" style="display: none; height: 0px; opacity: 0;">
{{else}}
&lt;p>
Expand All @@ -70,52 +71,67 @@ <h2>
&sdot;
&lt;button onclick="$$.clearCompleted()">clear completed&lt;/button>
&lt;/p>
&lt;pre class="code">{{ JSON.stringify(this.tasks, null, 2) }}&lt;/pre>
&lt;pre class="code">{{ JSON.stringify(this.data.tasks, null, 2) }}&lt;/pre>
`

toggleComplete(index) {
const task = this.tasks[index]
const task = this.data.tasks[index]
task.done = !task.done
this.html()
}

clearCompleted() {
this.tasks = this.tasks.filter((t) => !t.done)
this.html()
this.data.tasks = this.data.tasks.filter((t) => !t.done)
}

removeTask(index) {
this.tasks = this.tasks.filter((_, i) => i !== index);
this.html()
this.data.tasks = this.data.tasks.filter((_, i) => i !== index);
}

setName(index, name) {
this.tasks[index].name = name
this.html() // refresh full component on every key stroke. done for render speed demo purposes
this.data.tasks[index].name = name
}

addTask() {
// no need to force update template, this is automatic because we are using reactiveStore()
this.counter ||= 0
this.tasks.push({name: `new task ${++this.counter}`, done: false, animate: true})
this.html()
this.data.tasks.push({
name: `new task ${++this.counter}`,
done: false,
animate: true
})
}

animate(node) {
// same as in svelte, uf you define fez-use="methodName", method will be called when node is added to dom.
// in this case, we animate show new node
$(node)
.css('display', 'block')
.animate({height: '33px', opacity: 1}, 200, () => {
delete this.tasks[this.tasks.length-1].animate
delete this.data.tasks[this.data.tasks.length-1].animate
$(node).css('height', 'auto')
this.html()
})
}

connect() {
this.tasks = [
// creates reactive store, that calls this.html() state refresh after every data set
// you can pass function as argument to change default reactive behaviour
this.data = this.reactiveStore({})

this.data.tasks = [
{name: 'First task', done: false},
{name: 'Second task', done: false},
{name: 'Third task', done: true },
]

for (const i in [1,2,3,4,5]) {
this.data.i = i
}

window.requestAnimationFrame(()=>{
for (const i in [1,2,3,4,5]) {
this.data.i = i
}
})
}
})

Expand Down
39 changes: 39 additions & 0 deletions src/fez.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,45 @@ class FezBase {

methods.forEach(method => this[method] = this[method].bind(this))
}

reactiveStore(obj, handler) {
handler ||= (o, k, v) => {
window.requestAnimationFrame(()=>{
Fez.info('reactive render')
this.html()
},0)
}

handler.bind(this)

// licence ? -> generated by ChatGPT 2024
function createReactive(obj, handler) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}

return new Proxy(obj, {
set(target, property, value, receiver) {
if (typeof value === 'object' && value !== null) {
value = createReactive(value, handler)
}
const result = Reflect.set(target, property, value, receiver)
handler(target, property, value)
return result
},
get(target, property, receiver) {
const value = Reflect.get(target, property, receiver)
if (typeof value === 'object' && value !== null) {
return createReactive(value, handler)
}
return value
}
});
}

return createReactive(obj, handler);
}

}

// clear all unnatached nodes
Expand Down

0 comments on commit 3d19769

Please sign in to comment.