From ec2d9d016e473856adb485b90840bcf58bf72cc1 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 17 Mar 2023 12:38:07 +0200 Subject: [PATCH] update view benchmarks and remove ace template parser --- HISTORY.md | 10 +- README.md | 2 +- README_PT_BR.md | 2 +- README_ZH_HANT.md | 2 +- _benchmarks/view/README.md | 36 +- _benchmarks/view/ace/views/layouts/main.ace | 2 +- _benchmarks/view/amber/main.go | 29 -- _benchmarks/view/amber/views/index.amber | 5 - .../view/amber/views/layouts/main.amber | 8 - .../view/amber/views/partials/footer.amber | 2 - _benchmarks/view/chart.png | Bin 10183 -> 15917 bytes _benchmarks/view/tests.yml | 2 - _examples/README.md | 4 - _examples/README_ZH_HANT.md | 4 - .../templates/layouts/layout.html | 2 +- _examples/view/layout/amber/main.go | 28 -- _examples/view/layout/amber/views/index.amber | 5 - .../layout/amber/views/layouts/main.amber | 8 - .../layout/amber/views/partials/footer.amber | 2 - _examples/view/parse-template/amber/main.go | 32 -- _examples/view/template_ace_0/views/index.ace | 2 + _examples/view/template_amber_0/main.go | 23 -- .../view/template_amber_0/views/index.amber | 11 - .../template_amber_0/views/layouts/main.amber | 15 - .../view/template_amber_1_embedded/bindata.go | 358 ------------------ .../view/template_amber_1_embedded/main.go | 31 -- .../views/layouts/application.jet | 2 +- aliases.go | 5 +- go.mod | 1 - go.sum | 2 - view/README.md | 7 +- view/ace.go | 7 + view/amber.go | 281 -------------- 33 files changed, 45 insertions(+), 885 deletions(-) delete mode 100644 _benchmarks/view/amber/main.go delete mode 100644 _benchmarks/view/amber/views/index.amber delete mode 100644 _benchmarks/view/amber/views/layouts/main.amber delete mode 100644 _benchmarks/view/amber/views/partials/footer.amber delete mode 100644 _examples/view/layout/amber/main.go delete mode 100644 _examples/view/layout/amber/views/index.amber delete mode 100644 _examples/view/layout/amber/views/layouts/main.amber delete mode 100644 _examples/view/layout/amber/views/partials/footer.amber delete mode 100644 _examples/view/parse-template/amber/main.go delete mode 100644 _examples/view/template_amber_0/main.go delete mode 100644 _examples/view/template_amber_0/views/index.amber delete mode 100644 _examples/view/template_amber_0/views/layouts/main.amber delete mode 100644 _examples/view/template_amber_1_embedded/bindata.go delete mode 100644 _examples/view/template_amber_1_embedded/main.go delete mode 100644 view/amber.go diff --git a/HISTORY.md b/HISTORY.md index f22a8e9c..11405bbd 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -19,6 +19,12 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get github.com/kataras/iris/v12@latest` and `go mod tidy -compat=1.20`. +# Next + +Change applies to `master` branch. + +- Remove [ace](https://github.com/eknkc/amber) template parser support, as it was discontinued by its author more than five years ago. + # Sa, 11 March 2023 | v12.2.0 This release introduces new features and some breaking changes. @@ -41,7 +47,7 @@ All new features have been tested in production and seem to work fine. Fixed all - Add `Context.Render` method for compatibility. - Support of embedded [locale files](https://github.com/kataras/iris/blob/master/_examples/i18n/template-embedded/main.go) using standard `embed.FS` with the new `LoadFS` function. -- Support of direct embedded view engines (`HTML, Blocks, Django, Handlebars, Pug, Amber, Jet` and `Ace`) with `embed.FS` or `fs.FS` (in addition to `string` and `http.FileSystem` types). +- Support of direct embedded view engines (`HTML, Blocks, Django, Handlebars, Pug, Jet` and `Ace`) with `embed.FS` or `fs.FS` (in addition to `string` and `http.FileSystem` types). - Add support for `embed.FS` and `fs.FS` on `app.HandleDir`. - Add `iris.Patches()` package-level function to customize Iris Request Context REST (and more to come) behavior. @@ -306,7 +312,7 @@ func main() { - Fix Response Recorder `Clone` concurrent access afterwards. - Add a `ParseTemplate` method on view engines to manually parse and add a template from a text as [requested](https://github.com/kataras/iris/issues/1617). [Examples](https://github.com/kataras/iris/tree/master/_examples/view/parse-template). -- Full `http.FileSystem` interface support for all **view** engines as [requested](https://github.com/kataras/iris/issues/1575). The first argument of the functions(`HTML`, `Blocks`, `Pug`, `Amber`, `Ace`, `Jet`, `Django`, `Handlebars`) can now be either a directory of `string` type (like before) or a value which completes the `http.FileSystem` interface. The `.Binary` method of all view engines was removed: pass the go-bindata's latest version `AssetFile()` exported function as the first argument instead of string. +- Full `http.FileSystem` interface support for all **view** engines as [requested](https://github.com/kataras/iris/issues/1575). The first argument of the functions(`HTML`, `Blocks`, `Pug`, `Ace`, `Jet`, `Django`, `Handlebars`) can now be either a directory of `string` type (like before) or a value which completes the `http.FileSystem` interface. The `.Binary` method of all view engines was removed: pass the go-bindata's latest version `AssetFile()` exported function as the first argument instead of string. - Add `Route.ExcludeSitemap() *Route` to exclude a route from sitemap as requested in [chat](https://chat.iris-go.com), also offline routes are excluded automatically now. diff --git a/README.md b/README.md index 4c4c96f4..dde82ada 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ Some of the features Iris offers: * Builtin support for ngrok to put your app on the internet, the fastest way * Unique Router with dynamic path as parameter with standard types like :uuid, :string, :int... and the ability to create your own * Compression -* View Engines (HTML, Django, Amber, Handlebars, Pug/Jade and more) +* View Engines (HTML, Django, Handlebars, Pug/Jade and more) * Create your own File Server and host your own WebDAV server * Cache * Localization (i18n, sitemap) diff --git a/README_PT_BR.md b/README_PT_BR.md index de1d3b40..df94d769 100644 --- a/README_PT_BR.md +++ b/README_PT_BR.md @@ -184,7 +184,7 @@ Alguns dos recursos que o Iris Web Framework oferece: * Suporte integrado para ngrok para colocar seu aplicativo na internet da maneira mais rápida * Router único com caminho dinâmico como parametro com tipos padrões como :uuid, :string, :int... e a habilidade de criar o seu próprio router * Compressão -* View Engines (HTML, Django, Amber, Handlebars, Pug/Jade e mais) +* View Engines (HTML, Django, Handlebars, Pug/Jade e mais) * Cria seu próprio Servidor de Arquivo e hospeda seu próprio servidor WebDAV * Cache * Localização (i18n, sitemap) diff --git a/README_ZH_HANT.md b/README_ZH_HANT.md index 8fcbe8b6..c1e5eea7 100644 --- a/README_ZH_HANT.md +++ b/README_ZH_HANT.md @@ -195,7 +195,7 @@ Iris 提供了至少這些功能: - 內建 ngrok 支援,讓您可以把 app 以最快速的方式推上網際網路 - 包含動態路徑、具唯一性的路由,支援如 :uuid、:string、:int 等等的標準類型,並且可以自己建立 - 壓縮功能 -- 檢視 (View) 算繪引擎 (HTML、Django、Amber、Handlebars、Pug/Jade 等等) +- 檢視 (View) 算繪引擎 (HTML、Django、Handlebars、Pug/Jade 等等) - 建立自己的檔案伺服器,並寄存您自己的 WebDAV 伺服器 - 快取 - 本地化 (i18n、sitemap) diff --git a/_benchmarks/view/README.md b/_benchmarks/view/README.md index 90fc7cdb..209e4c6a 100644 --- a/_benchmarks/view/README.md +++ b/_benchmarks/view/README.md @@ -1,26 +1,27 @@ # View Engine Benchmarks -Benchmark between all 8 supported template parsers. +Benchmark between all 7 supported template parsers. -Amber, Ace and Pug parsers minifies the template before render. So, to have a fair benchmark, we must make sure that the byte amount of the total response body is exactly the same across all. Therefore, all other template files are minified too. +Ace and Pug parsers minifies the template before render. So, to have a fair benchmark, we must make sure that the byte amount of the total response body is exactly the same across all. Therefore, all other template files are minified too. ![Benchmarks Chart Graph](chart.png) -> Last updated: Oct 1, 2020 at 12:46pm (UTC) +> Last updated: Mar 17, 2023 at 10:31am (UTC) ## System | | | |----|:---| -| Processor | Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz | -| RAM | 15.85 GB | -| OS | Microsoft Windows 10 Pro | +| Processor | 12th Gen Intel(R) Core(TM) i7-12700H | +| RAM | 15.68 GB | +| OS | Microsoft Windows 11 Pro | | [Bombardier](https://github.com/codesenberg/bombardier) | v1.2.4 | -| [Go](https://golang.org) | go1.15.2 | +| [Go](https://golang.org) | go1.20.2 | +| [Node.js](https://nodejs.org/) | v19.5.0 | ## Terminology -**Name** is the name of the template engine used under a particular test. +**Name** is the name of the framework(or router) used under a particular test. **Reqs/sec** is the avg number of total requests could be processed per second (the higher the better). @@ -38,19 +39,18 @@ Amber, Ace and Pug parsers minifies the template before render. So, to have a fa | Name | Language | Reqs/sec | Latency | Throughput | Time To Complete | |------|:---------|:---------|:--------|:-----------|:-----------------| -| [Amber](./amber) | Go |125698 |0.99ms |44.67MB |7.96s | -| [Blocks](./blocks) | Go |123974 |1.01ms |43.99MB |8.07s | -| [Django](./django) | Go |118831 |1.05ms |42.17MB |8.41s | -| [Handlebars](./handlebars) | Go |101214 |1.23ms |35.91MB |9.88s | -| [Pug](./pug) | Go |89002 |1.40ms |31.81MB |11.24s | -| [Ace](./ace) | Go |64782 |1.93ms |22.98MB |15.44s | -| [HTML](./html) | Go |53918 |2.32ms |19.13MB |18.55s | -| [Jet](./jet) | Go |4829 |25.88ms |1.71MB |207.07s | +| [Jet](./jet) | Go |248957 |500.81us |88.26MB |4.02s | +| [Blocks](./blocks) | Go |238854 |521.76us |84.74MB |4.19s | +| [Pug](./pug) | Go |238153 |523.74us |85.07MB |4.20s | +| [Django](./django) | Go |224448 |555.40us |79.61MB |4.46s | +| [Handlebars](./handlebars) | Go |197267 |631.99us |69.96MB |5.07s | +| [Ace](./ace) | Go |157415 |792.53us |55.83MB |6.35s | +| [HTML](./html) | Go |120811 |1.03ms |42.82MB |8.29s | ## How to Run ```sh -$ go install github.com/kataras/server-benchmarks@latest -$ go install github.com/codesenberg/bombardier@latest +$ go install github.com/kataras/server-benchmarks@master +$ go install github.com/codesenberg/bombardier@master $ server-benchmarks --wait-run=3s -o ./results ``` diff --git a/_benchmarks/view/ace/views/layouts/main.ace b/_benchmarks/view/ace/views/layouts/main.ace index b1e74baa..9453acad 100644 --- a/_benchmarks/view/ace/views/layouts/main.ace +++ b/_benchmarks/view/ace/views/layouts/main.ace @@ -3,6 +3,6 @@ html head title {{.Title}} body - {{ yield . . }} + {{ yield . }} footer = include partials/footer.ace . diff --git a/_benchmarks/view/amber/main.go b/_benchmarks/view/amber/main.go deleted file mode 100644 index 4b63f684..00000000 --- a/_benchmarks/view/amber/main.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import "github.com/kataras/iris/v12" - -func main() { - app := iris.New() - // By default Amber engine minifies the template before render. - app.RegisterView(iris.Amber("./views", ".amber")) - - app.Get("/", index) - - app.Listen(":8080") -} - -func index(ctx iris.Context) { - data := iris.Map{ - "Title": "Page Title", - "FooterText": "Footer contents", - "Message": "Main contents", - } - - // On Amber this is ignored: ctx.ViewLayout("layouts/main") - // Layouts are only rendered from inside the index page itself - // using the "extends" keyword. - if err := ctx.View("index", data); err != nil { - ctx.HTML("

%s

", err.Error()) - return - } -} diff --git a/_benchmarks/view/amber/views/index.amber b/_benchmarks/view/amber/views/index.amber deleted file mode 100644 index e92604dc..00000000 --- a/_benchmarks/view/amber/views/index.amber +++ /dev/null @@ -1,5 +0,0 @@ -extends layouts/main.amber - -block content - h1 Index Body - h3 Message: #{Message} \ No newline at end of file diff --git a/_benchmarks/view/amber/views/layouts/main.amber b/_benchmarks/view/amber/views/layouts/main.amber deleted file mode 100644 index b0b751a8..00000000 --- a/_benchmarks/view/amber/views/layouts/main.amber +++ /dev/null @@ -1,8 +0,0 @@ -doctype html -html - head - title #{Title} - body - block content - footer - #{render("partials/footer.amber", $)} \ No newline at end of file diff --git a/_benchmarks/view/amber/views/partials/footer.amber b/_benchmarks/view/amber/views/partials/footer.amber deleted file mode 100644 index a202d2c0..00000000 --- a/_benchmarks/view/amber/views/partials/footer.amber +++ /dev/null @@ -1,2 +0,0 @@ -h3 Footer Partial -h4 #{FooterText} \ No newline at end of file diff --git a/_benchmarks/view/chart.png b/_benchmarks/view/chart.png index 0403fea8dadb1e7190ab5678c1bcfc4d1429f653..6ad909c6025bc04b2c969fdbfcb9f7d63826dc7a 100644 GIT binary patch literal 15917 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 QdMu}o8z0L*ivHvO0h0+sRsaA1 diff --git a/_benchmarks/view/tests.yml b/_benchmarks/view/tests.yml index 00ee224c..a50a03fc 100644 --- a/_benchmarks/view/tests.yml +++ b/_benchmarks/view/tests.yml @@ -11,8 +11,6 @@ Envs: - Name: Ace Dir: ./ace - - Name: Amber - Dir: ./amber - Name: Blocks Dir: ./blocks - Name: Django diff --git a/_examples/README.md b/_examples/README.md index 7291211e..1b316c66 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -138,7 +138,6 @@ * [Overview](view/overview/main.go) * [Layout](view/layout) * [Ace](view/layout/ace) - * [Amber](view/layout/amber) * [Blocks](view/layout/blocks) * [Django](view/layout/django) * [Handlebars](view/layout/handlebars) @@ -160,7 +159,6 @@ * Parse a Template from Text * [HTML, Pug and Ace](view/parse-parse/main.go) * [Django](view/parse-parse/django/main.go) - * [Amber](view/parse-parse/amber/main.go) * [Jet](view/parse-parse/jet/main.go) * [Handlebars](view/parse-parse/handlebars/main.go) * [Blocks](view/template_blocks_0) @@ -170,8 +168,6 @@ * [Pug Embedded`](view/template_pug_2_embedded) * [Ace](view/template_ace_0) * [Django](view/template_django_0) - * [Amber](view/template_amber_0) - * [Amber Embedded](view/template_amber_1_embedded) * [Jet](view/template_jet_0) * [Jet Embedded](view/template_jet_1_embedded) * [Jet 'urlpath' tmpl func](view/template_jet_2) diff --git a/_examples/README_ZH_HANT.md b/_examples/README_ZH_HANT.md index 5d8abbaa..13c852b1 100644 --- a/_examples/README_ZH_HANT.md +++ b/_examples/README_ZH_HANT.md @@ -136,7 +136,6 @@ - [概覽](view/overview/main.go) - [排版引擎](view/layout) - [Ace](view/layout/ace) - - [Amber](view/layout/amber) - [Blocks](view/layout/blocks) - [Django](view/layout/django) - [Handlebars](view/layout/handlebars) @@ -158,7 +157,6 @@ - 從文字解析樣板 - [HTML, Pug and Ace](view/parse-parse/main.go) - [Django](view/parse-parse/django/main.go) - - [Amber](view/parse-parse/amber/main.go) - [Jet](view/parse-parse/jet/main.go) - [Handlebars](view/parse-parse/handlebars/main.go) - [Blocks](view/template_blocks_0) @@ -168,8 +166,6 @@ - [Pug Embedded`](view/template_pug_2_embedded) - [Ace](view/template_ace_0) - [Django](view/template_django_0) - - [Amber](view/template_amber_0) - - [Amber Embedded](view/template_amber_1_embedded) - [Jet](view/template_jet_0) - [Jet Embedded](view/template_jet_1_embedded) - [Jet 'urlpath' tmpl func](view/template_jet_2) diff --git a/_examples/view/context-view-data/templates/layouts/layout.html b/_examples/view/context-view-data/templates/layouts/layout.html index 4e0179c7..ef74f39c 100644 --- a/_examples/view/context-view-data/templates/layouts/layout.html +++ b/_examples/view/context-view-data/templates/layouts/layout.html @@ -5,6 +5,6 @@ - {{ yield . . }} + {{ yield . }} diff --git a/_examples/view/layout/amber/main.go b/_examples/view/layout/amber/main.go deleted file mode 100644 index 92195ee3..00000000 --- a/_examples/view/layout/amber/main.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import "github.com/kataras/iris/v12" - -func main() { - app := iris.New() - app.RegisterView(iris.Amber("./views", ".amber")) - - app.Get("/", index) - - app.Listen(":8080") -} - -func index(ctx iris.Context) { - data := iris.Map{ - "Title": "Page Title", - "FooterText": "Footer contents", - "Message": "Main contents", - } - - // On Amber this is ignored: ctx.ViewLayout("layouts/main") - // Layouts are only rendered from inside the index page itself - // using the "extends" keyword. - if err := ctx.View("index", data); err != nil { - ctx.HTML("

%s

", err.Error()) - return - } -} diff --git a/_examples/view/layout/amber/views/index.amber b/_examples/view/layout/amber/views/index.amber deleted file mode 100644 index e92604dc..00000000 --- a/_examples/view/layout/amber/views/index.amber +++ /dev/null @@ -1,5 +0,0 @@ -extends layouts/main.amber - -block content - h1 Index Body - h3 Message: #{Message} \ No newline at end of file diff --git a/_examples/view/layout/amber/views/layouts/main.amber b/_examples/view/layout/amber/views/layouts/main.amber deleted file mode 100644 index 2dbc577a..00000000 --- a/_examples/view/layout/amber/views/layouts/main.amber +++ /dev/null @@ -1,8 +0,0 @@ -doctype html -html - head - title #{Title} - body - block content - footer - #{render("partials/footer.amber", $)} \ No newline at end of file diff --git a/_examples/view/layout/amber/views/partials/footer.amber b/_examples/view/layout/amber/views/partials/footer.amber deleted file mode 100644 index a202d2c0..00000000 --- a/_examples/view/layout/amber/views/partials/footer.amber +++ /dev/null @@ -1,2 +0,0 @@ -h3 Footer Partial -h4 #{FooterText} \ No newline at end of file diff --git a/_examples/view/parse-template/amber/main.go b/_examples/view/parse-template/amber/main.go deleted file mode 100644 index 3fa5829d..00000000 --- a/_examples/view/parse-template/amber/main.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import "github.com/kataras/iris/v12" - -func main() { - e := iris.Amber(nil, ".amber") // You can still use a file system though. - e.AddFunc("greet", func(name string) string { - return "Hello, " + name + "!" - }) - err := e.ParseTemplate("program.amber", []byte(`h1 #{ greet(Name) }`)) - if err != nil { - panic(err) - } - e.Reload(true) - - app := iris.New() - app.RegisterView(e) - app.Get("/", index) - - app.Listen(":8080") -} - -func index(ctx iris.Context) { - if err := ctx.View("program.amber", iris.Map{ - "Name": "Gerasimos", - // Or per template: - // "greet": func(....) - }); err != nil { - ctx.HTML("

%s

", err.Error()) - return - } -} diff --git a/_examples/view/template_ace_0/views/index.ace b/_examples/view/template_ace_0/views/index.ace index 776cc11f..144910f0 100644 --- a/_examples/view/template_ace_0/views/index.ace +++ b/_examples/view/template_ace_0/views/index.ace @@ -2,4 +2,6 @@ h2 {{.Title}} +h3 Body + = include partials/footer.ace . \ No newline at end of file diff --git a/_examples/view/template_amber_0/main.go b/_examples/view/template_amber_0/main.go deleted file mode 100644 index 4d6155c0..00000000 --- a/_examples/view/template_amber_0/main.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import "github.com/kataras/iris/v12" - -func main() { - app := iris.New() - - // Read about its markup syntax at: https://github.com/eknkc/amber - tmpl := iris.Amber("./views", ".amber") - - app.RegisterView(tmpl) - - app.Get("/", func(ctx iris.Context) { - if err := ctx.View("index.amber", iris.Map{ - "Title": "Title of The Page", - }); err != nil { - ctx.HTML("

%s

", err.Error()) - return - } - }) - - app.Listen(":8080") -} diff --git a/_examples/view/template_amber_0/views/index.amber b/_examples/view/template_amber_0/views/index.amber deleted file mode 100644 index ab89a92a..00000000 --- a/_examples/view/template_amber_0/views/index.amber +++ /dev/null @@ -1,11 +0,0 @@ -extends layouts/main.amber - -block append meta - meta[name="keywords"][content="These are added by the child template"] - -block menu - li Item 1 - li Item 2 - -block content - p Content from child template \ No newline at end of file diff --git a/_examples/view/template_amber_0/views/layouts/main.amber b/_examples/view/template_amber_0/views/layouts/main.amber deleted file mode 100644 index b2f9ad6c..00000000 --- a/_examples/view/template_amber_0/views/layouts/main.amber +++ /dev/null @@ -1,15 +0,0 @@ -!!! transitional -html - head - title #{Title} - - block meta - meta[name="description"][value="This is a sample"] - - body - header#mainHeader - ul - block menu - - div#content - block content \ No newline at end of file diff --git a/_examples/view/template_amber_1_embedded/bindata.go b/_examples/view/template_amber_1_embedded/bindata.go deleted file mode 100644 index d92c4889..00000000 --- a/_examples/view/template_amber_1_embedded/bindata.go +++ /dev/null @@ -1,358 +0,0 @@ -// Code generated by go-bindata. (@generated) DO NOT EDIT. - -// Package main generated by go-bindata.// sources: -// ../template_amber_0/views/index.amber -// ../template_amber_0/views/layouts/main.amber -package main - -import ( - "bytes" - "compress/gzip" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "strings" - "time" -) - -func bindataRead(data []byte, name string) ([]byte, error) { - gz, err := gzip.NewReader(bytes.NewBuffer(data)) - if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) - } - - var buf bytes.Buffer - _, err = io.Copy(&buf, gz) - clErr := gz.Close() - - if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) - } - if clErr != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -type asset struct { - bytes []byte - info os.FileInfo -} - -type bindataFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time -} - -// Name return file name -func (fi bindataFileInfo) Name() string { - return fi.name -} - -// Size return file size -func (fi bindataFileInfo) Size() int64 { - return fi.size -} - -// Mode return file mode -func (fi bindataFileInfo) Mode() os.FileMode { - return fi.mode -} - -// ModTime return file modify time -func (fi bindataFileInfo) ModTime() time.Time { - return fi.modTime -} - -// IsDir return file whether a directory -func (fi bindataFileInfo) IsDir() bool { - return fi.mode&os.ModeDir != 0 -} - -// Sys return file is sys mode -func (fi bindataFileInfo) Sys() interface{} { - return nil -} - -type assetFile struct { - *bytes.Reader - name string - childInfos []os.FileInfo - childInfoOffset int -} - -type assetOperator struct{} - -// Open implement http.FileSystem interface -func (f *assetOperator) Open(name string) (http.File, error) { - var err error - if len(name) > 0 && name[0] == '/' { - name = name[1:] - } - content, err := Asset(name) - if err == nil { - return &assetFile{name: name, Reader: bytes.NewReader(content)}, nil - } - children, err := AssetDir(name) - if err == nil { - childInfos := make([]os.FileInfo, 0, len(children)) - for _, child := range children { - childPath := filepath.Join(name, child) - info, errInfo := AssetInfo(filepath.Join(name, child)) - if errInfo == nil { - childInfos = append(childInfos, info) - } else { - childInfos = append(childInfos, newDirFileInfo(childPath)) - } - } - return &assetFile{name: name, childInfos: childInfos}, nil - } else { - // If the error is not found, return an error that will - // result in a 404 error. Otherwise the server returns - // a 500 error for files not found. - if strings.Contains(err.Error(), "not found") { - return nil, os.ErrNotExist - } - return nil, err - } -} - -// Close no need do anything -func (f *assetFile) Close() error { - return nil -} - -// Readdir read dir's children file info -func (f *assetFile) Readdir(count int) ([]os.FileInfo, error) { - if len(f.childInfos) == 0 { - return nil, os.ErrNotExist - } - if count <= 0 { - return f.childInfos, nil - } - if f.childInfoOffset+count > len(f.childInfos) { - count = len(f.childInfos) - f.childInfoOffset - } - offset := f.childInfoOffset - f.childInfoOffset += count - return f.childInfos[offset : offset+count], nil -} - -// Stat read file info from asset item -func (f *assetFile) Stat() (os.FileInfo, error) { - if len(f.childInfos) != 0 { - return newDirFileInfo(f.name), nil - } - return AssetInfo(f.name) -} - -// newDirFileInfo return default dir file info -func newDirFileInfo(name string) os.FileInfo { - return &bindataFileInfo{ - name: name, - size: 0, - mode: os.FileMode(2147484068), // equal os.FileMode(0644)|os.ModeDir - modTime: time.Time{}} -} - -// AssetFile return a http.FileSystem instance that data backend by asset -func AssetFile() http.FileSystem { - return &assetOperator{} -} - -var _indexAmber = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x8d\x31\x8a\xc3\x30\x14\x44\x7b\x83\xef\x30\xe8\x00\xbb\xec\xf6\xae\xb6\xda\x3e\x9d\x71\xf1\x6d\x4d\xb0\xb1\xbe\x24\xa4\x6f\x12\xdf\x3e\x10\x07\x4c\xd2\xcd\x63\x86\x79\xbc\x1b\xa3\xaf\x08\xb2\xa7\xcd\xea\xb7\xca\x12\xbf\x44\x47\x96\xb6\x69\x9b\x31\xa4\x69\x85\xe4\xcc\xe8\xa1\x34\x69\x1b\x00\xcf\xd4\x47\x51\x76\x6e\xe5\x7e\x4b\xc5\x57\x37\xf4\x53\x8a\xc6\x68\x9d\xbb\xcc\xac\x84\x14\x42\xbc\xa7\xc7\xb8\xc3\x66\x62\x9a\x97\xe0\x61\xd4\x1c\xc4\xe8\x86\x53\xa0\x8c\xdb\xf1\x1c\x16\xfc\x1b\x15\x3f\xef\xf8\x7b\x6e\x5f\x96\xa3\xcf\xf8\x3b\x10\xd7\x92\xf4\xc3\xf0\x08\x00\x00\xff\xff\xaa\xd2\x87\xd5\xdb\x00\x00\x00") - -func indexAmberBytes() ([]byte, error) { - return bindataRead( - _indexAmber, - "index.amber", - ) -} - -func indexAmber() (*asset, error) { - bytes, err := indexAmberBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "index.amber", size: 219, mode: os.FileMode(438), modTime: time.Unix(1599264617, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -var _layoutsMainAmber = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\x4e\xc1\xae\x83\x30\x0c\xbb\x23\xf1\x0f\xa1\x7c\x07\xf7\xf7\x01\xdc\x10\x87\x40\x23\x11\xbd\x34\x45\x34\x20\x4d\xd3\xfe\x7d\xea\xd8\x04\xf5\xa5\xae\xdd\xda\x6e\x9a\x06\x6c\x43\x4d\x6c\x1c\x15\xa5\xae\x16\x0b\x52\x57\x00\x00\x0b\xa1\x3f\x59\x86\xb1\x09\x41\xfb\xec\xf3\xf9\xaa\xab\xcb\x99\x24\xce\xff\x10\xc8\xf0\xd2\x32\xb2\x32\x28\x06\xea\x9c\xa7\x34\x6f\xbc\xe6\x0e\x37\x0e\x07\xca\x4e\x9d\xeb\x17\x4e\xc0\x09\x10\x12\x86\x55\xc8\x8d\xbf\xd4\x29\xfa\xc7\x95\x95\x77\xd0\xd6\x06\x64\xfd\xfb\xd0\xb2\x66\x97\xf2\x7e\x9f\xa4\xfb\x7d\xa8\xe7\xa3\x9d\xa3\x1a\xa9\x95\x5f\xce\xe7\x5f\xeb\x1d\x00\x00\xff\xff\x56\x99\x59\x53\x13\x01\x00\x00") - -func layoutsMainAmberBytes() ([]byte, error) { - return bindataRead( - _layoutsMainAmber, - "layouts/main.amber", - ) -} - -func layoutsMainAmber() (*asset, error) { - bytes, err := layoutsMainAmberBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "layouts/main.amber", size: 275, mode: os.FileMode(438), modTime: time.Unix(1599263735, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -// Asset loads and returns the asset for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func Asset(name string) ([]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) - } - return a.bytes, nil - } - return nil, fmt.Errorf("Asset %s not found", name) -} - -// MustAsset is like Asset but panics when Asset would return an error. -// It simplifies safe initialization of global variables. -func MustAsset(name string) []byte { - a, err := Asset(name) - if err != nil { - panic("asset: Asset(" + name + "): " + err.Error()) - } - - return a -} - -// AssetInfo loads and returns the asset info for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func AssetInfo(name string) (os.FileInfo, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) - } - return a.info, nil - } - return nil, fmt.Errorf("AssetInfo %s not found", name) -} - -// AssetNames returns the names of the assets. -func AssetNames() []string { - names := make([]string, 0, len(_bindata)) - for name := range _bindata { - names = append(names, name) - } - return names -} - -// _bindata is a table, holding each asset generator, mapped to its name. -var _bindata = map[string]func() (*asset, error){ - "index.amber": indexAmber, - "layouts/main.amber": layoutsMainAmber, -} - -// AssetDir returns the file names below a certain -// directory embedded in the file by go-bindata. -// For example if you run go-bindata on data/... and data contains the -// following hierarchy: -// -// data/ -// foo.txt -// img/ -// a.png -// b.png -// -// then AssetDir("data") would return []string{"foo.txt", "img"} -// AssetDir("data/img") would return []string{"a.png", "b.png"} -// AssetDir("foo.txt") and AssetDir("nonexistent") would return an error -// AssetDir("") will return []string{"data"}. -func AssetDir(name string) ([]string, error) { - node := _bintree - if len(name) != 0 { - canonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(canonicalName, "/") - for _, p := range pathList { - node = node.Children[p] - if node == nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - } - } - if node.Func != nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - rv := make([]string, 0, len(node.Children)) - for childName := range node.Children { - rv = append(rv, childName) - } - return rv, nil -} - -type bintree struct { - Func func() (*asset, error) - Children map[string]*bintree -} - -var _bintree = &bintree{nil, map[string]*bintree{ - "index.amber": {indexAmber, map[string]*bintree{}}, - "layouts": {nil, map[string]*bintree{ - "main.amber": {layoutsMainAmber, map[string]*bintree{}}, - }}, -}} - -// RestoreAsset restores an asset under the given directory -func RestoreAsset(dir, name string) error { - data, err := Asset(name) - if err != nil { - return err - } - info, err := AssetInfo(name) - if err != nil { - return err - } - err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) - if err != nil { - return err - } - err = os.WriteFile(_filePath(dir, name), data, info.Mode()) - if err != nil { - return err - } - err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) - if err != nil { - return err - } - return nil -} - -// RestoreAssets restores an asset under the given directory recursively -func RestoreAssets(dir, name string) error { - children, err := AssetDir(name) - // File - if err != nil { - return RestoreAsset(dir, name) - } - // Dir - for _, child := range children { - err = RestoreAssets(dir, filepath.Join(name, child)) - if err != nil { - return err - } - } - return nil -} - -func _filePath(dir, name string) string { - canonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) -} diff --git a/_examples/view/template_amber_1_embedded/main.go b/_examples/view/template_amber_1_embedded/main.go deleted file mode 100644 index 36b85315..00000000 --- a/_examples/view/template_amber_1_embedded/main.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import "github.com/kataras/iris/v12" - -// $ go install github.com/go-bindata/go-bindata/v3/go-bindata@latest -// -// $ go-bindata -fs -prefix "../template_amber_0/views" ../template_amber_0/views/... -// $ go run . -// # OR: go-bindata -fs -prefix "views" ./views/... if the views dir is rel to the executable. -// -// System files are not used, you can optionally delete the folder and run the example now. - -func main() { - app := iris.New() - - // Read about its markup syntax at: https://github.com/eknkc/amber - tmpl := iris.Amber(AssetFile(), ".amber") - - app.RegisterView(tmpl) - - app.Get("/", func(ctx iris.Context) { - if err := ctx.View("index.amber", iris.Map{ - "Title": "Title of The Page", - }); err != nil { - ctx.HTML("

%s

", err.Error()) - return - } - }) - - app.Listen(":8080") -} diff --git a/_examples/view/template_jet_0/views/layouts/application.jet b/_examples/view/template_jet_0/views/layouts/application.jet index 9fce2365..d8945a34 100644 --- a/_examples/view/template_jet_0/views/layouts/application.jet +++ b/_examples/view/template_jet_0/views/layouts/application.jet @@ -2,7 +2,7 @@ - {{ isset(title) ? title : "" }} + {{ isset(title) ? title : "Default" }} {{block documentBody()}}{{end}} diff --git a/aliases.go b/aliases.go index 47d04e5c..f83fad7d 100644 --- a/aliases.go +++ b/aliases.go @@ -46,7 +46,7 @@ type ( // Developers get request information from the client's request by a Context. Context = *context.Context // ViewEngine is an alias of `context.ViewEngine`. - // See HTML, Blocks, Django, Jet, Pug, Ace, Handlebars, Amber and e.t.c. + // See HTML, Blocks, Django, Jet, Pug, Ace, Handlebars and e.t.c. ViewEngine = context.ViewEngine // UnmarshalerFunc a shortcut, an alias for the `context#UnmarshalerFunc` type // which implements the `context#Unmarshaler` interface for reading request's body @@ -267,9 +267,6 @@ var ( // Pug view engine. // Shortcut of the view.Pug. Pug = view.Pug - // Amber view engine. - // Shortcut of the view.Amber. - Amber = view.Amber // Jet view engine. // Shortcut of the view.Jet. Jet = view.Jet diff --git a/go.mod b/go.mod index 9b11fb85..b279310a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/andybalholm/brotli v1.0.5 github.com/blang/semver/v4 v4.0.0 github.com/dgraph-io/badger/v2 v2.2007.4 - github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 github.com/fatih/structs v1.1.0 github.com/flosch/pongo2/v4 v4.0.2 github.com/golang/snappy v0.0.4 diff --git a/go.sum b/go.sum index aea4d4a7..a4e63bb6 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,6 @@ github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= diff --git a/view/README.md b/view/README.md index a5232966..84914048 100644 --- a/view/README.md +++ b/view/README.md @@ -1,6 +1,6 @@ # View -Iris supports 8 template engines out-of-the-box, developers can still use any external golang template engine, +Iris supports 7 template engines out-of-the-box, developers can still use any external golang template engine, as `Context.ResponseWriter()` is an `io.Writer`. All template engines share a common API i.e. @@ -13,9 +13,8 @@ Parse using embedded assets, Layouts and Party-specific layout, Template Funcs, | 3 | Django | [flosch/pongo2](https://github.com/flosch/pongo2) | | 4 | Pug | [Joker/jade](https://github.com/Joker/jade) | | 5 | Handlebars | [mailgun/raymond](https://github.com/mailgun/raymond) | -| 6 | Amber | [eknkc/amber](https://github.com/eknkc/amber) | -| 7 | Jet | [CloudyKit/jet](https://github.com/CloudyKit/jet) | -| 8 | Ace | [yosssi/ace](https://github.com/yosssi/ace) | +| 6 | Jet | [CloudyKit/jet](https://github.com/CloudyKit/jet) | +| 7 | Ace | [yosssi/ace](https://github.com/yosssi/ace) | [List of Examples](https://github.com/kataras/iris/tree/master/_examples/view). diff --git a/view/ace.go b/view/ace.go index 04fe746f..5da5e263 100644 --- a/view/ace.go +++ b/view/ace.go @@ -1,6 +1,7 @@ package view import ( + "strings" "sync" "github.com/yosssi/ace" @@ -62,6 +63,12 @@ func Ace(fs interface{}, extension string) *AceEngine { []*ace.File{}, ) + if strings.Contains(name, "layout") { + for k, v := range s.layoutFuncs { + funcs[k] = v + } + } + opts := &ace.Options{ Extension: extension[1:], FuncMap: funcs, diff --git a/view/amber.go b/view/amber.go deleted file mode 100644 index 72c6d90b..00000000 --- a/view/amber.go +++ /dev/null @@ -1,281 +0,0 @@ -package view - -import ( - "bytes" - "fmt" - "html/template" - "io" - "io/fs" - "net/http" - "os" - "path/filepath" - "strings" - "sync" - "sync/atomic" - - "github.com/kataras/iris/v12/context" - - "github.com/eknkc/amber" -) - -// AmberEngine contains the amber view engine structure. -type AmberEngine struct { - fs fs.FS - // files configuration - rootDir string - extension string - reload bool - // - rmu sync.RWMutex // locks for `ExecuteWiter` when `reload` is true. - templateCache map[string]*template.Template - bufPool *sync.Pool - - Options amber.Options -} - -var ( - _ Engine = (*AmberEngine)(nil) - _ EngineFuncer = (*AmberEngine)(nil) -) - -var amberOnce = new(uint32) - -// Amber creates and returns a new amber view engine. -// The given "extension" MUST begin with a dot. -// -// Usage: -// Amber("./views", ".amber") or -// Amber(iris.Dir("./views"), ".amber") or -// Amber(embed.FS, ".amber") or Amber(AssetFile(), ".amber") for embedded data. -func Amber(dirOrFS interface{}, extension string) *AmberEngine { - if atomic.LoadUint32(amberOnce) > 0 { - panic("Amber: cannot be registered twice as its internal implementation share the same template functions across instances.") - } else { - atomic.StoreUint32(amberOnce, 1) - } - - fileSystem := getFS(dirOrFS) - s := &AmberEngine{ - fs: fileSystem, - rootDir: "/", - extension: extension, - templateCache: make(map[string]*template.Template), - Options: amber.Options{ - PrettyPrint: false, - LineNumbers: false, - VirtualFilesystem: http.FS(fileSystem), - }, - bufPool: &sync.Pool{New: func() interface{} { - return new(bytes.Buffer) - }}, - } - - builtinFuncs := template.FuncMap{ - "render": func(name string, binding interface{}) (template.HTML, error) { - result, err := s.executeTemplateBuf(name, binding) - return template.HTML(result), err - }, - } - - for k, v := range builtinFuncs { - amber.FuncMap[k] = v - } - - return s -} - -// RootDir sets the directory to be used as a starting point -// to load templates from the provided file system. -func (s *AmberEngine) RootDir(root string) *AmberEngine { - if s.fs != nil && root != "" && root != "/" && root != "." && root != s.rootDir { - sub, err := fs.Sub(s.fs, s.rootDir) - if err != nil { - panic(err) - } - - s.fs = sub // here so the "middleware" can work. - } - - s.rootDir = filepath.ToSlash(root) - return s -} - -// Name returns the amber engine's name. -func (s *AmberEngine) Name() string { - return "Amber" -} - -// Ext returns the file extension which this view engine is responsible to render. -// If the filename extension on ExecuteWriter is empty then this is appended. -func (s *AmberEngine) Ext() string { - return s.extension -} - -// Reload if set to true the templates are reloading on each render, -// use it when you're in development and you're boring of restarting -// the whole app when you edit a template file. -// -// Note that if `true` is passed then only one `View -> ExecuteWriter` will be render each time, -// no concurrent access across clients, use it only on development status. -// It's good to be used side by side with the https://github.com/kataras/rizla reloader for go source files. -func (s *AmberEngine) Reload(developmentMode bool) *AmberEngine { - s.reload = developmentMode - return s -} - -// SetPrettyPrint if pretty printing is enabled. -// Pretty printing ensures that the output html is properly indented and in human readable form. -// Defaults to false, response is minified. -func (s *AmberEngine) SetPrettyPrint(pretty bool) *AmberEngine { - s.Options.PrettyPrint = true - return s -} - -// AddFunc adds the function to the template's function map. -// It is legal to overwrite elements of the default actions: -// - url func(routeName string, args ...string) string -// - urlpath func(routeName string, args ...string) string -// - render func(fullPartialName string) (template.HTML, error). -// -// Note that, Amber does not support functions per template, -// instead it's using the "call" directive so any template-specific -// functions should be passed using `Context.View/ViewData` binding data. -// This method will modify the global amber's FuncMap which considers -// as the "builtin" as this is the only way to actually add a function. -// Note that, if you use more than one amber engine, the functions are shared. -func (s *AmberEngine) AddFunc(funcName string, funcBody interface{}) { - s.rmu.Lock() - amber.FuncMap[funcName] = funcBody - s.rmu.Unlock() -} - -// Load parses the templates to the engine. -// It is responsible to add the necessary global functions. -// -// Returns an error if something bad happens, user is responsible to catch it. -func (s *AmberEngine) Load() error { - // If only custom templates should be loaded. - if (s.fs == nil || context.IsNoOpFS(s.fs)) && len(s.templateCache) > 0 { - return nil - } - - rootDirName := getRootDirName(s.fs) - - return walk(s.fs, "", func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if info == nil || info.IsDir() { - return nil - } - - if s.extension != "" { - if !strings.HasSuffix(path, s.extension) { - return nil - } - } - - if s.rootDir == rootDirName { - path = strings.TrimPrefix(path, rootDirName) - path = strings.TrimPrefix(path, "/") - } - - contents, err := asset(s.fs, path) - if err != nil { - return fmt.Errorf("%s: %w", path, err) - } - - err = s.ParseTemplate(path, contents) - if err != nil { - return fmt.Errorf("%s: %v", path, err) - } - return nil - }) -} - -// ParseTemplate adds a custom template from text. -// This template parser does not support funcs per template directly. -// Two ways to add a function: -// Globally: Use `AddFunc` or pass them on `View` instead. -// Per Template: Use `Context.ViewData/View`. -func (s *AmberEngine) ParseTemplate(name string, contents []byte) error { - s.rmu.Lock() - defer s.rmu.Unlock() - - comp := amber.New() - comp.Options = s.Options - - err := comp.ParseData(contents, name) - if err != nil { - return err - } - - data, err := comp.CompileString() - if err != nil { - return err - } - - name = strings.TrimPrefix(name, "/") - - /* - New(...).Funcs(s.builtinFuncs): - This won't work on amber, it loads only amber.FuncMap which is global. - Relative code: - https://github.com/eknkc/amber/blob/cdade1c073850f4ffc70a829e31235ea6892853b/compiler.go#L771-L794 - */ - - tmpl := template.New(name).Funcs(amber.FuncMap) - _, err = tmpl.Parse(data) - if err == nil { - s.templateCache[name] = tmpl - } - - return err -} - -func (s *AmberEngine) executeTemplateBuf(name string, binding interface{}) (string, error) { - buf := s.bufPool.Get().(*bytes.Buffer) - buf.Reset() - - tmpl := s.fromCache(name) - if tmpl == nil { - s.bufPool.Put(buf) - return "", ErrNotExist{Name: name, IsLayout: false, Data: binding} - } - - err := tmpl.ExecuteTemplate(buf, name, binding) - result := strings.TrimSuffix(buf.String(), "\n") // on amber it adds a new line. - s.bufPool.Put(buf) - return result, err -} - -func (s *AmberEngine) fromCache(relativeName string) *template.Template { - if s.reload { - s.rmu.RLock() - defer s.rmu.RUnlock() - } - - if tmpl, ok := s.templateCache[relativeName]; ok { - return tmpl - } - - return nil -} - -// ExecuteWriter executes a template and writes its result to the w writer. -// layout here is useless. -func (s *AmberEngine) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error { - // re-parse the templates if reload is enabled. - if s.reload { - if err := s.Load(); err != nil { - return err - } - } - - if tmpl := s.fromCache(filename); tmpl != nil { - return tmpl.Execute(w, bindingData) - } - - return ErrNotExist{Name: filename, IsLayout: false, Data: bindingData} -}