Статьи

Вернуться к основам с JSF и Ajax

На прошлой неделе я имел удовольствие посетить (и представить) Ajax Experience в Сан-Франциско. Коллекция бесед была хороша, и беседы «Reverse Ajax» (Comet) были особенно интересны. Я немного рассказал о JSF и Ajax, и я хотел бы поделиться техническими деталями небольшого «мэшапа», которое я собрал для своего выступления.

Эта демонстрация использует библиотеку Dynamic Faces (с JSF), чтобы проиллюстрировать стандартную службу поиска. Чтобы упростить вещи для демонстрации, в демонстрации используются только три артефакта:

  1. Одна страница JSP
  2. Один маленький файл JavaScript
  3. Один управляемый боб

Во-первых, давайте посмотрим на интерфейс:

акции-лица-ui.GIF

Пользовательский интерфейс довольно простой. Вы вводите один или несколько разделенных пробелами символов в текстовое поле «Символы» и нажимаете кнопку «Поиск». Вы можете ввести информацию прокси, если вы находитесь за брандмауэром. Наиболее интересной особенностью демо-версии является опция «Потоковая передача». Если для параметра «Потоковая передача» установлено значение «Вкл.», Клиент будет опрашивать сервер, вызывая транзакции Ajax каждые 10 секунд (или через определенный интервал времени). Опция «Удаленный / Локальный» позволяет переключать демонстрационную версию для использования локальных данных, если сетевое соединение недоступно. Если используется опция «Удаленный», для получения биржевых данных используется служба котировки акций Yahoo . Таблица данных о запасах будет динамически менять размер в зависимости от количества символов, используемых в запросе. Позволять’Взгляните на артефакты, использованные в этой демонстрации.

JSP

Вот фрагмент страницы JSP для демонстрации, показывая соответствующие части:

<f:view>
<html>
<head>
...
...
<jsfExt:scripts/>
<script type="text/javascript">
...
...
include_js('javascripts/stock-faces.js');
</script>
</head>
<body>
  <h:form id="form" prependId="false">
    <h:panelGrid styleClass="title-panel">
        <h:outputText value="Stock Query" styleClass="title-panel-text"/>
        <h:outputText value="Powered By Dynamic Faces" styleClass="title-panel-subtext"/>
    </h:panelGrid>
    <h:panelGrid border="1" columns="1" styleClass="panel-input-border">
        <h:panelGrid border="1" columns="7">
            <h:outputText value="Symbol:"/>
            <h:inputText id="symbol"/>
            <h:commandButton id="search" value="Search" 
                onclick="DynaFaces.fireAjaxTransaction(this, {});return false;"
                actionListener="#{bean.getStockInfo}" />
            <h:outputText value="Proxy Host:"/>
            <h:inputText id="proxyHost"/>
            <h:outputText value="Proxy Port:"/>
            <h:inputText id="proxyPort"/>
            <h:outputText value="Streaming:"/>
            <h:selectOneMenu id="streaming" value="Off" 
                onchange="toggleStreaming()">
                <f:selectItem itemValue="Off" itemLabel="Off"/>
                <f:selectItem itemValue="On" itemLabel="On"/>
            </h:selectOneMenu>
            <h:selectOneMenu id="connection" value="Local"> 
                <f:selectItem itemValue="Local" itemLabel="Local"/>
                <f:selectItem itemValue="Remote" itemLabel="Remote"/>
            </h:selectOneMenu>
        </h:panelGrid>
    </h:panelGrid>
          
    <h:panelGrid id="stockdata" border="1" columns="8"
        styleClass="panel-data-border" rendered="false">
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Symbol"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Name"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Open"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Last"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value=""/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Change"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Change %"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Volume"/>
            </f:facet>
        </h:panelGrid>
    </h:panelGrid>
           
  </h:form>
</body>
</html>
</f:view>

Вот объяснение «синих» разделов во фрагменте кода:

  1. <jsfExt: scripts /> — это стандартный тег для приложений Dynamic Faces, включающий библиотеку JavaScript Dynamic Faces.
  2. В include_js ( ‘JavaScripts / СТОК-faces.js’); line — это просто служебная функция, которая загружает JavaScript-файл приложения stock-faces.js (о котором мы поговорим далее).
  3. К тегу h: commandButton прикреплен обработчик события JavaScript onclickDynaFaces.fireAjaxTransaction , который отправляет Ajax-запрос на сервер при нажатии кнопки. ActionListener указано # {} bean.getStockInfo будут выполнены на сервере , как обычно. Но ключ в том, что любые представления или манипуляции компонентов JSF, выполняемые на сервере, будут происходить через Ajax.
  4. Опция streaming — это просто компонент h: selectOneMenu, который имеет обработчик события JavaScript onchange .
  5. Далее следует «заполнитель» для нашей динамической таблицы данных о запасах. Для отображаемого атрибута установлено значение false , но оно будет установлено равным true из кода приложения, когда есть данные о запасах для возврата.

Получить полный источник этого файла здесь .

Приложение JavaScript

stock-faces.js

var pollId;

/** Delay between requests to the server when polling. */
var pollDelay = 10000;

/** Start polling the server */
function start() {
    pollId = setInterval(poll, pollDelay);
}

/** Stop polling the server */
function stop() {
    clearInterval(pollId);
}

function poll() {
    queueEvent();
    DynaFaces.fireAjaxTransaction(null, {});
}

