Skip to content

Commit

Permalink
refactor to work with connectors as mentioned in #8
Browse files Browse the repository at this point in the history
  • Loading branch information
donseba committed Dec 1, 2024
1 parent 4ef3ae4 commit e81fce9
Show file tree
Hide file tree
Showing 20 changed files with 848 additions and 117 deletions.
445 changes: 445 additions & 0 deletions INTEGRATIONS.md

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,20 @@ go get github.com/donseba/go-partial
## Advanced use cases
Advanced usecases are documented in the [ADVANCED.md](ADVANCED.md) file

## Basic Usage
## Integrations
Several integrations are available, detailed information can be found in the [INTEGRATIONS.md](INTEGRATIONS.md) file
- htmx
- Turbo
- Stimulus
- Unpoly
- Alpine.js / Alpine Ajax (not great)
- Vue.js (not great)
- Standalone

## Basic Usage
Here's a simple example of how to use the package to render a template.

### 1. Create a Service

The `Service` holds global configurations and data.

```go
Expand All @@ -44,7 +52,6 @@ service.SetData(map[string]any{
```

## 2. Create a Layout

The `Layout` manages the overall structure of your templates.
```go
layout := service.NewLayout()
Expand All @@ -54,7 +61,6 @@ layout.SetData(map[string]any{
```

### 3. Define Partials

Create `Partial` instances for the content and any other components.

```go
Expand All @@ -81,7 +87,6 @@ func handler(w http.ResponseWriter, r *http.Request) {
```

## Template Files

