现象一

最近在项目中遇到这么一个问题,有些页面元素是在页面加载时通过JavaScript动态渲染而成。当生成这些元素的JavaScript脚本被放置于JSPX文件中时,界面渲染没有问题。但是当我们把生成这些页面元素的JS脚本放到JSFF时就会发现,JS脚本只在我们进入TaskFlow的第一个View被执行了,进入后续View时,后续View的JS代码加载和执行。

分析

通过分析,发现当进入TaskFlow的第一个View时,第一个View中通过<af:resource/>标签引入的JS代码能成功被添加到页面上,而且页面的onload事件也被执行,所以页面元素渲染成功。

但是当从第一个View流转到后续View时,后续View中通过<af:resource/>标签引入的JS代码则没有被加入页面中,所以页面元素都无法渲染。但是如果此时我们刷新页面,页面上的元素又能正确展现。这是因为刷新后ADF重新生成页面的HTML代码,重新解析了后View中的<af:resource/>标签,并将相关的代码引入页面。

解决

在<af:resource/>标签外套用<af:panelGroupLayout/>,这样resource就会每次都被包含到页面中。

现象二

解决了上一个问题,现在TaskFlow中所有View在展现时,JavaScript代码都能正确地被引入页面,并被执行以正确地生成页面元素。但是很快我发现了另外一个问题,那就是JavaScript代码只能在第一次展示View时成功被调用。如果我们从一个View流转到另一个View,然后再次流转回来的话,onload事件的JavaScript就不会被执行了。

分析

ADF渲染了某个View后,该View就会有缓存,再次访问时就不会再次渲染并触发onload事件。我尝试设置了TaskFlow的Refresh配置,但是无果。

问题的关键是现在无法保证每次进入View时都能触发onload事件,既然客户端脚本不行,那么是否能够通过服务端的方式在页面加载时激活相应的JavaScript代码进行元素的渲染呢?使页面在加载时能触发后台的一个Java方法,然后在Java方法内触发JS方法。按照这个思路,我们可以通过在页面添加Executable来实现。通过搜索我发现网上的确也有人遇到类似的问题,并通过这种方法解决。但是通过Executable来实现有个缺点,就是需要生成自定义的Java Bean Data Control以及配置起来比较繁琐(需要为每个页面配置Method Binding以及Executable)。

解决

经过不断的尝试,最后,我想到了一个简化的方法:

1. 在View中添加一个<af:outputText/>标签;

2. 将<af:outputText/>的Value绑定到一个bean的属性#{myBean.dummy};

3. 其实,无需在myBean中定义dummy属性,只需定义一个getDummy()方法,并在其中添加调用JS代码的逻辑;

每当JSFF展现时ADF会重新执行所有的EL表达式,包括#{myBean.dummy},这样在getDummy()中的逻辑就会被执行,相应的JS方法也会被调用。这样我们的需求就实现了。但是,我觉得将JS的调用逻辑完全放在Bean中完成可能会比较死板。如果我们需要更改调用JS的逻辑,就必须要修改Bean的方法。最后我将调用JS的逻辑进行修改,使其调用脚本内容指向前台<af:outputText/>标签的shortDesc属性:

private RichOutputText scriptSetting; //OutputText的binding变量 
   public String getDummy() { 
          String script = scriptSetting == null ? "" : scriptSetting.getShortDesc(); //获取脚本内容 
           invokeJavaScript(script);//调用JS 
        return ""; 

       }

以后如果某个页面需要在加载时调用JS脚本,只需要做下面3步:

1. 在TaskFlow中注册myBean;

2. 在页面中放置如下代码;

<af:outputText  shortDesc="youJSFunction();"  
                   value="#{myBean.dummy}" id="scriptControl" 
                      binding="#{myBean.scriptSetting}"/>

3. 修改shortDesc属性值为所需要的JS代码。