15.7.2. 创建自定义的站点模板

如果默认的Maven站点结构不符合你的要求,你可以自定义Maven站点模板。自定义该模板让你完全控制Maven Site插件的最终输出,也能让你使用非默认的目录结构。

Site插件使用了叫做Doxia的渲染引擎,而它实际上使用Velocity模板将页面渲染成XHTML。要更改默认被渲染的页面结构,我们可以在POM中配置site插件使用自定义的页面模板。site模板相当复杂,你需要有一个关于自定义配置的良好起点。一开始从Doxia的Subversion仓库default-site.vm复制默认的Velocity模板至src/site/site.vm。该模板使用Velocity模板语言编写。Velocity是一个简单的模板语言,它支持简单的宏定义,允许你使用简单标记访问对象的方法和属性。全面的介绍超出了本书的范围,要了解更多的关于Velocity的信息级全面介绍,访问Velocity的项目站点http://velocity.apache.org

default-site.xml模板相当复杂,但自定义左边导航菜单相对的比较简单。如果你试图更改一个menuItem的外观,找到menuItem宏。它位于一个如下的小节中:

#macro ( menuItem $item )

  ...
  
#end

如果你使用如下的宏定义替换了默认的宏定义,你将为每个菜单项嵌入Javascript引用,能让用户在不用重新载入整个页面的情况下展开或收缩菜单树。

#macro ( menuItem $item $listCount )
  #set ( $collapse = "none" )
  #set ( $currentItemHref = $PathTool.calculateLink( $item.href, $relativePath ) )
  #set ( $currentItemHref = $currentItemHref.replaceAll( "\\", "/" ) )

  #if ( $item && $item.items && $item.items.size() > 0 )
    #if ( $item.collapse == false )
      #set ( $collapse = "collapsed" )
    #else
      ## By default collapsed
      #set ( $collapse = "collapsed" )
    #end

    #set ( $display = false )
    #displayTree( $display $item )

    #if ( $alignedFileName == $currentItemHref || $display )
      #set ( $collapse = "expanded" )
    #end
  #end
  <li class="$collapse">
    #if ( $item.img )
      #if ( ! ( $item.img.toLowerCase().startsWith("http") || $item.img.toLowerCase().startsWith("https") ) )
        #set ( $src = $PathTool.calculateLink( $item.img, $relativePath ) )
        #set ( $src = $item.img.replaceAll( "\\", "/" ) )
        <img src="$src"/>
      #else
        <img src="$item.img" align="absbottom" style="border-width: 0"/>
      #end
    #end
    #if ( $alignedFileName == $currentItemHref )
      <strong>$item.name</strong>
    #else
      #if ( $item && $item.items && $item.items.size() > 0 )
      <a onclick="expand('list$listCount')" style="cursor:pointer">$item.name</a>
      #else
      <a href="$currentItemHref">$item.name</a>
      #end
    #end
  #if ( $item && $item.items && $item.items.size() > 0 )
    #if ( $collapse == "expanded" )
    <ul id="list$listCount" style="display:block">
    #else
    <ul id="list$listCount" style="display:none">
      #end
      #foreach( $subitem in $item.items )
        #set ( $listCounter = $listCounter + 1 )
        #menuItem( $subitem $listCounter )
      #end
    </ul>
  #end
  </li>
#end

该更改为menuItem宏添加了一个新的参数。为了使新功能正确工作,你需要更改所有对于该宏的引用,否则最终的模板可能会生成非预期的或者不一致的XHTML。要完成这些引用的更改,在mainMenu宏中进行一次替换。寻找类似下面模板片段的代码,以找到该宏。

#macro ( mainMenu $menus )
  ...
#end

使用如下的实现替换mainMenu宏。

#macro ( mainMenu $menus )
  #set ( $counter = 0 )
  #set ( $listCounter = 0 )
  #foreach( $menu in $menus )
    #if ( $menu.name )
    <h5 onclick="expand('menu$counter')">$menu.name</h5>
    #end
    <ul id="menu$counter" style="display:block">
      #foreach( $item in $menu.items )
        #menuItem( $item $listCounter )
        #set ( $listCounter = $listCounter + 1 )
      #end
    </ul>
    #set ( $counter = $counter + 1 )
  #end
