0.7.10 和 0.7.11 版本

  • 新增主题(点击 config 并选择主题)

  • 重构保存逻辑以避免使用 bind

0.7.9 版本

Example 1. 新增过滤器 filterFieldExpressionReverse
filterFieldExpressionReverse 过滤器
def parentParts() {
    UiFilterSpecifier f = new UiFilterSpecifier().sec PlmFreeCadPart, {         (1)
        PlmFreeCadLink link = new PlmFreeCadLink()
        filterFieldExpressionReverse(link.part_,                                (2)
                true,                                                           (3)
                new FilterExpression(null as Object, Operator.NE, link.part_))  (4)
    }
    taackUiService.show(new UiBlockSpecifier().ui {
        tableFilter(plmFreeCadUiService.buildPartFilter(), plmFreeCadUiService.buildPartTable(null, f), {
            menuIcon ActionIcon.GRAPH, this.&model as MC
        })
    }, buildMenu())
}
1 为 PlmFreeCadPart 构建过滤器
2 我们希望列出父装配件,即 PlmFreeCadLink part 字段未指向所列出的 PlmFreeCadPart 的项
3 true 代表 notIn
4 字面含义:我们想要获取所有非子项的 PlmFreeCadPart
生成的 HQL
select
    distinct sc  ,sc.dateCreated
from PlmFreeCadPart sc
where
    sc.id NOT IN (select auo.part.id from PlmFreeCadLink auo where auo.part IS NOT NULL)    (1)
    and sc.nextVersion IS NULL
order by sc.dateCreated DESC
1 由 filterFieldExpressionReverse 表达式添加的 where 子句

0.7.8 版本

  • 维护版本

0.7.7 版本

Example 2. 新增日历组件
表格视图
def calendarTable(TaackCalendarParams calendarParams) {
    UiTableSpecifier t = taackCalendarUiService.calendarUiTable(calendarEvents.collect {    (1)
        new TaackCalendarEventAdapter(it.key, it.value)
    }.iterator() as Iterator<TaackCalendarEvent>, calendarParams)
    taackUiService.show new UiBlockSpecifier().ui {
        table t
    }, RootController.buildMenu()
}
1 从 TaackCalendarEvent 迭代器创建表格。使用适配器模式(Adapter)将任意对象转换为 TaackCalendarEvent。
news calendar table view
常规视图
def calendarEntries(TaackCalendarParams calendarParams) {
    taackUiService.show taackCalendarUiService.calendarBlock(CalendarController.&fromCustom as MC, calendarEvents.collect {
        new TaackCalendarEventAdapter(it.key, it.value)
    }.iterator() as Iterator<TaackCalendarEvent>, calendarParams), RootController.buildMenu()}
news calendar regular view

0.7.6 版本

  • 改进自定义编辑器

  • Grails 7.2.0

0.7.5 版本

show grid layout

Example 3. Show 网格布局(Show Grid Layout)
通过 ShowLayout.GRID 将 Show 布局在响应式网格中
new UiShowSpecifier().ui ShowLayout.GRID, {                      (1)
    fieldLabeled item.ref_
    fieldLabeled item.name_
    fieldLabeled item.itemStatus_
    fieldLabeled item.gtin_
    fieldLabeled Style.SPAN_TWO, item.labeling_                  (2)
    fieldLabeled Style.BOLD + Style.SPAN_FULL, item.description_ (3)
}
1 传入 ShowLayout.GRID 以将字段排列在自适应网格中,而不是在单列中垂直排列。默认值为 ShowLayout.LIST。
2 默认情况下每个字段占据一个网格单元格。可以使用 Style.SPAN_TWO / Style.SPAN_THREE 让网格单元格跨越更多列。
3 Style.SPAN_FULL 使字段跨越整行。跨列样式可通过 + 与任何其他样式组合(此处与 Style.BOLD 组合)。

请注意,网格会根据容器宽度自动容纳尽可能多的列,每列至少宽 220px(taackShowGrid 类上的 grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)),最小宽度可在您的 CSS 中覆盖)。

