From e0f1ba6323ebcf2d390b7a3992ebbc957ba00042 Mon Sep 17 00:00:00 2001 From: shahrul Date: Mon, 29 Jan 2024 22:33:18 +0800 Subject: [PATCH] fix toggle all, move hyperscript to ._hs file --- hs/behaviors/add-todo._hs | 19 +++ hs/behaviors/clear-completed._hs | 3 + hs/behaviors/destroy._hs | 7 + hs/behaviors/footer._hs | 11 ++ hs/behaviors/todo-check._hs | 11 ++ hs/behaviors/todo-count._hs | 4 + hs/behaviors/todo-dblclick._hs | 6 + hs/behaviors/todo-edit._hs | 19 +++ hs/behaviors/toggle-all._hs | 14 ++ hs/behaviors/toggle-footer._hs | 24 ++++ hs/behaviors/toggle-main._hs | 9 ++ hs/behaviors/toggle-show._hs | 50 +++++++ hs/main._hs | 15 ++ hs/start-me-up._hs | 19 +++ main.go | 28 +++- todomvc.templ | 227 ++++--------------------------- tpl/clear_completed.templ | 5 +- tpl/toggle_all.templ | 12 +- 18 files changed, 263 insertions(+), 220 deletions(-) create mode 100644 hs/behaviors/add-todo._hs create mode 100644 hs/behaviors/clear-completed._hs create mode 100644 hs/behaviors/destroy._hs create mode 100644 hs/behaviors/footer._hs create mode 100644 hs/behaviors/todo-check._hs create mode 100644 hs/behaviors/todo-count._hs create mode 100644 hs/behaviors/todo-dblclick._hs create mode 100644 hs/behaviors/todo-edit._hs create mode 100644 hs/behaviors/toggle-all._hs create mode 100644 hs/behaviors/toggle-footer._hs create mode 100644 hs/behaviors/toggle-main._hs create mode 100644 hs/behaviors/toggle-show._hs create mode 100644 hs/main._hs create mode 100644 hs/start-me-up._hs diff --git a/hs/behaviors/add-todo._hs b/hs/behaviors/add-todo._hs new file mode 100644 index 0000000..931d42a --- /dev/null +++ b/hs/behaviors/add-todo._hs @@ -0,0 +1,19 @@ +behavior AddTodo + on load send focus to me + on focus + if $focus === undefined + my.focus() + set $isFocus to 'true' + end + on blur set $isFocus to undefined + on keyup[keyCode==13] + if $todo + htmx.ajax('GET', `/add-todo?title=${my.value}`, {target:'.todo-list', swap:'beforeend'}) + set my value to '' + else + htmx.ajax('GET', `/add-todo?title=${my.value}`, {target:'.header', swap:'beforeend'}) + set my value to '' + end + send toggleMain to + send toggleFooter to +end \ No newline at end of file diff --git a/hs/behaviors/clear-completed._hs b/hs/behaviors/clear-completed._hs new file mode 100644 index 0000000..96bd1b9 --- /dev/null +++ b/hs/behaviors/clear-completed._hs @@ -0,0 +1,3 @@ +behavior ClearCompleted + on load set $clearCompleted to me + on click send destroy to \ No newline at end of file diff --git a/hs/behaviors/destroy._hs b/hs/behaviors/destroy._hs new file mode 100644 index 0000000..b83fa8d --- /dev/null +++ b/hs/behaviors/destroy._hs @@ -0,0 +1,7 @@ +behavior Destroy + on htmx:afterRequest debounced at 5ms + send toggleMain to + send toggleFooter to + send focus to + if $todo + send toggleClearCompleted to \ No newline at end of file diff --git a/hs/behaviors/footer._hs b/hs/behaviors/footer._hs new file mode 100644 index 0000000..6bfd048 --- /dev/null +++ b/hs/behaviors/footer._hs @@ -0,0 +1,11 @@ +behavior Footer + on load set $footerFooter to me + on toggleClearCompleted debounced at 20ms + if $clearCompleted === undefined + htmx.ajax("GET", "/completed", {target:".filters", swap: "afterend"}) + else + -- need to first set to undefined in case the fetch may return empty which + -- will indiscriminately leave it in incorrect state + set $clearCompleted to undefined + htmx.ajax("GET", "/completed", {target:".clear-completed", swap: "outerHTML"}) + send toggleFooter to \ No newline at end of file diff --git a/hs/behaviors/todo-check._hs b/hs/behaviors/todo-check._hs new file mode 100644 index 0000000..649eaac --- /dev/null +++ b/hs/behaviors/todo-check._hs @@ -0,0 +1,11 @@ +behavior TodoCheck + on htmx:afterRequest + send toggleAll to + send toggleClearCompleted to + send toggleFooter to + on toggle + send toggleClearCompleted to + if $toggleAll.checked and my.checked === false + my.click() + else if $toggleAll.checked === false and my.checked + my.click() \ No newline at end of file diff --git a/hs/behaviors/todo-count._hs b/hs/behaviors/todo-count._hs new file mode 100644 index 0000000..fecf439 --- /dev/null +++ b/hs/behaviors/todo-count._hs @@ -0,0 +1,4 @@ +behavior TodoCount + on load send todoCount to me + on todoCount debounced at 100ms + fetch /update-counts then put the result into me \ No newline at end of file diff --git a/hs/behaviors/todo-dblclick._hs b/hs/behaviors/todo-dblclick._hs new file mode 100644 index 0000000..8639976 --- /dev/null +++ b/hs/behaviors/todo-dblclick._hs @@ -0,0 +1,6 @@ +behavior TodoDblclick + on dblclick + add .editing to the closest
  • + on htmx:afterRequest + set $el to my.parentNode.nextSibling + set $el.selectionStart to $el.value.length \ No newline at end of file diff --git a/hs/behaviors/todo-edit._hs b/hs/behaviors/todo-edit._hs new file mode 100644 index 0000000..d288e6c --- /dev/null +++ b/hs/behaviors/todo-edit._hs @@ -0,0 +1,19 @@ +behavior TodoEdit + on load + my.focus() + on keyup[keyCode==27] + set $keyup to "esc" + remove .editing from closest
  • + on keyup[keyCode==13] + set $keyup to "enter" + htmx.ajax("GET", `/update-todo?id=${@todo-id}&title=${my.value}`, {target: closest
  • , swap: "outerHTML"}) + on blur debounced at 10ms + if $keyup === "enter" + set $keyup to "none" + else if $keyup === "esc" + set $keyup to "none" + else + htmx.ajax("GET", `/update-todo?id=${@todo-id}&title=${my.value}`, {target: closest
  • , swap: "outerHTML"}) + end + send toggleMain to + send toggleFooter to \ No newline at end of file diff --git a/hs/behaviors/toggle-all._hs b/hs/behaviors/toggle-all._hs new file mode 100644 index 0000000..9f1f0bf --- /dev/null +++ b/hs/behaviors/toggle-all._hs @@ -0,0 +1,14 @@ +behavior ToggleAll + on load set $toggleAll to me + on toggleAll debounced at 100ms + fetch /toggle-all then + if it === "true" and my.checked === false then + set my.checked to true + else + if my.checked === true and it === "false" then set my.checked to false + end + end + on click + fetch `/swap-json?all=${my.checked}` then + send show to + send toggle to \ No newline at end of file diff --git a/hs/behaviors/toggle-footer._hs b/hs/behaviors/toggle-footer._hs new file mode 100644 index 0000000..4605dc1 --- /dev/null +++ b/hs/behaviors/toggle-footer._hs @@ -0,0 +1,24 @@ +behavior ToggleFooter + on toggleFooter debounced at 20ms + -- log 'footer' + if $footerFooter + fetch /todo-json as json then + if $todo.hasChildNodes() === false and it.length === 0 + remove $footerFooter + set $footerFooter to undefined + end + -- set-hash already update the hash on the server + -- this reassign the filter class selected base on user interaction + -- or location hash changes + for filter in $filter.children + if filter.textContent === 'All' and `${$initial}${$after}` === '' + add .selected to filter.firstChild + else if filter.textContent !== `${$initial}${$after}` + remove .selected from filter.firstChild + end + end + -- update counts + fetch /update-counts then put the result into + else + htmx.ajax('GET', '/footer', {target:'.header', swap:'beforeend'}) + end \ No newline at end of file diff --git a/hs/behaviors/toggle-main._hs b/hs/behaviors/toggle-main._hs new file mode 100644 index 0000000..d75ee7f --- /dev/null +++ b/hs/behaviors/toggle-main._hs @@ -0,0 +1,9 @@ +behavior ToggleMain + on toggleMain debounced at 20ms + -- log 'main' + if $sectionMain + set $sectionMain to undefined + htmx.ajax("GET", "/toggle-main", {target: "section.main", swap: "outerHTML"}) + else + htmx.ajax('GET', "/toggle-main", {target: ".todo-list", swap: "beforebegin"}) + end \ No newline at end of file diff --git a/hs/behaviors/toggle-show._hs b/hs/behaviors/toggle-show._hs new file mode 100644 index 0000000..299b544 --- /dev/null +++ b/hs/behaviors/toggle-show._hs @@ -0,0 +1,50 @@ +behavior ToggleShow + on show wait 20ms + -- log "show" + -- this is the DOM tree diffing of the todo-list, fetch only the needed + -- to render and remove accordingly base on route All/Active/Completed + fetch /todo-json as json then + if window.location.hash === "#/active" + for todo in it + if todo.done + document.getElementById(`todo-${todo.id}`) then if it remove it end + else + document.getElementById(`todo-${todo.id}`) then + if it === null + htmx.ajax("GET", `/todo-item?id=${todo.id}`, {target:".todo-list", swap: "beforeend"}) + end + end + end + else if window.location.hash === "#/completed" + for todo in it + if todo.done + document.getElementById(`todo-${todo.id}`) then + if it === null + htmx.ajax("GET", `/todo-item?id=${todo.id}`, {target:".todo-list", swap: "beforeend"}) + end + else + document.getElementById(`todo-${todo.id}`) then if it remove it end + end + end + else + -- loop through the JSON + for todo in it + -- check if the element exist in the current DOM, add if none + -- placement is decided according to order if there"s an element + -- with higher than the current todo swap as "beforebegin" + for el in $todo.children + if parseInt(el.id.slice(5)) > todo.id and document.getElementById(`todo-${todo.id}`) === null + htmx.ajax("GET", `/todo-item?id=${todo.id}`, {target: `#${el.id}`, swap: "beforebegin"}) + end + end + -- do reverse lookup for lower than the current todo swap as "afterend" + for el in Array.from($todo.children).reverse() + if parseInt(el.id.slice(5)) < todo.id and document.getElementById(`todo-${todo.id}`) === null + htmx.ajax("GET", `/todo-item?id=${todo.id}`, {target: `#${el.id}`, swap: "afterend"}) + end + end + -- if todo is empty initially recursively add all of it + if $todo.children.length === 0 + htmx.ajax("GET", `/todo-item?id=${todo.id}`, {target:".todo-list", swap: "beforeend"}) + end + end \ No newline at end of file diff --git a/hs/main._hs b/hs/main._hs new file mode 100644 index 0000000..34e368f --- /dev/null +++ b/hs/main._hs @@ -0,0 +1,15 @@ +def hashCache() + -- this is done to get current location hash then update todo-list and footer + set $initial to window.location.hash.slice(2).charAt(0).toUpperCase() + set $after to window.location.hash.slice(3) + fetch `/set-hash?name=${$initial}${$after}` then + send show to + send toggleFooter to + end + -- this to handle popstate event such as back/forward button + -- where it will automatically calling hashCache _hyperscript function + js + window.addEventListener('popstate', function(){ + hashCache(); + }); +end \ No newline at end of file diff --git a/hs/start-me-up._hs b/hs/start-me-up._hs new file mode 100644 index 0000000..65e95bb --- /dev/null +++ b/hs/start-me-up._hs @@ -0,0 +1,19 @@ +def startMeUp() + log " + ooooo ooooo ooooooooooooo ooo ooooo ooooooo ooooo + `888' `888' 8' 888 `8 `88. .888' `8888 d8' + 888 888 888 888b d'888 Y888..8P + 888ooooo888 888 8 Y88. .P 888 `8888' + 888 888 888 8 `888' 888 .8PY888. + 888 888 888 8 Y 888 d8' `888b + o888o o888o o888o o8o o888o o888o o88888o + =========================================================== + Build with GO, TEMPL, HTMX & _HYPERSCRIPT + _ _ _ _ _ + | |_| |__ ___ _ __(_) __ _| |__ | |_ __ ____ _ _ _ + | __| '_ \\ / _ \\ | '__| |/ _\` | '_ \\| __| \\ \\ /\\ / / _\` | | | | + | |_| | | | __/ | | | | (_| | | | | |_ \\ V V / (_| | |_| | + \\__|_| |_|\\___| |_| |_|\\__, |_| |_|\\__| \\_/\\_/ \\__,_|\\__, | + |___/ |___/ + by http://github.com/syarul/" +end \ No newline at end of file diff --git a/main.go b/main.go index 92ec7b4..174aa8a 100644 --- a/main.go +++ b/main.go @@ -112,16 +112,17 @@ func main() { http.Handle("/toggle-footer", http.HandlerFunc(t.toggleFooterHandler)) http.Handle("/todo-list", http.HandlerFunc(t.todoListHandler)) http.Handle("/todo-json", http.HandlerFunc(t.getJSON)) + http.Handle("/swap-json", http.HandlerFunc(t.swapJSON)) http.Handle("/todo-item", http.HandlerFunc(t.todoItemHandler)) // this is used to serve axe-core for the todomvc test dir := "./cypress-example-todomvc/node_modules" - // Use the http.FileServer to create a handler for serving static files - fs := http.FileServer(http.Dir(dir)) + // serve *._hs file + http.Handle("/hs/", http.StripPrefix("/hs/", http.FileServer(http.Dir("./hs")))) - // Use the http.Handle to register the file server handler for a specific route - http.Handle("/node_modules/", http.StripPrefix("/node_modules/", fs)) + // use the http.Handle to register the file server handler for a specific route + http.Handle("/node_modules/", http.StripPrefix("/node_modules/", http.FileServer(http.Dir(dir)))) // start the server. addr := os.Getenv("LISTEN_ADDRESS") @@ -200,6 +201,25 @@ func (t *todos) getJSON(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(*t) } +// swap json state if toggle is click +func (t *todos) swapJSON(w http.ResponseWriter, r *http.Request) { + all, err := strconv.ParseBool(r.FormValue("all")) + if err != nil { + fmt.Println("Error:", err) + return + } + + for _, todo := range *t { + id := todo.Id + if all { + t.crudOps(Toggle, Todo{id, "", true, false}) + } else { + t.crudOps(Toggle, Todo{id, "", false, false}) + } + } + byteRenderer(w, r, "") +} + func selectedFilter(filters []Filter) string { for _, filter := range filters { if filter.selected { diff --git a/todomvc.templ b/todomvc.templ index 3f86f99..5d4a72b 100644 --- a/todomvc.templ +++ b/todomvc.templ @@ -32,26 +32,7 @@ templ editTodo(todo Todo) { value="" } todo-id={ strconv.FormatUint(todo.Id, 10) } - _=" - on load - my.focus() - on keyup[keyCode==27] - set $keyup to 'esc' - remove .editing from closest
  • - on keyup[keyCode==13] - set $keyup to 'enter' - htmx.ajax('GET', `/update-todo?id=${@todo-id}&title=${my.value}`, {target: closest
  • , swap:'outerHTML'}) - on blur debounced at 10ms - if $keyup === 'enter' - set $keyup to 'none' - else if $keyup === 'esc' - set $keyup to 'none' - else - htmx.ajax('GET', `/update-todo?id=${@todo-id}&title=${my.value}`, {target: closest
  • , swap:'outerHTML'}) - end - send toggleMain to - send toggleFooter to - " + _="install TodoEdit" /> } @@ -63,17 +44,7 @@ templ todoCheck(todo Todo) { hx-patch={ fmt.Sprintf(`/toggle-todo?id=%s&done=%s`, strconv.FormatUint(todo.Id, 10), strconv.FormatBool(todo.Done)) } hx-target="closest
  • " hx-swap="outerHTML" - _=" - on htmx:afterRequest - send toggleAll to - send toggleClearCompleted to - on toggle - send toggleClearCompleted to - if $toggleAll.checked and my.checked === false - my.click() - else if $toggleAll.checked === false and my.checked - my.click() - " + _="install TodoCheck" /> } @@ -91,13 +62,7 @@ templ todoItem(todo Todo, filterName string) { hx-patch={ fmt.Sprintf(`/edit-todo?id=%s`, strconv.FormatUint(todo.Id, 10)) } hx-target="next input" hx-swap="outerHTML" - _=" - on dblclick - add .editing to the closest
  • - on htmx:afterRequest - set $el to my.parentNode.nextSibling - set $el.selectionStart to $el.value.length - " + _="install TodoDblclick" > { todo.title } } } \ No newline at end of file diff --git a/tpl/toggle_all.templ b/tpl/toggle_all.templ index 7361dab..93e09c2 100644 --- a/tpl/toggle_all.templ +++ b/tpl/toggle_all.templ @@ -3,16 +3,6 @@ package tpl templ ToggleAll(checked bool) { } \ No newline at end of file