65.9K
CodeProject 正在变化。 阅读更多。
Home

Introducing Layouts - Xaml for the Web

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.73/5 (4投票s)

2015 年 10 月 20 日

CPOL

5分钟阅读

viewsIcon

21454

downloadIcon

168

Layouts 是一个 Javascript 库,它允许您使用 Xaml 创建具有复杂 UI 的 Web 应用程序

Layouts Sample Application Screenshot

引言

使用 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 首次发布文章

© . All rights reserved.