
Приветствую, дорогой читатель! В данной статье я хотел изложить одну проблему, с которой я столкнулся при разработке, а также способ ее решения. Решение конечно не самое безупречное, но имеет место быть. Если вам что-то не понравиться, или вы знаете решение лучше, прошу большими огурцами меня не бить, так как я еще мал и зелен. Бейте маленькими с комментариями и поучениями.
Задача в следующем: у нас есть система, в которой есть страница на которой отображена некоторая отчетность. Там необходимо реализовать формирование Excel файла и выгрузку его для пользователя.
Страница имеет следующую приблизительную структуру, которая отображена на верхнем рисунке.
Причем особенностью является то, что таблица очень большая, и выгружать в Excel нужно много записей.
А при нажатии на строку выводиться панелька для редактирования записи.
Код формирующий файл:
public void generatePatientsExcel() { try{ log.info("Generating patients excel"); FacesContext facesContext = FacesContext.getCurrentInstance(); ExternalContext externalContext = facesContext.getExternalContext(); HttpServletResponse response = (HttpServletResponse) externalContext.getResponse(); //Create excel file Workbook wb = new HSSFWorkbook(); // // // // // // Header response.setHeader("Content-disposition", "attachment; filename=1.xls"); response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); response.setCharacterEncoding("UTF-8"); response.setContentType("application/vnd.ms-excel"); //Send Response ServletOutputStream responseOutputStream = response.getOutputStream(); wb.write(responseOutputStream); responseOutputStream.flush(); responseOutputStream.close(); facesContext.responseComplete(); log.info("Generating patients excel comlplete"); } catch (Exception ex) { ex.printStackTrace(); } }
Ну и собственно проблема
Проблема состоит в том, что при выгрузке файла формирование его происходит некоторое время, и если в этот момент нажать на строку то мы получим:
org.jboss.seam.ConcurrentRequestTimeoutException: Concurrent call to conversation
Чтобы этого не случилось необходимо навесить на onclick перед формированием файла JavaScript функцию, которая отображает Loader. И после action скрыть его тоже JavaScript функцией навешеной на oncomplete. Вроде все просто, не правда ли?
Подводные камни
Так как мы формируем файл динами��ески, мы не сохраняем его на диск, что могло б облегчить задачу, путем возвращения не HttpServletResponse, в котором файл, а переходом по ссылке, где сохранен файл на диске. Но это в свою очередь будет есть дисковое пространство сервера, поэтому динамическое формирование файла это то, что нам нужно.
Загвоздка в том, что кнопка h:commandButton не имеет oncomplete. И вообще все елементы «h:» не имеют этого event. Подумав немного: «Вообще зачем нам этот h:commandButton? Ведь у нас есть волшебный a4j:commandButton».
Реализуем выгрузку файла через a4j:commandButton, это выглядит так:
<a4j:commandButton onclick="showLoader();" image="exportExcel.png" action="#{importToExcelBean.getExcelFile()}" oncomplete="hideLoader();"/>
И все вроде бы прекрасно, но это только на первый взгляд. Так как a4j:commandButton выгружает HttpServletResponse в текущую страницу.
Пользователь выгружает файл в Excel формате, а ему открывается страница с корявками. «Непорядок»- сказал пользователь, ударив кулаком в клавиатуру, матерясь. И разочаровался в нашей системе на всю жизнь.
Можно попытаться на h:commandButton навесить a4j:support, который на onsubmit будет отображать лоадер, при action формировать файл, а на oncomplete скрывать лоадер. Но проблема в том что a4j:support не может вернуть HttpServletResponse с сервера. Он это делает тоже в страницу. Так как и все элементы a4j возвращают HttpServletResponse в страницу.
Незадача получается. Что делать, никто не знает, интернет молчит, пользователи плачут, и сам сидишь как укакался. А ведь такой простой event, но его так не хватает.
Решение, Костыли и jQuery
Решение пришло в голову коллеге. Все довольно просто.
- Делим процедуру формирования файла на две: генерацию и возвращение запроса.
- Создаем a4j:commandButton, который отвечает за формирование файла без его возвращения, отображение лоадера и его скрытие.
<a4j:commandButton onclick="showLoader();" image="exportExcel.png" action="#{importToExcelBean.generateExcel()}" oncomplete="hideLoader()"/> - Создаем скрытый h:commandButton, который возвращает файл, а также присваиваем ему id для последующего поиска.
<h:commandButton id="exelFileButton" style=" visibility: hidden;" action="#{importToExcelBean.returnResponse()}" /> - Создаем JavaScript функцию, которая с помошью jQuery находит скрытый h:commandButton по id и нажимает его.
<script type="text/javascript" > function clickButtonGetExelFile(){jQuery("#exelFileButton").click();} </script>
- Навешиваем нашу JavaScript функцию после скрытия лоадера.
<a4j:commandButton onclick="showLoader();" image="exportExcel.png" action="#{importToExcelBean.generatePatientsExcel()}" oncomplete="hideLoader();clickButtonGetExelFile();"/>
Готово, пользователь получает файл в том виде, в котором должен получать. Критическая ситуация разрешена.

