• JSF页面中的JS取得受管bean的数据(受管bean发送数据到页面)


      JSF中引入jsf.js文件之后,可以拦截jsf.ajax.request请求。一直希望有一种方法可以像jquery的ajax一样,能在js中异步取得服务器端发送的数据。无奈标准JSF并没有提供这样的方法。在一些JSF框架里面提供了这样的方法,比如primefaces的onComplete方法就是返回数据到一个js方法中的。JSF能在bean里面更新视图(非ajax更新),其中的PartialViewContext类就可以做到局部更新UI,在bean里获取到这个UI就可以了。于是在网上翻看了很多开源的JSF框架,无意中发现omnifaces这个开源框架。官网:http://omnifaces.org/。

      当然,一个框架的东西会有很多,以个人之力要全部参透会有一些难度。开源框架更是集思广益,不过我所需要的只是其中的一部分而已,即在js中取得bean返回的数据。

      根据omnifaces的showcase http://showcase.omnifaces.org/ 看到其中的一个Ajax工具类根据PartialViewContext类做到了js取得返回数据,不论字符串或者对象数据都可以发送,非常方便。于是根据提供的源码,顺藤摸瓜,copy了必须支持的类,整理出来了一个仅Ajax发送数据的jar。

      omnifaces做的步骤大概是先重写了PartialViewContextFactory这个类,然后在配置文件中配置该重写的类。

      

     1 /**
     2  * This partial view context factory takes care that the {@link OmniPartialViewContext} is properly initialized.
     3  *
     4  */
     5 public class CustomPartialViewContextFactory extends PartialViewContextFactory {
     6 
     7     // Variables ------------------------------------------------------------------------------------------------------
     8 
     9     private PartialViewContextFactory wrapped;
    10 
    11     // Constructors ---------------------------------------------------------------------------------------------------
    12 
    13     /**
    14      * Construct a new OmniFaces partial view context factory around the given wrapped factory.
    15      * @param wrapped The wrapped factory.
    16      */
    17     public CustomPartialViewContextFactory(PartialViewContextFactory wrapped) {
    18         this.wrapped = wrapped;
    19     }
    20 
    21     // Actions --------------------------------------------------------------------------------------------------------
    22 
    23     /**
    24      * Returns a new instance of {@link OmniPartialViewContext} which wraps the original partial view context.
    25      */
    26     @Override
    27     public PartialViewContext getPartialViewContext(FacesContext context) {
    28         return new CustomPartialViewContext(wrapped.getPartialViewContext(context));
    29     }
    30 
    31     /**
    32      * Returns the wrapped factory.
    33      */
    34     @Override
    35     public PartialViewContextFactory getWrapped() {
    36         return wrapped;
    37     }
    38 
    39 }

     该类重写还需要一个PartialViewContext ,此处没有直接继承PartialViewContext ,而是继承了PartialViewContext 的子类PartialViewContextWrapper,并重写了里面的方法:

      1 /**
      2  * <p>
      3  * This OmniFaces partial view context extends and improves the standard partial view context as follows:
      4  * <ul>
      5  * <li>Support for executing callback scripts by {@link PartialResponseWriter#startEval()}.</li>
      6  * <li>Support for adding arguments to an ajax response.</li>
      7  * <li>Any XML tags which Mojarra and MyFaces has left open after an exception in rendering of an already committed
      8  * ajax response, will now be properly closed. This prevents errors about malformed XML.</li>
      9  * <li>Fixes the no-feedback problem when a {@link ViewExpiredException} occurs during an ajax request on a page which
     10  * is restricted by <code>web.xml</code> <code>&lt;security-constraint&gt;</code>. The enduser will now properly be
     11  * redirected to the login page instead of retrieving an ajax response with only a changed view state (and effectively
     12  * thus no visual feedback at all).</li>
     13  * </ul>
     14  * You can use the {@link Ajax} utility class to easily add callback scripts and arguments.
     15  * <p>
     16  * This partial view context is already registered by OmniFaces' own <code>faces-config.xml</code> and thus gets
     17  * auto-initialized when the OmniFaces JAR is bundled in a web application, so end-users do not need to register this
     18  * partial view context explicitly themselves.
     19  *
     20  * @author Bauke Scholtz
     21  * @since 1.2
     22  * @see OmniPartialViewContextFactory
     23  */
     24 public class CustomPartialViewContext extends PartialViewContextWrapper {
     25 
     26     // Constants ------------------------------------------------------------------------------------------------------
     27 
     28     private static final String AJAX_DATA = "var Faces=Faces||{};Faces.Ajax={data:%s};";
     29     private static final String ERROR_NO_OMNI_PVC = "There is no current CustomPartialViewContext instance.";
     30 
     31     // Variables ------------------------------------------------------------------------------------------------------
     32 
     33     private PartialViewContext wrapped;
     34     private Map<String, Object> arguments;
     35     private List<String> callbackScripts;
     36     private CustomPartialResponseWriter writer;
     37 
     38     // Constructors ---------------------------------------------------------------------------------------------------
     39 
     40     /**
     41      * Construct a new OmniFaces partial view context around the given wrapped partial view context.
     42      * @param wrapped The wrapped partial view context.
     43      */
     44     public CustomPartialViewContext(PartialViewContext wrapped) {
     45         this.wrapped = wrapped;
     46         setCurrentInstance(this);
     47     }
     48 
     49     // Actions --------------------------------------------------------------------------------------------------------
     50 
     51     @Override
     52     public PartialResponseWriter getPartialResponseWriter() {
     53         if (writer == null) {
     54             writer = new CustomPartialResponseWriter(this, super.getPartialResponseWriter());
     55         }
     56 
     57         return writer;
     58     }
     59 
     60     @Override // Necessary because this is missing in PartialViewContextWrapper (will be fixed in JSF 2.2).
     61     public void setPartialRequest(boolean partialRequest) {
     62         getWrapped().setPartialRequest(partialRequest);
     63     }
     64 
     65     @Override
     66     public PartialViewContext getWrapped() {
     67         return wrapped;
     68     }
     69 
     70     /**
     71      * Add an argument to the partial response. This is as JSON object available by <code>OmniFaces.Ajax.data</code>.
     72      * For supported argument value types, read {@link Json#encode(Object)}. If a given argument type is not supported,
     73      * then an {@link IllegalArgumentException} will be thrown during end of render response.
     74      * @param name The argument name.
     75      * @param value The argument value.
     76      */
     77     public void addArgument(String name, Object value) {
     78         if (arguments == null) {
     79             arguments = new HashMap<>();
     80         }
     81 
     82         arguments.put(name, value);
     83     }
     84 
     85     /**
     86      * Add a callback script to the partial response. This script will be executed once the partial response is
     87      * successfully retrieved at the client side.
     88      * @param callbackScript The callback script to be added to the partial response.
     89      */
     90     public void addCallbackScript(String callbackScript) {
     91         if (callbackScripts == null) {
     92             callbackScripts = new ArrayList<>();
     93         }
     94 
     95         callbackScripts.add(callbackScript);
     96     }
     97 
     98     /**
     99      * Reset the partial response. This clears any JavaScript arguments and callbacks set any data written to the
    100      * {@link PartialResponseWriter}.
    101      * @see FullAjaxExceptionHandler
    102      */
    103     public void resetPartialResponse() {
    104         if (writer != null) {
    105             writer.reset();
    106         }
    107 
    108         arguments = null;
    109         callbackScripts = null;
    110     }
    111 
    112     /**
    113      * Close the partial response. If the writer is still in update phase, then end the update and the document. This
    114      * fixes the Mojarra problem of incomplete ajax responses caused by exceptions during ajax render response.
    115      * @see FullAjaxExceptionHandler
    116      */
    117     public void closePartialResponse() {
    118         if (writer != null && writer.updating) {
    119             try {
    120                 writer.endUpdate();
    121                 writer.endDocument();
    122             }
    123             catch (IOException e) {
    124                 throw new FacesException(e);
    125             }
    126         }
    127     }
    128 
    129     // Static ---------------------------------------------------------------------------------------------------------
    130 
    131     /**
    132      * Returns the current instance of the OmniFaces partial view context.
    133      * @return The current instance of the OmniFaces partial view context.
    134      * @throws IllegalStateException When there is no current instance of the OmniFaces partial view context. That can
    135      * happen when the {@link OmniPartialViewContextFactory} is not properly registered, or when there's another
    136      * {@link PartialViewContext} implementation which doesn't properly delegate through the wrapped instance.
    137      */
    138     public static CustomPartialViewContext getCurrentInstance() {
    139         return getCurrentInstance(getContext());
    140     }
    141 
    142     /**
    143      * Returns the current instance of the OmniFaces partial view context from the given faces context.
    144      * @param context The faces context to obtain the current instance of the OmniFaces partial view context from.
    145      * @return The current instance of the OmniFaces partial view context from the given faces context.
    146      * @throws IllegalStateException When there is no current instance of the OmniFaces partial view context. That can
    147      * happen when the {@link OmniPartialViewContextFactory} is not properly registered, or when there's another
    148      * {@link PartialViewContext} implementation which doesn't properly delegate through the wrapped instance.
    149      */
    150     public static CustomPartialViewContext getCurrentInstance(FacesContext context) {
    151         CustomPartialViewContext instance = getContextAttribute(context, CustomPartialViewContext.class.getName());
    152 
    153         if (instance != null) {
    154             return instance;
    155         }
    156 
    157         // Not found. Well, maybe the context attribute map was cleared for some reason. Get it once again.
    158         instance = unwrap(context.getPartialViewContext());
    159 
    160         if (instance != null) {
    161             setCurrentInstance(instance);
    162             return instance;
    163         }
    164 
    165         // Still not found. Well, maybe RichFaces is installed which doesn't use PartialViewContextWrapper.
    166         if (Hacks.isRichFacesInstalled()) {
    167             PartialViewContext pvc = Hacks.getRichFacesWrappedPartialViewContext();
    168 
    169             if (pvc != null) {
    170                 instance = unwrap(pvc);
    171 
    172                 if (instance != null) {
    173                     setCurrentInstance(instance);
    174                     return instance;
    175                 }
    176             }
    177         }
    178 
    179         // Still not found. Well, it's end of story.
    180         throw new IllegalStateException(ERROR_NO_OMNI_PVC);
    181     }
    182 
    183     private static void setCurrentInstance(CustomPartialViewContext instance) {
    184         setContextAttribute(CustomPartialViewContext.class.getName(), instance);
    185     }
    186 
    187     private static CustomPartialViewContext unwrap(PartialViewContext context) {
    188         PartialViewContext unwrappedContext = context;
    189 
    190         while (!(unwrappedContext instanceof CustomPartialViewContext) && unwrappedContext instanceof PartialViewContextWrapper) {
    191             unwrappedContext = ((PartialViewContextWrapper) unwrappedContext).getWrapped();
    192         }
    193 
    194         if (unwrappedContext instanceof CustomPartialViewContext) {
    195             return (CustomPartialViewContext) unwrappedContext;
    196         }
    197         else {
    198             return null;
    199         }
    200     }
    201 
    202     // Nested classes -------------------------------------------------------------------------------------------------
    203 
    204     /**
    205      * This OmniFaces partial response writer adds support for passing arguments to JavaScript context, executing
    206      * oncomplete callback scripts, resetting the ajax response (specifically for {@link FullAjaxExceptionHandler}) and
    207      * fixing incomlete XML response in case of exceptions.
    208      * @author Bauke Scholtz
    209      */
    210     private static class CustomPartialResponseWriter extends PartialResponseWriter {
    211 
    212         // Variables --------------------------------------------------------------------------------------------------
    213 
    214         private CustomPartialViewContext context;
    215         private PartialResponseWriter wrapped;
    216         private boolean updating;
    217 
    218         // Constructors -----------------------------------------------------------------------------------------------
    219 
    220         public CustomPartialResponseWriter(CustomPartialViewContext context, PartialResponseWriter wrapped) {
    221             super(wrapped);
    222             this.wrapped = wrapped; // We can't rely on getWrapped() due to MyFaces broken PartialResponseWriter.
    223             this.context = context;
    224         }
    225 
    226         // Overridden actions -----------------------------------------------------------------------------------------
    227 
    228         /**
    229          * An override which checks if the web.xml security constraint has been triggered during this ajax request
    230          * (which can happen when the session has been timed out) and if so, then perform a redirect to the originally
    231          * requested page. Otherwise the enduser ends up with an ajax response containing only the new view state
    232          * without any form of visual feedback.
    233          */
    234         @Override
    235         public void startDocument() throws IOException {
    236             wrapped.startDocument();
    237             String loginURL = WebXml.INSTANCE.getFormLoginPage();
    238 
    239             if (loginURL != null) {
    240                 FacesContext facesContext = FacesContext.getCurrentInstance();
    241                 String loginViewId = normalizeViewId(facesContext, loginURL);
    242 
    243                 if (loginViewId.equals(getViewId(facesContext))) {
    244                     String originalURL = getRequestAttribute(facesContext, "javax.servlet.forward.request_uri");
    245 
    246                     if (originalURL != null) {
    247                         redirect(originalURL);
    248                     }
    249                 }
    250             }
    251         }
    252 
    253         /**
    254          * An override which remembers if we're updating or not.
    255          * @see #endDocument()
    256          * @see #reset()
    257          */
    258         @Override
    259         public void startUpdate(String targetId) throws IOException {
    260             updating = true;
    261             wrapped.startUpdate(targetId);
    262         }
    263 
    264         /**
    265          * An override which remembers if we're updating or not.
    266          * @see #endDocument()
    267          * @see #reset()
    268          */
    269         @Override
    270         public void endUpdate() throws IOException {
    271             updating = false;
    272             wrapped.endUpdate();
    273         }
    274 
    275         /**
    276          * An override which writes all {@link OmniPartialViewContext#arguments} as JSON to the extension and all
    277          * {@link OmniPartialViewContext#callbackScripts} to the eval. It also checks if we're still updating, which
    278          * may occur when MyFaces is used and an exception was thrown during rendering the partial response, and then
    279          * gently closes the partial response which MyFaces has left open.
    280          */
    281         @Override
    282         public void endDocument() throws IOException {
    283             if (updating) {
    284                 // If endDocument() method is entered with updating=true, then it means that MyFaces is used and that
    285                 // an exception was been thrown during ajax render response. The following calls will gently close the
    286                 // partial response which MyFaces has left open.
    287                 // Mojarra never enters endDocument() method with updating=true, this is handled in reset() method.
    288                 endCDATA();
    289                 endUpdate();
    290             }
    291             else {
    292                 if (context.arguments != null) {
    293                     startEval();
    294                     write(String.format(AJAX_DATA, Json.encode(context.arguments)));
    295                     endEval();
    296                 }
    297 
    298                 if (context.callbackScripts != null) {
    299                     for (String callbackScript : context.callbackScripts) {
    300                         startEval();
    301                         write(callbackScript);
    302                         endEval();
    303                     }
    304                 }
    305             }
    306 
    307             wrapped.endDocument();
    308         }
    309 
    310         // Custom actions ---------------------------------------------------------------------------------------------
    311 
    312         /**
    313          * Reset the partial response writer. It checks if we're still updating, which may occur when Mojarra is used
    314          * and an exception was thrown during rendering the partial response, and then gently closes the partial
    315          * response which Mojarra has left open. This would clear the internal state of the wrapped partial response
    316          * writer and thus make it ready for reuse without risking malformed XML.
    317          */
    318         public void reset() {
    319             try {
    320                 if (updating) {
    321                     // If reset() method is entered with updating=true, then it means that Mojarra is used and that
    322                     // an exception was been thrown during ajax render response. The following calls will gently close
    323                     // the partial response which Mojarra has left open.
    324                     // MyFaces never enters reset() method with updating=true, this is handled in endDocument() method.
    325                     endCDATA();
    326                     endUpdate();
    327                     wrapped.endDocument();
    328                 }
    329             }
    330             catch (IOException e) {
    331                 throw new FacesException(e);
    332             }
    333             finally {
    334                 responseReset();
    335             }
    336         }
    337 
    338         // Delegate actions -------------------------------------------------------------------------------------------
    339         // Due to MyFaces broken PartialResponseWriter, which doesn't delegate to getWrapped() method, but instead to
    340         // the local variable wrapped, we can't use getWrapped() in our own PartialResponseWriter implementations.
    341 
    342         @Override
    343         public void startError(String errorName) throws IOException {
    344             wrapped.startError(errorName);
    345         }
    346 
    347         @Override
    348         public void startEval() throws IOException {
    349             wrapped.startEval();
    350         }
    351 
    352         @Override
    353         public void startExtension(Map<String, String> attributes) throws IOException {
    354             wrapped.startExtension(attributes);
    355         }
    356 
    357         @Override
    358         public void startInsertAfter(String targetId) throws IOException {
    359             wrapped.startInsertAfter(targetId);
    360         }
    361 
    362         @Override
    363         public void startInsertBefore(String targetId) throws IOException {
    364             wrapped.startInsertBefore(targetId);
    365         }
    366 
    367         @Override
    368         public void endError() throws IOException {
    369             wrapped.endError();
    370         }
    371 
    372         @Override
    373         public void endEval() throws IOException {
    374             wrapped.endEval();
    375         }
    376 
    377         @Override
    378         public void endExtension() throws IOException {
    379             wrapped.endExtension();
    380         }
    381 
    382         @Override
    383         public void endInsert() throws IOException {
    384             wrapped.endInsert();
    385         }
    386 
    387         @Override
    388         public void delete(String targetId) throws IOException {
    389             wrapped.delete(targetId);
    390         }
    391 
    392         @Override
    393         public void redirect(String url) throws IOException {
    394             wrapped.redirect(url);
    395         }
    396 
    397         @Override
    398         public void updateAttributes(String targetId, Map<String, String> attributes) throws IOException {
    399             wrapped.updateAttributes(targetId, attributes);
    400         }
    401 
    402     }
    403 }
    View Code

    在face-config.xml文件添加配置如下:

    1  <factory>
    2      <partial-view-context-factory>com.context.CustomPartialViewContextFactory</partial-view-context-factory>
    3  </factory>

    可以看到其中定义了两个常量,一个警告说明,一个名为AJAX_DATA,里面写的是一段js的字符串。这个常量用在了 被重写的endDocument()方法里。这个方法主要做了两件事,第一是写入要传递的数据,并且以json格式打包,第二件事是包含了一个js方法。那么可以这样认为:该方法的作用是用一个js方法取得发送的数据。

      首先我们必须清楚,JSF使用f:ajax更新客户端的底层操作是怎样的。JSF更新数据是向页面发送了xml数据的,可以在firefox的 firebug下面的网络-XML面板下看到发送的xml数据,类似于以下的数据:

    1 <partial-response id="j_id1">
    2   <changes>
    3     <update id="j_id1:javax.faces.ViewState:0">-4426271603414575392:-5845678956333562288</update>
    4   </changes>
    5 </partial-response>

    里面包含了需要更新的组件和组件状态。

    我把omnifaces的Ajax代码整理出来以后,根据官方例子的用法,写了一个小demo测试,其中页面form表单代码如下:

     1 <h:form prependId="false">
     2         <h:outputLabel value="#{test2.radom}" id="outLabel"/>
     3         <h:commandButton action="#{test2.callback}"  value="请求服务端js">
     4             <f:ajax/>
     5         </h:commandButton>
     6         <h:commandButton value="获取服务端数据到js">
     7             <f:ajax listener="#{test2.argument}"/>
     8         </h:commandButton>
     9         
    10         <h:commandButton value="showUser">
    11             <f:ajax listener="#{test2.parseUser}"/>
    12         </h:commandButton>
    13 </h:form>

    测试bean代码如下:

     1 @ManagedBean(name="test2")
     2 @ViewScoped
     3 public class Test2 implements Serializable {
     4 
     5     private static final long serialVersionUID = 8686669721840131192L;
     6 
     7     public void callback() {
     8         Ajax.oncomplete("alert('Hi, I am the oncomplete callback script!')");
     9     }
    10 
    11     public void argument() {
    12         Ajax.data("foo", "bar");
    13         Ajax.data("first", "one", "second", "two");
    14         Map<String, Object> data = new HashMap<>();
    15         data.put("bool", true);
    16         data.put("number", 1.2F);
    17         data.put("date", new Date());
    18         data.put("array", new Integer[] { 1, 2, 3, 4, 5 });
    19         data.put("list", Arrays.asList("one", "two", "three"));
    20         Ajax.data(data);
    21         Ajax.oncomplete("showData()");
    22     }
    23     
    24     public void parseUser(){
    25         Ajax.data("user", new User(1, "bigbang"));
    26         Ajax.oncomplete("showUser()");
    27     }
    28 
    29 }

    其中提供了三个页面响应的方法。当点击第一个button时,页面会弹出一个alert提示框。此时查看firefox下面的数据如下:

    1 <partial-response id="j_id1">
    2 <changes>
    3 <update id="j_id1:javax.faces.ViewState:0">-4426271603414575392:-5845678956333562288</update>
    4 <eval>alert('Hi, I am the oncomplete callback script!')</eval>
    5 </changes>
    6 </partial-response>

    点击“获取数据到js”按钮,查看xml数据:

    1 <partial-response id="j_id1">
    2 <changes>
    3 <update id="j_id1:javax.faces.ViewState:0">-3364647386979820288:-1391656100755852530</update>
    4 <eval>var Faces=Faces||{};Faces.Ajax={data:{"second":"two","number":1.2,"list":["one","two","three"],"foo":"bar","bool":true,"date":"Fri, 17 Jul 2015 09:17:50 GMT","first":"one","array":[1,2,3,4,5]}};</eval>
    5 <eval>showData()</eval>
    6 </changes>
    7 </partial-response>

    点击“showuser”按钮,查看xml数据:

    1 <partial-response id="j_id1">
    2 <changes>
    3 <update id="j_id1:javax.faces.ViewState:0">-3364647386979820288:-1391656100755852530</update>
    4 <eval>var Faces=Faces||{};Faces.Ajax={data:{"user":{"id":1,"name":"bigbang"}}};</eval>
    5 <eval>showUser()</eval>
    6 </changes>
    7 </partial-response>

    可以看出刚才的那个endDocumnt方法和AJAX_DATA常量的用意了,实际是构造了一个js对象,然后传入一个js方法到客户端,客户端会自动调用这个js方法,根据对象取得json数据。那么客户端的js只需要这样写就可以了:

     1   <script>
     2     function showData() {
     3            var data = Faces.Ajax.data;
     4            $.each(data, function(key, value) {
     5                 console.log(key+"---"+JSON.stringify(value));
     6             });
     7       }
     8 
     9       function showUser(){
    10         var data = Faces.Ajax.data;
    11         $.each(data, function(key,value){
    12             var user = JSON.stringify(value);
    13             var u = $.parseJSON(user);
    14             console.log("userName----"+u.name);
    15          }
    16   </script>

    控制台显示数据如下:

    OK,大功告成了!其中的细节不用细究,无非就是掺杂了各种数据转换为json格式、数据的包装和写入。最后说明一点,omnifaces需要CDI的支持,必须导入CDI的jar包。

  • 相关阅读:
    iOS SDK:预览和打开文档
    显示手机内联系人数量
    已知一点的经纬度和该点到另一点的距离,求另一点的经纬度
    坚持让自己的每次尝试都做到极限
    2016第52周一时间的朋友读书会
    2016年第51周日三岁看大?
    2016第51周五产品经理的十大错误
    2016第51周四外甥女走丢记
    2016第51周三产品经理如何更有说服力
    2016第51周二
  • 原文地址:https://www.cnblogs.com/bigbang92/p/jsf-javascript-bean-data.html
Copyright © 2020-2023  润新知