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包。

posted @ 2015-07-17 17:34  BigBang92  阅读(2172)  评论(0编辑  收藏  举报