templates/layout.html
```html
<!DOCTYPE html>
Expand All @@ -103,7 +108,6 @@ Note: In the layout template, we use {{ child "content" }} to render the content


### Using Global and Layout Data

- **Global Data (ServiceData)**: Set on the Service, accessible via {{.Service}} in templates.
- **Layout Data (LayoutData)**: Set on the Layout, accessible via {{.Layout}} in templates.
- **Partial Data (Data)**: Set on individual Partial instances, accessible via {{.Data}} in templates.
Expand Down
22 changes: 22 additions & 0 deletions connector/alpine-ajax.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package connector

import "net/http"

type AlpineAjax struct {
base
}

func NewAlpineAjax(c *Config) Connector {
return &AlpineAjax{
base: base{
config: c,
targetHeader: "X-Alpine-Target",
selectHeader: "X-Alpine-Select",
actionHeader: "X-Alpine-Action",
},
}
}

func (a *AlpineAjax) RenderPartial(r *http.Request) bool {
return r.Header.Get(a.targetHeader) != ""
}
22 changes: 22 additions & 0 deletions connector/alpine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package connector

import "net/http"

type Alpine struct {
base
}

func NewAlpine(c *Config) Connector {
return &Alpine{
base: base{
config: c,
targetHeader: "X-Alpine-Target",
selectHeader: "X-Alpine-Select",
actionHeader: "X-Alpine-Action",
},
}
}

func (a *Alpine) RenderPartial(r *http.Request) bool {
return r.Header.Get(a.targetHeader) != ""
}
87 changes: 87 additions & 0 deletions connector/connector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package connector

import "net/http"

type (
Connector interface {
RenderPartial(r *http.Request) bool
GetTargetValue(r *http.Request) string
GetSelectValue(r *http.Request) string
GetActionValue(r *http.Request) string

GetTargetHeader() string
GetSelectHeader() string
GetActionHeader() string
}

Config struct {
UseURLQuery bool
}

base struct {
config *Config
targetHeader string
selectHeader string
actionHeader string
}
)

func (x *base) RenderPartial(r *http.Request) bool {
return r.Header.Get(x.targetHeader) != ""
}

func (x *base) GetTargetHeader() string {
return x.targetHeader
}

func (x *base) GetSelectHeader() string {
return x.selectHeader
}

func (x *base) GetActionHeader() string {
return x.actionHeader
}

func (x *base) GetTargetValue(r *http.Request) string {
if targetValue := r.Header.Get(x.targetHeader); targetValue != "" {
return targetValue
}

if x.config.useURLQuery() {
return r.URL.Query().Get("target")
}

return ""
}

func (x *base) GetSelectValue(r *http.Request) string {
if selectValue := r.Header.Get(x.selectHeader); selectValue != "" {
return selectValue
}

if x.config.useURLQuery() {
return r.URL.Query().Get("select")
}

return ""
}

func (x *base) GetActionValue(r *http.Request) string {
if actionValue := r.Header.Get(x.actionHeader); actionValue != "" {
return actionValue
}

if x.config.useURLQuery() {
return r.URL.Query().Get("action")
}

return ""
}

func (c *Config) useURLQuery() bool {
if c == nil {
return false
}

return c.UseURLQuery
}
35 changes: 35 additions & 0 deletions connector/htmx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package connector

import (
"net/http"
)

type HTMX struct {
base

requestHeader string
boostedHeader string
historyRestoreRequestHeader string
}

func NewHTMX(c *Config) Connector {
return &HTMX{
base: base{
config: c,
targetHeader: "HX-Target",
selectHeader: "X-Select",
actionHeader: "X-Action",
},
requestHeader: "HX-Request",
boostedHeader: "HX-Boosted",
historyRestoreRequestHeader: "HX-History-Restore-Request",
}
}

func (h *HTMX) RenderPartial(r *http.Request) bool {
hxRequest := r.Header.Get(h.requestHeader)
hxBoosted := r.Header.Get(h.boostedHeader)
hxHistoryRestoreRequest := r.Header.Get(h.historyRestoreRequestHeader)

return (hxRequest == "true" || hxBoosted == "true") && hxHistoryRestoreRequest != "true"
}
16 changes: 16 additions & 0 deletions connector/partial.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package connector

type Partial struct {
base
}

func NewPartial(c *Config) Connector {
return &Partial{
base: base{
config: c,
targetHeader: "X-Target",
selectHeader: "X-Select",
actionHeader: "X-Action",
},
}
}
22 changes: 22 additions & 0 deletions connector/stimulus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package connector

import "net/http"

type Stimulus struct {
base
}

func NewStimulus(c *Config) Connector {
return &Stimulus{
base: base{
config: c,
targetHeader: "X-Stimulus-Target",
selectHeader: "X-Stimulus-Select",
actionHeader: "X-Stimulus-Action",
},
}
}

func (s *Stimulus) RenderPartial(r *http.Request) bool {
return r.Header.Get(s.targetHeader) != ""
}
16 changes: 16 additions & 0 deletions connector/turbo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package connector

type Turbo struct {
base
}

func NewTurbo(c *Config) Connector {
return &Turbo{
base: base{
config: c,
targetHeader: "Turbo-Frame",
selectHeader: "Turbo-Select",
actionHeader: "Turbo-Action",
},
}
}
22 changes: 22 additions & 0 deletions connector/unpoly.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package connector

import "net/http"

type Unpoly struct {
base
}

func NewUnpoly(c *Config) Connector {
return &Unpoly{
base: base{
config: c,
targetHeader: "X-Up-Target",
selectHeader: "X-Up-Select",
actionHeader: "X-Up-Action",
},
}
}

func (u *Unpoly) RenderPartial(r *http.Request) bool {
return r.Header.Get(u.targetHeader) != ""
}
22 changes: 22 additions & 0 deletions connector/vuejs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package connector

import "net/http"

type Vue struct {
base
}

func NewVue(c *Config) Connector {
return &Vue{
base: base{
config: c,
targetHeader: "X-Vue-Target",
selectHeader: "X-Vue-Select",
actionHeader: "X-Vue-Action",
},
}
}

func (v *Vue) RenderPartial(r *http.Request) bool {
return r.Header.Get(v.targetHeader) != ""
}
10 changes: 6 additions & 4 deletions examples/tabs-htmx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package main

import (
"fmt"
"github.com/donseba/go-partial"
"log/slog"
"net/http"

"github.com/donseba/go-partial"
"github.com/donseba/go-partial/connector"
)

type (
Expand All @@ -18,14 +20,14 @@ func main() {

app := &App{
PartialService: partial.NewService(&partial.Config{
PartialHeader: "HX-Target",
Logger: logger,
Logger: logger,
Connector: connector.NewHTMX(nil),
}),
}

mux := http.NewServeMux()

mux.Handle("GET /files/", http.StripPrefix("/files/", http.FileServer(http.Dir("./files"))))
mux.Handle("GET /js/", http.StripPrefix("/js/", http.FileServer(http.Dir("../../js"))))

mux.HandleFunc("GET /", app.home)

Expand Down
6 changes: 3 additions & 3 deletions examples/tabs/content.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
<!-- Tab Navigation -->
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<span class="nav-link {{ ifRequestedSelect "active" "tab1" ""}}" style="cursor:pointer;" x-get="/" x-partial="content" x-select="tab1">Tab 1</span>
<span class="nav-link {{ ifRequestedSelect "active" "tab1" ""}}" style="cursor:pointer;" x-get="/" x-target="#content" x-select="tab1">Tab 1</span>
</li>
<li class="nav-item">
<span class="nav-link {{ ifRequestedSelect "active" "tab2"}}" style="cursor:pointer;" x-get="/" x-partial="content" x-select="tab2">Tab 2</span>
<span class="nav-link {{ ifRequestedSelect "active" "tab2"}}" style="cursor:pointer;" x-get="/" x-target="#content" x-select="tab2">Tab 2</span>
</li>
<li class="nav-item">
<span class="nav-link {{ ifRequestedSelect "active" "tab3"}}" style="cursor:pointer;" x-get="/" x-partial="content" x-select="tab3">Tab 3</span>
<span class="nav-link {{ ifRequestedSelect "active" "tab3"}}" style="cursor:pointer;" x-get="/" x-target="#content" x-select="tab3">Tab 3</span>
</li>
</ul>

Expand Down
2 changes: 1 addition & 1 deletion examples/tabs/index.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<title>Tab Example</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
<script type="application/javascript" src="/js/standalone.js"></script>
<script type="application/javascript" src="/js/partial.js"></script>
</head>

<body>
Expand Down
Loading

0 comments on commit e81fce9

Please sign in to comment.