Speed up template compilation by not building the same parse trees multiple times.
Compile panel_debug. Add the ability to print int8, int16, int32, uint, uint8, uint16, uint32 and uint64 in compiled templates. Add the ability to pass string nodes to subtemplates in the template compiler. Fix bunit in the template compiler. Shorten some things.
This commit is contained in:
parent
f76af39a11
commit
df6e268a06
|
@ -6,14 +6,16 @@ import (
|
|||
"io"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Azareal/Gosora/common/alerts"
|
||||
"github.com/Azareal/Gosora/common/phrases"
|
||||
p "github.com/Azareal/Gosora/common/phrases"
|
||||
"github.com/Azareal/Gosora/common/templates"
|
||||
"github.com/Azareal/Gosora/query_gen"
|
||||
)
|
||||
|
||||
var Ctemplates []string // TODO: Use this to filter out top level templates we don't need
|
||||
|
@ -184,8 +186,7 @@ func CompileTemplates() error {
|
|||
|
||||
log.Print("Compiling the default templates")
|
||||
var wg sync.WaitGroup
|
||||
err := compileTemplates(&wg, c, "")
|
||||
if err != nil {
|
||||
if err := compileTemplates(&wg, c, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
oroots := c.GetOverridenRoots()
|
||||
|
@ -198,7 +199,7 @@ func CompileTemplates() error {
|
|||
c.SetPerThemeTmpls(tmpls)
|
||||
log.Print("theme: ", theme)
|
||||
log.Printf("perThemeTmpls: %+v\n", tmpls)
|
||||
err = compileTemplates(&wg, c, theme)
|
||||
err := compileTemplates(&wg, c, theme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -207,30 +208,30 @@ func CompileTemplates() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func compileCommons(c *tmpl.CTemplateSet, header *Header, header2 *Header, forumList []Forum, out TItemHold) error {
|
||||
func compileCommons(c *tmpl.CTemplateSet, head *Header, head2 *Header, forumList []Forum, o TItemHold) error {
|
||||
// TODO: Add support for interface{}s
|
||||
_, user2, user3 := tmplInitUsers()
|
||||
now := time.Now()
|
||||
|
||||
// Convienience function to save a line here and there
|
||||
htitle := func(name string) *Header {
|
||||
header.Title = name
|
||||
return header
|
||||
head.Title = name
|
||||
return head
|
||||
}
|
||||
/*htitle2 := func(name string) *Header {
|
||||
header2.Title = name
|
||||
return header2
|
||||
head2.Title = name
|
||||
return head2
|
||||
}*/
|
||||
|
||||
var topicsList []*TopicsRow
|
||||
topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "127.0.0.1", 1, 0, 1, 1, 0, "classname", 0, "", &user2, "", 0, &user3, "General", "/forum/general.2", nil})
|
||||
topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
|
||||
out.Add("topics", "c.TopicListPage", topicListPage)
|
||||
o.Add("topics", "c.TopicListPage", topicListPage)
|
||||
|
||||
forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
|
||||
forumPage := ForumPage{htitle("General Forum"), topicsList, forumItem, Paginator{[]int{1}, 1, 1}}
|
||||
out.Add("forum", "c.ForumPage", forumPage)
|
||||
out.Add("forums", "c.ForumsPage", ForumsPage{htitle("Forum List"), forumList})
|
||||
o.Add("forum", "c.ForumPage", forumPage)
|
||||
o.Add("forums", "c.ForumsPage", ForumsPage{htitle("Forum List"), forumList})
|
||||
|
||||
poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
|
||||
PollOption{0, "Nothing"},
|
||||
|
@ -247,8 +248,8 @@ func compileCommons(c *tmpl.CTemplateSet, header *Header, header2 *Header, forum
|
|||
replyList = append(replyList, ru)
|
||||
tpage := TopicPage{htitle("Topic Name"), replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, Paginator{[]int{1}, 1, 1}}
|
||||
tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
|
||||
out.Add("topic", "c.TopicPage", tpage)
|
||||
out.Add("topic_alt", "c.TopicPage", tpage)
|
||||
o.Add("topic", "c.TopicPage", tpage)
|
||||
o.Add("topic_alt", "c.TopicPage", tpage)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -362,6 +363,16 @@ func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string
|
|||
t.AddStd("panel", "c.Panel", Panel{basePage, "panel_dashboard_right", "", "panel_dashboard", inter})
|
||||
ges := []GridElement{GridElement{"","", "", 1, "grid_istat", "", "", ""}}
|
||||
t.AddStd("panel_dashboard", "c.DashGrids", DashGrids{ges,ges})
|
||||
|
||||
goVersion := runtime.Version()
|
||||
dbVersion := qgen.Builder.DbVersion()
|
||||
var memStats runtime.MemStats
|
||||
runtime.ReadMemStats(&memStats)
|
||||
debugCache := DebugPageCache{1, 1, 1, 2, 2, 2, true}
|
||||
debugDatabase := DebugPageDatabase{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
|
||||
debugDisk := DebugPageDisk{1,1,1,1,1,1}
|
||||
dpage := PanelDebugPage{basePage, goVersion, dbVersion, "0s", 1, qgen.Builder.GetAdapter().GetName(), 1, 1, memStats, debugCache, debugDatabase, debugDisk}
|
||||
t.AddStd("panel_debug", "c.PanelDebugPage", dpage)
|
||||
//t.AddStd("panel_analytics", "c.PanelAnalytics", Panel{basePage, "panel_dashboard_right","panel_dashboard", inter})
|
||||
|
||||
writeTemplate := func(name string, content interface{}) {
|
||||
|
@ -732,7 +743,7 @@ func initDefaultTmplFuncMap() {
|
|||
panic("phraseNameInt is not a string")
|
||||
}
|
||||
// TODO: Log non-existent phrases?
|
||||
return template.HTML(phrases.GetTmplPhrase(phraseName))
|
||||
return template.HTML(p.GetTmplPhrase(phraseName))
|
||||
}
|
||||
|
||||
// TODO: Implement this in the template generator too
|
||||
|
@ -743,7 +754,7 @@ func initDefaultTmplFuncMap() {
|
|||
}
|
||||
// TODO: Log non-existent phrases?
|
||||
// TODO: Optimise TmplPhrasef so we don't use slow Sprintf there
|
||||
return template.HTML(phrases.GetTmplPhrasef(phraseName, args...))
|
||||
return template.HTML(p.GetTmplPhrasef(phraseName, args...))
|
||||
}
|
||||
|
||||
fmap["level"] = func(levelInt interface{}) interface{} {
|
||||
|
@ -751,7 +762,7 @@ func initDefaultTmplFuncMap() {
|
|||
if !ok {
|
||||
panic("levelInt is not an integer")
|
||||
}
|
||||
return template.HTML(phrases.GetLevelPhrase(level))
|
||||
return template.HTML(p.GetLevelPhrase(level))
|
||||
}
|
||||
|
||||
fmap["bunit"] = func(byteInt interface{}) interface{} {
|
||||
|
|
|
@ -218,18 +218,15 @@ import "errors"
|
|||
|
||||
if !c.config.SkipInitBlock {
|
||||
stub += "// nolint\nfunc init() {\n"
|
||||
|
||||
if !c.config.SkipHandles && c.themeName == "" {
|
||||
stub += "\tc.Template_" + fname + "_handle = Template_" + fname + "\n"
|
||||
stub += "\tc.Ctemplates = append(c.Ctemplates,\"" + fname + "\")\n"
|
||||
}
|
||||
|
||||
if !c.config.SkipTmplPtrMap {
|
||||
stub += "tmpl := Template_" + fname + "\n"
|
||||
stub += "\tc.TmplPtrMap[\"" + fname + "\"] = &tmpl\n"
|
||||
stub += "\tc.TmplPtrMap[\"o_" + fname + "\"] = tmpl\n"
|
||||
}
|
||||
|
||||
stub += "}\n\n"
|
||||
}
|
||||
|
||||
|
@ -287,8 +284,7 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
|
|||
if r := recover(); r != nil {
|
||||
fmt.Println(r)
|
||||
debug.PrintStack()
|
||||
err := c.loggerf.Sync()
|
||||
if err != nil {
|
||||
if err := c.loggerf.Sync(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
log.Fatal("")
|
||||
|
@ -311,13 +307,14 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
|
|||
c.localDispStructIndex = 0
|
||||
c.stats = make(map[string]int)
|
||||
|
||||
tree := parse.New(name, c.funcMap)
|
||||
treeSet := make(map[string]*parse.Tree)
|
||||
tree, err = tree.Parse(content, "{{", "}}", treeSet, c.funcMap)
|
||||
//tree := parse.New(name, c.funcMap)
|
||||
//treeSet := make(map[string]*parse.Tree)
|
||||
treeSet, err := parse.Parse(name, content, "{{", "}}", c.funcMap)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c.detail(name)
|
||||
c.detailf("treeSet: %+v\n", treeSet)
|
||||
|
||||
fname := strings.TrimSuffix(name, filepath.Ext(name))
|
||||
if c.themeName != "" {
|
||||
|
@ -374,8 +371,21 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
|
|||
TemplateName: fname,
|
||||
OutBuf: &outBuf,
|
||||
}
|
||||
c.templateList = map[string]*parse.Tree{fname: tree}
|
||||
c.detail(c.templateList)
|
||||
|
||||
c.templateList = map[string]*parse.Tree{}
|
||||
for nname, tree := range treeSet {
|
||||
if name == nname {
|
||||
c.templateList[fname] = tree
|
||||
} else {
|
||||
if !strings.HasPrefix(nname, ".html") {
|
||||
c.templateList[nname] = tree
|
||||
} else {
|
||||
c.templateList[strings.TrimSuffix(nname, ".html")] = tree
|
||||
}
|
||||
}
|
||||
}
|
||||
c.detailf("c.templateList: %+v\n", c.templateList)
|
||||
|
||||
c.localVars = make(map[string]map[string]VarItemReflect)
|
||||
c.localVars[fname] = make(map[string]VarItemReflect)
|
||||
c.localVars[fname]["."] = VarItemReflect{".", con.VarHolder, con.HoldReflect}
|
||||
|
@ -392,8 +402,14 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe
|
|||
//}
|
||||
//c.detailf("c: %+v\n", c)
|
||||
|
||||
c.detailf("name: %+v\n", name)
|
||||
c.detailf("fname: %+v\n", fname)
|
||||
startIndex := con.StartTemplate("")
|
||||
c.rootIterate(c.templateList[fname], con)
|
||||
ttree := c.templateList[fname]
|
||||
if ttree == nil {
|
||||
panic("ttree is nil")
|
||||
}
|
||||
c.rootIterate(ttree, con)
|
||||
con.EndTemplate("")
|
||||
c.afterTemplate(con, startIndex)
|
||||
//c.templateFragmentCount[fname] = c.fragmentCursor[fname] + 1
|
||||
|
@ -574,6 +590,10 @@ w.Write([]byte(`, " + ", -1)
|
|||
|
||||
func (c *CTemplateSet) rootIterate(tree *parse.Tree, con CContext) {
|
||||
c.dumpCall("rootIterate", tree, con)
|
||||
if tree.Root == nil {
|
||||
c.detailf("tree: %+v\n", tree)
|
||||
panic("tree root node is empty")
|
||||
}
|
||||
c.detail(tree.Root)
|
||||
for _, node := range tree.Root.Nodes {
|
||||
c.detail("Node:", node.String())
|
||||
|
@ -1195,8 +1215,9 @@ ArgLoop:
|
|||
}
|
||||
leftParam, _ := c.compileIfVarSub(con, leftOperand)
|
||||
out = "{\nbyteFloat, unit := c.ConvertByteUnit(float64(" + leftParam + "))\n"
|
||||
out += "w.Write(fmt.Sprintf(\"%.1f\", byteFloat) + unit)\n"
|
||||
out += "w.Write(StringToBytes(fmt.Sprintf(\"%.1f\", byteFloat) + unit))\n}\n"
|
||||
literal = true
|
||||
c.importMap["fmt"] = "fmt"
|
||||
break ArgLoop
|
||||
case "abstime":
|
||||
// TODO: Implement level literals
|
||||
|
@ -1601,9 +1622,18 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
|
|||
if c.guestOnly && base == "StringToBytes("+con.RootHolder+".CurrentUser.Session))" {
|
||||
return
|
||||
}
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32:
|
||||
c.importMap["strconv"] = "strconv"
|
||||
base = "StringToBytes(strconv.FormatInt(int64(" + varname + "), 10))"
|
||||
case reflect.Int64:
|
||||
c.importMap["strconv"] = "strconv"
|
||||
base = "StringToBytes(strconv.FormatInt(" + varname + ", 10))"
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
c.importMap["strconv"] = "strconv"
|
||||
base = "StringToBytes(strconv.FormatUint(uint64(" + varname + "), 10))"
|
||||
case reflect.Uint64:
|
||||
c.importMap["strconv"] = "strconv"
|
||||
base = "StringToBytes(strconv.FormatUint(" + varname + ", 10))"
|
||||
case reflect.Struct:
|
||||
// TODO: Avoid clashing with other packages which have structs named Time
|
||||
if val.Type().Name() == "Time" {
|
||||
|
@ -1638,19 +1668,6 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
|
|||
defer c.retCall("compileSubTemplate")
|
||||
c.detail("Template Node: ", node.Name)
|
||||
|
||||
// TODO: Cascade errors back up the tree to the caller?
|
||||
content, err := c.loadTemplate(c.fileDir, node.Name)
|
||||
if err != nil {
|
||||
c.logger.Fatal(err)
|
||||
}
|
||||
|
||||
tree := parse.New(node.Name, c.funcMap)
|
||||
var treeSet = make(map[string]*parse.Tree)
|
||||
tree, err = tree.Parse(content, "{{", "}}", treeSet, c.funcMap)
|
||||
if err != nil {
|
||||
c.logger.Fatal(err)
|
||||
}
|
||||
|
||||
fname := strings.TrimSuffix(node.Name, filepath.Ext(node.Name))
|
||||
if c.themeName != "" {
|
||||
_, ok := c.perThemeTmpls[fname]
|
||||
|
@ -1666,6 +1683,36 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
|
|||
fname += "_member"
|
||||
}
|
||||
|
||||
_, ok := c.templateList[fname]
|
||||
if !ok {
|
||||
// TODO: Cascade errors back up the tree to the caller?
|
||||
content, err := c.loadTemplate(c.fileDir, node.Name)
|
||||
if err != nil {
|
||||
c.logger.Fatal(err)
|
||||
}
|
||||
|
||||
//tree := parse.New(node.Name, c.funcMap)
|
||||
//treeSet := make(map[string]*parse.Tree)
|
||||
treeSet, err := parse.Parse(node.Name, content, "{{", "}}", c.funcMap)
|
||||
if err != nil {
|
||||
c.logger.Fatal(err)
|
||||
}
|
||||
c.detailf("treeSet: %+v\n", treeSet)
|
||||
|
||||
for nname, tree := range treeSet {
|
||||
if node.Name == nname {
|
||||
c.templateList[fname] = tree
|
||||
} else {
|
||||
if !strings.HasPrefix(nname, ".html") {
|
||||
c.templateList[nname] = tree
|
||||
} else {
|
||||
c.templateList[strings.TrimSuffix(nname, ".html")] = tree
|
||||
}
|
||||
}
|
||||
}
|
||||
c.detailf("c.templateList: %+v\n", c.templateList)
|
||||
}
|
||||
|
||||
con := pcon
|
||||
con.VarHolder = "tmpl_" + fname + "_vars"
|
||||
con.TemplateName = fname
|
||||
|
@ -1675,7 +1722,6 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
|
|||
case *parse.FieldNode:
|
||||
// TODO: Incomplete but it should cover the basics
|
||||
cur := pcon.HoldReflect
|
||||
|
||||
var varBit string
|
||||
if cur.Kind() == reflect.Interface {
|
||||
cur = cur.Elem()
|
||||
|
@ -1707,6 +1753,11 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
|
|||
}
|
||||
con.VarHolder = pcon.VarHolder + varBit
|
||||
con.HoldReflect = cur
|
||||
case *parse.StringNode:
|
||||
//con.VarHolder = pcon.VarHolder
|
||||
//con.HoldReflect = pcon.HoldReflect
|
||||
con.VarHolder = p.Quoted
|
||||
con.HoldReflect = reflect.ValueOf(p.Quoted)
|
||||
case *parse.DotNode:
|
||||
con.VarHolder = pcon.VarHolder
|
||||
con.HoldReflect = pcon.HoldReflect
|
||||
|
@ -1730,7 +1781,7 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
|
|||
}
|
||||
}
|
||||
|
||||
c.templateList[fname] = tree
|
||||
//c.templateList[fname] = tree
|
||||
subtree := c.templateList[fname]
|
||||
c.detail("subtree.Root", subtree.Root)
|
||||
c.localVars[fname] = make(map[string]VarItemReflect)
|
||||
|
@ -1746,9 +1797,7 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
|
|||
c.rootIterate(subtree, con)
|
||||
con.EndTemplate(endBit)
|
||||
//c.templateFragmentCount[fname] = c.fragmentCursor[fname] + 1
|
||||
|
||||
_, ok := c.fragOnce[fname]
|
||||
if !ok {
|
||||
if _, ok := c.fragOnce[fname]; !ok {
|
||||
c.fragOnce[fname] = true
|
||||
}
|
||||
|
||||
|
@ -1785,11 +1834,11 @@ func (c *CTemplateSet) compileSubTemplate(pcon CContext, node *parse.TemplateNod
|
|||
|
||||
func (c *CTemplateSet) loadTemplate(fileDir string, name string) (content string, err error) {
|
||||
c.dumpCall("loadTemplate", fileDir, name)
|
||||
|
||||
c.detail("c.themeName: ", c.themeName)
|
||||
if c.themeName != "" {
|
||||
c.detail("per-theme override: ", "./themes/"+c.themeName+"/overrides/"+name)
|
||||
res, err := ioutil.ReadFile("./themes/" + c.themeName + "/overrides/" + name)
|
||||
t := "./themes/" + c.themeName + "/overrides/" + name
|
||||
c.detail("per-theme override: ", true)
|
||||
res, err := ioutil.ReadFile(t)
|
||||
if err == nil {
|
||||
content = string(res)
|
||||
if c.config.Minify {
|
||||
|
@ -1820,11 +1869,10 @@ func (c *CTemplateSet) afterTemplate(con CContext, startIndex int) {
|
|||
c.dumpCall("afterTemplate", con, startIndex)
|
||||
defer c.retCall("afterTemplate")
|
||||
|
||||
var loopDepth = 0
|
||||
loopDepth := 0
|
||||
var outBuf = *con.OutBuf
|
||||
var varcounts = make(map[string]int)
|
||||
var loopStart = startIndex
|
||||
|
||||
varcounts := make(map[string]int)
|
||||
loopStart := startIndex
|
||||
if outBuf[startIndex].Type == "startloop" && (len(outBuf) > startIndex+1) {
|
||||
loopStart++
|
||||
}
|
||||
|
@ -1852,7 +1900,7 @@ func (c *CTemplateSet) afterTemplate(con CContext, startIndex int) {
|
|||
|
||||
var varstr string
|
||||
var i int
|
||||
var varmap = make(map[string]int)
|
||||
varmap := make(map[string]int)
|
||||
for name, count := range varcounts {
|
||||
if count > 1 {
|
||||
varstr += "var cached_var_" + strconv.Itoa(i) + " = " + name + "\n"
|
||||
|
|
|
@ -18,10 +18,10 @@ func Debug(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
|
|||
|
||||
goVersion := runtime.Version()
|
||||
dbVersion := qgen.Builder.DbVersion()
|
||||
var uptime string
|
||||
upDuration := time.Since(c.StartTime)
|
||||
hours := int(upDuration.Hours())
|
||||
minutes := int(upDuration.Minutes())
|
||||
var uptime string
|
||||
if hours > 24 {
|
||||
days := hours / 24
|
||||
hours -= days * 24
|
||||
|
@ -63,7 +63,7 @@ func Debug(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
|
|||
debugCache := c.DebugPageCache{tlen, ulen, rlen, tcap, ucap, rcap, topicListThawed}
|
||||
|
||||
var fErr error
|
||||
var count = func(tbl string) int {
|
||||
count := func(tbl string) int {
|
||||
if fErr != nil {
|
||||
return 0
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ func Debug(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
|
|||
|
||||
debugDatabase := c.DebugPageDatabase{c.Topics.Count(),c.Users.Count(),c.Rstore.Count(),c.Prstore.Count(),c.Activity.Count(),c.Likes.Count(),attachs,polls,loginLogs,regLogs,modLogs,adminLogs,views,viewsAgents,viewsForums,viewsLangs,viewsReferrers,viewsSystems,postChunks,topicChunks}
|
||||
|
||||
var dirSize = func(path string) int {
|
||||
dirSize := func(path string) int {
|
||||
if fErr != nil {
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</div>
|
||||
<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_user_name"}}</a></div>
|
||||
<div class="formitem"><input form="user_form" name="user-name" type="text" value="{{.Something.Name}}" placeholder="{{lang "panel_user_name_placeholder"}}" /></div>
|
||||
<div class="formitem"><input form="user_form" name="user-name" type="text" value="{{.Something.Name}}" placeholder="{{lang "panel_user_name_placeholder"}}" autocomplete="off" /></div>
|
||||
</div>
|
||||
{{if .CurrentUser.Perms.EditUserPassword}}<div class="formrow">
|
||||
<div class="formitem formlabel"><a>{{lang "panel_user_password"}}</a></div>
|
||||
|
@ -34,7 +34,7 @@
|
|||
<div class="formitem formlabel"><a>{{lang "panel_user_group"}}</a></div>
|
||||
<div class="formitem">
|
||||
<select form="user_form" name="user-group">
|
||||
{{range .ItemList}}<option {{if eq .ID $.Something.Group}}selected {{end}}value="{{.ID}}">{{.Name}}</option>{{end}}
|
||||
{{range .ItemList}}<option{{if eq .ID $.Something.Group}} selected{{end}} value="{{.ID}}">{{.Name}}</option>{{end}}
|
||||
</select>
|
||||
</div>
|
||||
</div>{{end}}
|
||||
|
|
Loading…
Reference in New Issue