#end

这个新的mainMenu宏现在和前面的menuItem宏匹配了,同时也为顶层的菜单提供了Javascript支持。点击带有子项的顶层菜单,会得到展开的菜单,能让用户在不用等待页面重新载入的情况下查看整个树。

对于menuItem宏的更改引入了一个expand() Javascript函数。该方法需要被加入到模板文件底部的主XHTML模板中。找到类似于下面的代码片段:

  <head>
    ...
    <meta http-equiv="Content-Type" content="text/html; charset=${outputEncoding}" />
    ...
  </head>

然后进行如下的替换:

  <head>
    ...
    <meta http-equiv="Content-Type" content="text/html; charset=${outputEncoding}" />
    <script type="text/javascript">
      function expand( item ) {
        var expandIt = document.getElementById( item );
        if( expandIt.style.display == "block" ) {
          expandIt.style.display = "none";
          expandIt.parentNode.className = "collapsed";
        } else {
          expandIt.style.display = "block";
          expandIt.parentNode.className = "expanded";
        }
      }
    </script>
    #if ( $decoration.body.head )
      #foreach( $item in $decoration.body.head.getChildren() )
        #if ( $item.name == "script" )
          $item.toUnescapedString()
        #else
          $item.toString()
        #end
      #end
    #end
  </head>

在修改了默认站点模板之后,你需要配置项目POM以引用这个新的站点模板。为此,你需要使用Maven Site插件的templateDirectorytemplate配置属性。

Example 15.10. 在一个项目的POM中自定义页面模板

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-site-plugin</artifactId>
        <configuration>
          <templateDirectory>src/site</templateDirectory>
          <template>site.vm</template>
        </configuration>
      </plugin>
    </plugins>
  </build>
  ...
</project>


现在,你应该能够重新生成你项目的web站点了。这时你可能会注意到maven站点的资源和CSS丢失了。当一个Maven项目自定义站点模板的时候,Site插件认为该项目会提供所有默认的图片和CSS。为了提供项目的资源,你可以从默认的Doxia站点渲染项目中复制资源到你项目的资源目录中,运行如下命令:

$ svn co \
        http://svn.apache.org/repos/asf/maven/doxia/doxia-sitetools/trunk/doxia-site-renderer
$ rm \
        doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/\
        css/maven-theme.css
$ cp -rf \
      doxia-site-renderer/src/main/resources/org/apache/maven/doxia/siterenderer/resources/* \
      sample-project/src/site/resources

签出doxia-site-renderer项目,删除默认的maven-theme.css文件,然后复制所有的资源到你的src/site/resources目录。

当你重新生成站点的时候,你会注意到一些菜单项看起来像常规未修饰的文本。这是因为站点CSS和新的自定义页面模板发生了一些诡异的交互。修改site.css,为菜单恢复正确的链接颜色后便可修复。只要添加如下配置:

li.collapsed, li.expanded, a:link {
  color:#36a;
}

在重新生成站点之后,菜单的链接颜色看起来正确了。如果你将这个新的站点模板应用到本章的sample-project项目中,你会看到菜单现在包含了一棵树。点击“Developer Resources”不会再打开“Developer Resources”页面;取而代之的,是展开子菜单。由于你将Developer Resources转换成了一个动态折叠的子菜单,你不再可以通过该菜单打开developer/index.apt页面。为了对付这种变化,你可以在该子菜单下添加一个Overview链接,指向同样的页面:

Example 15.11. 给站点描述符添加一个菜单项

<project name="Hello World">
  ...
  <menu name="Main Menu">
    ...
    <item name="Developer Resources" collapse="true">
      <item name="Overview" href="/developer/index.html"/>
      <item name="System Architecture" href="/developer/architecture.html"/>
      <item name="Embedder's Guide" href="/developer/embedding.html"/>
    </item>
  </menu>
  ...
</project>