function queueEvent() {
    var actionEvent =
        new DynaFaces.ActionEvent("search",
        DynaFaces.PhaseId.INVOKE_APPLICATION);
    DynaFaces.queueFacesEvent(actionEvent);
    return false;
}

function toggleStreaming() {
    var menu = document.getElementById("streaming");
    var idx = menu.selectedIndex;
    var streaming = menu[idx].value;
    if (streaming == "Off") {
        stop();
    } else if (streaming == "On") {
        start();
    }
}

 

 

  1. Задержка опроса (или интервал времени между вызовами на сервер составляет 10 секунд.
  2. Функция start () запускает опрос сервера.
  3. Функция stop () останавливает опрос сервера.
  4. Функция poll () выполняет две функции:

    1. Он ставит в очередь событие действия JSF на стороне сервера .
    2. Запускает Ajax-запрос к серверу, используя библиотеку Dynamic Faces.
  5. Функция queueEvent () ставит в очередь событие действия JSF на стороне сервера, используя библиотеку Dynamic Faces. Это событие действия будет обработано во время стандартной обработки жизненного цикла JSF при прохождении запроса Ajax.
  6. Функция toggleStreaming () просто переключает значение элемента управления «потоковым» меню.

Получить полный источник этого файла здесь .

JSF Managed Bean

Bean.java (насколько оригинально …)

/**
 * This bean has methods to retrieve stock information from
 * the Yahoo quote service.
 */
public class Bean {

    private static final String SERVICE_URL =
            "http://quote.yahoo.com/d/quotes.csv";

    /**
     * Action method that is used to retrieve stock information.
     * This method uses two helper methods - one to get the
     * stock information, and the other to dynamically build
     * the "data" components for the UI.
     */
    public void getStockInfo(ActionEvent ae) {
...
...
                        stockData = getStockData(symbols);
                        buildUI(stockData);
...
    }

    /**
     * Helper method to get the stock data (remotely).
     */
    private String[] getStockData(String[] symbols)
        throws IOException, MalformedURLException {
        String[] data = new String[symbols.length];
        for (int i=0; i<symbols.length; i++) {
            StringBuffer sb = new StringBuffer(SERVICE_URL);
            sb.append("?s=");
            sb.append(symbols[i]);
            sb.append("&f=snol1cp2v=.csv");
            String url = sb.toString();
            URLConnection urlConn = null;
            InputStreamReader inputReader = null;
            BufferedReader buff = null;
            try {
                urlConn = new URL(url).openConnection();
                inputReader = new InputStreamReader(
                    urlConn.getInputStream());
                buff = new BufferedReader(inputReader);
                data[i] = buff.readLine();
                data[i] = data[i].replace( "\"", "" );
...
...
        }
        return data;
    }

    /**
     * Helper method to dynamically add JSF components to display
     * the data.
     */
    private void buildUI(String[] stockData) {
        FacesContext context = FacesContext.getCurrentInstance();
        UIForm form = (UIForm)context.getViewRoot().findComponent("form");
        UIPanel dataPanel = (UIPanel)form.findComponent("stockdata");
        String buffer = null;
        dataPanel.getChildren().clear();
        for (int i=0; i<stockData.length; i++) {
            String[] data = stockData[i].split("\\,");
            UIOutput outputComponent = null;
            UIGraphic imageComponent = null;
...
...
            // Create and add components wth data values

            // Symbol

            outputComponent = new UIOutput();
            outputComponent.setValue(data[0]);
            dataPanel.getChildren().add(outputComponent);

            // Name

            outputComponent = new UIOutput();
            outputComponent.setValue(data[1]);
            dataPanel.getChildren().add(outputComponent);

            // Open Price (if any)

            outputComponent = new UIOutput();
            try {
                openPrice = new Double(data[2]).doubleValue();
            } catch (NumberFormatException nfe) {
                openPriceAvailable = false;
            }
            outputComponent.setValue(data[2]);
            dataPanel.getChildren().add(outputComponent);
...
...
        }
        dataPanel.setRendered(true);
    }

Этот управляемый компонент JSF имеет метод действия getStockInfo, который выполняет две функции:

  1. Он использует вспомогательный метод getStockData для связи со службой котировок акций Yahoo (как определено в SERVICE_URL) для получения биржевых данных для всех символов.
  2. Он использует вспомогательный метод buildUI для создания компонентов JSF (из исходных данных) и добавляет компоненты JSF в представление компонентов JSF. После того, как все компоненты созданы и добавлены, он устанавливает для атрибута рендеринга значение true в компоненте JSF «stockdata».

Метод действия getStockInfo вызывается в двух отдельных случаях:

  1. Вызывается при нажатии кнопки «Поиск».
  2. Он также вызывается в результате запроса Ajax «опрос». Это связано с тем, что каждый опрос клиента ставит в очередь событие, связанное с этим обработчиком событий. Обратитесь к методу queueEvent в файле JavaScript stock-faces.js .

Получить полный источник этого файла здесь .

Резюме

Мы увидели, как мы можем объединить возможности сборки компонентов JSF с Ajax для создания динамических приложений. В этом приложении показано использование двух функций Dynamic Faces:

  1. fireAjaxTransaction
  2. Удаленная организация событий JSF из JavaScript

Это приложение включено в качестве одного из примеров приложений в проекте jsf-extensions . Пожалуйста, обратитесь к FAQ по jsf-extensions для получения и сборки исходного кода.

Ресурсы

jsf-расширений
GlassFish
javaserverfaces