forgot to include th efiles oops

This commit is contained in:
maddalax 2024-09-24 14:56:44 -05:00
parent 74312f2260
commit 9acab64380
9 changed files with 404 additions and 0 deletions

View file

@ -0,0 +1,94 @@
## Pages ##
Pages are the entry point of an htmgo application.
A simple page may look like:
```go
// route will be automatically registered based on the file name
func HelloHtmgoPage(ctx *h.RequestContext) *h.Page {
return h.NewPage(
h.Html(
h.HxExtension(h.BaseExtensions()),
h.Head(
h.Link("/public/main.css", "stylesheet"),
h.Script("/public/htmgo.js"),
),
h.Body(
h.Pf("Hello, htmgo!"),
),
),
)
}
```
htmgo uses [Echo Go](https://echo.labstack.com/docs/context) as its web server, ***h.RequestContext** is a thin wrapper around **echo.Context**. A page
must return *h.Page, and accept *h.RequestContext as a parameter
<br>
**Auto Registration**
htmgo uses file based routing. This means that we will automatically generate and register your routes with echo based on the files you have in the 'pages' directory.
For example, if you have a directory structure such as:
```bash
pages
index.go
users.go
users.$id //id parameter can be accessed in your page with ctx.Param("id")
```
it will get registered into Echo as follows:
```bash
/
/users
/users/:id
```
You may put any functions you like in your pages file, auto registration will **ONLY** register functions that return ***h.Page**
<br>
**Tips:**
Generally it is it recommended to abstract common parts of your page into its own component and re-use it, such as script tags, including styling, etc.
Example:
```go
func RootPage(children ...h.Ren) *h.Element {
return h.Html(
h.HxExtension(h.BaseExtensions()),
h.Head(
h.Meta("viewport", "width=device-width, initial-scale=1"),
h.Link("/public/main.css", "stylesheet"),
h.Script("/public/htmgo.js"),
h.Style(`
html {
scroll-behavior: smooth;
}
`),
),
h.Body(
h.Class("bg-stone-50 min-h-screen overflow-x-hidden"),
partials.NavBar(false),
h.Fragment(children...),
),
)
}
```
```go
func UserPage(ctx *h.RequestContext) *h.Page {
return h.NewPage(
base.RootPage(
h.Div(
h.Pf("User ID: %s", ctx.Param("id")),
),
))
}
```

View file

@ -0,0 +1,58 @@
## Partials ##
Partials are where things get interesting. Partials allow you to start adding interactivity to your website by swapping in content, setting headers, redirecting, etc.
Partials have a similiar structure to pages. A simple partial may look like:
```go
func CurrentTimePartial(ctx *h.RequestContext) *h.Partial {
now := time.Now()
return h.NewPartial(
h.Div(
h.Pf("The current time is %s", now.Format(time.RFC3339)),
),
)
}
```
This will get automatically registered in the same way that pages are registered, based on the file path. This allows you to reference partials directly via the function itself when rendering them, instead of worrying about the route.
**Example:**
I want to build a page that renders the current time, updating every second. Here is how that may look:
<br>
**pages/time.go**
```go
package pages
func CurrentTimePage(ctx *h.RequestContext) *h.Page {
return h.NewPage(
base.RootPage(
h.GetPartial(
partials.CurrentTimePartial,
"load, every 1s"),
))
}
```
**partials/time.go**
```go
package partials
func CurrentTimePartial(ctx *h.RequestContext) *h.Partial {
now := time.Now()
return h.NewPartial(
h.Div(
h.Pf("The current time is %s", now.Format(time.RFC3339)),
),
)
}
```
When the page load, the partial will be loaded in via htmx, and then swapped in every 1 second. With this
little amount of code and zero written javascript, you have a page that shows the current time and updates
every second.

View file

@ -0,0 +1,29 @@
**Components**
Components are re-usable bits of logic to render HTML. Similar to how in React components are Javascript functions, in htmgo, components are pure go functions.
A component can be pure, or it can have data fetching logic inside of it. Since htmgo uses htmx for interactivity, there is NO re-rendering of your UI automatically from the framework, which means you can safely put data fetching logic inside of components since you can be sure they will only be called by your own code.
<br>
**Example:**
```go
func Card(ctx *h.RequestContext) *h.Element {
service := tasks.NewService(ctx.ServiceLocator())
list, _ := service.List()
return h.Div(
h.Id("task-card"),
h.Class("bg-white w-full rounded shadow-md"),
CardBody(list, getActiveTab(ctx)),
)
}
```
My card component here fetches all my tasks I have on my list, and renders each task.
If you are familar with React, then you would likely place this fetch logic inside of a useEffect or (useQuery library) so it is not constantly refetched as the component re-renders.
With **htmgo**, the only way to update content on the page is to use htmx to swap out the content from loading a partial. Therefore you control exactly when this Card component is called, not the framework behind the scenes.
See [#interactivity-swapping](#interactivity-swapping) for more information

View file

@ -0,0 +1,19 @@
**HTML Tags**
htmgo provides many methods to render html tags:
```go
h.Html(children ...Ren) *Element
h.Head(children ...Ren) *Element
h.Div(children ...Ren) *Element
h.Button(children ...Ren) *Element
h.P(children ...Ren) *Element
h.H1(children ...Ren) *Element
h.H2(children ...Ren) *Element
h.Tag(tag string, children ...Ren) *Element
... etc
```
All methods can be found in the `h` package in htmgo/framework
See [#conditionals](#control-if-else) for more information about conditionally rendering tags or attributes.

View file

@ -0,0 +1,22 @@
**Attributes**
Attributes are one of the main ways we can add interactivity to the pages with [htmx](http://htmx.org). If you have not read over the htmx documentation, please do so before continuing.
htmgo provides many methods to add attributes
```go
h.Class(string)
h.ClassX(string, h.ClassMap)
h.Href(string)
h.Attribute(key, value)
h.AttributeIf(condition, key, value)
h.AttributePairs(values...string) // set multiple attributes, must be an even number of parameters
h.Attributes(h.AttributeMap) // set multiple attributes as key/value pairs
h.Id(string)
h.Trigger(hx.Trigger) //htmx trigger using additional functions to construct the trigger
h.TriggerString(string) // htmx trigger in pure htmx string form
```

View file

@ -0,0 +1,51 @@
**If / Else Statements**
If / else statements are useful when you want to conditionally render attributes or elements / components.
htmgo provides a couple of utilities to do so:
```go
h.If(condition, node)
h.Ternary(condition, node, node2)
h.ElementIf(condition, element) // this is neccessary if a method requires you to pass in *h.element
h.IfElse(condition, node, node2) //essentially an alias to h.Ternary
h.IfElseLazy(condition, func()node, func()node2) // useful for if something should only be called based on the condition
h.AttributeIf(condition, key string, value string) // adds an attribute if condition is true
h.ClassIf(condition, class string) // adds a class if condition is true
h.ClassX(classes, m.ClassMap{}) // allows you to include classes, but also render specific classes conditionally
```
**Examples:**
- Render `border-green-500` or `border-slate-400` conditionally
```go
h.ClassX("w-10 h-10 border rounded-full", map[string]bool {
"border-green-500": task.CompletedAt != nil,
"border-slate-400": task.CompletedAt == nil,
})
```
- Render an icon if the task is complete
```go
h.If(task.CompletedAt != nil, CompleteIcon())
```
- Render different elements based on a condition
```go
h.IfElse(editing, EditTaskForm(), ViewTask())
```
Note: This will execute both **EditTaskForm** and **ViewTask**, no matter if the condition is true or false, since a function is being called here.
If you do not want to call the function at all unless the condition is true, use **h.IfElseLazy**
```go
h.IfElseLazy(editing, EditTaskForm, ViewTask)
```

View file

@ -0,0 +1,42 @@
**Loops / Dealing With Lists**
Very commonly you will need to render a list or slice of items onto the page. Frameworks generally solve this in different ways, such as React uses regular JS .map function to solve it.
We offer the same conveniences in htmgo.
```go
h.List(items, func(item, index)) *h.Element
h.IterMap(map, mapper func(key, value) *Element) *Element
```
**Example:**
- Render a list of tasks
```go
h.List(list, func(item *ent.Task, index int) *h.Element {
if tab == TabComplete && item.CompletedAt == nil {
return h.Empty()
}
return Task(item, false)
})
```
- Render a map
```go
values := map[string]string{
"key": "value",
}
IterMap(values, func(key string, value string) *Element {
return Div(
Text(key),
Text(value),
)
})
```
-

View file

@ -0,0 +1,85 @@
### Interactivity
1. Adding interactivity to your website is done through [htmx](http://htmx.org) by utilizing various attributes/headers. This should cover most use cases.
htmgo offers utility methods to make this process a bit easier
Here are a few methods we offer:
Partial Response methods
```go
SwapManyPartialWithHeaders(ctx *RequestContext, headers *Headers, swaps ...*Element) *Partial
SwapPartial(ctx *RequestContext, swap *Element) *Partial
SwapManyPartial(ctx *RequestContext, swaps ...*Element) *Partial
SwapManyXPartial(ctx *RequestContext, swaps ...SwapArg) *Partial
GetPartialPath(partial PartialFunc) string
GetPartialPathWithQs(partial PartialFunc, qs *Qs) string
```
Swapping can also be done by adding a child to an element
```go
OobSwapWithSelector(ctx *RequestContext, selector string, content *Element, option ...SwapOption) *Element
OobSwap(ctx *RequestContext, content *Element, option ...SwapOption) *Element
SwapMany(ctx *RequestContext, elements ...*Element)
```
Usage:
1. I have a Card component that renders a list of tasks. I want to add a new button that completes all the tasks and updates the Card component with the completed tasks.
**/components/task.go**
```go
func Card(ctx *h.RequestContext) *h.Element {
service := tasks.NewService(ctx.ServiceLocator())
list, _ := service.List()
return h.Div(
h.Id("task-card"),
h.Class("bg-white w-full rounded shadow-md"),
CardBody(list, getActiveTab(ctx)),
CompleteAllButton(list)
)
}
```
```go
func CompleteAllButton(list []*ent.Task) *h.Element {
notCompletedCount := len(h.Filter(list, func(item *ent.Task) bool {
return item.CompletedAt == nil
}))
return h.Button(
h.TextF("Complete %s tasks", notCompletedCount),
h.PostPartialWithQs(CompleteAll,
h.NewQs("complete",
h.Ternary(notCompletedCount > 0, "true", "false"),
)),
)
}
```
**/partials/task.go**
```go
func CompleteAll(ctx *h.RequestContext) *h.Partial {
service := tasks.NewService(ctx.ServiceLocator())
service.SetAllCompleted(ctx.QueryParam("complete") == "true")
return h.SwapPartial(ctx,
Card(ctx),
)
}
```
When the **CompleteAll** button is clicked, a **POST** will be sent to the **CompleteAll** partial, which will complete all the tasks and then swap out the Card content with the updated list of tasks. Pretty cool right?
**SwapManyPartial** can be used to swap out multiple items on the page instead of a single one.
Note: These partial swap methods use https://htmx.org/attributes/hx-swap-oob/ behind the scenes, so it must match
the swap target by id.
**If** you are only wanting to swap the element that made the xhr request for the partial in the first place, just use `h.NewPartial` instead, it will use the default htmx swapping, and not hx-swap-oob.

View file

@ -0,0 +1,4 @@
## **Troubleshooting:**
**command not found: htmgo**
ensure you installed htmgo above and ensure GOPATH is set in your shell