在任意图形用户界面上创建动态可交互的布局是一项挑战。使用HTML与CSS创建布局需要我们具备很多的能力。CSS叶落散尽,Dojo创建了一些可扩展的挂件作为Dijit的一部分-Dojo的UI框架。本教程讲解的是,使用Dijit创建布局需要些什么以及如何利用一些挂件轻松的构建复杂的布局。
初探布局管理
CSS是布局语言?为什么又要使用JavaScript与挂件来解决布局问题呢?
布局挂件没有替代CSS的一般目的,在页面中定位与浮动内容。相反,它们允许精确的定位与管理我们想要处理的页面区域:
- 响应缩放事件
- 为用户控制布局及如何合理布置空间提供支持
- 为当前的横竖空间自适应控制元素与内容
布局管理是页面加载完后活动控制布局的过程,响应与传播页面上驱动布局的事件。在Dijit中布局是由专门的挂件来完成的。这些挂件主要扮演一个或更多内容或子挂件容器的角色,其控制尺寸与展示子元素。
开始
你可以管理整个页面的布局,也可以值管理其一部分。对于这份教程,我们将开发类桌面应用的布局,一些控制元素与内容固定在页面上。看起来就像这样:
Dijit为开发这样布局的需要提供了一个灵活挂件的集合。我们使用HTML与CSS先准备好场地,然后介绍利用挂件搭建一个典型布局:
1 <!DOCTYPE HTML> 2 <html lang="en"> 3 <head> 4 <meta charset="utf-8"> 5 <title>Demo: Layout with Dijit</title> 6 <link rel="stylesheet" href="style.css" media="screen"> 7 <link rel="stylesheet" href="dijit/themes/claro/claro.css" media="screen"> 8 </head> 9 <body class="claro"> 10 <div id="appLayout" class="demoLayout"> 11 <div class="centerPanel"> 12 <div> 13 <h4>Group 1 Content</h4> 14 <p>Lorem ipsum dolor sit amet,...</p> 15 </div> 16 <div> 17 <h4>Group 2 Content</h4> 18 </div> 19 <div> 20 <h4>Group 3 Content</h4> 21 </div> 22 </div> 23 <div class="edgePanel">Header content (top)</div> 24 <div class="leftCol" class="edgePanel">Siderbar content (left)</div> 25 </div> 26 <script src="dojo/dojo.js" data-dojo-config="async: 1, parseOnLoad: 1"></script> 27 </body> 28 </html>
我们有了顶部,边栏,中央内容的DIV包裹器,获取Dojo的script标签也放置好了。在head标签中我们载入了claro主题样式表和我们页面的样式表。在body标签上,为了应用claro的主题,为其添加类claro是必须的。经常容易被忽略。
我们需要为布局定几条样式规则:
1 html, body { 2 height: 100%; 3 margin: 0; 4 overflow: hidden; 5 padding: 0; 6 } 7 8 #appLayout { 9 height: 100%; 10 } 11 12 #leftCol { 13 width: 14em; 14 } 15 16 .claro .demoLayout .edgePanel { 17 background-color: #d0e9fc; 18 } 19 20 #viewsChart { 21 width: 550px; 22 height: 500px; 23 }
为了得到想要布局与区域内容的行为,我们想要让布局充满视口。我们把文档和最外层的标签设置高度100%。overflow: hidden;是不想使用document的滚动条;滚动将在布局不同的区域中,需要的时候发生。我们给左边的DIV使用em来定义宽度。其他固定的内容将从初始化内容中获取尺寸。
添加挂件
未来实现布局,我们需要Dijit的三个挂件类:dijit/layout/BorderContainer,dijit/layout/TabContainer和dijit/layout/ContentPane。你可以在Dojo工具集“功能”页面查看Dijit挂件的样例。
首先,调用require载入依赖包:
1 <script src="dojo/dojo.js" data-dojo-config="async: true, parseOnLoad: true"></script> 2 <script> 3 require([‘dojo/parser‘, ‘dijit/layout/BorderContainer‘, ‘dijit/layout/TabContainer‘, ‘dijit/layout/ContentPane‘]); 4 </script>
注意我们在Dojo的script标签中设置了data-dojo-config属性parseOnLoad为true。这就告诉Dojo当发现挂件化的元素时自动解析。我们完全依赖解析器,甚至都不需要dojo/domReady!之类的,载入完了直接使用。
注意我们需要载入dojo/parser模块。这是非常重要的,尽管经常被忽略,即便是在配置将parseOnLoad设置为了true,dojo/parser也不会自动载入的,以后也不会。版本1.7之前可以不是这样的,由于很多挂件载入dijie/_Templated(它将载入dojo/parser)。
挂件类会在后台载入,且解析器将遍历DOM树。但是实际上接着什么也不会发生了——我们需要创建布局挂件。
例如,我们使用标记或描述的方式实例化挂件。标记元素中的data-dojo-属性为解析器提供挂件的种类与实例化需要的属性配置:
1 <body class="claro"> 2 <div 3 id="appLayout" 4 class="demoLayout" 5 data-dojo-type="dijit/layout/BorderContainer" 6 data-dojo-props="design: ‘headline‘"> 7 <div 8 class="centerPanel" 9 data-dojo-type="dijit/layout/ContentPane" 10 dojo-dojo-props="region: ‘center‘"> 11 <div> 12 <h4>Group 1 Content</h4> 13 <p>Lorem ipsum dolor sit amet, consectetur ad...</p> 14 </div> 15 <div> 16 <h4>Group 2 Content</h4> 17 </div> 18 <div> 19 <h4>Group 3 Content</h4> 20 </div> 21 </div> 22 <div 23 class="edgePanel" 24 data-dojo-type="dijit/layout/ContentPane" 25 data-dojo-props="region: ‘top‘">Header content (top)</div> 26 <div 27 id="leftCol" 28 class="edgePanel" 29 data-dojo-type="dijit/layout/ContentPane" 30 data-dojo-props="region: ‘left‘, splitter: true">Sidebar content (left)</div> 31 </div> 32 </body>
外层的appLayout元素被配置为BorderContainer,它内部的DIV都是ContentPane。这样就呈现了一个满屏灵活的布局。试着改变demo窗口的大小,看看左边的区域是如何固定的,中间与右边区域会自适应大小。你可能也注意到了左边与中间区域之间竖向的控制手柄,可以自由的调整相对宽度。
这就是我们所谓的动态的灵活的布局。接下来我们将在demo中添加tab面板,但是首先让我们回过头来了解一下分隔布局挂件及他们的用法。
边框容器
如果你在其他GUI工具中使用过布局管理器与容器,dijit/layout/BorderContainer对你来说应该不陌生,and if not we can soon catch you up。
边框容器允许你定义一个细分为区域的布局。中间区域总是灵活的自适应大小,当其他区域都固定尺寸后:"top", "bottom", "leading", "trailing", "left"和"right"。
所有的Dijit挂件支持国际化,所以Dijit不能臆断页面上的内容和控制元素是从左至右的。针对从左至右的地区,leading部分将在左边,trailing部分在右边。针对从右至左的地区(如:阿拉伯语,希伯来语),是相反的。你可以使用left和right确保内容在你想要放置的一边,不管是什么地域。全依赖与内容的逻辑。
正如我们在示例中看到的,每个区域都作为子挂件呈现。Dijit的挂件都支持region属性,所以原则上,你可以使用任何挂件,尽管有些确实比其他工作的更好。固定的区域可以通过设置splitter属性显示一个用户可移动的分割器。
当使用边框容器时,区域的尺寸是通过CSS指定的——使用样式表或行内样式。注意尽管你可以使用百分比来初始化尺寸,但在渲染时会被转化为像素。所以不能使用百分比的值来定义边框容器的尺寸。中心区域不应该设置高度或宽度;它会自动填充剩余空间。
我们建立的布局,所有的区域都是ContentPane——都是出于载入内容和作为挂件内容容器的目的,但是在第一个布局中心的的TabContainer区域,就不是这样的。实际上,边框容器经常嵌套使用。下面是一个更复杂布局嵌套使用边框容器的例子:
1 <div class="demoLayout" style="height: 300px; width: 300px" data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="design: ‘headline‘"> 2 <div class="centerPanel" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: ‘center‘">center</div> 3 <div class="demoLayout" style="height: 50%" data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="region: ‘top‘, splitter: true, design: ‘headline‘"> 4 <div class="centerPanel" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: ‘center‘">center</div> 5 <div class="edgePanel" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: ‘bottom‘">bottom</div> 6 </div> 7 <div class="edgePanel" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="splitter: true, region: ‘left‘">left</div> 8 <div class="demoLayout" style="width: 50%" data-dojo-type="dijit/layout/BorderContainer" data-dojo-props="region: ‘right‘, design: ‘headline‘"> 9 <div class="centerPanel" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: ‘center‘">center</div> 10 <div class="edgePanel" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="region: ‘bottom‘">left</div> 11 </div> 12 <div class="edgePanel" data-dojo-type="dijit/layout/ContentPane" data-dojo-props="splitter: true, region: ‘bottom‘">bottom</div> 13 </div>
关于边框容器的更新细节请参考:边框容器文档。
创建Tab
一个布局挂件的职责就是布局与在其有效区域内展示其内容。很多时候它们的内容是一个或多个挂件。一个常规的需求就是一次只展示其一个子挂件,把它们作为堆栈,用户可以移动它们。最大利用空间,还允许只有在挂件被选中时才载入其内容。Dijit提供了解决方案,使用StackContainer,TabContainer和AccordionContainer。
布局要创建作为选项卡式的面板拥有不同组的div,在中心区域的底部有个选项卡条。这是一个模拟文件系统的常规而直观的UI模式。dijit/layout/TabContainer挂件实现了这个模式。它表现为包含选项卡条的子挂件,再剩余空间内展示选项卡对应的内容。
1 <div class="centerPanel" 2 data-dojo-type="dijit/layout/TabContainer" 3 data-dojo-props="region: ‘center‘, tabPosition: ‘bottom‘"> 4 <div 5 data-dojo-type="dijit/layout/ContentPane" 6 data-dojo-props="title: ‘Group 1‘"> 7 <h4>Group 1 Content</h4> 8 <p>Lorem ...</p> 9 </div> 10 <div 11 data-dojo-type="dijit/layout/ContentPane" 12 data-dojo-props="title: ‘Group Two‘"> 13 <h4>Group 2 Content</h4> 14 </div> 15 <div 16 data-dojo-type="dijit/layout/ContentPane" 17 data-dojo-props="title: ‘Long Tab Label for this One‘"> 18 <h4>Group 3 Content</h4> 19 </div> 20 </div>
为了在TabContainer中展示“group”的部分,我们先创建一个容器元素TabContainer。这个挂件本身就是BorderContainer的一个子元素,所以依然需要region属性。TabContainer为选项卡与其内容如何展示提供了一些配置选项;我们通过设置tabPosition属性将选项卡条放置在底部。TabContainer也是一个容器挂件——它管理其子挂件——所以我们需要将适当的片段块包裹其中。它们不需要有奇幻的功能,ContentPane就是不错的选择。注意它们都提供了一个title属性。title就是选项卡条中与其内容对应的标签。
StackContainer与它的朋友们
我们的布局完成了,但是如何灵活运用建立属于自己的布局,甚至是自己的布局挂件,我们需要更深入的学习。TabContainer实际上是dijit/layout/StackContainer的一个子类;汲取了StackContainer的很多功能(StackContainer继承自dijit/layout/_LayoutWidget),TabContainer特有的是如何排列与呈现内容面板,StackContainer是一个通用挂件。没有固有的控制元素来导航子挂件,但是dijit/layout/StackContainer作为一个简单的例子还是可用的。下图就是我们将TabContainer换成StackContainer的样子,我们在底部添加了一些控制挂件。为了让一切整齐,我们将边栏也使用了BorderContainer。
Dijit还提供了dijit/layout/AccordionContainer使下拉变得容易,且在dojox/layout包中还有其他的StackContainer的子类也许能满足你的需求。通常可使用dojox/layout/ExpandoPane替换ContentPane以获得一个可折叠的面板。一般情况下,在你想要自定义之前应当先看看这些已有的挂件提供的配置选项,也许已满足了你的需求。
启动与调整大小
目前为止我们以标记的方式欢快地搭建了布局,且Dojo的parser会根据标记按序实例化挂件。真正的理解dijit的布局还有很长的路要走。如果你需要以程序的方式新建并插入挂件,巫术帮不了我们。我们需要了解一下布局是何时及如何发生的。
让我们回顾一下,我们已知了:
- 根据我们定义的步骤按序创建挂件
- 布局本质上连接着有效空间的测量
- 知道startup调用挂件的domNode才接入到DOM中
- 布局挂件灵活的布置它的子挂件
当我们以程序的方式创建挂件,我们必须完成顺序由调用startup方法。这一步包含任何事情可以值发生一次一个挂件是在DOM中-包含测量与大小。布局挂件在其子挂件调用startup,所以启动事件从高层传递了下去。
默认的,所有的布局挂件都用个resize方法。这个方法在启动时候调用,且在改变尺寸(如改变窗口大小)或添加子挂件的时候也会调用。想startup一样,resize也会向下传递,允许每个挂件在布局自适应大小,传递新的尺寸给其子挂件。
让我们来看看代码如何实现:
1 <head> 2 <script src="dojo/dojo.js" data-dojo-config="async: 1"></script> 3 <script> 4 require([ 5 ‘dijit/registry‘, 6 ‘dijit/layout/BorderContainer‘, 7 ‘dijit/layout/ContentPane‘, 8 ‘dijit/layout/TabContainer‘, 9 ‘dojo/domReady!‘ 10 ], function(registry, BorderContainer, ContentPane, TabContainer) { 11 // ... 12 13 }); 14 </script> 15 </head> 16 <body class="claro"> 17 <div id="appLayout" class="demoLayout"></div> 18 </body>
我们省略了parseOnLoad,默认为false;我们使用了dojo/domReady!等待DOM载入完成。
1 // create the BorderContainer and attach it to our appLayout div 2 var appLayout = new BorderContainer({ 3 design: ‘headline‘ 4 }, ‘appLayout‘); 5 6 // create the TabContainer 7 var contentTabs = new TabContainer({ 8 region: ‘center‘, 9 id: ‘contentTabs‘, 10 tabPosition: ‘bottom‘, 11 ‘class‘: ‘centerPanel‘ 12 }); 13 14 // add the TabContainer as a child of the BorderContainer 15 appLayout.addChild(contentTabs); 16 17 // create and add the BorderContainer edge regions 18 appLayout.addChild( 19 new ContentPane({ 20 region: ‘top‘, 21 ‘class‘: ‘edgePanel‘, 22 content: ‘Header content (top)‘ 23 }) 24 ); 25 appLayout.addChild( 26 new ContentPane({ 27 region: ‘left‘, 28 id: ‘leftCol‘, 29 ‘class‘: ‘edgePanel‘, 30 content: ‘Sidebar content (left)‘, 31 splitter: true 32 }) 33 ); 34 35 // add initial content to the TabContainer 36 contentTabs.addChild( 37 new ContentPane({ 38 href: ‘contentGroup1.html‘, 39 title: ‘Group 1‘ 40 }) 41 ); 42 43 // start up and do layout 44 appLayout.startup();
每个挂件实例化的参数与之前在data-dojo-props中配置的一样。相对与标记那种隐式的包含关系,每个挂件都被使用addChild添加到了父元素下。
注意在挂件都添加了之后才调用的appLayout的startup方法。直到调用了startup,addChild只是将挂件注册为子元素。在启动以后,addChild才呈现在布局中,在父元素上自动触发resize,其子挂件也是如此。
我们可以实际添加一个子元素测试下上面所讲:
1 function addTab(name) { 2 var pane = new ContentPane({ 3 title: name, 4 content: ‘<h4>‘ + name + ‘</h4>‘ 5 }); 6 7 // add the new pane to our contentTabs widget 8 registry.byId(‘contentTabs‘).addChild(pane); 9 }
总结
我们学会了如何使用Dijit提供的组件构建动态布局的方法,一种是使用标记,另一种是以编程的方式。该方法允许你通过配置选项定义和组合你的UI。随着我们探索更多的Dijit你会发现同样的灵活性。我们可以增加我们自己的选项来定义属于自己的挂件,创建Dijit的基础内容。这个后面教程要探讨的专题。