678 lines
16 KiB
Go
678 lines
16 KiB
Go
// Copyright 2013 wetalk authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"math"
|
|
"net/url"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-tango/wego/setting"
|
|
"github.com/go-xweb/xweb/validation"
|
|
)
|
|
|
|
func init() {
|
|
initCommonField()
|
|
initExtraField()
|
|
}
|
|
|
|
type FormLocaler interface {
|
|
Tr(string, ...interface{}) string
|
|
}
|
|
|
|
type FormHelper interface {
|
|
Helps() map[string]string
|
|
}
|
|
|
|
type FormLabeler interface {
|
|
Labels() map[string]string
|
|
}
|
|
|
|
type FormPlaceholder interface {
|
|
Placeholders() map[string]string
|
|
}
|
|
|
|
type FieldCreater func(*FieldSet)
|
|
|
|
type FieldFilter func(*FieldSet)
|
|
|
|
var customCreaters = make(map[string]FieldCreater)
|
|
|
|
var customFilters = make(map[string]FieldFilter)
|
|
|
|
type fakeLocale struct{}
|
|
|
|
func (*fakeLocale) Tr(text string, args ...interface{}) string {
|
|
return text
|
|
}
|
|
|
|
var fakeLocaler FormLocaler = new(fakeLocale)
|
|
|
|
// register a custom label/input creater
|
|
func RegisterFieldCreater(name string, field FieldCreater) {
|
|
customCreaters[name] = field
|
|
}
|
|
|
|
// register a custom label/input creater
|
|
func RegisterFieldFilter(name string, field FieldFilter) {
|
|
customFilters[name] = field
|
|
}
|
|
|
|
type HtmlLazyField func() template.HTML
|
|
|
|
func (f HtmlLazyField) String() string {
|
|
return string(f())
|
|
}
|
|
|
|
type FieldSet struct {
|
|
Label template.HTML
|
|
Field HtmlLazyField
|
|
Id string
|
|
Name string
|
|
LabelText string
|
|
Value interface{}
|
|
Help string
|
|
Error string
|
|
Type string
|
|
Kind string
|
|
Placeholder string
|
|
Attrs string
|
|
FormElm reflect.Value
|
|
Locale FormLocaler
|
|
}
|
|
|
|
type FormSets struct {
|
|
FieldList []*FieldSet
|
|
Fields map[string]*FieldSet
|
|
Locale FormLocaler
|
|
inited bool
|
|
form interface{}
|
|
errs map[string]*validation.ValidationError
|
|
}
|
|
|
|
func (this *FormSets) SetError(fieldName, errMsg string) {
|
|
if fSet, ok := this.Fields[fieldName]; ok {
|
|
fSet.Error = this.Locale.Tr(errMsg)
|
|
}
|
|
}
|
|
|
|
// create formSets for generate label/field html code
|
|
func NewFormSets(form interface{}, errs map[string]*validation.ValidationError, locale FormLocaler) *FormSets {
|
|
fSets := new(FormSets)
|
|
fSets.errs = errs
|
|
fSets.Fields = make(map[string]*FieldSet)
|
|
if locale != nil {
|
|
fSets.Locale = locale
|
|
} else {
|
|
fSets.Locale = fakeLocaler
|
|
}
|
|
|
|
val := reflect.ValueOf(form)
|
|
|
|
panicAssertStructPtr(val)
|
|
|
|
elm := val.Elem()
|
|
|
|
var helps map[string]string
|
|
var labels map[string]string
|
|
var places map[string]string
|
|
|
|
// get custom field helo messages
|
|
if f, ok := form.(FormHelper); ok {
|
|
hlps := f.Helps()
|
|
if hlps != nil {
|
|
helps = hlps
|
|
}
|
|
}
|
|
|
|
// ge custom field labels
|
|
if f, ok := form.(FormLabeler); ok {
|
|
lbls := f.Labels()
|
|
if lbls != nil {
|
|
labels = lbls
|
|
}
|
|
}
|
|
|
|
// ge custom field placeholders
|
|
if f, ok := form.(FormPlaceholder); ok {
|
|
phs := f.Placeholders()
|
|
if phs != nil {
|
|
places = phs
|
|
}
|
|
}
|
|
|
|
outFor:
|
|
for i := 0; i < elm.NumField(); i++ {
|
|
f := elm.Field(i)
|
|
fT := elm.Type().Field(i)
|
|
|
|
name := fT.Name
|
|
value := f.Interface()
|
|
fTyp := "text"
|
|
|
|
switch f.Kind() {
|
|
case reflect.Bool:
|
|
fTyp = "checkbox"
|
|
default:
|
|
switch value.(type) {
|
|
case time.Time:
|
|
fTyp = "datetime"
|
|
}
|
|
}
|
|
|
|
fName := name
|
|
|
|
var attrm map[string]string
|
|
|
|
// parse struct tag settings
|
|
for _, v := range strings.Split(fT.Tag.Get("form"), ";") {
|
|
v = strings.TrimSpace(v)
|
|
if v == "-" {
|
|
continue outFor
|
|
} else if i := strings.Index(v, "("); i > 0 && strings.Index(v, ")") == len(v)-1 {
|
|
tN := v[:i]
|
|
v = strings.TrimSpace(v[i+1 : len(v)-1])
|
|
switch tN {
|
|
case "type":
|
|
fTyp = v
|
|
case "name":
|
|
fName = v
|
|
case "attr":
|
|
if attrm == nil {
|
|
attrm = make(map[string]string)
|
|
}
|
|
parts := strings.SplitN(v, ",", 2)
|
|
if len(parts) > 1 {
|
|
attrm[parts[0]] = parts[1]
|
|
} else {
|
|
attrm[v] = v
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var attrs string
|
|
if attrm != nil {
|
|
for k, v := range attrm {
|
|
attrs += fmt.Sprintf(` %s="%s"`, k, v)
|
|
}
|
|
}
|
|
|
|
// set field id
|
|
fId := elm.Type().Name() + "-" + fName
|
|
|
|
var fSet FieldSet
|
|
|
|
fSet.Id = fId
|
|
fSet.Name = fName
|
|
fSet.Value = value
|
|
fSet.Attrs = attrs
|
|
fSet.FormElm = elm
|
|
fSet.Locale = locale
|
|
|
|
if i := strings.IndexRune(fTyp, ','); i != -1 {
|
|
fSet.Type = fTyp[:i]
|
|
fSet.Kind = fTyp[i+1:]
|
|
fTyp = fSet.Type
|
|
} else {
|
|
fSet.Type = fTyp
|
|
fSet.Kind = fTyp
|
|
}
|
|
|
|
// get field label text
|
|
fSet.LabelText = fName
|
|
if labels != nil {
|
|
if _, ok := labels[name]; ok {
|
|
fSet.LabelText = labels[name]
|
|
}
|
|
}
|
|
fSet.LabelText = locale.Tr(fSet.LabelText)
|
|
|
|
// get field help
|
|
if helps != nil {
|
|
if _, ok := helps[name]; ok {
|
|
fSet.Help = helps[name]
|
|
}
|
|
}
|
|
fSet.Help = locale.Tr(helps[name])
|
|
|
|
if places != nil {
|
|
if _, ok := places[name]; ok {
|
|
fSet.Placeholder = places[name]
|
|
}
|
|
}
|
|
fSet.Placeholder = locale.Tr(fSet.Placeholder)
|
|
|
|
if len(fSet.Placeholder) > 0 {
|
|
fSet.Placeholder = fmt.Sprintf(` placeholder="%s"`, fSet.Placeholder)
|
|
}
|
|
|
|
// create error string
|
|
if errs != nil {
|
|
if err, ok := errs[name]; ok {
|
|
fSet.Error = locale.Tr(err.Tmpl, err.LimitValue)
|
|
}
|
|
}
|
|
|
|
// create label html
|
|
switch fTyp {
|
|
case "checkbox", "hidden":
|
|
default:
|
|
fSet.Label = template.HTML(fmt.Sprintf(`
|
|
<label class="control-label" for="%s">%s</label>`, fSet.Id, fSet.LabelText))
|
|
}
|
|
|
|
if creater, ok := customCreaters[fTyp]; ok {
|
|
// use custome creater generate label/input html
|
|
creater(&fSet)
|
|
|
|
if filter, ok := customFilters[fTyp]; ok {
|
|
// use custome filter replace label/input html
|
|
filter(&fSet)
|
|
}
|
|
}
|
|
|
|
if fSet.Field == nil {
|
|
fSet.Field = func() template.HTML { return "" }
|
|
}
|
|
|
|
fSets.FieldList = append(fSets.FieldList, &fSet)
|
|
fSets.Fields[name] = &fSet
|
|
}
|
|
|
|
fSets.inited = true
|
|
|
|
return fSets
|
|
}
|
|
|
|
func initCommonField() {
|
|
RegisterFieldCreater("text", func(fSet *FieldSet) {
|
|
fSet.Field = func() template.HTML {
|
|
return template.HTML(fmt.Sprintf(
|
|
`<input id="%s" name="%s" type="text" value="%v" class="form-control"%s%s>`,
|
|
fSet.Id, fSet.Name, fSet.Value, fSet.Placeholder, fSet.Attrs))
|
|
}
|
|
})
|
|
|
|
RegisterFieldCreater("textarea", func(fSet *FieldSet) {
|
|
fSet.Field = func() template.HTML {
|
|
return template.HTML(fmt.Sprintf(
|
|
`<textarea id="%s" name="%s" rows="5" class="form-control"%s%s>%v</textarea>`,
|
|
fSet.Id, fSet.Name, fSet.Placeholder, fSet.Attrs, fSet.Value))
|
|
}
|
|
})
|
|
|
|
RegisterFieldCreater("password", func(fSet *FieldSet) {
|
|
fSet.Field = func() template.HTML {
|
|
return template.HTML(fmt.Sprintf(
|
|
`<input id="%s" name="%s" type="password" value="%v" class="form-control"%s%s>`,
|
|
fSet.Id, fSet.Name, fSet.Value, fSet.Placeholder, fSet.Attrs))
|
|
}
|
|
})
|
|
|
|
RegisterFieldCreater("hidden", func(fSet *FieldSet) {
|
|
fSet.Field = func() template.HTML {
|
|
return template.HTML(fmt.Sprintf(
|
|
`<input id="%s" name="%s" type="hidden" value="%v"%s>`, fSet.Id, fSet.Name, fSet.Value, fSet.Attrs))
|
|
}
|
|
})
|
|
|
|
datetimeFunc := func(fSet *FieldSet) {
|
|
fSet.Field = func() template.HTML {
|
|
t := fSet.Value.(time.Time)
|
|
tval := Date(t, setting.DateTimeFormat)
|
|
if tval == "0001-01-01 00:00:00" {
|
|
t = time.Now()
|
|
}
|
|
if fSet.Type == "date" {
|
|
tval = Date(t, setting.DateFormat)
|
|
}
|
|
return template.HTML(fmt.Sprintf(
|
|
`<input id="%s" name="%s" type="%s" value="%s" class="form-control"%s%s>`,
|
|
fSet.Id, fSet.Name, fSet.Type, tval, fSet.Placeholder, fSet.Attrs))
|
|
}
|
|
}
|
|
|
|
RegisterFieldCreater("date", datetimeFunc)
|
|
RegisterFieldCreater("datetime", datetimeFunc)
|
|
|
|
RegisterFieldCreater("checkbox", func(fSet *FieldSet) {
|
|
fSet.Field = func() template.HTML {
|
|
var checked string
|
|
if b, ok := fSet.Value.(bool); ok && b {
|
|
checked = "checked"
|
|
}
|
|
return template.HTML(fmt.Sprintf(
|
|
`<label for="%s" class="checkbox">%s<input id="%s" name="%s" type="checkbox" %s></label>`,
|
|
fSet.Id, fSet.LabelText, fSet.Id, fSet.Name, checked))
|
|
}
|
|
})
|
|
|
|
RegisterFieldCreater("select", func(fSet *FieldSet) {
|
|
fSet.Field = func() template.HTML {
|
|
var options string
|
|
str := fmt.Sprintf(`<select id="%s" name="%s" class="form-control"%s%s>%s</select>`,
|
|
fSet.Id, fSet.Name, fSet.Placeholder, fSet.Attrs)
|
|
|
|
fun := fSet.FormElm.Addr().MethodByName(fSet.Name + "SelectData")
|
|
|
|
if fun.IsValid() {
|
|
results := fun.Call([]reflect.Value{})
|
|
if len(results) > 0 {
|
|
v := results[0]
|
|
if v.CanInterface() {
|
|
if vu, ok := v.Interface().([][]string); ok {
|
|
|
|
var vs []string
|
|
val := reflect.ValueOf(fSet.Value)
|
|
if val.Kind() == reflect.Slice {
|
|
vs = make([]string, 0, val.Len())
|
|
for i := 0; i < val.Len(); i++ {
|
|
vs = append(vs, ToStr(val.Index(i).Interface()))
|
|
}
|
|
}
|
|
|
|
isMulti := len(vs) > 0
|
|
for _, parts := range vu {
|
|
var n, v string
|
|
switch {
|
|
case len(parts) > 1:
|
|
n, v = fSet.Locale.Tr(parts[0]), parts[1]
|
|
case len(parts) == 1:
|
|
n, v = fSet.Locale.Tr(parts[0]), parts[0]
|
|
}
|
|
var selected string
|
|
if isMulti {
|
|
for _, e := range vs {
|
|
if e == v {
|
|
selected = ` selected="selected"`
|
|
break
|
|
}
|
|
}
|
|
} else if ToStr(fSet.Value) == v {
|
|
selected = ` selected="selected"`
|
|
}
|
|
options += fmt.Sprintf(`<option value="%s"%s>%s</option>`, v, selected, n)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(options) == 0 {
|
|
options = fmt.Sprintf(`<option value="%v">%v</option>`, fSet.Value, fSet.Value)
|
|
}
|
|
|
|
return template.HTML(fmt.Sprintf(str, options))
|
|
}
|
|
})
|
|
}
|
|
|
|
func initExtraField() {
|
|
// a example to create a bootstrap style checkbox creater
|
|
RegisterFieldCreater("checkbox", func(fSet *FieldSet) {
|
|
fSet.Field = func() template.HTML {
|
|
value := false
|
|
if b, ok := fSet.Value.(bool); ok {
|
|
value = b
|
|
}
|
|
active := ""
|
|
if value {
|
|
active = " active"
|
|
}
|
|
return template.HTML(fmt.Sprintf(`<label>
|
|
<input type="hidden" name="%s" value="%v">
|
|
<button type="button" data-toggle="button" data-name="%s" class="btn btn-default btn-xs btn-checked%s">
|
|
<span class="glyphicon glyphicon-ok"></span>
|
|
</button>%s
|
|
</label>`, fSet.Name, value, fSet.Name, active, fSet.LabelText))
|
|
}
|
|
})
|
|
|
|
// a example to create a select2 box
|
|
RegisterFieldFilter("select", func(fSet *FieldSet) {
|
|
if strings.Index(fSet.Attrs, `rel="select2"`) != -1 {
|
|
field := fSet.Field.String()
|
|
fSet.Field = func() template.HTML {
|
|
field = strings.Replace(field, "<option", "<option></option><option", 1)
|
|
return template.HTML(field)
|
|
}
|
|
}
|
|
})
|
|
|
|
// a example to create a captcha field
|
|
RegisterFieldCreater("captcha", func(fSet *FieldSet) {
|
|
fSet.Label = template.HTML(fmt.Sprintf(`
|
|
<label class="control-label" for="%s">%s</label>`, fSet.Id, fSet.LabelText))
|
|
|
|
fSet.Field = func() template.HTML {
|
|
return template.HTML(fmt.Sprintf(`%v
|
|
<input id="%s" name="%s" type="text" value="" class="form-control" autocomplete="off"%s%s>`,
|
|
setting.Captcha.CreateHtml(), fSet.Id, fSet.Name, fSet.Placeholder, fSet.Attrs))
|
|
}
|
|
})
|
|
|
|
RegisterFieldCreater("empty", func(fSet *FieldSet) {
|
|
fSet.Label = ""
|
|
fSet.Field = func() template.HTML { return "" }
|
|
})
|
|
}
|
|
|
|
// parse request.Form values to form
|
|
func ParseForm(form interface{}, values url.Values) {
|
|
val := reflect.ValueOf(form)
|
|
elm := reflect.Indirect(val)
|
|
|
|
panicAssertStructPtr(val)
|
|
|
|
outFor:
|
|
for i := 0; i < elm.NumField(); i++ {
|
|
f := elm.Field(i)
|
|
fT := elm.Type().Field(i)
|
|
|
|
fName := fT.Name
|
|
|
|
for _, v := range strings.Split(fT.Tag.Get("form"), ";") {
|
|
v = strings.TrimSpace(v)
|
|
if v == "-" {
|
|
continue outFor
|
|
} else if i := strings.Index(v, "("); i > 0 && strings.Index(v, ")") == len(v)-1 {
|
|
tN := v[:i]
|
|
v = strings.TrimSpace(v[i+1 : len(v)-1])
|
|
switch tN {
|
|
case "name":
|
|
fName = v
|
|
}
|
|
}
|
|
}
|
|
|
|
value := ""
|
|
var vs []string
|
|
if v, ok := values[fName]; ok {
|
|
vs = v
|
|
if len(v) > 0 {
|
|
value = v[0]
|
|
}
|
|
}
|
|
|
|
switch fT.Type.Kind() {
|
|
case reflect.Bool:
|
|
b, _ := StrTo(value).Bool()
|
|
f.SetBool(b)
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
x, _ := StrTo(value).Int64()
|
|
f.SetInt(x)
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
x, _ := StrTo(value).Uint64()
|
|
f.SetUint(x)
|
|
case reflect.Float32, reflect.Float64:
|
|
x, _ := StrTo(value).Float64()
|
|
f.SetFloat(x)
|
|
case reflect.Struct:
|
|
if fT.Type.String() == "time.Time" {
|
|
if len(value) > 10 {
|
|
t, err := DateParse(value, setting.DateTimeFormat)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
f.Set(reflect.ValueOf(t))
|
|
} else {
|
|
t, err := DateParse(value, setting.DateFormat)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
f.Set(reflect.ValueOf(t))
|
|
}
|
|
}
|
|
case reflect.String:
|
|
f.SetString(value)
|
|
case reflect.Slice:
|
|
f.Set(reflect.ValueOf(vs))
|
|
}
|
|
}
|
|
}
|
|
|
|
// assert an object must be a struct pointer
|
|
func panicAssertStructPtr(val reflect.Value) {
|
|
if val.Kind() == reflect.Ptr && val.Elem().Kind() == reflect.Struct {
|
|
return
|
|
}
|
|
panic(fmt.Errorf("%s must be a struct pointer", val.Type().Name()))
|
|
}
|
|
|
|
// set values from one struct to other struct
|
|
// both need ptr struct
|
|
func SetFormValues(from interface{}, to interface{}, skips ...string) {
|
|
val := reflect.ValueOf(from)
|
|
elm := reflect.Indirect(val)
|
|
|
|
valTo := reflect.ValueOf(to)
|
|
elmTo := reflect.Indirect(valTo)
|
|
|
|
panicAssertStructPtr(val)
|
|
panicAssertStructPtr(valTo)
|
|
|
|
outFor:
|
|
for i := 0; i < elmTo.NumField(); i++ {
|
|
toF := elmTo.Field(i)
|
|
name := elmTo.Type().Field(i).Name
|
|
|
|
// skip specify field
|
|
for _, skip := range skips {
|
|
if skip == name {
|
|
continue outFor
|
|
}
|
|
}
|
|
f := elm.FieldByName(name)
|
|
if f.Kind() != reflect.Invalid {
|
|
// set value if type matched
|
|
if f.Type().String() == toF.Type().String() {
|
|
toF.Set(f)
|
|
} else {
|
|
fInt := false
|
|
switch f.Interface().(type) {
|
|
case int, int8, int16, int32, int64:
|
|
fInt = true
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
default:
|
|
continue outFor
|
|
}
|
|
switch toF.Interface().(type) {
|
|
case int, int8, int16, int32, int64:
|
|
var v int64
|
|
if fInt {
|
|
v = f.Int()
|
|
} else {
|
|
vu := f.Uint()
|
|
if vu > math.MaxInt64 {
|
|
continue outFor
|
|
}
|
|
v = int64(vu)
|
|
}
|
|
if toF.OverflowInt(v) {
|
|
continue outFor
|
|
}
|
|
toF.SetInt(v)
|
|
case uint, uint8, uint16, uint32, uint64:
|
|
var v uint64
|
|
if fInt {
|
|
vu := f.Int()
|
|
if vu < 0 {
|
|
continue outFor
|
|
}
|
|
v = uint64(vu)
|
|
} else {
|
|
v = f.Uint()
|
|
}
|
|
if toF.OverflowUint(v) {
|
|
continue outFor
|
|
}
|
|
toF.SetUint(v)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// compare field values between two struct pointer
|
|
// return changed field names
|
|
func FormChanges(base interface{}, modified interface{}, skips ...string) (fields []string) {
|
|
val := reflect.ValueOf(base)
|
|
elm := reflect.Indirect(val)
|
|
|
|
valMod := reflect.ValueOf(modified)
|
|
elmMod := reflect.Indirect(valMod)
|
|
|
|
panicAssertStructPtr(val)
|
|
panicAssertStructPtr(valMod)
|
|
|
|
outFor:
|
|
for i := 0; i < elmMod.NumField(); i++ {
|
|
modF := elmMod.Field(i)
|
|
name := elmMod.Type().Field(i).Name
|
|
|
|
fT := elmMod.Type().Field(i)
|
|
|
|
for _, v := range strings.Split(fT.Tag.Get("form"), ";") {
|
|
v = strings.TrimSpace(v)
|
|
if v == "-" {
|
|
continue outFor
|
|
}
|
|
}
|
|
|
|
// skip specify field
|
|
for _, skip := range skips {
|
|
if skip == name {
|
|
continue outFor
|
|
}
|
|
}
|
|
f := elm.FieldByName(name)
|
|
if f.Kind() == reflect.Invalid {
|
|
continue
|
|
}
|
|
|
|
// compare two values use string
|
|
if ToStr(modF.Interface()) != ToStr(f.Interface()) {
|
|
fields = append(fields, name)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|