scripting enhancements

This commit is contained in:
maddalax 2024-10-29 07:08:44 -05:00
parent 10f48af304
commit cd8d08c0a5
8 changed files with 229 additions and 11 deletions

View file

@ -2,6 +2,7 @@ package h
import (
"fmt"
"github.com/google/uuid"
"github.com/maddalax/htmgo/framework/hx"
"github.com/maddalax/htmgo/framework/internal/util"
"strings"
@ -233,6 +234,54 @@ func ToggleClass(class string) SimpleJsCommand {
return SimpleJsCommand{Command: fmt.Sprintf("this.classList.toggle('%s')", class)}
}
// ToggleText toggles the given text on the element.
func ToggleText(text string, textTwo string) Command {
// language=JavaScript
return EvalJs(fmt.Sprintf(`
if(self.innerText === "%s") {
self.innerText = "%s";
} else {
self.innerText = "%s";
}
`, text, textTwo, text))
}
// ToggleTextOnSibling toggles the given text on the siblings of the element.
func ToggleTextOnSibling(selector, text string, textTwo string) Command {
// language=JavaScript
return EvalJsOnSibling(selector, fmt.Sprintf(`
if(element.innerText === "%s") {
element.innerText = "%s";
} else {
element.innerText = "%s";
}
`, text, textTwo, text))
}
// ToggleTextOnChildren toggles the given text on the children of the element.
func ToggleTextOnChildren(selector, text string, textTwo string) Command {
// language=JavaScript
return EvalJsOnChildren(selector, fmt.Sprintf(`
if(element.innerText === "%s") {
element.innerText = "%s";
} else {
element.innerText = "%s";
}
`, text, textTwo, text))
}
// ToggleTextOnParent toggles the given text on the parent of the element.
func ToggleTextOnParent(text string, textTwo string) Command {
// language=JavaScript
return EvalJsOnParent(fmt.Sprintf(`
if(element.innerText === "%s") {
element.innerText = "%s";
} else {
element.innerText = "%s";
}
`, text, textTwo, text))
}
// ToggleClassOnElement toggles the given class on the elements returned by the selector.
func ToggleClassOnElement(selector, class string) ComplexJsCommand {
// language=JavaScript
@ -276,24 +325,42 @@ func EvalJsOnSibling(selector, js string) ComplexJsCommand {
`, selector, js))
}
// SetClassOnParent sets the given class on the parent of the element. Reference the element using 'element'.
// SetClassOnParent sets the given class on the parent of the element.
func SetClassOnParent(class string) ComplexJsCommand {
// language=JavaScript
return EvalJsOnParent(fmt.Sprintf("element.classList.add('%s')", class))
}
// RemoveClassOnParent removes the given class from the parent of the element. Reference the element using 'element'.
// RemoveClassOnParent removes the given class from the parent of the element.
func RemoveClassOnParent(class string) ComplexJsCommand {
// language=JavaScript
return EvalJsOnParent(fmt.Sprintf("element.classList.remove('%s')", class))
}
// SetClassOnChildren sets the given class on the children of the element. Reference the element using 'element'.
// SetClassOnChildren sets the given class on the children of the element.
func SetClassOnChildren(selector, class string) ComplexJsCommand {
// language=JavaScript
return EvalJsOnChildren(selector, fmt.Sprintf("element.classList.add('%s')", class))
}
// ToggleClassOnChildren toggles the given class on the children of the element.
func ToggleClassOnChildren(selector, class string) ComplexJsCommand {
// language=JavaScript
return EvalJsOnChildren(selector, fmt.Sprintf("element.classList.toggle('%s')", class))
}
// ToggleClassOnParent toggles the given class on the parent of the element.
func ToggleClassOnParent(class string) ComplexJsCommand {
// language=JavaScript
return EvalJsOnParent(fmt.Sprintf("element.classList.toggle('%s')", class))
}
// ToggleClassOnSibling toggles the given class on the siblings of the element.
func ToggleClassOnSibling(selector, class string) ComplexJsCommand {
// language=JavaScript
return EvalJsOnSibling(selector, fmt.Sprintf("element.classList.toggle('%s')", class))
}
// SetClassOnSibling sets the given class on the siblings of the element. Reference the element using 'element'.
func SetClassOnSibling(selector, class string) ComplexJsCommand {
// language=JavaScript
@ -330,6 +397,36 @@ func EvalJs(js string) ComplexJsCommand {
return NewComplexJsCommand(js)
}
func EvalCommandsOnSelector(selector string, cmds ...Command) ComplexJsCommand {
lines := make([]string, len(cmds))
for i, cmd := range cmds {
lines[i] = Render(cmd)
lines[i] = strings.ReplaceAll(lines[i], "this.", "self.")
// some commands set the element we need to fix it so we arent redeclaring it
lines[i] = strings.ReplaceAll(lines[i], "let element =", "element =")
}
code := strings.Join(lines, "\n")
return EvalJs(fmt.Sprintf(`
let element = document.querySelector("%s");
if(!element) {
return;
}
self = element;
%s
`, selector, code))
}
func EvalCommands(element *Element, cmds ...Command) ComplexJsCommand {
id := strings.ReplaceAll(uuid.NewString(), "-", "")
element.AppendChildren(
Attribute("data-eval-commands-id", id),
)
return EvalCommandsOnSelector(
fmt.Sprintf(`[data-eval-commands-id='%s']`, id), cmds...)
}
// PreventDefault prevents the default action of the event.
func PreventDefault() SimpleJsCommand {
// language=JavaScript

View file

@ -35,9 +35,15 @@ var voidTags = map[string]bool{
"wbr": true,
}
type ScriptEntry struct {
Body string
ChildOf *Element
}
type RenderContext struct {
builder *strings.Builder
scripts []string
builder *strings.Builder
scripts []ScriptEntry
currentElement *Element
}
func (ctx *RenderContext) AddScript(funcName string, body string) {
@ -48,7 +54,11 @@ func (ctx *RenderContext) AddScript(funcName string, body string) {
%s
}
</script>`, funcName, funcName, body)
ctx.scripts = append(ctx.scripts, script)
ctx.scripts = append(ctx.scripts, ScriptEntry{
Body: script,
ChildOf: ctx.currentElement,
})
}
func (node *Element) Render(context *RenderContext) {
@ -56,6 +66,8 @@ func (node *Element) Render(context *RenderContext) {
return
}
context.currentElement = node
if node.tag == CachedNodeTag {
meta := node.meta.(*CachedNode)
meta.Render(context)
@ -147,7 +159,7 @@ func (node *Element) Render(context *RenderContext) {
}
if node.tag != "" {
renderScripts(context)
renderScripts(context, node)
if !voidTags[node.tag] {
context.builder.WriteString("</")
context.builder.WriteString(node.tag)
@ -156,11 +168,19 @@ func (node *Element) Render(context *RenderContext) {
}
}
func renderScripts(context *RenderContext) {
for _, script := range context.scripts {
context.builder.WriteString(script)
func renderScripts(context *RenderContext, parent *Element) {
if len(context.scripts) == 0 {
return
}
context.scripts = []string{}
notWritten := make([]ScriptEntry, 0)
for _, script := range context.scripts {
if script.ChildOf == parent {
context.builder.WriteString(script.Body)
} else {
notWritten = append(notWritten, script)
}
}
context.scripts = notWritten
}
func (a *AttributeR) Render(context *RenderContext) {

View file

@ -14,6 +14,9 @@ var SetDisabled = h.SetDisabled
var RemoveClass = h.RemoveClass
var Alert = h.Alert
var SetClassOnChildren = h.SetClassOnChildren
var ToggleClassOnChildren = h.ToggleClassOnChildren
var ToggleClassOnParent = h.ToggleClassOnParent
var ToggleClassOnSibling = h.ToggleClassOnSibling
var RemoveClassOnChildren = h.RemoveClassOnChildren
var EvalJsOnChildren = h.EvalJsOnChildren
var EvalJsOnSibling = h.EvalJsOnSibling
@ -23,6 +26,8 @@ var RemoveClassOnSibling = h.RemoveClassOnSibling
var Remove = h.Remove
var PreventDefault = h.PreventDefault
var EvalJs = h.EvalJs
var EvalCommands = h.EvalCommands
var EvalCommandsOnSelector = h.EvalCommandsOnSelector
var ConsoleLog = h.ConsoleLog
var SetValue = h.SetValue
var SubmitFormOnEnter = h.SubmitFormOnEnter
@ -36,3 +41,7 @@ var GetWithQs = h.GetWithQs
var PostWithQs = h.PostWithQs
var ToggleClass = h.ToggleClass
var ToggleClassOnElement = h.ToggleClassOnElement
var ToggleText = h.ToggleText
var ToggleTextOnSibling = h.ToggleTextOnSibling
var ToggleTextOnChildren = h.ToggleTextOnChildren
var ToggleTextOnParent = h.ToggleTextOnParent

View file

@ -70,9 +70,31 @@ var ClickToEditSnippet = Snippet{
partial: snippets.ClickToEdit,
}
var JsSetTextOnClick = Snippet{
category: "Interactivity (JS)",
name: "Set Element Text On Click",
description: "A simple example of how to use htmgo with javascript",
sidebarName: "Set Text On Click",
path: "/examples/js-set-text-on-click",
partial: snippets.SetTextOnClick,
}
var JsHideChildrenOnClick = Snippet{
category: "Interactivity (JS)",
name: "Hide / Show Children On Click",
description: "Use JS to hide and show children elements on click",
sidebarName: "Hide / Show Children",
path: "/examples/js-hide-children-on-click",
partial: snippets.JsHideChildrenOnClick,
}
var examples = []Snippet{
FormWithLoadingStateSnippet,
ClickToEditSnippet,
JsSetTextOnClick,
JsHideChildrenOnClick,
UserAuthSnippet,
ChatSnippet,
HackerNewsSnippet,

View file

@ -0,0 +1,10 @@
package examples
import (
"github.com/maddalax/htmgo/framework/h"
)
func JsHideChildrenOnClickPage(ctx *h.RequestContext) *h.Page {
SetSnippet(ctx, &JsHideChildrenOnClick)
return Index(ctx)
}

View file

@ -0,0 +1,10 @@
package examples
import (
"github.com/maddalax/htmgo/framework/h"
)
func JsSetTextOnClickPage(ctx *h.RequestContext) *h.Page {
SetSnippet(ctx, &JsSetTextOnClick)
return Index(ctx)
}

View file

@ -0,0 +1,32 @@
package snippets
import (
"github.com/maddalax/htmgo/framework/h"
"github.com/maddalax/htmgo/framework/js"
)
func JsHideChildrenOnClick(ctx *h.RequestContext) *h.Partial {
text := h.Pf("- Parent")
return h.NewPartial(
h.Div(
text,
h.Class("cursor-pointer"),
h.Id("js-test"),
h.OnClick(
js.ToggleClassOnChildren("div", "hidden"),
js.EvalCommands(
text,
js.ToggleText("+ Parent", "- Parent"),
),
),
h.Div(
h.Class("ml-4"),
h.Text("Child 1"),
),
h.Div(
h.Class("ml-4"),
h.Text("Child 2"),
),
),
)
}

View file

@ -0,0 +1,18 @@
package snippets
import (
"github.com/maddalax/htmgo/framework/h"
"github.com/maddalax/htmgo/framework/js"
)
func SetTextOnClick(ctx *h.RequestContext) *h.Partial {
return h.NewPartial(
h.Button(
h.Text("Click to set text"),
h.Class("bg-slate-900 text-white py-2 px-4 rounded"),
h.OnClick(
js.SetText("Hello World"),
),
),
)
}