2023-07-26 18:46:09 +00:00
/ *
* SPDX - FileCopyrightText : 2023 jordi fita mas < jfita @ peritasoft . com >
* SPDX - License - Identifier : AGPL - 3.0 - only
* /
package form
import (
"context"
"database/sql/driver"
2024-04-28 18:28:45 +00:00
"dev.tandem.ws/tandem/camper/pkg/locale"
2023-07-26 18:46:09 +00:00
"net/http"
2024-01-26 21:27:54 +00:00
"strconv"
2023-07-26 18:46:09 +00:00
"dev.tandem.ws/tandem/camper/pkg/database"
)
type Select struct {
Name string
Selected [ ] string
Options [ ] * Option
Error error
}
func ( s * Select ) setError ( err error ) {
s . Error = err
}
func ( s * Select ) Value ( ) ( driver . Value , error ) {
return s . String ( ) , nil
}
func ( s * Select ) String ( ) string {
if s . Selected == nil {
return ""
}
return s . Selected [ 0 ]
}
2024-01-26 21:27:54 +00:00
func ( s * Select ) Int ( ) int {
i , err := strconv . Atoi ( s . String ( ) )
if err != nil {
panic ( err )
}
return i
}
2023-07-26 18:46:09 +00:00
func ( s * Select ) FillValue ( r * http . Request ) {
s . Selected = r . Form [ s . Name ]
}
2024-04-26 15:09:36 +00:00
func ( s * Select ) FillValueIndex ( r * http . Request , idx int ) {
if vs := r . Form [ s . Name ] ; len ( vs ) > idx {
s . Selected = [ ] string { vs [ idx ] }
} else {
s . Selected = nil
}
}
Handle the booking cart entirely with HTMx
Besides the dynamic final cart, that was already handled by HTMx, i had
to check the maximum number of guests, whether the accommodation allows
“overflow”, whether dogs are allowed, and that the booking dates were
within the campground’s opening and closing dates.
I could do all of this with AlpineJS, but then i would have to add the
same validation to the backend, prior to accept the payment. Would not
make more sense to have them in a single place, namely the backend? With
HTMx i can do that.
However, i now have to create the form “piecemeal”, because i may not
have the whole information when the visitor arrives to the booking page,
and i still had the same problem as in commit d2858302efa—parsing the
whole form as is would leave guests and options field empty, rather than
at their minimum values.
One of the fieldsets in that booking form are the arrival and departure
dates, that are the sames we use in the campsite type’s page to
“preselect” these values. Since now are in a separate struct, i can
reuse the same type and validation logic for both pages, making my
JavaScript code useless, but requiring HTMx. I think this is a good
tradeoff, in fact.
2024-02-10 02:49:44 +00:00
func ( s * Select ) ValidOptionsSelected ( ) bool {
2024-04-26 15:09:36 +00:00
if len ( s . Selected ) == 0 {
return false
}
2023-07-26 18:46:09 +00:00
for _ , selected := range s . Selected {
if ! s . isValidOption ( selected ) {
return false
}
}
return true
}
func ( s * Select ) isValidOption ( selected string ) bool {
for _ , option := range s . Options {
if option . Value == selected {
return true
}
}
return false
}
func ( s * Select ) IsSelected ( v string ) bool {
for _ , selected := range s . Selected {
if selected == v {
return true
}
}
return false
}
type Option struct {
Value string
Label string
}
func MustGetOptions ( ctx context . Context , conn * database . Conn , sql string , args ... interface { } ) [ ] * Option {
rows , err := conn . Query ( ctx , sql , args ... )
if err != nil {
panic ( err )
}
defer rows . Close ( )
var options [ ] * Option
for rows . Next ( ) {
option := & Option { }
err = rows . Scan ( & option . Value , & option . Label )
if err != nil {
panic ( err )
}
options = append ( options , option )
}
if rows . Err ( ) != nil {
panic ( rows . Err ( ) )
}
return options
}
2024-04-28 18:28:45 +00:00
func MustGetCountryOptions ( ctx context . Context , conn * database . Conn , l * locale . Locale ) [ ] * Option {
return MustGetOptions ( ctx , conn , "select country.country_code, coalesce(i18n.name, country.name) as l10n_name from country left join country_i18n as i18n on country.country_code = i18n.country_code and i18n.lang_tag = $1 order by l10n_name" , l . Language )
}
func MustGetDocumentTypeOptions ( ctx context . Context , conn * database . Conn , l * locale . Locale ) [ ] * Option {
return MustGetOptions ( ctx , conn , "select idt.id_document_type_id::text, coalesce(i18n.name, idt.name) as l10n_name from id_document_type as idt left join id_document_type_i18n as i18n on idt.id_document_type_id = i18n.id_document_type_id and i18n.lang_tag = $1 order by l10n_name" , l . Language )
}