Skip to content

Domkit UI 工具包文档(docDomkit) ​

原文: https://finproducts.atlassian.net/wiki/spaces/JIDD3/pages/34793160794/FIN+5.3.1+docDomkit 中文整理基于 FIN 5.3.1


manual ​

Domkit UI 工具包文档(docDomkit) ​

Fantom DomKit HTML5 UI 框架文档


chapters ​

Intro简介
Dom底层 DOM 层文档
Layout布局容器文档
Controls控件库文档
Modals模态框库文档
Drag and Drop拖放文档
Theming自定义 DomKit CSS

Controls(控件) ​

概述 ​

控件是用户在页面上交互的「部件」。Domkit 提供一套标准控件,可用于构建 UI:

Button ​

Button 是按下时触发动作的控件。

Button
{
  it.text = "Press me"
  it.onAction { echo("Pressed!") }
}

Button
{
  it.add(Elem("b") { it.text="Really Press me!" })
  it.onAction { echo("Pressed!") }
}

Button

完整 API 见 Button。

另见: ToggleButton, ListButton

ButtonGroup ​

ButtonGroup 将一组切换按钮或单选按钮编组,并保证组内同时只有一个被选中。

group := ButtonGroup
{
  it.add(ToggleButton { ... })
  it.add(ToggleButton { ... })
  it.add(ToggleButton { ... })
}

group.selIndex = 1      // set group selection
sel := group.selIndex   // get current selection

完整 API 见 ButtonGroup。

另见: ToggleButton, RadioButton

Checkbox ​

Checkbox 显示可开关的复选框。

Checkbox {}
Checkbox { it.checked = true }

单独使用时只显示复选框本身。通常希望附带文本标签;可用 wrap 方法把点击区域扩展到标签:

Checkbox {}.wrap("You can click here too!")

Checkbox

状态变化时接收回调,请添加 onAction 事件处理:

Checkbox
{
  it.onAction |c| { echo("checked: $c.checked") }
}

完整 API 见 Checkbox。

Combo ​

Combo 把 TextField 与 ListButton 合为一体,既可从列表选择也可手工输入。内部 TextField 通过 Combo 暴露;实际使用与 TextField 相同,应在 Combo.field 上注册 onModify、onAction 等回调。

