github.com/kotovmak/go-admin@v1.1.1/template/types/info.go (about)

     1  package types
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"html"
     7  	"html/template"
     8  	"net/http"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/kotovmak/go-admin/modules/config"
    13  
    14  	"github.com/kotovmak/go-admin/context"
    15  	"github.com/kotovmak/go-admin/modules/db"
    16  	"github.com/kotovmak/go-admin/modules/errors"
    17  	"github.com/kotovmak/go-admin/modules/language"
    18  	"github.com/kotovmak/go-admin/modules/logger"
    19  	"github.com/kotovmak/go-admin/modules/utils"
    20  	"github.com/kotovmak/go-admin/plugins/admin/modules"
    21  	"github.com/kotovmak/go-admin/plugins/admin/modules/parameter"
    22  	"github.com/kotovmak/go-admin/template/types/form"
    23  	"github.com/kotovmak/go-admin/template/types/table"
    24  )
    25  
    26  // FieldModel is the single query result.
    27  type FieldModel struct {
    28  	// The primaryKey of the table.
    29  	ID string
    30  
    31  	// The value of the single query result.
    32  	Value string
    33  
    34  	// The current row data.
    35  	Row map[string]interface{}
    36  
    37  	// Post type
    38  	PostType PostType
    39  }
    40  
    41  type PostType uint8
    42  
    43  const (
    44  	PostTypeCreate = iota
    45  	PostTypeUpdate
    46  )
    47  
    48  func (m FieldModel) IsCreate() bool {
    49  	return m.PostType == PostTypeCreate
    50  }
    51  
    52  func (m FieldModel) IsUpdate() bool {
    53  	return m.PostType == PostTypeUpdate
    54  }
    55  
    56  // PostFieldModel contains ID and value of the single query result and the current row data.
    57  type PostFieldModel struct {
    58  	ID    string
    59  	Value FieldModelValue
    60  	Row   map[string]string
    61  	// Post type
    62  	PostType PostType
    63  }
    64  
    65  func (m PostFieldModel) IsCreate() bool {
    66  	return m.PostType == PostTypeCreate
    67  }
    68  
    69  func (m PostFieldModel) IsUpdate() bool {
    70  	return m.PostType == PostTypeUpdate
    71  }
    72  
    73  type InfoList []map[string]InfoItem
    74  
    75  type InfoItem struct {
    76  	Content template.HTML `json:"content"`
    77  	Value   string        `json:"value"`
    78  }
    79  
    80  func (i InfoList) GroupBy(groups TabGroups) []InfoList {
    81  
    82  	var res = make([]InfoList, len(groups))
    83  
    84  	for key, value := range groups {
    85  		var newInfoList = make(InfoList, len(i))
    86  
    87  		for index, info := range i {
    88  			var newRow = make(map[string]InfoItem)
    89  			for mk, m := range info {
    90  				if modules.InArray(value, mk) {
    91  					newRow[mk] = m
    92  				}
    93  			}
    94  			newInfoList[index] = newRow
    95  		}
    96  
    97  		res[key] = newInfoList
    98  	}
    99  
   100  	return res
   101  }
   102  
   103  type Callbacks []context.Node
   104  
   105  func (c Callbacks) AddCallback(node context.Node) Callbacks {
   106  	if node.Path != "" && node.Method != "" && len(node.Handlers) > 0 {
   107  		for _, item := range c {
   108  			if strings.EqualFold(item.Path, node.Path) &&
   109  				strings.EqualFold(item.Method, node.Method) {
   110  				return c
   111  			}
   112  		}
   113  		parr := strings.Split(node.Path, "?")
   114  		if len(parr) > 1 {
   115  			node.Path = parr[0]
   116  			return append(c, node)
   117  		}
   118  		return append(c, node)
   119  	}
   120  	return c
   121  }
   122  
   123  type FieldModelValue []string
   124  
   125  func (r FieldModelValue) Value() string {
   126  	return r.First()
   127  }
   128  
   129  func (r FieldModelValue) First() string {
   130  	if len(r) > 0 {
   131  		return r[0]
   132  	}
   133  	return ""
   134  }
   135  
   136  // FieldDisplay is filter function of data.
   137  type FieldFilterFn func(value FieldModel) interface{}
   138  
   139  // PostFieldFilterFn is filter function of data.
   140  type PostFieldFilterFn func(value PostFieldModel) interface{}
   141  
   142  // Field is the table field.
   143  type Field struct {
   144  	Head     string
   145  	Field    string
   146  	TypeName db.DatabaseType
   147  
   148  	Joins Joins
   149  
   150  	Width       int
   151  	Sortable    bool
   152  	EditAble    bool
   153  	Fixed       bool
   154  	Filterable  bool
   155  	Hide        bool
   156  	HideForList bool
   157  
   158  	EditType    table.Type
   159  	EditOptions FieldOptions
   160  
   161  	FilterFormFields []FilterFormField
   162  
   163  	IsEditParam   bool
   164  	IsDeleteParam bool
   165  	IsDetailParam bool
   166  
   167  	FieldDisplay
   168  }
   169  
   170  type QueryFilterFn func(param parameter.Parameters, conn db.Connection) (ids []string, stopQuery bool)
   171  type UpdateParametersFn func(param *parameter.Parameters)
   172  
   173  type FilterFormField struct {
   174  	Type        form.Type
   175  	Options     FieldOptions
   176  	OptionTable OptionTable
   177  	Width       int
   178  	HeadWidth   int
   179  	InputWidth  int
   180  	Style       template.HTMLAttr
   181  	Operator    FilterOperator
   182  	OptionExt   template.JS
   183  	Head        string
   184  	Placeholder string
   185  	HelpMsg     template.HTML
   186  	NoIcon      bool
   187  	ProcessFn   func(string) string
   188  }
   189  
   190  func (f Field) GetFilterFormFields(params parameter.Parameters, headField string, sql ...*db.SQL) []FormField {
   191  
   192  	var (
   193  		filterForm               = make([]FormField, 0)
   194  		value, value2, keySuffix string
   195  	)
   196  
   197  	for index, filter := range f.FilterFormFields {
   198  
   199  		if index > 0 {
   200  			keySuffix = parameter.FilterParamCountInfix + strconv.Itoa(index)
   201  		}
   202  
   203  		if filter.Type.IsRange() {
   204  			value = params.GetFilterFieldValueStart(headField)
   205  			value2 = params.GetFilterFieldValueEnd(headField)
   206  		} else if filter.Type.IsMultiSelect() {
   207  			value = params.GetFieldValuesStr(headField)
   208  		} else {
   209  			if filter.Operator == FilterOperatorFree {
   210  				value2 = GetOperatorFromValue(params.GetFieldOperator(headField, keySuffix)).String()
   211  			}
   212  			value = params.GetFieldValue(headField + keySuffix)
   213  		}
   214  
   215  		var (
   216  			optionExt1 = filter.OptionExt
   217  			optionExt2 template.JS
   218  		)
   219  
   220  		if filter.OptionExt == template.JS("") {
   221  			op1, op2, js := filter.Type.GetDefaultOptions(headField + keySuffix)
   222  			if op1 != nil {
   223  				s, _ := json.Marshal(op1)
   224  				optionExt1 = template.JS(string(s))
   225  			}
   226  			if op2 != nil {
   227  				s, _ := json.Marshal(op2)
   228  				optionExt2 = template.JS(string(s))
   229  			}
   230  			if js != template.JS("") {
   231  				optionExt1 = js
   232  			}
   233  		}
   234  
   235  		field := &FormField{
   236  			Field:       headField + keySuffix,
   237  			FieldClass:  headField + keySuffix,
   238  			Head:        filter.Head,
   239  			TypeName:    f.TypeName,
   240  			HelpMsg:     filter.HelpMsg,
   241  			NoIcon:      filter.NoIcon,
   242  			FormType:    filter.Type,
   243  			Editable:    true,
   244  			Width:       filter.Width,
   245  			HeadWidth:   filter.HeadWidth,
   246  			InputWidth:  filter.InputWidth,
   247  			Style:       filter.Style,
   248  			Placeholder: filter.Placeholder,
   249  			Value:       template.HTML(value),
   250  			Value2:      value2,
   251  			Options:     filter.Options,
   252  			OptionExt:   optionExt1,
   253  			OptionExt2:  optionExt2,
   254  			OptionTable: filter.OptionTable,
   255  			Label:       filter.Operator.Label(),
   256  		}
   257  
   258  		field.setOptionsFromSQL(sql[0])
   259  
   260  		if filter.Type.IsSingleSelect() {
   261  			field.Options = field.Options.SetSelected(params.GetFieldValue(f.Field), filter.Type.SelectedLabel())
   262  		}
   263  
   264  		if filter.Type.IsMultiSelect() {
   265  			field.Options = field.Options.SetSelected(params.GetFieldValues(f.Field), filter.Type.SelectedLabel())
   266  		}
   267  
   268  		filterForm = append(filterForm, *field)
   269  
   270  		if filter.Operator.AddOrNot() {
   271  			ff := headField + parameter.FilterParamOperatorSuffix + keySuffix
   272  			filterForm = append(filterForm, FormField{
   273  				Field:      ff,
   274  				FieldClass: ff,
   275  				Head:       f.Head,
   276  				TypeName:   f.TypeName,
   277  				Value:      template.HTML(filter.Operator.Value()),
   278  				FormType:   filter.Type,
   279  				Hide:       true,
   280  			})
   281  		}
   282  	}
   283  
   284  	return filterForm
   285  }
   286  
   287  func (f Field) Exist() bool {
   288  	return f.Field != ""
   289  }
   290  
   291  type FieldList []Field
   292  
   293  type TableInfo struct {
   294  	Table      string
   295  	PrimaryKey string
   296  	Delimiter  string
   297  	Delimiter2 string
   298  	Driver     string
   299  }
   300  
   301  func (f FieldList) GetTheadAndFilterForm(info TableInfo, params parameter.Parameters, columns []string,
   302  	sql ...func() *db.SQL) (Thead, string, string, string, []string, []FormField) {
   303  	var (
   304  		thead      = make(Thead, 0)
   305  		fields     = ""
   306  		joinFields = ""
   307  		joins      = ""
   308  		joinTables = make([]string, 0)
   309  		filterForm = make([]FormField, 0)
   310  		tableName  = info.Delimiter + info.Table + info.Delimiter2
   311  	)
   312  	for _, field := range f {
   313  		if field.Field != info.PrimaryKey && modules.InArray(columns, field.Field) &&
   314  			!field.Joins.Valid() {
   315  			fields += tableName + "." + modules.FilterField(field.Field, info.Delimiter, info.Delimiter2) + ","
   316  		}
   317  
   318  		headField := field.Field
   319  
   320  		if field.Joins.Valid() {
   321  			headField = field.Joins.Last().GetTableName() + parameter.FilterParamJoinInfix + field.Field
   322  			joinFields += db.GetAggregationExpression(info.Driver, field.Joins.Last().GetTableName(info.Delimiter, info.Delimiter2)+"."+
   323  				modules.FilterField(field.Field, info.Delimiter, info.Delimiter2), headField, JoinFieldValueDelimiter) + ","
   324  			for _, join := range field.Joins {
   325  				if !modules.InArray(joinTables, join.GetTableName(info.Delimiter, info.Delimiter2)) {
   326  					joinTables = append(joinTables, join.GetTableName(info.Delimiter, info.Delimiter2))
   327  					if join.BaseTable == "" {
   328  						join.BaseTable = info.Table
   329  					}
   330  					joins += " left join " + modules.FilterField(join.Table, info.Delimiter, info.Delimiter2) + " " + join.TableAlias + " on " +
   331  						join.GetTableName(info.Delimiter, info.Delimiter2) + "." + modules.FilterField(join.JoinField, info.Delimiter, info.Delimiter2) + " = " +
   332  						modules.Delimiter(info.Delimiter, info.Delimiter2, join.BaseTable) + "." + modules.FilterField(join.Field, info.Delimiter, info.Delimiter2)
   333  				}
   334  			}
   335  		}
   336  
   337  		if field.Filterable {
   338  			if len(sql) > 0 {
   339  				filterForm = append(filterForm, field.GetFilterFormFields(params, headField, sql[0]())...)
   340  			} else {
   341  				filterForm = append(filterForm, field.GetFilterFormFields(params, headField)...)
   342  			}
   343  		}
   344  
   345  		if field.Hide {
   346  			continue
   347  		}
   348  		if field.HideForList {
   349  			continue
   350  		}
   351  		thead = append(thead, TheadItem{
   352  			Head:       field.Head,
   353  			Sortable:   field.Sortable,
   354  			Field:      headField,
   355  			Hide:       !modules.InArrayWithoutEmpty(params.Columns, headField),
   356  			Editable:   field.EditAble,
   357  			EditType:   field.EditType.String(),
   358  			EditOption: field.EditOptions,
   359  			Width:      strconv.Itoa(field.Width) + "px",
   360  		})
   361  	}
   362  
   363  	return thead, fields, joinFields, joins, joinTables, filterForm
   364  }
   365  
   366  func (f FieldList) GetThead(info TableInfo, params parameter.Parameters, columns []string) (Thead, string, string) {
   367  	var (
   368  		thead      = make(Thead, 0)
   369  		fields     = ""
   370  		joins      = ""
   371  		joinTables = make([]string, 0)
   372  	)
   373  	for _, field := range f {
   374  		if field.Field != info.PrimaryKey && modules.InArray(columns, field.Field) &&
   375  			!field.Joins.Valid() {
   376  			fields += info.Table + "." + modules.FilterField(field.Field, info.Delimiter, info.Delimiter2) + ","
   377  		}
   378  
   379  		headField := field.Field
   380  
   381  		if field.Joins.Valid() {
   382  			headField = field.Joins.Last().GetTableName(info.Delimiter, info.Delimiter2) + parameter.FilterParamJoinInfix + field.Field
   383  			for _, join := range field.Joins {
   384  				if !modules.InArray(joinTables, join.GetTableName(info.Delimiter, info.Delimiter2)) {
   385  					joinTables = append(joinTables, join.GetTableName(info.Delimiter, info.Delimiter2))
   386  					if join.BaseTable == "" {
   387  						join.BaseTable = info.Table
   388  					}
   389  					joins += " left join " + modules.FilterField(join.Table, info.Delimiter, info.Delimiter2) + " " + join.TableAlias + " on " +
   390  						join.GetTableName(info.Delimiter, info.Delimiter2) + "." + modules.FilterField(join.JoinField, info.Delimiter, info.Delimiter2) + " = " +
   391  						modules.Delimiter(info.Delimiter, info.Delimiter2, join.BaseTable) + "." + modules.FilterField(join.Field, info.Delimiter, info.Delimiter2)
   392  				}
   393  			}
   394  		}
   395  
   396  		if field.Hide {
   397  			continue
   398  		}
   399  		thead = append(thead, TheadItem{
   400  			Head:       field.Head,
   401  			Sortable:   field.Sortable,
   402  			Field:      headField,
   403  			Hide:       !modules.InArrayWithoutEmpty(params.Columns, headField),
   404  			Editable:   field.EditAble,
   405  			EditType:   field.EditType.String(),
   406  			EditOption: field.EditOptions,
   407  			Width:      strconv.Itoa(field.Width) + "px",
   408  		})
   409  	}
   410  
   411  	return thead, fields, joins
   412  }
   413  
   414  func (f FieldList) GetFieldFilterProcessValue(key, value, keyIndex string) string {
   415  	field := f.GetFieldByFieldName(key)
   416  	index := 0
   417  	if keyIndex != "" {
   418  		index, _ = strconv.Atoi(keyIndex)
   419  	}
   420  	if field.FilterFormFields != nil && len(field.FilterFormFields) > index {
   421  		if field.FilterFormFields[index].ProcessFn != nil {
   422  			value = field.FilterFormFields[index].ProcessFn(value)
   423  		}
   424  	}
   425  	return value
   426  }
   427  
   428  func (f FieldList) GetFieldJoinTable(key string) string {
   429  	field := f.GetFieldByFieldName(key)
   430  	if field.Exist() {
   431  		return field.Joins.Last().Table
   432  	}
   433  	return ""
   434  }
   435  
   436  func (f FieldList) GetFieldByFieldName(name string) Field {
   437  	for _, field := range f {
   438  		if field.Field == name {
   439  			return field
   440  		}
   441  		if JoinField(field.Joins.Last().GetTableName(), field.Field) == name {
   442  			return field
   443  		}
   444  	}
   445  	return Field{}
   446  }
   447  
   448  // Join store join table info. For example:
   449  //
   450  //	Join {
   451  //	    BaseTable:   "users",
   452  //	    Field:       "role_id",
   453  //	    Table:       "roles",
   454  //	    JoinField:   "id",
   455  //	}
   456  //
   457  // It will generate the join table sql like:
   458  //
   459  // ... left join roles on roles.id = users.role_id ...
   460  type Join struct {
   461  	Table      string
   462  	TableAlias string
   463  	Field      string
   464  	JoinField  string
   465  	BaseTable  string
   466  }
   467  
   468  type Joins []Join
   469  
   470  func JoinField(table, field string) string {
   471  	return table + parameter.FilterParamJoinInfix + field
   472  }
   473  
   474  func GetJoinField(field string) string {
   475  	return strings.Split(field, parameter.FilterParamJoinInfix)[1]
   476  }
   477  
   478  func (j Joins) Valid() bool {
   479  	for i := 0; i < len(j); i++ {
   480  		if j[i].Valid() {
   481  			return true
   482  		}
   483  	}
   484  	return false
   485  }
   486  
   487  func (j Joins) Last() Join {
   488  	if len(j) > 0 {
   489  		return j[len(j)-1]
   490  	}
   491  	return Join{}
   492  }
   493  
   494  func (j Join) Valid() bool {
   495  	return j.Table != "" && j.Field != "" && j.JoinField != ""
   496  }
   497  
   498  func (j Join) GetTableName(delimiter ...string) string {
   499  	if j.TableAlias != "" {
   500  		return j.TableAlias
   501  	}
   502  	if len(delimiter) > 0 {
   503  		return delimiter[0] + j.Table + delimiter[1]
   504  	}
   505  	return j.Table
   506  }
   507  
   508  var JoinFieldValueDelimiter = utils.Uuid(8)
   509  
   510  type TabGroups [][]string
   511  
   512  func (t TabGroups) Valid() bool {
   513  	return len(t) > 0
   514  }
   515  
   516  func NewTabGroups(items ...string) TabGroups {
   517  	var t = make(TabGroups, 0)
   518  	return append(t, items)
   519  }
   520  
   521  func (t TabGroups) AddGroup(items ...string) TabGroups {
   522  	return append(t, items)
   523  }
   524  
   525  type TabHeaders []string
   526  
   527  func (t TabHeaders) Add(header string) TabHeaders {
   528  	return append(t, header)
   529  }
   530  
   531  type GetDataFn func(param parameter.Parameters) ([]map[string]interface{}, int)
   532  
   533  type DeleteFn func(ids []string) error
   534  type DeleteFnWithRes func(ids []string, res error) error
   535  
   536  type Sort uint8
   537  
   538  const (
   539  	SortDesc Sort = iota
   540  	SortAsc
   541  )
   542  
   543  type primaryKey struct {
   544  	Type db.DatabaseType
   545  	Name string
   546  }
   547  
   548  type ExportProcessFn func(param parameter.Parameters) (PanelInfo, error)
   549  
   550  // InfoPanel
   551  type InfoPanel struct {
   552  	FieldList         FieldList
   553  	curFieldListIndex int
   554  
   555  	Table       string
   556  	Title       string
   557  	Description string
   558  
   559  	// Warn: may be deprecated future.
   560  	TabGroups  TabGroups
   561  	TabHeaders TabHeaders
   562  
   563  	Sort      Sort
   564  	SortField string
   565  
   566  	PageSizeList    []int
   567  	DefaultPageSize int
   568  
   569  	ExportType      int
   570  	ExportProcessFn ExportProcessFn
   571  
   572  	primaryKey primaryKey
   573  
   574  	IsHideNewButton    bool
   575  	IsHideExportButton bool
   576  	IsHideEditButton   bool
   577  	IsHideDeleteButton bool
   578  	IsHideDetailButton bool
   579  	IsHideFilterButton bool
   580  	IsHideRowSelector  bool
   581  	IsHidePagination   bool
   582  	IsHideFilterArea   bool
   583  	IsHideQueryInfo    bool
   584  	FilterFormLayout   form.Layout
   585  
   586  	FilterFormHeadWidth  int
   587  	FilterFormInputWidth int
   588  
   589  	Wheres    Wheres
   590  	WhereRaws WhereRaw
   591  
   592  	Callbacks Callbacks
   593  
   594  	Buttons Buttons
   595  
   596  	TableLayout string
   597  
   598  	DeleteHook  DeleteFn
   599  	PreDeleteFn DeleteFn
   600  	DeleteFn    DeleteFn
   601  
   602  	DeleteHookWithRes DeleteFnWithRes
   603  
   604  	GetDataFn GetDataFn
   605  
   606  	processChains DisplayProcessFnChains
   607  
   608  	ActionButtons    Buttons
   609  	ActionButtonFold bool
   610  
   611  	DisplayGeneratorRecords map[string]struct{}
   612  
   613  	QueryFilterFn       QueryFilterFn
   614  	UpdateParametersFns []UpdateParametersFn
   615  
   616  	Wrapper ContentWrapper
   617  
   618  	// column operation buttons
   619  	Action     template.HTML
   620  	HeaderHtml template.HTML
   621  	FooterHtml template.HTML
   622  
   623  	PageError     errors.PageError
   624  	PageErrorHTML template.HTML
   625  
   626  	NoCompress  bool
   627  	HideSideBar bool
   628  
   629  	AutoRefresh uint
   630  }
   631  
   632  type Where struct {
   633  	Join     string
   634  	Field    string
   635  	Operator string
   636  	Arg      interface{}
   637  }
   638  
   639  type Wheres []Where
   640  
   641  func (whs Wheres) Statement(wheres, delimiter, delimiter2 string, whereArgs []interface{}, existKeys, columns []string) (string, []interface{}) {
   642  	pwheres := ""
   643  	for k, wh := range whs {
   644  
   645  		whFieldArr := strings.Split(wh.Field, ".")
   646  		whField := ""
   647  		whTable := ""
   648  		if len(whFieldArr) > 1 {
   649  			whField = whFieldArr[1]
   650  			whTable = whFieldArr[0]
   651  		} else {
   652  			whField = whFieldArr[0]
   653  		}
   654  
   655  		if modules.InArray(existKeys, whField) {
   656  			continue
   657  		}
   658  
   659  		// TODO: support like operation and join table
   660  		if modules.InArray(columns, whField) {
   661  
   662  			joinMark := ""
   663  			if k != len(whs)-1 {
   664  				joinMark = whs[k+1].Join
   665  			}
   666  
   667  			if whTable != "" {
   668  				pwheres += whTable + "." + modules.FilterField(whField, delimiter, delimiter2) + " " + wh.Operator + " ? " + joinMark + " "
   669  			} else {
   670  				pwheres += modules.FilterField(whField, delimiter, delimiter2) + " " + wh.Operator + " ? " + joinMark + " "
   671  			}
   672  			whereArgs = append(whereArgs, wh.Arg)
   673  		}
   674  	}
   675  	if wheres != "" && pwheres != "" {
   676  		wheres += " and "
   677  	}
   678  	return wheres + pwheres, whereArgs
   679  }
   680  
   681  type WhereRaw struct {
   682  	Raw  string
   683  	Args []interface{}
   684  }
   685  
   686  func (wh WhereRaw) check() int {
   687  	index := 0
   688  	for i := 0; i < len(wh.Raw); i++ {
   689  		if wh.Raw[i] == ' ' {
   690  			continue
   691  		}
   692  		if wh.Raw[i] == 'a' {
   693  			if len(wh.Raw) < i+3 {
   694  				break
   695  			} else if wh.Raw[i+1] == 'n' && wh.Raw[i+2] == 'd' {
   696  				index = i + 3
   697  			}
   698  		} else if wh.Raw[i] == 'o' {
   699  			if len(wh.Raw) < i+2 {
   700  				break
   701  			} else if wh.Raw[i+1] == 'r' {
   702  				index = i + 2
   703  			}
   704  		} else {
   705  			break
   706  		}
   707  	}
   708  	return index
   709  }
   710  
   711  func (wh WhereRaw) Statement(wheres string, whereArgs []interface{}) (string, []interface{}) {
   712  
   713  	if wh.Raw == "" {
   714  		return wheres, whereArgs
   715  	}
   716  
   717  	if wheres != "" {
   718  		if wh.check() != 0 {
   719  			wheres += wh.Raw + " "
   720  		} else {
   721  			wheres += " and " + wh.Raw + " "
   722  		}
   723  
   724  		whereArgs = append(whereArgs, wh.Args...)
   725  	} else {
   726  		wheres += wh.Raw[wh.check():] + " "
   727  		whereArgs = append(whereArgs, wh.Args...)
   728  	}
   729  
   730  	return wheres, whereArgs
   731  }
   732  
   733  type Handler func(ctx *context.Context) (success bool, msg string, data interface{})
   734  
   735  func (h Handler) Wrap() context.Handler {
   736  	return func(ctx *context.Context) {
   737  		defer func() {
   738  			if err := recover(); err != nil {
   739  				logger.Error(err)
   740  				ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
   741  					"code": 500,
   742  					"data": "",
   743  					"msg":  "error",
   744  				})
   745  			}
   746  		}()
   747  
   748  		code := 0
   749  		s, m, d := h(ctx)
   750  
   751  		if !s {
   752  			code = 500
   753  		}
   754  		ctx.JSON(http.StatusOK, map[string]interface{}{
   755  			"code": code,
   756  			"data": d,
   757  			"msg":  m,
   758  		})
   759  	}
   760  }
   761  
   762  type ContentWrapper func(content template.HTML) template.HTML
   763  
   764  type Action interface {
   765  	Js() template.JS
   766  	BtnAttribute() template.HTML
   767  	BtnClass() template.HTML
   768  	ExtContent() template.HTML
   769  	FooterContent() template.HTML
   770  	SetBtnId(btnId string)
   771  	SetBtnData(data interface{})
   772  	GetCallbacks() context.Node
   773  }
   774  
   775  type NilAction struct{}
   776  
   777  func (def *NilAction) SetBtnId(btnId string)        {}
   778  func (def *NilAction) SetBtnData(data interface{})  {}
   779  func (def *NilAction) Js() template.JS              { return "" }
   780  func (def *NilAction) BtnAttribute() template.HTML  { return "" }
   781  func (def *NilAction) BtnClass() template.HTML      { return "" }
   782  func (def *NilAction) ExtContent() template.HTML    { return "" }
   783  func (def *NilAction) FooterContent() template.HTML { return "" }
   784  func (def *NilAction) GetCallbacks() context.Node   { return context.Node{} }
   785  
   786  type Actions []Action
   787  
   788  type DefaultAction struct {
   789  	Attr   template.HTML
   790  	JS     template.JS
   791  	Ext    template.HTML
   792  	Footer template.HTML
   793  }
   794  
   795  func NewDefaultAction(attr, ext, footer template.HTML, js template.JS) *DefaultAction {
   796  	return &DefaultAction{Attr: attr, Ext: ext, Footer: footer, JS: js}
   797  }
   798  
   799  func (def *DefaultAction) SetBtnId(btnId string)        {}
   800  func (def *DefaultAction) SetBtnData(data interface{})  {}
   801  func (def *DefaultAction) Js() template.JS              { return def.JS }
   802  func (def *DefaultAction) BtnAttribute() template.HTML  { return def.Attr }
   803  func (def *DefaultAction) BtnClass() template.HTML      { return "" }
   804  func (def *DefaultAction) ExtContent() template.HTML    { return def.Ext }
   805  func (def *DefaultAction) FooterContent() template.HTML { return def.Footer }
   806  func (def *DefaultAction) GetCallbacks() context.Node   { return context.Node{} }
   807  
   808  var _ Action = (*DefaultAction)(nil)
   809  
   810  var DefaultPageSizeList = []int{10, 20, 30, 50, 100}
   811  
   812  const DefaultPageSize = 10
   813  
   814  func NewInfoPanel(pk string) *InfoPanel {
   815  	return &InfoPanel{
   816  		curFieldListIndex:       -1,
   817  		PageSizeList:            DefaultPageSizeList,
   818  		DefaultPageSize:         DefaultPageSize,
   819  		processChains:           make(DisplayProcessFnChains, 0),
   820  		Buttons:                 make(Buttons, 0),
   821  		Callbacks:               make(Callbacks, 0),
   822  		DisplayGeneratorRecords: make(map[string]struct{}),
   823  		Wheres:                  make([]Where, 0),
   824  		WhereRaws:               WhereRaw{},
   825  		SortField:               pk,
   826  		TableLayout:             "auto",
   827  		FilterFormInputWidth:    10,
   828  		FilterFormHeadWidth:     2,
   829  		AutoRefresh:             0,
   830  	}
   831  }
   832  
   833  func (i *InfoPanel) Where(field string, operator string, arg interface{}) *InfoPanel {
   834  	i.Wheres = append(i.Wheres, Where{Field: field, Operator: operator, Arg: arg, Join: "and"})
   835  	return i
   836  }
   837  
   838  func (i *InfoPanel) WhereOr(field string, operator string, arg interface{}) *InfoPanel {
   839  	i.Wheres = append(i.Wheres, Where{Field: field, Operator: operator, Arg: arg, Join: "or"})
   840  	return i
   841  }
   842  
   843  func (i *InfoPanel) WhereRaw(raw string, arg ...interface{}) *InfoPanel {
   844  	i.WhereRaws.Raw = raw
   845  	i.WhereRaws.Args = arg
   846  	return i
   847  }
   848  
   849  func (i *InfoPanel) AddSelectBox(placeholder string, options FieldOptions, action Action, width ...int) *InfoPanel {
   850  	options = append(FieldOptions{{Value: "", Text: language.Get("All")}}, options...)
   851  	action.SetBtnData(options)
   852  	i.addButton(GetDefaultSelection(placeholder, options, action, width...)).
   853  		addFooterHTML(action.FooterContent()).
   854  		addCallback(action.GetCallbacks())
   855  
   856  	return i
   857  }
   858  
   859  func (i *InfoPanel) ExportValue() *InfoPanel {
   860  	i.ExportType = 1
   861  	return i
   862  }
   863  
   864  func (i *InfoPanel) IsExportValue() bool {
   865  	return i.ExportType == 1
   866  }
   867  
   868  func (i *InfoPanel) AddButtonRaw(btn Button, action Action) *InfoPanel {
   869  	i.Buttons = append(i.Buttons, btn)
   870  	i.addFooterHTML(action.FooterContent()).addCallback(action.GetCallbacks())
   871  	return i
   872  }
   873  
   874  func (i *InfoPanel) AddButton(title template.HTML, icon string, action Action, color ...template.HTML) *InfoPanel {
   875  	i.addButton(GetDefaultButtonGroup(title, icon, action, color...)).
   876  		addFooterHTML(action.FooterContent()).
   877  		addCallback(action.GetCallbacks())
   878  	return i
   879  }
   880  
   881  func (i *InfoPanel) AddActionIconButton(icon string, action Action, ids ...string) *InfoPanel {
   882  	i.addActionButton(GetActionIconButton(icon, action, ids...)).
   883  		addFooterHTML(action.FooterContent()).
   884  		addCallback(action.GetCallbacks())
   885  
   886  	return i
   887  }
   888  
   889  func (i *InfoPanel) AddActionButtonFront(title template.HTML, action Action, ids ...string) *InfoPanel {
   890  	i.SetActionButtonFold()
   891  	i.ActionButtons = append([]Button{GetActionButton(title, action, ids...)}, i.ActionButtons...)
   892  	i.addFooterHTML(action.FooterContent()).
   893  		addCallback(action.GetCallbacks())
   894  	return i
   895  }
   896  
   897  func (i *InfoPanel) AddActionButton(title template.HTML, action Action, ids ...string) *InfoPanel {
   898  	i.SetActionButtonFold()
   899  	i.addActionButton(GetActionButton(title, action, ids...)).
   900  		addFooterHTML(action.FooterContent()).
   901  		addCallback(action.GetCallbacks())
   902  
   903  	return i
   904  }
   905  
   906  func (i *InfoPanel) SetActionButtonFold() *InfoPanel {
   907  	i.ActionButtonFold = true
   908  	return i
   909  }
   910  
   911  func (i *InfoPanel) AddLimitFilter(limit int) *InfoPanel {
   912  	i.processChains = addLimit(limit, i.processChains)
   913  	return i
   914  }
   915  
   916  func (i *InfoPanel) AddTrimSpaceFilter() *InfoPanel {
   917  	i.processChains = addTrimSpace(i.processChains)
   918  	return i
   919  }
   920  
   921  func (i *InfoPanel) AddSubstrFilter(start int, end int) *InfoPanel {
   922  	i.processChains = addSubstr(start, end, i.processChains)
   923  	return i
   924  }
   925  
   926  func (i *InfoPanel) AddToTitleFilter() *InfoPanel {
   927  	i.processChains = addToTitle(i.processChains)
   928  	return i
   929  }
   930  
   931  func (i *InfoPanel) AddToUpperFilter() *InfoPanel {
   932  	i.processChains = addToUpper(i.processChains)
   933  	return i
   934  }
   935  
   936  func (i *InfoPanel) AddToLowerFilter() *InfoPanel {
   937  	i.processChains = addToLower(i.processChains)
   938  	return i
   939  }
   940  
   941  func (i *InfoPanel) AddXssFilter() *InfoPanel {
   942  	i.processChains = addXssFilter(i.processChains)
   943  	return i
   944  }
   945  
   946  func (i *InfoPanel) AddXssJsFilter() *InfoPanel {
   947  	i.processChains = addXssJsFilter(i.processChains)
   948  	return i
   949  }
   950  
   951  func (i *InfoPanel) SetExportProcessFn(fn ExportProcessFn) *InfoPanel {
   952  	i.ExportProcessFn = fn
   953  	return i
   954  }
   955  
   956  func (i *InfoPanel) SetDeleteHook(fn DeleteFn) *InfoPanel {
   957  	i.DeleteHook = fn
   958  	return i
   959  }
   960  
   961  func (i *InfoPanel) SetDeleteHookWithRes(fn DeleteFnWithRes) *InfoPanel {
   962  	i.DeleteHookWithRes = fn
   963  	return i
   964  }
   965  
   966  func (i *InfoPanel) SetQueryFilterFn(fn QueryFilterFn) *InfoPanel {
   967  	i.QueryFilterFn = fn
   968  	return i
   969  }
   970  
   971  func (i *InfoPanel) AddUpdateParametersFn(fn UpdateParametersFn) *InfoPanel {
   972  	i.UpdateParametersFns = append(i.UpdateParametersFns, fn)
   973  	return i
   974  }
   975  
   976  func (i *InfoPanel) SetWrapper(wrapper ContentWrapper) *InfoPanel {
   977  	i.Wrapper = wrapper
   978  	return i
   979  }
   980  
   981  func (i *InfoPanel) SetPreDeleteFn(fn DeleteFn) *InfoPanel {
   982  	i.PreDeleteFn = fn
   983  	return i
   984  }
   985  
   986  func (i *InfoPanel) SetDeleteFn(fn DeleteFn) *InfoPanel {
   987  	i.DeleteFn = fn
   988  	return i
   989  }
   990  
   991  func (i *InfoPanel) SetGetDataFn(fn GetDataFn) *InfoPanel {
   992  	i.GetDataFn = fn
   993  	return i
   994  }
   995  
   996  func (i *InfoPanel) SetPrimaryKey(name string, typ db.DatabaseType) *InfoPanel {
   997  	i.primaryKey = primaryKey{Name: name, Type: typ}
   998  	return i
   999  }
  1000  
  1001  func (i *InfoPanel) SetTableFixed() *InfoPanel {
  1002  	i.TableLayout = "fixed"
  1003  	return i
  1004  }
  1005  
  1006  func (i *InfoPanel) AddColumn(head string, fun FieldFilterFn) *InfoPanel {
  1007  	i.FieldList = append(i.FieldList, Field{
  1008  		Head:     head,
  1009  		Field:    head,
  1010  		TypeName: db.Varchar,
  1011  		Sortable: false,
  1012  		EditAble: false,
  1013  		EditType: table.Text,
  1014  		FieldDisplay: FieldDisplay{
  1015  			Display:              fun,
  1016  			DisplayProcessChains: chooseDisplayProcessChains(i.processChains),
  1017  		},
  1018  	})
  1019  	i.curFieldListIndex++
  1020  	return i
  1021  }
  1022  
  1023  func (i *InfoPanel) AddColumnButtons(head string, buttons ...Button) *InfoPanel {
  1024  	var content, js template.HTML
  1025  	for _, btn := range buttons {
  1026  		btn.GetAction().SetBtnId("." + btn.ID())
  1027  		btnContent, btnJs := btn.Content()
  1028  		content += btnContent
  1029  		js += template.HTML(btnJs)
  1030  		i.FooterHtml += template.HTML(ParseTableDataTmpl(btn.GetAction().FooterContent()))
  1031  		i.Callbacks = i.Callbacks.AddCallback(btn.GetAction().GetCallbacks())
  1032  	}
  1033  	i.FooterHtml += template.HTML("<script>") + template.HTML(ParseTableDataTmpl(js)) + template.HTML("</script>")
  1034  	i.FieldList = append(i.FieldList, Field{
  1035  		Head:     head,
  1036  		Field:    head,
  1037  		TypeName: db.Varchar,
  1038  		Sortable: false,
  1039  		EditAble: false,
  1040  		EditType: table.Text,
  1041  		FieldDisplay: FieldDisplay{
  1042  			Display: func(value FieldModel) interface{} {
  1043  				pk := db.GetValueFromDatabaseType(i.primaryKey.Type, value.Row[i.primaryKey.Name], i.isFromJSON())
  1044  				var v = make(map[string]InfoItem)
  1045  				for key, item := range value.Row {
  1046  					itemValue := fmt.Sprintf("%v", item)
  1047  					v[key] = InfoItem{Value: itemValue, Content: template.HTML(itemValue)}
  1048  				}
  1049  				return template.HTML(ParseTableDataTmplWithID(pk.HTML(), string(content), v))
  1050  			},
  1051  			DisplayProcessChains: chooseDisplayProcessChains(i.processChains),
  1052  		},
  1053  	})
  1054  	i.curFieldListIndex++
  1055  	return i
  1056  }
  1057  
  1058  func (i *InfoPanel) AddFieldTr(ctx *context.Context, head, field string, typeName db.DatabaseType) *InfoPanel {
  1059  	return i.AddFieldWithTranslation(ctx, head, field, typeName)
  1060  }
  1061  
  1062  func (i *InfoPanel) AddFieldWithTranslation(ctx *context.Context, head, field string, typeName db.DatabaseType) *InfoPanel {
  1063  	return i.AddField(language.GetWithLang(head, ctx.Lang()), field, typeName)
  1064  }
  1065  
  1066  func (i *InfoPanel) AddField(head, field string, typeName db.DatabaseType) *InfoPanel {
  1067  	i.FieldList = append(i.FieldList, Field{
  1068  		Head:     head,
  1069  		Field:    field,
  1070  		TypeName: typeName,
  1071  		Sortable: false,
  1072  		Joins:    make(Joins, 0),
  1073  		EditAble: false,
  1074  		EditType: table.Text,
  1075  		FieldDisplay: FieldDisplay{
  1076  			Display: func(value FieldModel) interface{} {
  1077  				return value.Value
  1078  			},
  1079  			DisplayProcessChains: chooseDisplayProcessChains(i.processChains),
  1080  		},
  1081  	})
  1082  	i.curFieldListIndex++
  1083  	return i
  1084  }
  1085  
  1086  func (i *InfoPanel) AddFilter(head, field string, typeName db.DatabaseType, fn UpdateParametersFn, filterType ...FilterType) *InfoPanel {
  1087  	return i.AddField(head, field, typeName).FieldHide().FieldFilterable(filterType...).AddUpdateParametersFn(fn)
  1088  }
  1089  
  1090  // Field attribute setting functions
  1091  // ====================================================
  1092  
  1093  func (i *InfoPanel) FieldDisplay(filter FieldFilterFn) *InfoPanel {
  1094  	i.FieldList[i.curFieldListIndex].Display = filter
  1095  	return i
  1096  }
  1097  
  1098  type FieldLabelParam struct {
  1099  	Color template.HTML
  1100  	Type  string
  1101  }
  1102  
  1103  func (i *InfoPanel) FieldLabel(args ...FieldLabelParam) *InfoPanel {
  1104  	i.addDisplayChains(displayFnGens["label"].Get(args))
  1105  	return i
  1106  }
  1107  
  1108  func (i *InfoPanel) FieldImage(width, height string, prefix ...string) *InfoPanel {
  1109  	i.addDisplayChains(displayFnGens["image"].Get(width, height, prefix))
  1110  	return i
  1111  }
  1112  
  1113  func (i *InfoPanel) FieldBool(flags ...string) *InfoPanel {
  1114  	i.addDisplayChains(displayFnGens["bool"].Get(flags))
  1115  	return i
  1116  }
  1117  
  1118  func (i *InfoPanel) FieldLink(src string, openInNewTab ...bool) *InfoPanel {
  1119  	i.addDisplayChains(displayFnGens["link"].Get(src, openInNewTab))
  1120  	return i
  1121  }
  1122  
  1123  func (i *InfoPanel) FieldFileSize() *InfoPanel {
  1124  	i.addDisplayChains(displayFnGens["filesize"].Get())
  1125  	return i
  1126  }
  1127  
  1128  func (i *InfoPanel) FieldDate(format string) *InfoPanel {
  1129  	i.addDisplayChains(displayFnGens["date"].Get(format))
  1130  	return i
  1131  }
  1132  
  1133  func (i *InfoPanel) FieldIcon(icons map[string]string, defaultIcon string) *InfoPanel {
  1134  	i.addDisplayChains(displayFnGens["link"].Get(icons, defaultIcon))
  1135  	return i
  1136  }
  1137  
  1138  type FieldDotColor string
  1139  
  1140  const (
  1141  	FieldDotColorDanger  FieldDotColor = "danger"
  1142  	FieldDotColorInfo    FieldDotColor = "info"
  1143  	FieldDotColorPrimary FieldDotColor = "primary"
  1144  	FieldDotColorSuccess FieldDotColor = "success"
  1145  )
  1146  
  1147  func (i *InfoPanel) FieldDot(icons map[string]FieldDotColor, defaultDot FieldDotColor) *InfoPanel {
  1148  	i.addDisplayChains(displayFnGens["dot"].Get(icons, defaultDot))
  1149  	return i
  1150  }
  1151  
  1152  type FieldProgressBarData struct {
  1153  	Style string
  1154  	Size  string
  1155  	Max   int
  1156  }
  1157  
  1158  func (i *InfoPanel) FieldProgressBar(data ...FieldProgressBarData) *InfoPanel {
  1159  	i.addDisplayChains(displayFnGens["progressbar"].Get(data))
  1160  	return i
  1161  }
  1162  
  1163  func (i *InfoPanel) FieldLoading(data []string) *InfoPanel {
  1164  	i.addDisplayChains(displayFnGens["loading"].Get(data))
  1165  	return i
  1166  }
  1167  
  1168  func (i *InfoPanel) FieldDownLoadable(prefix ...string) *InfoPanel {
  1169  	i.addDisplayChains(displayFnGens["downloadable"].Get(prefix))
  1170  	return i
  1171  }
  1172  
  1173  func (i *InfoPanel) FieldCopyable(prefix ...string) *InfoPanel {
  1174  	i.addDisplayChains(displayFnGens["copyable"].Get(prefix))
  1175  	if _, ok := i.DisplayGeneratorRecords["copyable"]; !ok {
  1176  		i.addFooterHTML(`<script>` + displayFnGens["copyable"].JS() + `</script>`)
  1177  		i.DisplayGeneratorRecords["copyable"] = struct{}{}
  1178  	}
  1179  	return i
  1180  }
  1181  
  1182  type FieldGetImgArrFn func(value string) []string
  1183  
  1184  func (i *InfoPanel) FieldCarousel(fn FieldGetImgArrFn, size ...int) *InfoPanel {
  1185  	i.addDisplayChains(displayFnGens["carousel"].Get(fn, size))
  1186  	return i
  1187  }
  1188  
  1189  func (i *InfoPanel) FieldQrcode() *InfoPanel {
  1190  	i.addDisplayChains(displayFnGens["qrcode"].Get())
  1191  	if _, ok := i.DisplayGeneratorRecords["qrcode"]; !ok {
  1192  		i.addFooterHTML(`<script>` + displayFnGens["qrcode"].JS() + `</script>`)
  1193  		i.DisplayGeneratorRecords["qrcode"] = struct{}{}
  1194  	}
  1195  	return i
  1196  }
  1197  
  1198  func (i *InfoPanel) FieldWidth(width int) *InfoPanel {
  1199  	i.FieldList[i.curFieldListIndex].Width = width
  1200  	return i
  1201  }
  1202  
  1203  func (i *InfoPanel) FieldSortable() *InfoPanel {
  1204  	i.FieldList[i.curFieldListIndex].Sortable = true
  1205  	return i
  1206  }
  1207  
  1208  func (i *InfoPanel) FieldEditOptions(options FieldOptions, extra ...map[string]string) *InfoPanel {
  1209  	if i.FieldList[i.curFieldListIndex].EditType.IsSwitch() {
  1210  		if len(extra) == 0 {
  1211  			options[0].Extra = map[string]string{
  1212  				"size":     "small",
  1213  				"onColor":  "primary",
  1214  				"offColor": "default",
  1215  			}
  1216  		} else {
  1217  			if extra[0]["size"] == "" {
  1218  				extra[0]["size"] = "small"
  1219  			}
  1220  			if extra[0]["onColor"] == "" {
  1221  				extra[0]["onColor"] = "primary"
  1222  			}
  1223  			if extra[0]["offColor"] == "" {
  1224  				extra[0]["offColor"] = "default"
  1225  			}
  1226  			options[0].Extra = extra[0]
  1227  		}
  1228  	}
  1229  	i.FieldList[i.curFieldListIndex].EditOptions = options
  1230  	return i
  1231  }
  1232  
  1233  func (i *InfoPanel) FieldEditAble(editType ...table.Type) *InfoPanel {
  1234  	i.FieldList[i.curFieldListIndex].EditAble = true
  1235  	if len(editType) > 0 {
  1236  		i.FieldList[i.curFieldListIndex].EditType = editType[0]
  1237  	}
  1238  	return i
  1239  }
  1240  
  1241  func (i *InfoPanel) FieldAsEditParam() *InfoPanel {
  1242  	i.FieldList[i.curFieldListIndex].IsEditParam = true
  1243  	return i
  1244  }
  1245  
  1246  func (i *InfoPanel) FieldAsDeleteParam() *InfoPanel {
  1247  	i.FieldList[i.curFieldListIndex].IsDeleteParam = true
  1248  	return i
  1249  }
  1250  
  1251  func (i *InfoPanel) FieldAsDetailParam() *InfoPanel {
  1252  	i.FieldList[i.curFieldListIndex].IsDetailParam = true
  1253  	return i
  1254  }
  1255  
  1256  func (i *InfoPanel) FieldFixed() *InfoPanel {
  1257  	i.FieldList[i.curFieldListIndex].Fixed = true
  1258  	return i
  1259  }
  1260  
  1261  type FilterType struct {
  1262  	Options     FieldOptions
  1263  	Process     func(string) string
  1264  	OptionExt   map[string]interface{}
  1265  	FormType    form.Type
  1266  	HelpMsg     template.HTML
  1267  	Style       template.HTMLAttr
  1268  	Operator    FilterOperator
  1269  	Head        string
  1270  	Placeholder string
  1271  	Width       int
  1272  	HeadWidth   int
  1273  	InputWidth  int
  1274  	NoHead      bool
  1275  	NoIcon      bool
  1276  }
  1277  
  1278  func (i *InfoPanel) FieldFilterable(filterType ...FilterType) *InfoPanel {
  1279  	i.FieldList[i.curFieldListIndex].Filterable = true
  1280  
  1281  	if len(filterType) == 0 {
  1282  		i.FieldList[i.curFieldListIndex].FilterFormFields = append(i.FieldList[i.curFieldListIndex].FilterFormFields,
  1283  			FilterFormField{
  1284  				Type:        form.Text,
  1285  				Head:        i.FieldList[i.curFieldListIndex].Head,
  1286  				Placeholder: language.Get("input") + " " + i.FieldList[i.curFieldListIndex].Head,
  1287  			})
  1288  	}
  1289  
  1290  	for _, filter := range filterType {
  1291  		var ff FilterFormField
  1292  		ff.Operator = filter.Operator
  1293  		if filter.FormType == form.Default {
  1294  			ff.Type = form.Text
  1295  		} else {
  1296  			ff.Type = filter.FormType
  1297  		}
  1298  		ff.Head = modules.AorB(!filter.NoHead && filter.Head == "",
  1299  			i.FieldList[i.curFieldListIndex].Head, filter.Head)
  1300  		ff.Width = filter.Width
  1301  		ff.HeadWidth = filter.HeadWidth
  1302  		ff.InputWidth = filter.InputWidth
  1303  		ff.HelpMsg = filter.HelpMsg
  1304  		ff.NoIcon = filter.NoIcon
  1305  		ff.Style = filter.Style
  1306  		ff.ProcessFn = filter.Process
  1307  		ff.Placeholder = modules.AorB(filter.Placeholder == "", language.Get("input")+" "+ff.Head, filter.Placeholder)
  1308  		ff.Options = filter.Options
  1309  		if len(filter.OptionExt) > 0 {
  1310  			s, _ := json.Marshal(filter.OptionExt)
  1311  			ff.OptionExt = template.JS(s)
  1312  		}
  1313  		i.FieldList[i.curFieldListIndex].FilterFormFields = append(i.FieldList[i.curFieldListIndex].FilterFormFields, ff)
  1314  	}
  1315  
  1316  	return i
  1317  }
  1318  
  1319  func (i *InfoPanel) FieldFilterOptions(options FieldOptions) *InfoPanel {
  1320  	i.FieldList[i.curFieldListIndex].FilterFormFields[0].Options = options
  1321  	i.FieldList[i.curFieldListIndex].FilterFormFields[0].OptionExt = `{"allowClear": "true"}`
  1322  	return i
  1323  }
  1324  
  1325  func (i *InfoPanel) FieldFilterOptionsFromTable(table, textFieldName, valueFieldName string, process ...OptionTableQueryProcessFn) *InfoPanel {
  1326  	var fn OptionTableQueryProcessFn
  1327  	if len(process) > 0 {
  1328  		fn = process[0]
  1329  	}
  1330  	i.FieldList[i.curFieldListIndex].FilterFormFields[0].OptionTable = OptionTable{
  1331  		Table:          table,
  1332  		TextField:      textFieldName,
  1333  		ValueField:     valueFieldName,
  1334  		QueryProcessFn: fn,
  1335  	}
  1336  	return i
  1337  }
  1338  
  1339  func (i *InfoPanel) FieldFilterProcess(process func(string) string) *InfoPanel {
  1340  	i.FieldList[i.curFieldListIndex].FilterFormFields[0].ProcessFn = process
  1341  	return i
  1342  }
  1343  
  1344  func (i *InfoPanel) FieldFilterOptionExt(m map[string]interface{}) *InfoPanel {
  1345  	s, _ := json.Marshal(m)
  1346  	i.FieldList[i.curFieldListIndex].FilterFormFields[0].OptionExt = template.JS(s)
  1347  	return i
  1348  }
  1349  
  1350  func (i *InfoPanel) FieldFilterOnSearch(url string, handler Handler, delay ...int) *InfoPanel {
  1351  	ext, callback := searchJS(i.FieldList[i.curFieldListIndex].FilterFormFields[0].OptionExt, url, handler, delay...)
  1352  	i.FieldList[i.curFieldListIndex].FilterFormFields[0].OptionExt = ext
  1353  	i.Callbacks = append(i.Callbacks, callback)
  1354  	return i
  1355  }
  1356  
  1357  func (i *InfoPanel) FieldFilterOnChooseCustom(js template.HTML) *InfoPanel {
  1358  	i.FooterHtml += chooseCustomJS(i.FieldList[i.curFieldListIndex].Field, js)
  1359  	return i
  1360  }
  1361  
  1362  func (i *InfoPanel) FieldFilterOnChooseMap(m map[string]LinkField) *InfoPanel {
  1363  	i.FooterHtml += chooseMapJS(i.FieldList[i.curFieldListIndex].Field, m)
  1364  	return i
  1365  }
  1366  
  1367  func (i *InfoPanel) FieldFilterOnChoose(val, field string, value template.HTML) *InfoPanel {
  1368  	i.FooterHtml += chooseJS(i.FieldList[i.curFieldListIndex].Field, field, val, value)
  1369  	return i
  1370  }
  1371  
  1372  func (i *InfoPanel) OperationURL(id string) string {
  1373  	return config.Url("/operation/" + utils.WrapURL(id))
  1374  }
  1375  
  1376  func (i *InfoPanel) FieldFilterOnChooseAjax(field, url string, handler Handler) *InfoPanel {
  1377  	js, callback := chooseAjax(i.FieldList[i.curFieldListIndex].Field, field, i.OperationURL(url), handler)
  1378  	i.FooterHtml += js
  1379  	i.Callbacks = append(i.Callbacks, callback)
  1380  	return i
  1381  }
  1382  
  1383  func (i *InfoPanel) FieldFilterOnChooseHide(value string, field ...string) *InfoPanel {
  1384  	i.FooterHtml += chooseHideJS(i.FieldList[i.curFieldListIndex].Field, []string{value}, field...)
  1385  	return i
  1386  }
  1387  
  1388  func (i *InfoPanel) FieldFilterOnChooseShow(value string, field ...string) *InfoPanel {
  1389  	i.FooterHtml += chooseShowJS(i.FieldList[i.curFieldListIndex].Field, []string{value}, field...)
  1390  	return i
  1391  }
  1392  
  1393  func (i *InfoPanel) FieldFilterOnChooseDisable(value string, field ...string) *InfoPanel {
  1394  	i.FooterHtml += chooseDisableJS(i.FieldList[i.curFieldListIndex].Field, []string{value}, field...)
  1395  	return i
  1396  }
  1397  
  1398  func (i *InfoPanel) FieldHide() *InfoPanel {
  1399  	i.FieldList[i.curFieldListIndex].Hide = true
  1400  	return i
  1401  }
  1402  
  1403  func (i *InfoPanel) FieldHideForList() *InfoPanel {
  1404  	i.FieldList[i.curFieldListIndex].HideForList = true
  1405  	return i
  1406  }
  1407  
  1408  func (i *InfoPanel) FieldJoin(join Join) *InfoPanel {
  1409  	i.FieldList[i.curFieldListIndex].Joins = append(i.FieldList[i.curFieldListIndex].Joins, join)
  1410  	return i
  1411  }
  1412  
  1413  func (i *InfoPanel) FieldLimit(limit int) *InfoPanel {
  1414  	i.FieldList[i.curFieldListIndex].DisplayProcessChains = i.FieldList[i.curFieldListIndex].AddLimit(limit)
  1415  	return i
  1416  }
  1417  
  1418  func (i *InfoPanel) FieldTrimSpace() *InfoPanel {
  1419  	i.FieldList[i.curFieldListIndex].DisplayProcessChains = i.FieldList[i.curFieldListIndex].AddTrimSpace()
  1420  	return i
  1421  }
  1422  
  1423  func (i *InfoPanel) FieldSubstr(start int, end int) *InfoPanel {
  1424  	i.FieldList[i.curFieldListIndex].DisplayProcessChains = i.FieldList[i.curFieldListIndex].AddSubstr(start, end)
  1425  	return i
  1426  }
  1427  
  1428  func (i *InfoPanel) FieldToTitle() *InfoPanel {
  1429  	i.FieldList[i.curFieldListIndex].DisplayProcessChains = i.FieldList[i.curFieldListIndex].AddToTitle()
  1430  	return i
  1431  }
  1432  
  1433  func (i *InfoPanel) FieldToUpper() *InfoPanel {
  1434  	i.FieldList[i.curFieldListIndex].DisplayProcessChains = i.FieldList[i.curFieldListIndex].AddToUpper()
  1435  	return i
  1436  }
  1437  
  1438  func (i *InfoPanel) FieldToLower() *InfoPanel {
  1439  	i.FieldList[i.curFieldListIndex].DisplayProcessChains = i.FieldList[i.curFieldListIndex].AddToLower()
  1440  	return i
  1441  }
  1442  
  1443  func (i *InfoPanel) FieldXssFilter() *InfoPanel {
  1444  	i.FieldList[i.curFieldListIndex].DisplayProcessChains = i.FieldList[i.curFieldListIndex].DisplayProcessChains.
  1445  		Add(func(value FieldModel) interface{} {
  1446  			return html.EscapeString(value.Value)
  1447  		})
  1448  	return i
  1449  }
  1450  
  1451  // InfoPanel attribute setting functions
  1452  // ====================================================
  1453  
  1454  func (i *InfoPanel) SetTable(table string) *InfoPanel {
  1455  	i.Table = table
  1456  	return i
  1457  }
  1458  
  1459  func (i *InfoPanel) SetPageSizeList(pageSizeList []int) *InfoPanel {
  1460  	i.PageSizeList = pageSizeList
  1461  	return i
  1462  }
  1463  
  1464  func (i *InfoPanel) SetDefaultPageSize(defaultPageSize int) *InfoPanel {
  1465  	i.DefaultPageSize = defaultPageSize
  1466  	return i
  1467  }
  1468  
  1469  func (i *InfoPanel) GetPageSizeList() []string {
  1470  	var pageSizeList = make([]string, len(i.PageSizeList))
  1471  	for j := 0; j < len(i.PageSizeList); j++ {
  1472  		pageSizeList[j] = strconv.Itoa(i.PageSizeList[j])
  1473  	}
  1474  	return pageSizeList
  1475  }
  1476  
  1477  func (i *InfoPanel) GetSort() string {
  1478  	switch i.Sort {
  1479  	case SortAsc:
  1480  		return "asc"
  1481  	default:
  1482  		return "desc"
  1483  	}
  1484  }
  1485  
  1486  func (i *InfoPanel) SetTitle(title string) *InfoPanel {
  1487  	i.Title = title
  1488  	return i
  1489  }
  1490  
  1491  func (i *InfoPanel) SetTabGroups(groups TabGroups) *InfoPanel {
  1492  	i.TabGroups = groups
  1493  	return i
  1494  }
  1495  
  1496  func (i *InfoPanel) SetTabHeaders(headers ...string) *InfoPanel {
  1497  	i.TabHeaders = headers
  1498  	return i
  1499  }
  1500  
  1501  func (i *InfoPanel) SetDescription(desc string) *InfoPanel {
  1502  	i.Description = desc
  1503  	return i
  1504  }
  1505  
  1506  func (i *InfoPanel) SetFilterFormLayout(layout form.Layout) *InfoPanel {
  1507  	i.FilterFormLayout = layout
  1508  	return i
  1509  }
  1510  
  1511  func (i *InfoPanel) SetFilterFormHeadWidth(w int) *InfoPanel {
  1512  	i.FilterFormHeadWidth = w
  1513  	return i
  1514  }
  1515  
  1516  func (i *InfoPanel) SetFilterFormInputWidth(w int) *InfoPanel {
  1517  	i.FilterFormInputWidth = w
  1518  	return i
  1519  }
  1520  
  1521  func (i *InfoPanel) SetSortField(field string) *InfoPanel {
  1522  	i.SortField = field
  1523  	return i
  1524  }
  1525  
  1526  func (i *InfoPanel) SetSortAsc() *InfoPanel {
  1527  	i.Sort = SortAsc
  1528  	return i
  1529  }
  1530  
  1531  func (i *InfoPanel) SetSortDesc() *InfoPanel {
  1532  	i.Sort = SortDesc
  1533  	return i
  1534  }
  1535  
  1536  func (i *InfoPanel) SetAction(action template.HTML) *InfoPanel {
  1537  	i.Action = action
  1538  	return i
  1539  }
  1540  
  1541  func (i *InfoPanel) SetHeaderHtml(header template.HTML) *InfoPanel {
  1542  	i.HeaderHtml += header
  1543  	return i
  1544  }
  1545  
  1546  func (i *InfoPanel) SetFooterHtml(footer template.HTML) *InfoPanel {
  1547  	i.FooterHtml += footer
  1548  	return i
  1549  }
  1550  
  1551  func (i *InfoPanel) HasError() bool {
  1552  	return i.PageError != nil
  1553  }
  1554  
  1555  func (i *InfoPanel) SetError(err errors.PageError, content ...template.HTML) *InfoPanel {
  1556  	i.PageError = err
  1557  	if len(content) > 0 {
  1558  		i.PageErrorHTML = content[0]
  1559  	}
  1560  	return i
  1561  }
  1562  
  1563  func (i *InfoPanel) SetNoCompress() *InfoPanel {
  1564  	i.NoCompress = true
  1565  	return i
  1566  }
  1567  
  1568  func (i *InfoPanel) SetHideSideBar() *InfoPanel {
  1569  	i.HideSideBar = true
  1570  	return i
  1571  }
  1572  
  1573  func (i *InfoPanel) SetAutoRefresh(interval uint) *InfoPanel {
  1574  	i.AutoRefresh = interval
  1575  	return i
  1576  }
  1577  
  1578  func (i *InfoPanel) Set404Error(content ...template.HTML) *InfoPanel {
  1579  	i.SetError(errors.PageError404, content...)
  1580  	return i
  1581  }
  1582  
  1583  func (i *InfoPanel) Set403Error(content ...template.HTML) *InfoPanel {
  1584  	i.SetError(errors.PageError403, content...)
  1585  	return i
  1586  }
  1587  
  1588  func (i *InfoPanel) Set400Error(content ...template.HTML) *InfoPanel {
  1589  	i.SetError(errors.PageError401, content...)
  1590  	return i
  1591  }
  1592  
  1593  func (i *InfoPanel) Set500Error(content ...template.HTML) *InfoPanel {
  1594  	i.SetError(errors.PageError500, content...)
  1595  	return i
  1596  }
  1597  
  1598  func (i *InfoPanel) HideNewButton() *InfoPanel {
  1599  	i.IsHideNewButton = true
  1600  	return i
  1601  }
  1602  
  1603  func (i *InfoPanel) HideExportButton() *InfoPanel {
  1604  	i.IsHideExportButton = true
  1605  	return i
  1606  }
  1607  
  1608  func (i *InfoPanel) HideFilterButton() *InfoPanel {
  1609  	i.IsHideFilterButton = true
  1610  	return i
  1611  }
  1612  
  1613  func (i *InfoPanel) HideRowSelector() *InfoPanel {
  1614  	i.IsHideRowSelector = true
  1615  	return i
  1616  }
  1617  
  1618  func (i *InfoPanel) HidePagination() *InfoPanel {
  1619  	i.IsHidePagination = true
  1620  	return i
  1621  }
  1622  
  1623  func (i *InfoPanel) HideFilterArea() *InfoPanel {
  1624  	i.IsHideFilterArea = true
  1625  	return i
  1626  }
  1627  
  1628  func (i *InfoPanel) HideQueryInfo() *InfoPanel {
  1629  	i.IsHideQueryInfo = true
  1630  	return i
  1631  }
  1632  
  1633  func (i *InfoPanel) HideEditButton() *InfoPanel {
  1634  	i.IsHideEditButton = true
  1635  	return i
  1636  }
  1637  
  1638  func (i *InfoPanel) HideDeleteButton() *InfoPanel {
  1639  	i.IsHideDeleteButton = true
  1640  	return i
  1641  }
  1642  
  1643  func (i *InfoPanel) HideDetailButton() *InfoPanel {
  1644  	i.IsHideDetailButton = true
  1645  	return i
  1646  }
  1647  
  1648  func (i *InfoPanel) HideCheckBoxColumn() *InfoPanel {
  1649  	return i.HideColumn(1)
  1650  }
  1651  
  1652  func (i *InfoPanel) HideColumn(n int) *InfoPanel {
  1653  	i.AddCSS(template.CSS(fmt.Sprintf(`
  1654  	.box-body table.table tbody tr td:nth-child(%v), .box-body table.table tbody tr th:nth-child(%v) {
  1655  		display: none;
  1656  	}`, n, n)))
  1657  	return i
  1658  }
  1659  
  1660  func (i *InfoPanel) addFooterHTML(footer template.HTML) *InfoPanel {
  1661  	i.FooterHtml += template.HTML(ParseTableDataTmpl(footer))
  1662  	return i
  1663  }
  1664  
  1665  func (i *InfoPanel) AddCSS(css template.CSS) *InfoPanel {
  1666  	return i.addFooterHTML(template.HTML("<style>" + css + "</style>"))
  1667  }
  1668  
  1669  func (i *InfoPanel) AddJS(js template.JS) *InfoPanel {
  1670  	return i.addFooterHTML(template.HTML("<script>" + js + "</script>"))
  1671  }
  1672  
  1673  func (i *InfoPanel) AddJSModule(js template.JS) *InfoPanel {
  1674  	return i.addFooterHTML(template.HTML("<script type='module'>" + js + "</script>"))
  1675  }
  1676  
  1677  func (i *InfoPanel) addCallback(node context.Node) *InfoPanel {
  1678  	i.Callbacks = i.Callbacks.AddCallback(node)
  1679  	return i
  1680  }
  1681  
  1682  func (i *InfoPanel) addButton(btn Button) *InfoPanel {
  1683  	i.Buttons = append(i.Buttons, btn)
  1684  	return i
  1685  }
  1686  
  1687  func (i *InfoPanel) addActionButton(btn Button) *InfoPanel {
  1688  	i.ActionButtons = append(i.ActionButtons, btn)
  1689  	return i
  1690  }
  1691  
  1692  func (i *InfoPanel) isFromJSON() bool {
  1693  	return i.GetDataFn != nil
  1694  }
  1695  
  1696  func (i *InfoPanel) addDisplayChains(fn FieldFilterFn) *InfoPanel {
  1697  	i.FieldList[i.curFieldListIndex].DisplayProcessChains =
  1698  		i.FieldList[i.curFieldListIndex].DisplayProcessChains.Add(fn)
  1699  	return i
  1700  }