I have a form on an admin panel I am working on that doesnt allow me to resubmit changes on it unless I reload the page. I am using HTMX, and GIN primarily:
Form:
<tr data-id="{{ .ID }}">
<form id="update-vehicle-form-{{ .ID }}" action="/admin/vehicles/{{ .ID }}/update" hx-post="/admin/vehicles/{{ .ID }}/update" hx-target="closest tr" hx-swap="outerHTML">
<td>{{ .ID }}</td>
<td><input type="text" name="brand-{{ .ID }}" value="{{ .Brand }}" class="edit-input"></td>
<td><input type="text" name="model_name-{{ .ID }}" value="{{ .ModelName }}" class="edit-input"></td>
<td><input type="number" name="year-{{ .ID }}" value="{{ .Year }}" class="edit-input"></td>
<td><input type="number" name="price-{{ .ID }}" value="{{ .Price }}" class="edit-input"></td>
<td>
<select name="condition-{{ .ID }}" class="edit-select">
<option value="new" {{ if eq .Condition "new" }}selected{{ end }}>New</option>
<option value="used" {{ if eq .Condition "used" }}selected{{ end }}>Used</option>
</select>
</td>
<td><textarea name="description-{{ .ID }}" class="edit-textarea">{{ .Description }}</textarea></td>
<td>
<select name="status-{{ .ID }}" class="edit-select">
<option value="available" {{ if eq .Status "available" }}selected{{ end }}>Available</option>
<option value="sold" {{ if eq .Status "sold" }}selected{{ end }}>Sold</option>
<option value="pending" {{ if eq .Status "pending" }}selected{{ end }}>Pending</option>
</select>
</td>
<td>
{{ if .Media }}
{{ range .Media }}
<div>
<a href="{{ .FileURL }}" target="_blank">{{ .FileName }}</a>
<button class="delete-media-btn" hx-delete="/admin/vehicles/media/{{ .ID }}/delete" hx-confirm="Are you sure you want to delete this media?" hx-target="this.parentNode" hx-swap="outerHTML">Delete</button>
</div>
{{ end }}
{{ else }}
No media available.
{{ end }}
</td>
<td>
<button type="submit" class="update-btn" >Update</button>
<button type="button" class="delete-btn" hx-delete="/admin/vehicles/{{ .ID }}/delete" hx-confirm="Are you sure you want to delete this vehicle?" hx-target="closest tr" hx-swap="outerHTML">Delete</button>
</td>
</form>
</tr>
{{ end }}
The update Handler:
fields := map[string]interface{}{
"Brand": c.PostForm("brand-" + c.Param("id")),
"ModelName": c.PostForm("model_name-" + c.Param("id")),
"Year": parseYear(c.PostForm("year-" + c.Param("id"))),
"Price": parsePrice(c.PostForm("price-" + c.Param("id"))),
"Condition": c.PostForm("condition-" + c.Param("id")),
"Description": c.PostForm("description-" + c.Param("id")),
"Status": c.PostForm("status-" + c.Param("id")),
}
for key, value := range fields {
if value != "" && value != 0.0 && value != nil {
setField(vehicle, key, value)
}
}
if vehicle.Status == "sold" {
vehicle.DecisionDate = time.Now()
} else if vehicle.Status == "available" || vehicle.Status == "pending" {
vehicle.DecisionDate = time.Time{}
}
}
func parseYear(yearStr string) int {
year, _ := strconv.Atoi(yearStr)
return year
}
func parsePrice(priceStr string) float64 {
price, _ := strconv.ParseFloat(priceStr, 64)
return price
}
func setField(vehicle *data.Vehicle, fieldName string, value interface{}) {
switch fieldName {
case "Brand":
vehicle.Brand = value.(string)
case "ModelName":
vehicle.ModelName = value.(string)
case "Year":
vehicle.Year = value.(int)
case "Price":
vehicle.Price = value.(float64)
case "Condition":
vehicle.Condition = value.(string)
case "Description":
vehicle.Description = value.(string)
case "Status":
vehicle.Status = value.(string)
}
}
func (vs *VehicleService) renderUpdatedVehicleRow(c *gin.Context, vehicle data.Vehicle) {
var renderedRow bytes.Buffer
tmpl := template.Must(template.New("vehicle_row").ParseFiles(filepath.Join("templates/admin/partials", "vehicle_row.html")))
if err := tmpl.ExecuteTemplate(&renderedRow, "carRow", vehicle); err != nil {
log.Printf("Error executing partial template: %v", err)
c.String(http.StatusInternalServerError, "Internal Server Error")
return
}
c.Data(http.StatusOK, "text/html; charset=utf-8", renderedRow.Bytes())
}
how can I fix this so that no matter how many times i click update once something has changed on the form the request will be sent to the back end
I tried changing the form like this:
<tr data-id="{{ .ID }}">
<form id="update-vehicle-form-{{ .ID }}" action="/admin/vehicles/{{ .ID }}/update" hx-post="/admin/vehicles/{{ .ID }}/update" hx-target="closest tr" hx-swap="outerHTML" hx-trigger="my-custom-event from:form">
<td>{{ .ID }}</td>
<td><input type="text" name="brand-{{ .ID }}" value="{{ .Brand }}" class="edit-input"></td>
<td><input type="text" name="model_name-{{ .ID }}" value="{{ .ModelName }}" class="edit-input"></td>
<td><input type="number" name="year-{{ .ID }}" value="{{ .Year }}" class="edit-input"></td>
<td><input type="number" name="price-{{ .ID }}" value="{{ .Price }}" class="edit-input"></td>
<td>
<select name="condition-{{ .ID }}" class="edit-select">
<option value="new" {{ if eq .Condition "new" }}selected{{ end }}>New</option>
<option value="used" {{ if eq .Condition "used" }}selected{{ end }}>Used</option>
</select>
</td>
<td><textarea name="description-{{ .ID }}" class="edit-textarea">{{ .Description }}</textarea></td>
<td>
<select name="status-{{ .ID }}" class="edit-select">
<option value="available" {{ if eq .Status "available" }}selected{{ end }}>Available</option>
<option value="sold" {{ if eq .Status "sold" }}selected{{ end }}>Sold</option>
<option value="pending" {{ if eq .Status "pending" }}selected{{ end }}>Pending</option>
</select>
</td>
<td>
{{ if .Media }}
{{ range .Media }}
<div>
<a href="{{ .FileURL }}" target="_blank">{{ .FileName }}</a>
<button class="delete-media-btn" hx-delete="/admin/vehicles/media/{{ .ID }}/delete" hx-confirm="Are you sure you want to delete this media?" hx-target="this.parentNode" hx-swap="outerHTML">Delete</button>
</div>
{{ end }}
{{ else }}
No media available.
{{ end }}
</td>
<td>
<button type="submit" class="update-btn">Update</button>
<button type="button" class="delete-btn" hx-delete="/admin/vehicles/{{ .ID }}/delete" hx-confirm="Are you sure you want to delete this vehicle?" hx-target="closest tr" hx-swap="outerHTML">Delete</button>
</td>
</form>
</tr>
{{ end }}
and creating new events like this:
function initializeAdminFeatures() {
console.log("Initializing Admin Features");
document.querySelectorAll('.collapsible-btn').forEach(button => {
button.addEventListener('click', toggleCollapsible);
});
document.querySelectorAll('form[id^="update-vehicle-form-"]').forEach(form => {
form.addEventListener('submit', function(event) {
const customEventName = `my-custom-event-${Date.now()}`;
setTimeout(() => {
form.dispatchEvent(new Event(customEventName));
}, 0);
});
});
}
function toggleCollapsible(event) {
const content = event.target.nextElementSibling;
document.querySelectorAll('.collapsible-content').forEach(c => {
c.style.maxHeight = c === content ? (c.style.maxHeight ? null : `${c.scrollHeight}px`) : null;
});
console.log("Collapsible section toggled");
}