Combo
{
  it.items = ["Alpha, "Beta", "Gamma"]
  it.field.onAction |f| { echo("value: $f.val") }
}

Combo

完整 API 见 Combo。

FilePicker ​

FilePicker 允许从客户端浏览器选择要上传的文件。

简单表单上传时,FilePicker 底层是 <input type="file">,在 <form> 内只需指定 name 即可:

FilePicker { it->name="upload" }

配置可选文件类型,或允许多选:

FilePicker
{
  it.accept = "image/*"   // allow only images
  it.multi  = true        // allow multiple files to upload
}

选中文件时接收回调,请添加 onSelect:

FilePicker
{
  it.onSelect |p| { ... }
}

已选文件列表可在客户端通过 files 字段查看:

// list of files
files := picker.files

f.name    // filename of file
f.size    // size of file
f.type    // MIME type of file

// async load file contents as a text string client-side
f.readAsText |text| { ... }

// async load file contents and encode as a data:// URI client-side
f.readAsDataUri |uri| { ... }

可隐藏真正的 FilePicker DOM,再用 open 编程触发浏览器原生文件选择框:

picker := FilePicker { it.style->display="none" }

button := Button
{
  it.text = "Choose Files"
  it.onAction { picker.open }
}

parent.add(picker)   // make sure FilePicke is actually mounted in DOM
parent.add(button)

完整 API 见 FilePicker and DomFile。

Label ​

Label 仅显示文本,设计上可与 Button 等控件自然垂直对齐:

Label { it.text="My Label" }

完整 API 见 Label。

ListButton ​

ListButton 在按钮按下时弹出列表框,供用户选择一项:

ListButton
{
  it.items = ["Alpha", "Beta", "Gamma"]
  it.onSelect |b| { echo("Selected $b.sel.item") }
}

ListButton

默认用 toStr 显示各项。要自定义显示元素,使用 onElem:

ListButton
{
  it.items = [1,2,3,4]
  it.onElem |v| { "Item #$v" }
}

ListButton

完整 API 见 ListButton。

Link 创建链接用的 <a> 标签:

Link
{
  it.uri  = `https://fantom.org`
  it.text = "Fantom"
}

用 target 指定链接目标:

Link
{
  it.uri    = `https://fantom.org`
  it.text   = "Fantom"
  it.target = "_blank"
}

完整 API 见 Link。

Menu 显示可选的 MenuItem 菜单。

menu := Menu
{
  MenuItem { it.text="Alpha"; it.onAction { ... } },
  MenuItem { it.text="Beta";  it.onAction { ... } },
  MenuItem { it.text="Gamma"; it.onAction { ... } },
  MenuItem { it.text="Delta"; it.onAction { ... } },
}

menu.open(100, 100)

Menu

完整 API 见 Menu。

ProgressBar ​

ProgressBar 可视化长时间操作的进度。

ProgressBar {}

ProgressBar
{
  it.val = 25                     // set progress value
  it.onText |p| { "${p.val}%" }   // set bar text
}

ProgressBar
{
  it.val = 75
  it.onText |p| { "${p.val}%" }
  it.onBarColor |p| { "#2ecc71" }  // set bar color
}

ProgressBar

完整 API 见 ProgressBar。

RadioButton ​

RadioButton 显示单选按钮:

RadioButton {}
RadioButton { it.checked = true }

单独使用时只显示单选圆点。通常附带文本标签;可用 wrap 扩展点击区域:

RadioButton {}.wrap("You can click here too!")

RadioButton

状态变化时接收回调,请添加 onAction 事件处理:

RadioButton
{
  it.onAction |c| { echo("checked: $c.checked") }
}

需要互斥编组时,见 ButtonGroup。

完整 API 见 RadioButton。

Table ​

Table 以行列网格显示数据。

@Js class MyTableModel : TableModel
{
  override Int numCols() { 100 }
  override Int numRows() { 10 }
  override Void onCell(Elem cell, Int col, Int row, TableFlags flags)
  {
    cell.text = "C$col:R$row""
  }
}

Table
{
  // Note that 'rebuild' is required to display the initial
  // table, and to update the table due to any model changes

  it.model = MyTableModel()
  it.rebuild
}

Table

常用表格操作:

// toggle table header
table.showHeader = false

// sort a column
table.sort(2, Dir.down)

// customize zebra-striping for table rows; use empty list
// to remove background color from all rows
table.stripeClasses = [,]
table.stripeClasses = ["even", "odd"]

// enable multiple selection
table.sel.multi = true

// callback when selection changes
table.onSelect |t| { echo(t.sel.index) }

// callback when row is double-clicked; to access Selection.item
// be sure to override TableModel.item to return backing object
table.onAction |t| { echo(t.sel.item) }

// callback for cell events
table.onTableEvent("mousedown") |e| { echo(e) }

// enable custom header popup located in top-right corner of table
table.onHeaderPopup |t| { return Popup { ... }}

完整 API 详见: Table, TableModel, Selection, TableEvent

TextArea ​

TextArea 支持多行文本输入。

TextArea
{
  it.cols = 40
  it.rows = 10
  it.val = "Some text\n  Here\nAnd there"
}

TextArea

文本修改时用 onModify 接收回调:

TextArea
{
  it.onModify |f| { echo(f.val) }
}

完整 API 见 TextArea。

TextField ​

TextField 用于单行文本输入。

TextField {}
TextField { it.val = "Hello, World" }
TextField { it.placeholder = "Search..." }

TextField

文本修改时用 onModify 接收回调:

TextField
{
  it.onModify |f| { echo(f.val) }
}

在 TextField 中按 Enter 时用 onAction 接收回调:

TextField
{
  it.onAction |f| { echo(f.val) }
}

完整 API 见 TextField。

ToggleButton ​

ToggleButton 用按钮切换布尔状态:

ToggleButton
{
  it.text = "Toggle Me"
  it.onAction |b| { echo("state: $b.selected") }
}

可根据选中状态用 elemOn / elemOff 切换内容:

ToggleButton
{
  it.elemOn  = Elem { it.text="On" }
  it.elemOff = Elem { it.text="Off" }
  it.selected = false   // make sure to set default state last
}

也可向 elemOn / elemOff 传入任意对象,将用 Obj.toStr 创建 Elem:

ToggleButton
{
  it.elemOn  = "On"
  it.elemOff = "Off"
  it.selected = false
}

ToggleButton

互斥编组见 ButtonGroup。

完整 API 见 ToggleButton。

Tooltip ​

Tooltip 在鼠标悬停绑定节点时显示小提示,移出时关闭。

Tooltip
{
  it.text = "More info here!"
  it.bind(parent)
}

完整 API 见 Tooltip。

Tree ​

Tree 将 TreeNode 显示为可展开节点树。

@Js class MyTreeNode : TreeNode
{
  new make(Obj item) { this.item = item }
  override TreeNode[] children() { ... }
  override Void onElem(Elem elem, TreeFlags flags)
  {
    elem.text = obj.toStr
  }
  private Obj obj
}

Tree
{
  // Note that 'rebuild' is required to display the initial
  // tree, and to update the tree due to any model changes

  it.roots = [MyTreeNode(...), MyTreeNode(...), ...]
  it.rebuild
}

Tree

常用树操作:

// callback when selection changes; item is TreeNode instance
tree.onSelect |t| { echo(t.sel.item) }

// callback when row is double-clicked; item is TreeNode instance
tree.onAction |t| { echo(t.sel.item) }

// callback for node events
tree.onTreeEvent("mousedown") |e| { echo(e) }

注意:Selection.index 对 Tree 无效。

惰性加载树节点:

@Js class LazyTreeNode : TreeNode
{
  new make(Tree tree, Obj item)
  {
    this.tree = tree
    this.item = item
  }

  ...

  override Bool hasChildren()
  {
    // return true if kids not loaded
    kids==null ? true : kids.size > 0
  }

  override TreeNode[] children()
  {
    // return kids if already loaded
    if (kids != null) return kids

    // async load kids
    doAsyncLoad(this) |items|
    {
      this.kids = items.map |i| { LazyTreeNode(tree, i) }
      tree.refreshNode(this)
    }

    // return empty; doAsyncLoad will refresh
    return TreeNode#.emptyList
  }

  private Tree tree
  private Obj item
  private LazyTreeNode[]? kids
}

完整 API 详见: Tree, TreeNode, Selection, TreeEvent


Drag and Drop(拖放) ​

概述 ​

任意 DOM 节点都可通过 DragTarget 与 DropTarget API 支持拖放。(原文 Dran 为 Drag 笔误)

拖放(拖放) ​

任意 Elem 可用 DragTarget 变成拖动源:

Box
{
  it.text = "Drag me"
  DragTarget.bind(it)
  {
    // get the data payload when a drag event starts
    it.onDrag { "Hi :)" }
  }
}

同样可用 DropTarget 变成放置目标:

Box
{
  it.text = "Drop on me"
  DropTarget.bind(it)
  {
    it.onDrop |data| { echo("# drop: $data") }
  }
}

用 canDrop 判断拖动数据是否可放到该 DropTarget:

DropTarget.bind(elem)
{
  it.canDrop |data| { data == "foo" }
  it.onDrop  |data| { echo("# drop: $data") }
}

Dom(底层 DOM) ​

概述 ​

说到 DomKit,通常也包含底层 dom 程序包(pod)。该 pod 可独立于 DomKit 使用,但为 DomKit 承担了大部分底层工作。

Dom API 大体逐字对齐 W3C DOM 规范,以便复用既有知识与术语,也避免浏览器厂商日后加特性时产生冲突。

本章简要介绍主要的 dom API 特性。拖放、动画等更高级主题见后续章节

Win ​

Win 对应 W3C Window。多数 API 从当前 Win 实例出发:

// get the current Win instance for this Window
win := Win.cur

Win 上的部分示例 API:

win.alert("Hello!")  // display a modal dialog
win.uri              // the URI for this page
win.hyperlink(uri)   // hyperlink to new page
win.hisBack          // go to previous page in session history
win.viewport         // get size of window viewport

// timers
win.setTimeout(10sec) { ... }        // invoke func in 10sec
id := win.setInterval(1sec) { ... }  // invoke func every 1sec
win.clearInterval(id)                // cancel timer with id

完整 API 见 Win 完整 Win API.

Doc ​

Doc 对应 W3C Document。

// get the Doc for the current Win instance
doc := Win.cur.doc

Doc 上的部分示例 API:

doc.elemById("someId")            // return the Elem with id='someId'
doc.createElem("div")             // create a new <div> element
doc.querySelector("div.foo")      // find first <div> element where class='foo'
doc.querySelectorAll("div.bar")   // find all <div> elements where class='bar'

完整 API 见 Doc 完整 Doc API.

Elem ​

Elem 对应 W3C Element。

// create Elems
Elem("div")   // create new unattached <div> element
Elem("img")   // create new unattached <img> element

// add Elem to parent
p := Elem("p") { it.text="Lorem ipsum" }
parent.add(p)

属性(Attributes) ​

Elem 属性(attribute)访问 API:

elem["alt"]                 // get an attr value
elem["alt"] = "Alt text"    // set an attr value
elem.attr("alt")            // get an attr (equivalent to elem["alt"])
elem.setAttr("alt", "...")  // set an attr (equivalent to elem["alt"] = "...")
elem.attrs                  // map of all defined Elem attributes

elem.tagName                // tag name of this element
elem.id                     // 'id' attribute
elem.enabled                // 'enabled' attribute
elem.text = "Foo"           // set innerText content of this element
elem.html = "<b>Foo</b>"    // set innerHTML content of this element

属性(Properties) ​

Elem 的 property API 访问底层 DOM 对象上的 JavaScript 属性:

elem.prop("tabIndex")           // get a prop
elem.setProp("tabIndex", true)  // set a prop

注意:与 attribute 不同,property 名区分大小写。

FFI ​

trap 运算符可便捷调用 prop / setProp:

elem->tabIndex          // get a prop
elem->tabIndex = true   // set a prop

完整 API 见 Elem.trap for details when using a non-HTML namespace.

invoke 可调用 Elem API 未暴露的 JavaScript 函数:

a.invoke("click")

树操作 ​

查询与修改 DOM 树的方法:

elem.parent                   // parent element
elem.prevSibling              // prev sibling
elem.nextSibling              // next sibling
elem.children                 // List of child elements
elem.firstChild               // first child, or null if no children
elem.lastChild                // last child, or null if no children
elem.containsChild(child)     // is child a descendant of parent
elem.add(child)               // add a new child element
elem.replace(cur, newChild)   // replace a child with new element
elem.remove(child)            // remove a child element
elem.querySelector("img")     // find first <img> descendant
elem.querySelectorAll("img")  // find all <img> descendants

完整 API 见 Elem 完整 Elem API.

Style ​

Style 同时提供内联与 class CSS,通过 style 方法访问:

style := elem.style

CSS 类 ​

操作 CSS class 的方法:

style.classes                // return the current class name(s)
style.hasClass("alpha")      // does elem have the given class name?
style.addClass("beta")       // add a new class to current class list
style.removeClass("gamma")   // remove a class, leaving others remaining
style.toggleClass("beta")    // add class if missing, remove if exists

属性(Properties) ​

操作 CSS 属性的方法:

style["background-color"] = "#f00"  // set style background-color: #f00
style->backgroundColor = "#f00"     // set style background-color: #f00
style["color"]                      // get color property value
style->color                        // get color property value
style.computed("color")             // get the computed color property value

style.setAll(["color":"#f00", "background:"#eee"])  // set list of properties
style.setCss("color: #f00; background: #eee")       // set with CSS grammar

上例中可用 trap 代替 get/set。带连字符的属性在 trap 中应写成驼峰:

style->backgroundColor == style["background-color"]

伪类 ​

addPseudoClass 可在运行时定义 CSS 伪类:

style.addPseudoClass(":hover", "background: #eee")

厂商前缀 ​

Style 会自动加厂商前缀,实现代码中不必手写。

完整 API 见 Style 完整 Style API.

Events(事件) ​

DOM 用户事件用 Event 建模。可用 Win.onEvent 注册回调:

win.onEvent("hashchange", false) |e| { Win.cur.alert("hashchanged!") }

Elem 同样可用 Elem.onEvent 注册:

elem.onEvent("mousedown", false) |e| { echo("Pressed $e.target" }

移除已注册处理函数时,把函数实例传给 removeEvent:

func := elem.onEvent("mousedown", false) { ... }
elem.removeEvent("mousedown", false, func)

完整 API 见 Win, Elem and Event 完整 API details.

HttpReq ​

HttpReq 用 XmlHttpRequest 在浏览器发起后台 HTTP 请求。同步/异步都会在回调闭包里收到 response:

req := HttpReq { uri=`/foo` }
req.send("POST", "some content") |res|
{
  Win.cur.alert(res.content)
}

常见请求方法有便捷封装:

HttpReq { uri=`/foo` }.get |res| {...}
HttpReq { uri=`/foo` }.post("some content") |res| {...}
HttpReq { uri=`/foo` }.postForm(["name":"Barney Stinson"]) |res| {...}

postForm 会自动编码为普通 HTML 表单提交形式。

设置请求头示例:

req := HttpReq {}
req.uri = `/foo`
req.headers["Content-Type"] = "text/csv; charset=utf-8"
req.post("a,b,c") |res| { ... }

完整 API 见 HttpReq and HttpRes 完整 API.

Web Storage(浏览器存储) ​

Win API 可访问 session 与 local 浏览器 storage:

// local
win.localStorage["bar"]           // return value for bar from local storage
win.localStorage["foo"] = 25      // store foo:25 in local storage
win.localStorage.remove("foo")    // remove foo from local storage
win.localStorage.clear            // clear all contents from local storage

// session
win.sessionStorage["bar"]         // return value for bar from session storage
win.sessionStorage["foo"] = 33    // store foo:33 in session storage
win.sessionStorage.remove("foo")  // remove foo from session storage
win.sessionStorage.clear          // clear all contents from session storage

完整 API 见 Storage 完整 API.

MutationObserver ​

MutationObserver 封装 W3C Mutation 观察 API。先创建实例并提供变更回调:

observer := MutationObserver() |recs|
{
  // callback when DOM mutation occurs where
  // recs is the list of mutations
}

每次变更用 MutationRec 表示。再把观察者挂到 DOM 节点开始监听:

// listen for child add/remove events on elem
observer.observe(elem, ["childList":true])

若要停止该观察者的全部事件,调用 disconnect

// detach this observer instance from all mutation events
observer.disconnect

Intro(简介) ​

概述 ​

DomKit 是面向 Web UI 的 HTML5 工具包,整体分两层:

  • dom: dom pod provides low-level access to the browser DOM
  • domkit:可复用控件与工具库,用于搭建 UI

DomKit 的主要设计目标之一是尽量少在 W3C DOM 上加抽象,好处包括:

  • 可随时间直接受益于浏览器新能力
  • 减少与第三方 HTML 组件混用时的冲突
  • 复用构建 HTML 界面的既有知识与资源
  • 保持库更小、更简单

要点:DomKit「控件」就是标准 DOM 元素节点(如 <div>),可放在 DOM 树任意位置;控件子节点也可以是几乎任意合法 HTML。

本手册分 6 大部分:

想快速体验 Domkit 能力与示例,可运行测试套件:

$ fan testDomkit

该命令在 8080 端口启动本地 Web 服务器,用浏览器打开:

http://localhost:8080/

Layout(布局) ​

概述 ​

DomKit 控件本质是 HTML + CSS,可放在 DOM 几乎任意位置。面向富 UI,还提供标准布局容器以简化常见界面:

Box ​

Box 是按特定方式布局子节点的 Elem 基类,默认若干 CSS 以简化组合:

display: block;
box-sizing: border-box;
width: 100%;
height: 100%;
position: relative;

GridBox ​

GridBox 以固定行列的表格方式布局子节点。

示例 ​

GridBox
{
  it.cellStyle("*",    "*", "padding: 4px")
  it.cellStyle("*", "even", "background: #f8f8f8")
  it.cellStyle("*",     4,  "background: #e8e8e8; border-top: 1px solid #ccc")

  it.addRow([Label {
    it.text = "Enter User Information"
    it.style->fontWeight = "bold"
  }], [2])
  it.addRow([Label { it.text="First Name:"  }, TextField {}])
  it.addRow([Label { it.text="Middle Name:" }, TextField {}])
  it.addRow([Label { it.text="Last Name:"   }, TextField {}])
  it.addRow([null, Button { it.text="Submit" }])
}

GridBox

cellStyle ​

cellStyle 可为成组单元格设样式。col / row 合法值:

  • 具体下标(0、1、2…)
  • 下标范围(0..4、7..<8 等)
  • "*": apply to all row or columns
  • "even":仅偶数行/列
  • "odd":仅奇数行/列
cellStyle("*",   "*", "padding: 4px")
cellStyle("*", "odd", "padding: 4px")
cellStyle(  0,     1, "padding: 4px")
cellStyle(  3,  2..5, "padding: 4px")

addRow ​

用 addRow 按行添加子节点;某位置传 null 则该格留空:

// leave first cell empty
it.addRow([null, Button { it.text="Submit" }])

可选参数可指定子单元格跨多列:

// pass a single child, but specify it should span 2 columns
it.addRow([Label {
  it.text = "Enter User Information"
  it.style->fontWeight = "bold"
}], [2])

colspan 可作用于多个子项,列表值与子项位置对应:

// Add two children, where child1 spans 2 columns, and child2 3 columns
grid.addRow([child1, child2], [2,3])

removeRow ​

可用 removeRow 按下标删除行:

grid.removeRow(2)

若该下标没有行,则无操作。

halign ​

默认各子格用首选尺寸,周围常有空隙。用 GridBox.halign 调整在空隙中的水平位置:

GridBox { it.halign = Align.center }

FlowBox ​

FlowBox 水平并排布局子节点。

FlowBox
{
  Button { it.text="Alpha" },
  Button { it.text="Beta"  },
  Button { it.text="Gamma" },
}

FlowBox-1

子项对齐可用 halign:

FlowBox
{
  it.halign = Align.center
  Button { it.text="Alpha" },
  Button { it.text="Beta"  },
  Button { it.text="Gamma" },
}

FlowBox-2

子项间距用 gaps 配置,每项是 index 与 index+1 之间的空隙:

FlowBox
{
  it.gaps = ["4px", "16px"]
  Button { it.text="Alpha" },  // 4px gap after Alpha
  Button { it.text="Beta"  },  // 16px gap after Beta
  Button { it.text="Gamma" },
}

FlowBox-3

若子项多于 gaps 长度,则循环使用 gaps:

FlowBox
{
  it.gaps = ["4px", "1px"]
  Button { ... },   // 4px gap after
  Button { ... },   // 1px gap after
  Button { ... },   // 4px gap after
  Button { ... },
}

FlowBox
{
  it.gaps = ["4px"]
  ...  // 4px gap between all children
}

SashBox ​

SashBox 在一个方向上按灵活尺寸分割容器:

SashBox
{
  it.sizes = ["25px, "100%", "50px"]
  Box { it.text="Child 1" },
  Box { it.text="Child 2" },
  Box { it.text="Child 3" },
}

+----------+----------------------------------+--------------------+
|          |                                  |                    |
|  Child 1 |            Child 2               |      Child 3       |
|          |                                  |                    |
+----------+----------------------------------+--------------------+

可混合固定 px 与 %。先算像素尺寸,剩余按百分比分配;通过 CSS calc() 应用。

默认从左到右;自上而下请设 dir:

SashBox
{
  it.dir = Dir.down
  it.sizes = ["100px", "100%"]
  Box { it.text="Child 1" },
  Box { it.text="Child 2" },
}

默认分隔固定;可设 resizable 并加入 dividers 以允许拖拽调整:

SashBox
{
  it.resizable = true
  it.sizes = ["40%", "5px", "60%"]
  Box { it.text="Child 1" },
  SashBox.div,
  Box { it.text="Child 2" },
}

resizable 为 true 时,可用 minSize 限制最小尺寸。

CardBox ​

CardBox 把子元素叠成卡片,同时只显示一张:

CardBox
{
  Box { it.style->background="#e74c3c"; it.text="Card #0" },
  Box { it.style->background="#2ecc71"; it.text="Card #1" },
  Box { it.style->background="#3498db"; it.text="Card #2" },
}

用 selIndex 切换可见卡片:

cardBox.selIndex = 2

CardBox

WellBox ​

WellBox 以凹陷井样式显示内容:

WellBox { content, }

WellBox

FlexBox ​

FlexBox 基于 CSS Flexbox 布局。Flexbox 概念较多,推荐教程:

https://css-tricks.com/snippets/css/a-guide-to-flexbox/

实践中 Domkit 只用 flex 的子集,适合「微布局」。最常见是工具栏:垂直居中,并左/中/右对齐。

下例创建左对齐多按钮的工具栏:

FlexBox
{
  it.flex = ["1 1 auto"]
  FlowBox {
    it.gaps = ["4px"]
    Button { ... },
    Button { ... },
    Button { ... },
  },
}

再添加一个右对齐按钮:

FlexBox
{
  it.flex = ["1 1 auto", "0 0 auto"]
  FlowBox { ... },
  Button { ... },
}

flex 指定父尺寸如何分给各子项,每个子项一条。"0 0 auto" 表示用首选宽度且不随父变化伸缩;"1 1 auto" 表示先按首选尺寸布局再吃掉剩余空间,从而把第二个子项推到右侧。


Modals(模态框) ​

概述 ​

DomKit 提供两类模态内容:Dialog 与 Popup。

Dialog ​

Dialog 在页面内容之上打开模态窗口。

dialog := Dialog { it.title = "Hello" }
dialog.add(SashBox
{
  it.dir = Dir.down
  it.sizes = ["auto", "auto"]
  it.style->padding = "12px"
  Box
  {
    it.text= "Lorem ipsum dolor sit amet, consectetur adipiscing
              elit. Etiam accumsan, felis vestibulum elementum
              efficitur, ligula sem porta magna, sit amet semper
              lacus lorem vitae lacus."
  },
  FlowBox
  {
    it.style->paddingTop = "12px"
    it.halign = Align.right
    it.gaps = ["4px"]
    Button { it.text="OK";     it.onAction { dialog.close; echo("# OK") }},
    Button { it.text="Cancel"; it.onAction { dialog.close; echo("# Cancel") }},
  },
})

dialog.open

Dialog

要改默认尺寸,覆盖 width / height 样式:

Dialog
{
  it.style->width  = "600px"
  it.style->height = "400px"
  ...
}

Dialog 挂载/卸载 DOM 时用 onOpen / onClose 回调:

dialog.onOpen  { echo("Opened!") }
dialog.onClose { echo("Closed!") }

完整 API 见 Dialog。

Popup 在页面上打开弹出层;点击外部或按 Esc 关闭:

popup := Popup
{
  it.style->width = "400px"
  it.style->padding = "12px"
  Box
  {
    it.text= "Lorem ipsum dolor sit amet, consectetur adipiscing
              elit. Etiam accumsan, felis vestibulum elementum
              efficitur, ligula sem porta magna, sit amet semper
              lacus lorem vitae lacus."
  },
}

popup.open(100, 100)

Popup

完整 API 见 Popup。


Theming(主题) ​

概述 ​

DomKit 设计为纯 CSS 覆盖样式类来定制。控件主要继承下列「基类」样式:

.domkit-control
.domkit-control-button
.domkit-control-text

每种控件还有约定名 domkit-<TypeName> 的专用类,例如 Button 用 domkit-Button。页末有 示例主题 骨架可供参考。

字体 ​

字体继承自父节点或 <body>;设好页面字体通常即可。也可用基类或具体类型类单独定制,例如:

body { font: 14px 'Helvetica Neue', Arial, sans-serif; }
.domkit-control-text { font: 14px Monaco, Courier, monospace; }

div.domkit-TextArea { font: 12px Monaco, Courier, monospace; }

垂直对齐 ​

控件并排放置时会自动垂直对齐。因与字体无关(见上文「字体」),做法是统一 line-height,并用 padding 补偿边框。

.domkit-control {
  padding: 5px 0;
}

.domkit-control-button {
  border: 1px solid #ccc;
  padding: 4px 10px;  // -1px for border to keep valign with .domkit-control
}

.domkit-control-text {
  border: 1px solid #ccc;
  padding: 4px 10px;  // -1px for border to keep valign with .domkit-control
}

边框 ​

DomKit 定义了若干标准边框色与样式类:

.domkit-border
.domkit-border-top
.domkit-border-left
.domkit-border-right
.domkit-border-bottom

复杂控件如 Table、Tree 会用到。用法示例:

// adding a border
Box { it.style.addClass("domkit-border") }

// adding a bottom-border
Box { it.style.addClass("domkit-border-bottom") }

// removing a left/right borders
Table {
  it.style.removeClass("domkit-border")
  it.style.addClass("domkit-border-top").addClass("domkit-border-bottom")
}

选中态 ​

选中项加 domkit-sel 类。尽量考虑焦点,因此需分别定义有焦点/无焦点选中样式:

.domkit-sel {
  background-color: #dcdcdc !important;
}

:focus .domkit-sel, .domkit-sel.pin {
  background-color: #3498db !important;
  color: #fff !important;
}

焦点 ​

控件焦点环可用 :focus 伪类定制:

.domkit-control-button:focus
.domkit-control-text:focus

Modals(模态框) ​

基础 Dialog 样式用 .domkit-Dialog-frame 与 .domkit-Dialog-title;标题栏还继承 .domkit-control。

div.domkit-Dialog-frame {
  background: #f2f2f2;
  border: 1px solid #aaa;
  border-radius: 4px 4px 0 0;
  box-shadow: 0px 12px 32px rgba(0, 0, 0, 0.4);
}

div.domkit-Dialog-title {
  text-align: center;
  font-weight: 500;
}

Popup 样式:

div.domkit-Popup {
  background: rgba(248, 248, 248, 0.95);
  border: 1px solid #ddd;
  box-shadow: 0px 9px 18px rgba(0, 0, 0, 0.25);
}

模态下有遮罩阻止点击,样式为 .domkit-Dialog-mask:

div.domkit-Dialog-mask {
  background: rgba(0, 0, 0, 0.25);
}

div.domkit-Popup-mask {
  background: none;
}

Table ​

主要 Table 样式:

div.domkit-Table
div.domkit-Table-header
div.domkit-Table-cell
div.domkit-Table-cell.odd
div.domkit-Table-cell.even

Table 较特殊:为大数据集性能,单元格尺寸需写死,故 -header / -cell 不会 继承父级字号(只继承样式)。若加大字号,可能还需配置 rowHeight:

div.domkit-Table-header { font-size: 13px; }
div.domkit-Table-cell   { font-size: 14px; }

@Js class MyModel : TableModel
{
  override Int rowHeight() { 30 }
}

全屏 ​

若要让文档 body 铺满视口(类似桌面应用),加如下样式:

html { height: 100%; }
body { height: 100%; overflow: hidden; }

示例主题 ​

自定义样式表起点示例:

.domkit-sel {
  background-color: #dcdcdc !important;
}

:focus .domkit-sel, .domkit-sel.pin {
  background-color: #8e44ad !important;
  color: #fff !important;
}

:focus .domkit-sel a {
  color: #fff !important;
}

.domkit-control {
  font: 16px 'Helvetica Neue', Arial, sans-serif;
}

.domkit-control-button {
  background: #f8f8f8;
  border: 1px solid #444;
  border-radius: 5px;
}

.domkit-control-button:hover { background: #f0f0f0; }
.domkit-control-button.down  { background: #ccc; }
.domkit-control-button.selected,
.domkit-control-button.selected:hover {
  color: #fff;
  background-color: #8e44ad;
  border-color: #6d2f87;
}

.domkit-control-text {
  background: #fff;
  border: 1px solid #444;
  border-radius: 5px;
}

.domkit-control-button:focus, .domkit-control-text:focus {
  border-color: #8e44ad;
}

div.domkit-Table-header {
  font: bold 13px 'Helvetica Neue', Arial, sans-serif;
}
div.domkit-Table-cell {
  font: 14px 'Helvetica Neue', Arial, sans-serif;
}
div.domkit-Table-cell.odd  { background: #fff; }
div.domkit-Table-cell.even { background: #f8f8f8; }

div.domkit-Tree {
  font: 16px 'Helvetica Neue', Arial, sans-serif;
}

div.domkit-Dialog-frame {
  background: #fff;
  border: 1px solid #ccc;
  border-radius: 5px;
  box-shadow: 0px 12px 32px rgba(0, 0, 0, 0.4);
}

div.domkit-Dialog-title {
  text-align: center;
  font-weight: bold;
  background: #f8f8f8;
  border-bottom: 1px solid #ccc;
}

div.domkit-Dialog-mask {
  background: rgba(0, 0, 0, 0.25);
}

div.domkit-Popup {
  background: #fff;
  border: 1px solid #ccc;
  border-radius: 5px;
  box-shadow: 0px 9px 18px rgba(0, 0, 0, 0.25);
}

div.domkit-Popup-mask {
  background: none;
}