h:commandButton multiple actions: download file and render ajax table
Asked Answered
H

2

3

I currently have 2 command buttons and one listbox. Based on listbox selection, the result generated can be shown in a download-able file or rendered as an HTML table. The getFile() code is based on BalusC's PDF Handling tutorial, while getTable() sets resultTable.

<h:form>
<fieldset>
    <h:selectManyListbox id="listbox" value="#{form.items}">
        <f:selectItems value="#{form.allItems}">
    </h:selectManyListbox>
</fieldset>
<h:commandButton value="Get File" action="#{form.getFile}">
<h:commandButton value="Get Table" action="#{form.getTable}">
    <f:ajax render="result_table" execute="listbox" />
</h:commandButton>
<h:panelGrid id="result_table">
    <table>
        <thead></thead>
        <tbody>
            <ui:repeat var="table" value="#{form.resultTable}">
            </ui:repeat>
        </tbody>
    </table>
</h:panelGrid>

Both buttons are working fine so far. However, I want to combine both actions into one button. When I test this out with a button that fires off both actions, nothing happens (no file save as dialog or table rendered). Is this because one action is ajax or because the other action finishes with facesContext.responseComplete();?

<h:commandButton value="Get Both" action="#{form.getBoth}">
    <f:ajax render="result_table" execute="listbox" />
</h:commandButton>

getBoth() {
    getTable();
    getFile();
}

Additionally I would like a checkbox where if it is checked, save as dialog pops up and table is rendered. If it is not checked, only table is rendered.

Hiding answered 29/11, 2010 at 13:20 Comment(0)
P
1

Unfortunately, that's not possible with HTTP. You can send only one response back per request. You cannot merge the response containing the PDF file and the ajax response into one response. Since this is a HTTP restriction, JSF can't do any much for you. Also, downloading a file using Ajax is not possible at all since JavaScript can't force the browser to pop a Save As dialogue nor have any access to local disk file system due to security restrictions.

A workaround would be to fire two HTTP requests on a single button click where the second request returns Content-Disposition: attachment so that the response of the other request keeps untouched. You can achieve this by adding an onclick to the command button.

<h:commandButton onclick="window.location='/context/pdfservlet/filename.pdf'">

and create a PDF servlet which roughly look like this FileServlet example. As you see, it's not possible to invoke a JSF action by this. You have to refactor the PDF download method to a HttpServlet class which does the job in doGet() method. For any necessary communication between the JSF managed bean and the servlet, you could use the session scope or pass the desired information (just the PDF file identifier?) by request path or parameter.

Percolator answered 29/11, 2010 at 13:42 Comment(22)
Thanks for the explanation. I'll just keep using the two buttons.Hiding
I currently have the Content-Disposition as attachment already, but instead of a File as input input = new BufferedInputStream(new FileInputStream(file), DEFAULT_BUFFER_SIZE); , I have another inputStream new BufferedInputStream(new ByteArrayInputStream(output_b.toByteArray()), DEFAULT_BUFFER_SIZE); because file is created on the fly into output_b without saving to server. So I don't think I would be able to use this onclick.Hiding
How do you create ByteArrayInputStream? Is it been created on basis of a certain set of parameters? You could basically supply the servlet the same set of parameters so that the servlet can obtain the same InputStream. E.g. onclick="servlet?param1=#{bean.param1}&amp;param2=#{bean.param2}" and the in servlet String param1 = request.getParameter("param1") and so on.Percolator
I'm using an external library which allows me to turn Java into xml, and I'm outputting them into a ByteArrayOutputStream output_b. I will have to think about this HttpServlet more. Thanks.Hiding
It should be possible to move that to a servlet. You just have to supply it the same parameters as you're using in your JSF bean to feed the library.Percolator
I haven't implemented your suggestion yet, but what if I want to have the option to choose whether to download file or not. i.e. If I check this box, button will show download dialog plus render table. If box not checked, then only render table. Can I fire onclick only if isChecked = trueHiding
Ah yes, that gets complicated. You could add another JS code which checks that. E.g. if (document.getElementById('formid:checkboxid').checked) window.location='....pdf file';.Percolator
ok, I refactored code into fileServlet class and have <h:commandButton value="Get Both" onclick="fileServlet?param1=test&amp;param2=#{form.itemsString}" action="#{form.getTable}"> but it's only rendering table. For web.xml I completed the <servlet></servlet> portion but I don't know how to fill out <servlet-mapping>. Do I need it if everything is under root?Hiding
You forgot the window.location part. So: onclick="window.location='fileServlet?param1=test&amp;param2=#{form.itemsString}'"Percolator
Actually I thought it was working but it isn't. param2=#{form.itemsString}" doesn't return the value(s) selected in the selectManyListbox, it was just return the first item on the list (my default value). As a test, I put System.out.println(getItemString()); in getTable() and it was returning the correct selected values. So How do I get the Javascript to pull the latest data?Hiding
How does the string look like? In HTTP, multiple values for the same parameter should be sent like as name=value1&name=value2&name=value3 and should be obtained in servlet by request.getParameterValues("name") which returns String[].Percolator
Oh, I have been getting the items property and then delimit with ; in the background. I do this with the rest of my code as well. I didn't know the parameter can be send in a chain like that. So for itemString it's like 1;2;3, and then when I retrieve using getParameter("param2") I just parse the string.Hiding
I tried doing "window.location='fileServlet?param1=test&amp;param2=<h:outputText value='#{form.itemsString}' />'" as described in ajaxprojects.com/ajax/tutorialdetails.php?itemid=600 but I'm getting same default value.Hiding
The h:outputText is unnecessary when using Facelets instead of JSP. Assuming that you have items one, two, three, then the resulting onclick in HTML source (rightclick page, View Source in browser) should look like this: "window.location='fileServlet?param1=test&amp;param2=one&amp;param2=two&amp;param2=three'". Then you'll be able to obtain the as String[] param2 = request.getParameterValues("param2");. The &amp; can in plain HTML also be &, but that would result in a XML parser error because Facelets is based on XML.Percolator
How do I chain like that if selection (not just one, two, and three) depends on #{form.items}. I would have to get items as a Collection in JS, and then loop through to get param2=...?Hiding
Add another getter which converts it to String with help of StringBuilder in a simple for loop.Percolator
Thanks for the example with StringBuilder. However, I just tried changing the selectManyListbox into a selectOneMenu value="#{form.item}" and "window.location='fileServlet?param1=test&amp;param2=#{form.item}' yet I'm still not getting the selected item, but just the first item on the list (which is set on page load).Hiding
Oh yes, you're right. It doesn't submit the form at all so the values are not updated at the point when you're printing the onclick URL. You would indeed need to populate the query string with help of JavaScript based on the current form during onclick. Nasty, nasty, but doable.Percolator
When I asked this question, I didn't envision it to be such a challenge haha. I'm really grateful for your help.Hiding
I shall go look up how to get selected list box values in JS.Hiding
You're welcome. This is actually a different subject and your initial question has to the point already been answered. Feel free to post a new question on [html][javascript] tags if you stucks. Hint: var dropdown = document.getElementById('dropdownid'); for (var i = 0; i < dropdown.length; i++) { if (dropdown.options[i].selected) { ....Percolator
See stackoverflow.com/questions/4338538/… for Javascript question.Hiding
K
0

I pass for a similar case, in my case i get resolved using ajax richfaces tag lib and surround commandbuton with ajax form tag.

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j">

<a4j:form id="formDownloads">
<rich:panel>
            <h:commandButton value="Exportar para PDF" status= "block" ... />
</rich:panel>
</a4j:form> 
Kara answered 29/9, 2014 at 16:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.