Example 4. 新增替换模态框内容功能
替换上层模态框的任意内容
@Transactional
def doComplexStuff() {
    boolean isDone = false

    // doComplexStuff

    taackUiService.show(new UiBlockSpecifier().ui {
        replaceModal {
            custom("<h1> ${isDone ? 'Done.' : 'Not Done.'} </h1>")
        }
    })
}
Example 5. 新增保存或关闭模态框并刷新功能
代码
@Transactional
@Secured(['ROLE_ADMIN', 'ROLE_CONSOLATOR_ADMIN'])
def saveLlmRag() {
    taackSaveService.saveOrCloseModalAndRefresh(LlmRag)     (1)
}
1 如果堆叠了多个模态框,现在可以在关闭最上层模态框的同时刷新其下方的模态框。

0.7.4 版本

Example 6. 表格固定列(Table Sticky Columns)
通过 TableOption 冻结最左侧的列
TableOption tableOption = new TableOption.TableOptionBuilder()
        .stickyColumns(2).build() (1)

new UiTableSpecifier().ui tableOption, {
    header {
        sortableFieldHeader p.ref_
        sortableFieldHeader p.name_
        // ... any number of columns ...
    }
}
1 要冻结的最左侧列数。当表格水平滚动时,这些列保持可见。

请注意,使用固定列的表格不会受到宽度限制,而是使用 taack-sticky-table CSS 类将每列的最大宽度限制为 12rem。

0.7.2 版本

