Omnifaces перед загрузкой не работает в некоторых случаях - PullRequest
0 голосов
/ 06 ноября 2018

Я использую JSF с omnifaces и cdi. На странице xhtml я использую некоторые js similiar для ( более подробно на странице jsf ниже )

window.onbeforeunload = function(e){ // some other stuff return 'unsaved data'; };

для активации диалогового окна подтверждения по умолчанию в браузере, если окно собирается быть выгруженным с сохранением несохраненных данных.

При заполнении формы и перенаправлении на ту же страницу, появляется предупреждение. Если я решу «остаться на странице», я смогу продолжить свою работу и впоследствии отправить свою форму, как я и ожидал.

Проблема: Если я перейду к другим страницам или попытаюсь закрыть вкладку , предупреждение также появится, как и ожидалось. Но в этом случае отправка http POST 'omnifaces.event: unload' отправляется на сервер. Насколько я могу судить, это вызывает onDestroy () Бина. Если я решу остаться, все значения в форме все еще присутствуют на странице, но при отправке формы NPE выбрасываются для значений (я думаю, потому что бин уже был уничтожен, не учитывая мое решение в диалоговом окне подтверждения) .

После нескольких часов исследований я не мог понять, почему во втором случае происходит выгрузка bean-компонента, в то время как первый подход ожидает результата диалога подтверждения ... Есть идеи?

Я уже заметил, что window.onbeforeunload следует вызывать с обычным js, как в ViewScoped . Это работает для первого случая, но не для второго.

EDIT :

Шаги для воспроизведения находятся на странице JSF. Если вы будете следовать им, вы сможете понять мою проблему.

SomeBean

import org.omnifaces.cdi.ViewScoped;    
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Named;
import java.io.Serializable;

@Named
@ViewScoped
public class SomeBean implements Serializable {

    private String fieldA;
    private String fieldB;
    private String info;

    @PostConstruct
    public void init(){
        this.fieldA = null;
        this.fieldB = null;
        this.info = "bean = " + this;
    }

    @PreDestroy
    public void preDestroy() {
        /* triggered when performing a navigation to other resources or
           closing the browser tab (unwanted), but not invoked if navigation is done
           within the same resource, e.g by using templates and compositions (wanted) */
        this.info = "destroy will be invoked for bean " + this;
    }

    public void submit(){
        // do smth. with the fields
    }

    public String getFieldA() {
        return fieldA;
    }

    public void setFieldA(String fieldA) {
        this.fieldA = fieldA;
    }

    public String getFieldB() {
        return fieldB;
    }

    public void setFieldB(String fieldB) {
        this.fieldB = fieldB;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}

JSF page

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"
      xml:lang="en" lang="en">
<h:head>
    <title>reproduce example</title>

    <script type="application/javascript">
        $(document).ready(function () {
            var unsavedData = false;

            function setUnsavedData(flag) {
                unsavedData = flag;
            }

            window.onbeforeunload = function (e) {
                e = e || window.event;
                if (unsavedData) {
                    if (e) {
                        e.preventDefault();
                        e.returnValue = 'Any string';
                    }
                    return 'Any string';
                } else {
                    return undefined;
                }
            };

            $(document).on("change", ":input:not(.stateless)", function () {
                setUnsavedData(true);
            });

            $(document).on("click", "button.stateless", function () {
                setUnsavedData(false);
            });
        });
    </script>
</h:head>
<h:body>
    <h:form id="myform">
        <h3>steps to reproduce:</h3><br/>
        1) enter some value for fieldA<br/>
        2) click the 'navigate via navigationBean' link > confirm the dialog and 'stay on page'<br/>
        3) press the submit button > as you can see, the bean instance is still the same and value is passed.<br/>
        4) enter some value for fieldB<br/>
        5) click somewhere else on the page to lose the input's focus (otherwise the confirm dialog won't show up!)<br/>
        7) close the browser tab or use the browser's nav buttons > confirm the dialog and 'stay on page'<br/>
        8) press the submit button again > as you can see now, submit has not been called! Pressing submit shows the bean instance has changed!<br/><br/>
        <h4> > For this example, values are still usable after the new bean was initialized. Because the original page is way more complex then this example, <br/>
            I really need to prevent onmnifaces from initializing a new bean, when a user confirms to stay on the page, even if he tries to close the tab!</h4><br/>
        <br/>
        fieldA
        <p:inputText value="#{someBean.fieldA}"/>

        <!-- note: in real code this is represented by a NavigationBean logic!-->
        <p:commandLink value="navigate via navigationBean"
                       action="#"
                       ajax="false">
        </p:commandLink>
        <br/>
        fieldB
        <p:inputText value="#{someBean.fieldB}"/>
        <br/>
        <p:commandButton
                id="submitBtn"
                value="submit"
                action="#{someBean.submit()}"
                process="@form"
                update="@form" styleClass="stateless">
        </p:commandButton>
        <br/>
        <br/>
        bean info: <p:outputLabel id="output_1" value="#{someBean.info}"/>
        <br/>
        value info for fieldA: <p:outputLabel id="output_2" value="#{someBean.fieldA}"/>
        <br/>
        value info for fieldB: <p:outputLabel id="output_3" value="#{someBean.fieldB}"/>
    </h:form>
</h:body>
</html>

Примечание : это свернутая версия, оригинал содержит больше деталей, но из-за некоторых ограничений нашей компании я не могу показать полный стек кода. Я надеюсь, что этого все еще достаточно, и imo проблемы, связанные части показаны.

Версия Omnifaces составляет 2,7

1 Ответ

0 голосов
/ 07 ноября 2018

Сценарий выгрузки @ViewScoped инициализируется в конце HTML <body>. В этот момент он проверит существующие функции window.onbeforeunload перед его украшением.

Ваша функция window.onbeforeunload определена во время $(document).ready(). Но это еще не выполнено в конце HTML <body>. Он выполняется только после конца <html>. Следовательно, сценарий выгрузки @ViewScoped не сможет правильно его декорировать.

Необходимо убедиться, что window.onbeforeunload определен до того, как сценарий выгрузки @ViewScoped будет инициализирован. Вы можете сделать это, поместив его вне $(document).ready() и импортировав файл JavaScript, содержащий определение, через <h:outputScript target="head"> или <h:outputScript target="body">. Помещение скрипта в <head> также работает, но это не рекомендуется, так как это только увеличивает размер HTML-документа и не дает браузеру возможность кэшировать скрипты.

...