Default Supported Datatypes
Out of the box, Snorlogue has support for the majority of "normal" data-types in nim. For every datatype, the following 2 steps need to be defined:
- How to map norm model-fields to FormField instances via
toFormField
procs. - How to map form-field values (which are always strings) to norm model-field types via
toModelValue
procs
FormFields define the type of HTML form-fields that are available in Snorlogue to represent a nim-type of a model-field in an HTML form. These are the fields Snorlogue provides:
FormFieldKind | HTML field | nim-types | FormField-fields |
---|---|---|---|
STRING | Text input | string | kind, name, strVal |
INT | Number input | int, int32, int64, uint, uint32, uint64, Natural | kind, name, iVal |
FLOAT | Number input | float, float32, float64 | kind, name, fVal |
BOOL | Checkbox input | bool | kind, name, bVal |
DATE | Datetime-local input | DateTime | kind, name, dtVal |
FILE | File input | FilePath | kind, name, fileVal |
INTSELECT | Select | foreignKey* | kind, name, intSeqVal, intOptions |
STRSELECT | Select | -** | kind, name, strSeqVal, strOptions |
*type int64 annotated with norm's fk
pragma. The Model in the pragma must also be registered to Snorlogue
**exists for users that want select fields with string values
Custom Datatypes
To extend that list with your own datatypes, just define toFormField
and toModelValue
procs for them!
This takes care of only the frontend though, you will still need to define dbType
, dbValue
and to
procs for norm.
For example if we had a distinct string type of UID
and wanted to support this in snorlogue:
import prologue
import snorlogue
import norm/[sqlite, model]
import std/[options, sequtils, algorithm, strformat, sugar]
# Type Definitions
type Level = 0..9
type UID = distinct string
type CreatureType = enum
A, B, C
type Creature* = ref object of Model
uid*: UID
name*: string
level*: Level
kind*: CreatureType
func `$`*(model: Creature): string = model.name
# Converts a `string` value to `UID`
func toModelValue*(formValue: string, T: typedesc[UID]): T = formValue.UID
# Maps `UID` to the `String` `FormField` and any value such a field might have is to be converted to `string` as well.
func toFormField*(value: Option[UID], fieldName: string): FormField =
let compatibilityValue: Option[string] = value.map(val => val.string)
result = FormField(
kind: FormFieldKind.STRING,
name: fieldName,
strVal: compatibilityValue
)
## Procs for norm DB interaction
# UID
func dbType*(T: typedesc[UID]): string = "TEXT"
func dbValue*(val: UID): DbValue = dbValue(val.string)
proc to*(dbVal: DbValue, T: typedesc[UID]): T = dbVal.s.UID
# CreatureType
func dbType*(T: typedesc[CreatureType]): string = "TEXT"
func dbValue*(val: CreatureType): DbValue = dbValue($val)
proc to*(dbVal: DbValue, T: typedesc[CreatureType]): T = parseEnum[CreatureType](dbVal.s)
# Level
func dbType*(T: typedesc[Level]): string = "INT"
func dbValue*(val: Level): DbValue = dbValue(val.int)
proc to*(dbVal: DbValue, T: typedesc[Level]): T = dbVal.i.Level
## Procs for Snorlogue Form interaction
# Maps `CreatureType` to the `IntSelect` `FormField` and any value such a field might have is to be converted to an int on the form.
func toFormField*(value: Option[CreatureType], fieldName: string): FormField =
var options: seq[IntOption] = @[]
for enumValue in CreatureType:
options.add(IntOption(name: $enumValue, value: enumValue.int))
let formFieldValue: Option[int64] = value.map(val => val.int64)
FormField(
name: fieldName,
kind: FormFieldKind.INTSELECT,
intSeqVal: formFieldValue,
intOptions: options
)
# Maps `Level` to the `IntSelect` `FormField` and any value such a field might have is to be converted to an int on the form.
func toFormField*(value: Option[Level], fieldName: string): FormField =
var options: seq[IntOption] = @[]
const rangeName = $Level
for rangeVal in Level.low..Level.high:
let optionLabel = fmt"{rangeName} {rangeVal}"
let optionValue = rangeVal.int
options.add(IntOption(name: optionLabel, value: optionValue))
let formFieldValue: Option[int64] = value.map(val => val.int64)
options.sort((opt1, opt2: IntOption) => cmp(opt1.name, opt2.name))
FormField(
name: fieldName,
kind: FormFieldKind.INTSELECT,
intSeqVal: formFieldValue,
intOptions: options
)
# Example Usage
putEnv("DB_HOST", ":memory:")
withDb:
var human = Creature(name: "Karl", level: 5, uid: "12345abcde".UID)
db.createTables(human)
# Setup the server
var app: Prologue = newApp()
app.addCrudRoutes(Creature)
app.addAdminRoutes()
# app.run()
Note that no toModelValue
had to be defined for CreatureType
or Level
.
Enums and range are somwhat special in that they do have default toModelValue
procs defined for them, but can not have a default toFormField
proc.
It is heavily discouraged to try and supply a generic toFormField
for either of those types, as the nim compiler appears to not act consistently when those procs are defined, leading to various runtime bugs.