Introducing Layouts - Xaml for the Web






2.73/5 (4投票s)
Layouts 是一个 Javascript 库,
- Layouts 源码和文档在 Github 上
- Layouts 页面编辑器
- 下载 LayoutsTestApp.zip - 120.7 KB
- (最新) 使用 Layouts 编写的 4x4 滑动方块示例游戏
- (最新) 提供 NuGet 包
引言
使用 Layouts,您可以使用 Xaml 标记语言创建 Web 应用程序来定义其 UI。实际上,Layouts 的功能远不止于此:您可以使用最佳的 MVVM 实践创建完整的 SPA(单页应用程序)。Layouts 适用于构建复杂的 UI,如仪表板或 LOB 应用程序,同时又足够简单,可以轻松地构建现有应用程序的单个页面。
简而言之,Layouts 用 XAML 替代了 HTML,并保留了 Web 开发人员习惯使用的 Javascript 和 CSS。
在阅读本文之前,最好对 TypeScript (https://typescript.net.cn/) 有基本的了解,或者查看 Layouts 的源代码。
入门
首先,在 Visual Studio (或 Sublime 或任何其他编辑器) 中创建一个空的 TypeScript 项目。
放入以下文件
index.xml 是一个空的 HTML 页面,包含指向 layouts.js、linq.min.js 和 app.js 的链接。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>Layouts Sample</title> <link rel="stylesheet" href="app.css" type="text/css" /> <script src="linq.min.js" type="text/javascript"></script> <script src="layouts.js" type="text/javascript"></script> <script src="app.js" type="text/javascript"></script> </head> <body> </body> </html>
同时创建一个 CSS 文件 (app.css),它会移除所有边距并将 body 设置为占据整个页面。
* {
margin: 0px;
}
html {
height:100%;
}
body {
font-family: 'Segoe UI', sans-serif;
height:100%;
margin:0px;
}
#helloworld {
color: red;
}
最后,从 github.com 仓库复制最新的 layouts.js 文件,从 https://linqjs.codeplex.com/ 复制优秀的 linq.min.js,并设置 TypeScript 编译器将所有内容生成到一个名为 app.js 的文件中。
Hello World
添加一个如下所示的 TypeScript 文件 app.ts
/// <reference path="layouts.d.ts"/>
window.onload = () => {
var app = new layouts.Application();
var lmlReader = new layouts.XamlReader();
var lmlTest = `<?xml version="1.0" encoding="utf-8" ?>
<Page>
<TextBlock id="helloworld" Text="Hello World" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Page>
`;
app.page = lmlReader.Parse(lmlTest);
};
如果一切编译并运行成功,您应该会看到一个白色的页面,上面以红色居中显示“Hello World”。
查看上面的代码,您会发现我创建了一个 Application (变量 app) 和一个 xaml 解析器 (lmlReader)。接下来,我将描述我的页面应该如何用类 Xaml 的标记来构建,然后将其传递给 XamlReader.Parse 方法来获取一个 Page 对象。Application.Page 最后会被设置为新创建的 page。在 layouts 中只能有一个 Application 对象。Application.Page 返回当前页面:可以通过仅设置此属性来切换页面。
有趣的部分是 Page 在 Xaml 中的定义方式。我们使用包含 TextBlock 元素的 Page 头来定义 Page 本身。查看 TextBlock,它在垂直和水平方向上定义了一个段落对象 (p) 并居中。TextBlock 的 id 属性直接传递给生成的 HTML,因此我们可以在 CSS 文件 (app.css) 中选择它来给它添加红色。
让我们用这个 xaml 做一些实验,例如将 HorizontalAlignment 设置为 Left 或 Right,将 VerticalAlignment 设置为 Top 或 Bottom。
XamlReader.Parse 方法非常强大,因为它不仅仅管理 Page 对象。它可以解析并创建其他 layouts 控件,以及您在代码中定义的控件。
示例登录页面
现在,让我们稍微复杂化一下我们的示例,创建一个登录页面。
window.onload = () => {
var app = new layouts.Application();
var lmlReader = new layouts.XamlReader();
var lmlTest = `<?xml version= "1.0" encoding= "utf-8" ?>
<Stack Orientation="Vertical" VerticalAlignment= "Center" HorizontalAlignment= "Center" >
<TextBlock Text="Welcome to Login Page" Margin= "8" />
<TextBox Placeholder= "User name" Margin= "8" />
<TextBox Type= "password" Placeholder= "Password" Margin= "8" />
<Button Text="Sign In" Margin= "8,16,8,8" />
</Stack>
`;
app.page = lmlReader.Parse(lmlTest);
};
Stack 元素是 Layouts 中(以及 Grid)的基本元素,它允许按垂直或水平方向排列子元素。TextBox 被渲染为 HTML input 控件,Button 被渲染为 html button。Margin 是一个属性,用于确定元素相对于其边框的位置。Margin="8" 表示“在顶部、右侧、底部和左侧保留 8 像素的空间”。
请注意,按钮是禁用的:我们还没有为按钮指定命令,所以 Layouts 会禁用它(见下文)。
花点时间尝试 Layouts 的 Margin 和 Orientation 属性。您也可以将一个 Stack 嵌套在另一个 Stack 中。
再做一点小修改
var lmlTest = `<?xml version= "1.0" encoding= "utf-8" ?>
<Stack Orientation="Vertical" VerticalAlignment= "Center" HorizontalAlignment= "Center">
<TextBlock Text="Welcome to Login Page" Margin= "8" />
<TextBox Placeholder= "User name" Margin= "8" />
<TextBox Type= "password" Placeholder= "Password" Margin= "8" />
<Grid Columns="* Auto" Margin= "8,16,8,8" MaxWidth="300">
<Button Text="Sign In"/>
<TextBlock Text="Not yet registered?" Grid.Column="1" Margin="10,0,0,0"/>
</Grid>
</Stack>
`;
我们在 stack 中添加了一个 Grid 面板。Grid 是 Layouts 中最强大的元素。Grid 通过由行和列组成的网格布局来排列其子项。上面我们创建了一个有 2 列和 1 行(默认)的 Grid。第一列的宽度为星号 (*),第二列设置为 Auto。Grid 也支持固定大小的列或行,您可以指定列或行的大小(以像素为单位)。
再次花点时间尝试 Grid。请注意,如果您更改文本“Not yet registered”为其他内容,Grid 会预留足够的空间来完全显示新文本。自动调整大小是 Layouts 的一个重要特性:它允许创建适应内容大小的界面。
MVVM
Layouts 鼓励使用 MVVM 模式:它提供了一些您需要实现的类,以便将 UI 元素与模型连接起来。我在这里无法深入描述 MVVM,我确定外面有比我写得更好的指南和教程来描述它。
让我们创建一个可以处理登录过程的 ViewModel 类。
class LoginViewModel extends layouts.DepObject {
static typeName: string = "app.LoginViewModel";
get typeName(): string {
return LoginViewModel.typeName;
}
constructor() {
super();
}
private _username: string;
get username(): string {
return this._username;
}
set username(value: string) {
if (this._username != value) {
var oldValue = this._username;
this._username = value;
this.onPropertyChanged("username", value, oldValue);
this._loginCommand.canExecuteChanged();
}
}
private _password: string;
get password(): string {
return this._password;
}
set password(value: string) {
if (this._password != value) {
var oldValue = this._password;
this._password = value;
this.onPropertyChanged("password", value, oldValue);
this._loginCommand.canExecuteChanged();
}
}
private _loginCommand: layouts.Command;
get loginCommand(): layouts.Command {
if (this._loginCommand == null)
this._loginCommand = new layouts.Command((cmd, p) => this.onLogin(), (cmd, p) => this.canLogin());
return this._loginCommand;
}
onLogin() {
if (this._username == "test" &&
this._password == "test") {
alert("Logged in!");
}
else
alert("Unable to login!");
}
canLogin(): boolean {
return this._username != null && this._username.trim().length > 0 &&
this._password != null && this._password.trim().length > 0;
}
}
Layouts 定义了一个名为 DepObject 的类型,它提供了一些创建 ViewModel 所需的基本功能。例如,上面我们定义了一个派生自 DepObject 的 LoginViewModel 类。我们的 ViewModel 定义了几个属性(Username 和 Password)和一个命令(loginCommand)。声明类型名称的第一部分对于使 Layouts 绑定生效是必需的(因为在 JavaScript 中无法在运行时发现类型名称)。
现在,让我们将视图(登录页面)链接到上面的 ViewModel。
window.onload = () => {
var app = new layouts.Application();
var lmlReader = new layouts.XamlReader();
var lmlTest = `<?xml version= "1.0" encoding= "utf-8" ?>
<Stack Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="Welcome to Login Page" Margin="8"/>
<TextBox Text="{username,mode:twoway}" Placeholder="User name (test)" Margin="8"/>
<TextBox Text="{password,mode:twoway}" Type="password" Placeholder="Password (test)" Margin="8"/>
<Button Text="Sign In" Command="{loginCommand}" Margin="8,16,8,8"/>
</Stack>
`;
app.page = lmlReader.Parse(lmlTest);
app.page.dataContext = new LoginViewModel();
};
首先要注意的是,我已将当前页面的 dataContext 属性设置为 LoginViewModel 的一个新实例。然后,查看 xaml 定义,您肯定会注意到我已告知 Layouts 将第一个文本框绑定到 ViewModel 的 Username 属性,第二个文本框绑定到 Password,最后将按钮绑定到 loginCommand。现在,如果您运行示例应用程序,您应该能够编辑用户名和密码,然后登录。
接下来呢?
Layouts 是一个大型框架,不可能在一篇文章中描述所有细节。这是您可以通过它获得的功能的摘要:
- 诸如 ItemsControl、ControlTemplate 和 ContentTemplate 等控件,以及许多其他控件。
- 能够通过派生自 FrameworkElement 或 UserContol 来创建自定义控件。在 github 仓库中,您可以找到 TreeView 和 TabView 等控件。
- 能够嵌入外部 UI 框架(我在项目中成功使用了 Kendo UI、JQuery UI 和 datatables.net)。
- 复杂的绑定场景,支持自定义转换器和目标(self、dataContext、element)。
- 能够直接将 HTML 作为原生元素嵌入。
- 嵌入式导航系统,用于 SPA 实现。
结论
Layouts 是我尝试将 Xaml 和 WPF 范式移植到 Web 应用程序开发中的尝试。我使用 Layouts 构建了一个复杂的仪表板应用程序,该应用程序现在已投入生产,并将由数十人使用。在过去的 3 个月里,我深入研究以使其稳定且功能齐全。
历史
2015-10-19 首次发布文章