Example 7. 支持拖放的看板(Kan-ban)
无过滤器的看板规范
new UiKanbanSpecifier().ui {
    ReminderStatus.values().each { ReminderStatus status ->             (1)
        column(this.&changeStatus as MC, [newStatus: status.name()]) {  (2)
            header status.name()                                        (3)
            reminders.grep { Reminder t -> t.status == status }
                     .sort { -it.lastUpdated.getTime() }
                     .each { Reminder rIt ->
                card(rIt.selfObject_) {                                 (4)
                    cardField rIt.title_, null, Style.BOLD + Style.BLUE
                    cardFieldRaw this.getReminderDoersHtml(rIt),
                        new Style('', 'position: absolute; right: 3px; top: 12px;')
                    cardAction ActionIcon.SHOW * IconStyle.SCALE_DOWN,
                        ReminderController.&showReminder as MethodClosure, rIt.id
                    cardField rIt.priority_
                }
            }
        }
    }
}
1 遍历列
2 定义列
3 该列的表头名称
4 使用 cardField、cardFieldRaw 和 cardAction 构建卡片
screenshot kanban
Figure 1. 看板
Example 8. 在表格中编辑对象
声明 rowQuickEdit 块
rowQuickEdit(CrmController.&applyLeadLine as MC, ll.id) {           (1)
    rowColumn {
        rowFieldEdit ll.salePrice_                                  (2)
        rowFieldEdit ll.qty_                                        (2)
        rowField nf.format(tot), style                              (3)
    }
}
1 rowQuickEdit 声明行内编辑时要调用的 Action
2 rowFieldEdit 在输入框中编辑值,或者在不允许编辑时显示内容
3 其他字段会在编辑过程中重新计算
applyLeadLine 的定义
@Transactional
def applyLeadLine() {
    LeadLine ll = new LeadLine()
    taackSaveService.saveThenReloadOrRenderErrors
                    LeadLine, ll.qty_, ll.salePrice_            (1)
}
1 保存时,列出仅允许在 rowQuickEdit 块中更新的字段
screenshot inline edit
Figure 2. 带有本地化错误反馈的行内编辑
Example 9. 新增过滤器序列化功能
声明过滤器
UiFilterSpecifier createReminderFilter(FieldInfo... leftField) {
    Reminder r = new Reminder()
    User u = new User()
    new UiFilterSpecifier(leftField).ui Reminder, {                 (1)
        section Reminder, {                                         (2)
            filterField r.assignee_, u.username_
            filterField r.status_
            filterField r.priority_
            filterField r.title_
        }
    }
}
1 leftField 列出嵌入对象中指向 Reminder 的字段
2 新增带类参数的 section 以自动计算 i18n 标签
连接过滤器(Joining Filter)
UiFilterSpecifier createReminderLinkFilter(FieldInfo... leftField) {
    ReminderLink rl = new ReminderLink()
    new UiFilterSpecifier(leftField).ui(ReminderLink, {
        section tr('reminderLink.label'), {
            filterField rl.kind_
        }
    }).join(createReminderFilter(rl.reminder_))                     (1)
}
1 字段 reminder 是 Reminder 类型,因此可以复用 Reminder 类的过滤器。
screenshot filter join
Figure 3. 连接后的过滤器
Example 10. 新增 rowCol 布局选项
        taackUiService.show createModalForm(reminder, {
            section {
                field reminder.title_
                rowCols 3, {                                        (1)
                        field reminder.dueDate_
                        field reminder.status_
                        field reminder.priority_
                }
                ajaxField reminder.assignee_, CrewController.&selectUserM2O as MC
                field reminder.body_
            }
1 避免先写 row,再重复写 col、col、col …​
screenshot form rowcol
Figure 4. 表单中的 rowCol
Example 11. 新增 poke 布局刷新块指令
taackUiService.show(new UiBlockSpecifier().ui {
    table statsService.buildTable()                                 (1)

    row {
        poke p1, {                                                  (2)
            col BlockSpec.Width.HALF, {
                diagram(statsService.&buildChart1 as MethodClosure)
            }
        }
        poke p2, {                                                  (2)
            col BlockSpec.Width.HALF, {
                diagram(statsService::buildChart2 as MethodClosure)
            }
        }
    }
}
1 点击表格时,可以刷新其他块
2 poke 的 p1 和 p2 是布尔值。如果为 true,poke 块内的块将被刷新
Example 12. 手风琴折叠面板(Accordion)
accordion {
    accordionItem('Filter') {
        form CrmDashboardUiService.filterForm(filter)
    }
}
Example 13. scrollPanel
scrollPanel('512px') {
    UiTableSpecifier t
    boolean seeSalesmen = params.boolean('seeSalesmen')
    boolean seeTop = params.boolean('seeTop')
    if (seeTop) {
        t = crmDashboardUiService.topSalesmen(leads)
    } else if (seeSalesmen) {
        t = this.crmDashboardUiService.topCountries(leads)
    } else {
        t = crmDashboardUiService.topCustomer(leads)
    }
    table(t) {
        menu 'Salesmen', this.&group as MC, [seeTop: true, seeSalesmen: false]
        menu 'Customers', this.&group as MC, [seeTop: false, seeSalesmen: false]
        menu 'Countries', this.&group as MC, [seeTop: false, seeSalesmen: true]
    }
}
screenshot scrollpanel

0.7.1 版本

  • 修复启用热重载(hot-reload)时 bootRun 任务的 i18n 问题

0.7.1 版本新特性

0.7.0 版本

上下文菜单代码示例
TaackUiService.registerContextualMenuClosure(                           (1)
    new Lead().leadStatus_,
    new UiMenuSpecifier().ui { Long id ->                               (2)
        label 'Change Status'
        Lead.read(id)?.leadStatus?.transitionsTo()?.each {
            menu it.toString(),                                         (3)
                CrmController.&changeLeadStatus as MC,
                [id: id, leadStatus: it.toString()]
    }
})
1 为字段注册上下文菜单
2 使用常规的 MenuSpecifier 闭包(与普通菜单相同)
news contextual menu on field
Figure 5. 右键点击 Lead Status 的结果
创建标记未读条目的过滤器
TaackFilter.FilterBuilder tf = taackFilterService.getBuilder(Item)
        .setMaxNumberOfLine(10)
        .setSortOrder(TaackFilter.Order.DESC, i.dateCreated_)

tf.setLastReadingDate(lastAccessDateFromUser)   (1)
1 最新对象将以粗体显示,参见 对象 lastUpdated > lastAccessDateFromUser 的未读行
news notif
Figure 6. 对象 lastUpdated > lastAccessDateFromUser 的未读行
注册对象的显示方式
TaackGormClassRegisterService.register(
        new TaackGormClass(ItemRange.class).builder
                .setShowMethod(Bp2Controller.&showRange as MethodClosure)   (1)
                .build(),
        new TaackGormClass(Item.class).builder
                .setShowMethod(Bp2Controller.&showItem as MethodClosure)    (1)
                .setShowLabel({ Long id ->                                  (2)
                    def i = Item.read(id)
                    return "${i.name} ${i.ref ?: ''} ($id)"
                })
                .setTypeLabel({ Long id ->                                  (3)
                    return TaackUiService.tr('default.item.label')
                }).build()
)
1 如何显示对象
2 自定义标签
3 用于分面搜索(Faceting)的类型
图表构建器(Diagram Builder)
new UiDiagramSpecifier().ui {
    bar {
        labels 'xLabel1', 'xLabel2', 'xLabel3', 'xLabel4'
        dataset 'dataset1', 1.0, 2.0, 3.0, 4.0
        dataset 'dataset2', 6.0, 1.0, 2.0, 4.0

        option DiagramOption.builder
            .setClickAction(Rma2Controller.&editClaim as MethodClosure)
            // "diagramAction" was moved to here
            .showDataCount()
            // The numbers are hidden by default. Use this option if need to show
            .hideLegend()
            // The legends are shown by default. Use this option if need to hide
            .setResolution(DiagramOption.DiagramResolution.DEFAULT_4K)
            // Default resolution is [SVG: 960x480, PNG: 2160x1080].
            // Allow to set to 540p, 720p, 1080p, 2K, 1440p, 4K
            .setKeyColors(Color.RED, Color.BLUE)
            // Allow to self-define the colors for each dataset
            // (If not set, there are by default 7 colors)
            .setTitle("Test Chart") // Diagram title
            .build()
    }
}
Asciidoc 编辑器
//...
EditorOption.EditorOptionBuilder editor = EditorOption.getBuilder()
editor.addSpanRegexes(TaackBaseAsciidocSpans.spans)                 (1)
editor.addSpanRegexes(TaackAsciidocTable.spans)                     (1)
editor.addSpanRegexes(TaackAsciidocPlantUML.spans)                  (1)
editor.onDrop(this.&dropEditor as MC, cmsPage.id)                   (2)
fieldEditor cmsPage.bodyContent_, editor.build()                    (3)
innerFormAction this.&previewBody as MC                             (4)
//...

@Transactional
def dropEditor() {                                                  (2)
    CmsPage page = CmsPage.get(params.long('cmsPage'))
    params.remove('cmsPage')
    String past = params.get('onpaste')
    if (past) {                                                     (5)
        render taackAsciidocService.convertFromHtml(past)
    } else {                                                        (6)
        final List<MultipartFile> mfl = (request as MultipartHttpServletRequest).getFiles('filePath')
        final mf = mfl.first()

        if ([AttachmentContentType.SHEET_ODS.mimeType,
             AttachmentContentType.LO_TEXT.mimeType
            ].contains(mf.contentType)) {                           (7)
            render taackAsciidocService.convert(page, mf.inputStream)
        } else {                                                    (8)
            CmsImage cmsImage = taackSaveService.save(CmsImage)
            if (cmsImage && page) {
                cmsImage.cmsPage = page
            }
            render "image::${cmsImage.originalName}[]"
        }
    }
}
1 添加语法高亮扩展(基本上是用于代码着色的正则表达式和类)
2 拖放文件或从其他页面/应用程序粘贴内容时的回调
3 表单中的字段
4 可选的预览 Action
5 粘贴 HTML 内容,内容将被转换为 Asciidoc
6 文件拖放处理部分
7 如果文件是 ODS 或 Writer 文件,它将被转换为 Asciidoc 并导入图片
8 如果文件是图片,它将被保存到 CMS 中。
taackAsciidocService 使用了来自 taack-ui-asciidoc 的 TaackEditorService 服务,参见 表单 DSL
TaackUiConfiguration
@CompileStatic
class TaackUiConfiguration {
    static String defaultTitle = 'Taack'
    static String logoFileName = 'logo-taack-web.svg'
    static int logoWidth = 70
    static int logoHeight = 60

    static boolean fixedTop = false
    static boolean hasMenuLogin = true
    static boolean outlineContainer = false
    static String bgColor = '#05294c'
    static String fgColor = '#eeeeee'
    static String bodyBgColor = '#fff'

    static String home = System.getProperty('user.home')
    static String root = home + '/intranetFiles'
    static String taack = home + '/taack'
    static String resources = taack + '/resources'
    static String javaPath = '/usr/bin/java'
    static String plantUmlPath = home + '/plantuml-1.2022.7.jar'
    static String solrUrl = 'http://localhost:8983/solr/taack'
    static Boolean disableSecurity = false
}
Application.groovy
@CompileStatic
class Application extends GrailsAutoConfiguration {
    static void main(String[] args) {
        TaackUiConfiguration.disableSecurity = true
        TaackUiConfiguration.hasMenuLogin = false
        GrailsApp.run(Application, args)
    }
}
拖放到表格上(Drop on Table)
        TableOption tableOption = new TableOption.TableOptionBuilder()
                .onDropAction(this.&testDrop as MC).build()             (1)

        new UiTableSpecifier().ui tableOption, {
            header {
                sortableFieldHeader cb.position_
                sortableFieldHeader cb.subsidiary_
                label 'Page OR Menu Entry'
            }
1 当在表格任意位置拖放时调用的 Action。
拖放到单元格上(Drop on Cells),行中的列为可拖放区域
cellDropAction(CmsController.&testCellDrop as MC)                       (1)
rowColumn {
    rowField cp.title_
    rowField 'test drop'
}
1 当在单元格任意位置拖放时调用的 Action。

0.6.1 版本

图表 Action 用法
new UiDiagramSpecifier().ui {
    bar {
        labels 'date1', 'date2', 'date3'
        dataset 'Stuff1', 3.0, 4.0, 5.0

        diagramAction this.&clickDiagram as MC, id, (1)
        [optionalParam: 'value']                    (2)
    }
}

def clickDiagram() {
    println(params)
    // [id: 123456, dataset: "Stuff1",
    // optionalParam: "value"                       (3)
    // x: "date1", y: "3.0"]
}
1 图表 Action
2 可传递 Map 参数
3 Action 参数包含 label、value、dataset name 和 map
静态辅助方法用法
import org.codehaus.groovy.runtime.MethodClosure as MC

import static taack.ui.TaackUi.createMenu           (1)

@Override
UiMenuSpecifier editorCreate() {
    createMenu {                                    (2)
        menu this.&createFromTemplate as MC
        menuIcon
            CollaboraIcons.WRITER,
            this.&createFromTemplate as MC,
            [collaboraApp: CollaboraApp.WRITER]
    }
}
1 静态导入
2 静态调用 createMenu,为 new UiMenuSpecifier().ui 的简写;其他简写包括返回 UiBlockSpecifier 的 createModal

0.6 版本

  • 加载动画

  • 图表缩放 / 滚动

  • 图表悬浮提示(tooltips)

  • 表格多选功能(参见 doc/DSLs/filter-table-dsl.html)

  • 标签页延迟加载(lazy loading)

  • 改进分页功能

  • 优化绘制,避免不必要的重绘

  • 避免块被重复绘制两次的情况

  • 改进搜索布局

  • 允许在表格中使用 BigDecimal,并按用户的区域设置显示

  • 开发中(WiP):简单的 Asciidoc 所见即所得(WYSIWYG)编辑器

0.5.7 版本

  • 清理 show DSL 代码,弃用向 UiShowSpecifier().ui 传递对象参数的做法

  • 初始 Asciidoctor 所见即所得(WYSIWYG)编辑器

    • 支持拖放图片和文件

  • 更多图表 DSL

    • timeSeries

    • areaChart

    • bubbleChart

  • 安全性

    • 默认清理(sanitize)显示的信息(使用 fieldRaw 可跳过清理)

    • 检查所有入口点的访问权限

  • 修复错误并升级依赖项

请将 field <html code> 替换为 fieldRaw <html code>

0.5.6 版本(修复 0.5.5 版本中的错误)

  • 缩小 Solr 和 Tika 依赖项的作用域

  • 显示表格可排序列和排序方向(参见 列标题显示排序方向)

  • 块菜单现在会像块一样刷新

  • 对于复杂布局,可以轻松复用代码。现在我们可以轻松保留使布局具备上下文的变量(参见 点击表格时如何保留上下文)

screenshot news sorting
Figure 7. 列标题显示排序方向
点击表格时如何保留上下文
def showPart(PlmFreeCadPart part, Long partVersion, Boolean isHistory) {(1)
    taackUiService.show(
            plmFreeCadUiService.buildFreeCadPartBlockShow(
                    part, partVersion, false, isHistory),               (2)
            buildMenu(),
            "isHistory")                                                (3)
}
1 isHistory 是一个 Action 参数
2 绘制块时会用到 isHistory;我们需要重新传输它,以便通过保留上下文来绘制完全相同的块布局
3 isHistory 键作为 taackUiService.show 的最后一个参数传入。您可以传入多个需要保留的键。

0.5.4 版本

0.5.3 版本

  • 修复表单复选框

  • 允许在 TQL 中为公式列使用别名

  • 代码清理并升级依赖版本

0.5.2 版本

  • JDBC 客户端现在也是 AsciidoctorJ 扩展

  • 为可通过 JDBC 访问的域字段添加 getter

  • 添加 DSL TQL 和 TDL (Taack Display Language) 用于描述如何展示查询数据(表格或条形图)

  • 恢复菜单上的手动标签

  • 图表 DSL 的更多改进(感谢 Chong 和 ZhenQing)

  • 更好的自定义支持

TQL 和 TDL (Taack Display Language)
select
    u.rawImg,
    u.username,
    u.manager.username
from User u
where u.dateCreated > '2024-01-01' and u.manager.username = 'admin';
--
table rawImg as "Pic", username as "Name", manager as "Manager"
news table
Figure 8. 结果

0.5.1 版本

替换 TaackPlugin
@PostConstruct
void init() {
    TaackUiEnablerService.securityClosure(
        this.&securityClosure,
        CrewController.&editUser as MC,
        CrewController.&saveUser as MC)
    TaackAppRegisterService.register(
        new TaackApp(
            CrewController.&index as MC,                    (1)
            new String(
                this.class
                    .getResourceAsStream("/crew/crew.svg")  (2)
                    .readAllBytes()
            )
        )
    )
}
1 入口点
2 图标
将 Charts 替换为 Diagrams
private static UiDiagramSpecifier d1() {
    new UiDiagramSpecifier().ui {
        bar(["T1", "T2", "T3", "T4"] as List<String>, false, {
            dataset 'Truc1', [1.0, 2.0, 1.0, 4.0]
            dataset 'Truc2', [2.0, 0.1, 1.0, 0.0]
            dataset 'Truc3', [2.0, 0.1, 1.0, 1.0]
        }, DiagramTypeSpec.HeightWidthRadio.ONE)
    }
}
包含图表的 PDF
printableBody {
    diagram(d1(), BlockSpec.Width.HALF)
    diagram(d2(), BlockSpec.Width.HALF)
}
news diagram
Figure 9. 堆叠柱状图

0.5.0 版本

基于 Bootstrap

  • 支持主题定制

    • 适配桌面系统的配色方案

    • 可调节组件尺寸

  • 更易于扩展

加载速度提升

  • 更少的静态资源(Assets)

  • 精简了 CSS 与 JS 代码

主要代码变更

全新布局 DSL

  • forms 与 blocks 采用统一的布局方式

    • col 与 row

    • tabs 与 tab

With 仅适用于 col

面向区块的菜单 DSL

  • 使用菜单 DSL 替代原有操作

  • 无需强制使用图标

  • 菜单支持自动翻译

更精简的代码

带来:

  • 更轻松的维护体验

  • 更低的参与贡献门槛

0.4.2 版本

即将发布…​ 此版本将带来一些不错的改进(有时会破坏部分旧代码)

  • 改进 DSL 层次结构

    • 隐藏字段仅置于顶部以提高可读性

    • 表单中取消冗余参数传递

    • 过滤器中取消冗余参数传递

    • filterField 仅可在 section 下使用

    • 表单顶层字段仅在 header 上

  • 为表单字段提供优雅显示 M2M 的钩子(hook)

  • 用于注册典型对象过滤器的钩子(hook)

  • 改进状态恢复

  • 修复带分页的表格分组 / 树结构

  • 待定(TBD)

0.4.1 版本

screenshot news menu 0.4.1
Figure 10. 更新后的菜单布局
菜单布局代码
private UiMenuSpecifier buildMenu(String q = null) {
    new UiMenuSpecifier().ui {
        menu CrewController.&index as MC
        menu CrewController.&listRoles as MC
        menu CrewController.&hierarchy as MC
        menuIcon ActionIcon.CONFIG_USER, this.&editUser as MC
        menuIcon ActionIcon.EXPORT_PDF, this.&downloadBinPdf as MC
        menuSearch this.&search as MethodClosure, q
        menuOptions(SupportedLanguage.fromContext())            (1)
    }
}
1 语言选择位于搜索栏右侧,还可以添加其他枚举
Kotlin JS 调试指南
$ cd infra/browser/client                             (1)
$ ./gradlew browserDevelopmentRun                     (2)
$ vi intranet/server/grails-app/conf/application.yml  (3)
# Uncomment line bellow
# client.js.path: 'http://localhost:8080/client.js'

# Then your browser should show Kotlin code !
1 进入生成 JS 代码的 client 目录
2 启动提供 client.js 和 client.js.map 服务的服务器…​
3 编辑您的内网 application.yml 文件
新的 Solr DSL 简化(不再需要标签)
@PostConstruct
private void init() {
    taackSearchService.registerSolrSpecifier(this,
            new SolrSpecifier(User,
                CrewController.&showUserFromSearch as MethodClosure,
                this.&labeling as MethodClosure, { User u ->
        u ?= new User()
        indexField SolrFieldType.TXT_NO_ACCENT, u.username_
        indexField SolrFieldType.TXT_GENERAL, u.username_
        indexField SolrFieldType.TXT_NO_ACCENT, u.firstName_
        indexField SolrFieldType.TXT_NO_ACCENT, u.lastName_
        indexField SolrFieldType.POINT_STRING, "mainSubsidiary", true, u.subsidiary?.toString()
        indexField SolrFieldType.POINT_STRING, "businessUnit", true, u.businessUnit?.toString()
        indexField SolrFieldType.DATE, 0.5f, true, u.dateCreated_
        indexField SolrFieldType.POINT_STRING, "userCreated", 0.5f, true, u.userCreated?.username
    }))
}

0.4.0 版本

  • 表格中不再有 paginate。参见 新的 iterate 用法

  • 不再使用 list,而是使用接受闭包作为参数的 iterate,并采用构建器模式传递参数

  • 菜单现在会自动生成标签(在 URL 中使用 lang=test 来翻译菜单)。参见 新的 menu 代码

  • 表格中不再有 isAjax 参数…​ 参见 新的 rowAction 代码

  • 将 rowLink 更改为 rowAction [i18n_isAjax]

  • 表格中的 rowAction 不再需要标签。参见 新的 rowAction 代码

  • 表格、表单、tableFilters 不再需要 ajaxBlock

  • formAction 不再有 isAjax 参数

  • formAction 不再强制要求 i18n 参数

  • form 不再强制要求 i18n 参数,i18n 基于当前 Action 名称

  • 块 Action 不再强制要求 i18n 参数,i18n 基于目标 Action

  • 块 Action 不再强制要求 isAjax 参数

新的 iterate 用法
iterate(taackFilterService.getBuilder(Role)                     (1)
        .setMaxNumberOfLine(20)                                 (2)
        .setSortOrder(TaackFilter.Order.DESC, u.authority_)     (3)
        .build()) { Role r, Long counter ->
            row {
                rowColumn {
                    rowField r.authority
                    if (hasSelect)
                        rowAction
                            ActionIcon.SELECT * IconStyle.SCALE_DOWN,
                            CrewController.&selectRole as MC
                            r.id                                (4)
                }
            }
        }
1 迭代
2 当有更多行时,指定 max 即可触发分页
3 替换用于描述初始排序字段和顺序的旧低效模式
4 不再有 i18n 和 isAjax 参数
新的 menu 代码
private UiMenuSpecifier buildMenu(String q = null) {
    UiMenuSpecifier m = new UiMenuSpecifier()
    m.ui {
        menu CrewController.&index as MC        (1)
        menu CrewController.&listRoles as MC
        menu CrewController.&hierarchy as MC
        menuSearch this.&search as MethodClosure, q
    }
    m
}
1 没有 i18n 参数
新的 rowAction 代码
if (hasActions) {
    rowColumn {
        rowAction ActionIcon.EDIT * IconStyle.SCALE_DOWN, this.&roleForm as MC, r.id (1)
    }
}
1 没有 i18n 参数,没有最后的 isAjax 参数

0.3.9 版本

好久没有更新,冬眠即将结束。此版本提供:

  • Grails 6.2.0

  • Groovy 3.0.21

  • 升级多项依赖…​(参见 更新日志)