[JSF] Warnen beim Verlassen der Seite mit ungespeicherten Änderungen

MadM

Mitglied
Hi Folks,

ich arbeite an einer JSF(1.2)-Anwendung mit RichFaces 3.3.2. Ich will den Benutzer warnen, falls er ein Formular verlässt (d.h. einen anderen Menüpunkt anwählt), ohne seine Änderungen zu speichern.
Knackpunkt: Wie stelle ich fest, ob er was geändert hat? Ich habe kein eindeutiges Ergebnis googeln können. Hier und da fällt mal das Stichwort onBeforeUnload...

Szenario:
Menü verwendet MenuBean (request-scoped)
Formular verwendet CustomerBean (request-scoped)
Context-Daten (über den angemeldeten User, seine Berechtigungen etc.) werden in ContextBean (session-scoped) verwaltet.

Danke für die Hilfe
Gruß
Matthias
 
*PUSH*

Hat denn wirklich niemand schon einmal ein vergleichbares Szenario gehabt? Oder zumindest einen Lösungsansatz?

Gruß
Matthias
 
Eigentlich ist es doch ganz einfach...
Du hast zwei Möglichkeiten, die Eine etwas aufwändig: Du kannst die Werte aus der DB mit den tatsächlichen Werten in den Variablen
vergleichen ODER:
du setzt eine boolsche Variable in der Methode, die zwingend aufgerufen wird bei einer Änderung auf true und prüfst dies
mit einer gerenderten Message in deiner JSP ab...

LG,

Bexx

EDIT:

Achso, in deiner Save-Methode musst du die Variable natürlich wieder auf false setzen :)
 
Zuletzt bearbeitet:
Naja, so einfach ist es dann doch nicht. Das Problem ist doch, überhaupt zu merken, dass der Benutzer das Formular verlässt. Er drückt ja keinen Button innerhalb des Formulars, sondern wählt einfach einen anderen Menüpunkt.
Ich müsste also die erwähnte boolsche Variable setzen, sobald der Benutzer etwas ändert. Wie soll ich das aber feststellen? Es wird ja kein HTTP-Request abgeschickt, wenn ein Formularfeld geändert wird, und somit auch keine Methode der Managed-Bean aufgerufen. Und selbst wenn, ich will ja nicht in jedem Setter prüfen, ob sich was geändert hat.
Du erkennst das Problem? Mir fällt dazu nur eine unschöne JavaScript-Lösung (onKey... o.Ä.) ein...
 
Ich sehe hierbei noch ganz andere Probleme.
Wie fragst du ab, ob der Benutzer den Browser schließt, eine andere URL eingibt ... ?

Die meiner Meinung nach klügste Variante so etwas zu realisieren währe ein mitführen von 2 Tabelleneinträgen. Einen für die gespeicherten Informationen, und einen für die ungespeicherten (welche natürlich dynamisch während der Änderung an den Server geschickt werden müssen)

Wenn der User dann das nächste mal auf die Seite kommt, kannst du diesen darauf hinweisen, dass er nicht gespeicherte Daten etc hat und ob er sich diese ansehen möchte (bzw. diese parallel dazu in der selben maske anzeigen)

Ein weiteres Poblem ist dann die eindeutige User-Zuordnung. In deinem Fall, wenn ich das richtig verstehe, handelt es sich um registrierte Benutzer, was das ganze enorm erleichtert. Bei "unbekannten" Usern ist das ganze gleich schon blöder.

Ich hoffe ich konnte dir ein wenig weiterhelfen... wenn auch nicht in die Richtung in die du wolltest.

Gruß
Christoph
 
Also Browser schliessen oder andere URL eingeben halte ich für grob fahrlässig, würde ich unter "selbst schuld" abhaken.
Die Lösung mit zwei Tabelleneinträgen geht nicht in die Richtung, die ich will. Dann hab ich ja quasi meine "ungespeicherten Daten" doch gespeichert.
Ich will lediglich den Benutzer warnen, bevor er das Formular mit ungespeicherten Veränderungen verlässt.
Dieser (ziemlich alte) Post schlägt eine Lösung mittels NavigationHandler vor, allerdings unter Oracles ADF:
http://plasticscotsman.blogspot.com/2007/09/how-to-warn-there-are-unsaved-changes.html

Werde ich morgen mal versuchen....
 
Zuletzt bearbeitet:
@ Biergamasda

das wäre dein perfomancetechnischer Tod...
bei 100 Datensätzen ok, aber wenn wir hier von zehntausenden reden,
dann ist es absoluter Schwachsinn.

du könntest zum Beispiel bei bestehenden Datensätzen einen "Ändern" Button einbauen in dem du
dir merkst, dass der User etwas verändert hat.

Ohne deinen Quellcode näher zu kennen, den du mir sehr wahrscheinlich nicht zeigen kannst,
fällt mir spontan auch nichts anderes ein im Moment :(

EDIT:
Der zündende Einfall ist mir grad gekommen... :D
du könntest deine InputTextFelder einfach tatsächlich mit einem onclick oder onkeyup event versehen, das diese Variable auf true setzt und
voila, du weißt ob etwas verändert wurde.
Dein Link hat die Lösung doch eigentlich auch schon parat, denn wenn du mal genauer nachliest,
wurde lediglich ursprünglich eine Lösung unter ADF entwickelt, die diese Entwickler auf JSF anwendbar gemacht haben...
Verstehe nicht, wie du daran vorbeilesen konntest :D

Code:
  ....
    <application>
    ....
    <!-- Fully qualified class name -->
    <navigation-handler>CommitNavigationHandler</navigation-handler>
    ....
    </application>
    ....
Java:
    public class CommitNavigationHandler
    extends NavigationHandler
    {
    private NavigationHandler handler = null;

    public static final String COMMIT_ENABLED =
    "#{bindings.Commit.enabled}";

    public CommitNavigationHandler(NavigationHandler handler)
    {
    super();
    this.handler = handler;
    }

    @Override
    public void handleNavigation(FacesContext facesContext,
    String fromAction, String outcome)
    {
    Boolean outstandingChangesWrapper =
    resolveExpressionAsBoolean(CommitNavigationHandler.COMMIT_ENABLED, facesContext);
    if (outcome != null && outstandingChangesWrapper != null &&
    outstandingChangesWrapper.booleanValue())
    {
    // The outcome is not null (so we are navigating away from the current page)
    // and the page has some unsaved changes so display the same page with a
    // message to tell user to either save the changes or reset the form.
    FacesMessage message =
    new FacesMessage("You have uncommitted changes! Please either save or reset.");
    facesContext.addMessage(null, message);
    this.handler.handleNavigation(facesContext, fromAction, null);
    }
    else
    {
    // No changes or it's a postback, do navigation as intended
    this.handler.handleNavigation(facesContext, fromAction, outcome);
    }
    }

    private Boolean resolveExpressionAsBoolean(String el, FacesContext fc)
    {
    ValueBinding valueBinding =
    fc.getApplication().createValueBinding(el);
    return (Boolean) valueBinding.getValue(fc);
    }
    }
HTML:
          <af:commandButton actionListener="#{bindings.Rollback.execute}"
          text="Rollback" disabled="#{!bindings.Rollback.enabled}"
          immediate="true">
          <af:resetActionListener/>
          </af:commandButton>
 
Zuletzt bearbeitet:
@ Biergamasda

das wäre dein perfomancetechnischer Tod...
bei 100 Datensätzen ok, aber wenn wir hier von zehntausenden reden,
dann ist es absoluter Schwachsinn.

Mir ist durchaus klar, dass dies einen enormen Overhead und Redundanz in der Datenbank erzeugen würde.
Nur währe dies, imo der einzige Weg, wirklich alle Möglichkeiten zu prüfen bzw. zu berücksichtigen. Aber wie von MadM bereits erwähnt werden diese Fälle sowieso ignoriert, demnach bin ich auch der Meinung dass es schwachsinn währe so etwas zu realisieren.

Wünsche dir trotzdem noch viel Erfolg bei deinen Versuchen, würde mich interessieren wie du das dann realisiert hast.

MfG
 
@Bexx
Ignoriert wird
a) Browser schliessen und
b) (manuell) eine andere URL eingeben.
Das macht der Benutzer mit Absicht und hat es dann nicht anders gewollt.

Wenn er aber (versehentlich) den Menüpunkt wechselt, ohne zu Speichern, soll er gewarnt werden. Schwachsinn hin oder her, Pflichtenheft ist Pflichtenheft ;-)

@Biergamasda
Nun ja, die gezeigte Lösung verwendet immer noch ADF (<af:commandButton..), ich sehe nicht wie das ganze mit JSF/RichFaces funktionieren sollte.
Zudem ist ValueBinding (Zeile 41) deprecated, wobei Application.evaluateExpressionGet() das selbe tun sollte.
Werde mal noch etwas experimentieren..
 
Moinsen,

habe gestern und heute damit ein wenig rum gespielt und gesucht. Das beste was ich ans Laufen bekomme habe ist folgendes
Code:
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<%@ taglib uri="http://richfaces.org/rich" prefix="rich"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://richfaces.org/a4j" prefix="a4j"%>
<html>
<head>
<title>Warn for unsaved changes</title>
</head>
<body>
<f:view>
	<a4j:form>
		<h:outputLabel id="label_name" value="Name" for="in_name" />
		<h:inputText id="in_name" value="#{bean.name}"
			onchange="storeChange(true, this)" />
		<br />

		<h:outputLabel id="label_email" value="E-Mail" for="in_email" />
		<h:inputText id="in_email" value="#{bean.email}"
			onchange="storeChange(true, this)" />
		<br />
		<a4j:commandButton oncomplete="javascript:changed=false"
			value="Speichern" />
	</a4j:form>
</f:view>
</body>

<script language="javascript" type="text/javascript">
	// flag um zu merken, dass etwas geändert wurde.
	var changed = false;
	/*
	Speichert im changed flag, ob eine Eingabe vom Nutzer gemacht wurde. 
	Anschließend wird der Aufruf dieser Methode aus dem entsprechenden Attribut
	des input elements entfernt, um unnötige Aufrufe zu sparen.
	*/
	function storeChange(aChanged, aElement) {
		changed = aChanged;
		aElement.setAttribute('onchange', '');
	}
	/*
	Falls das changed flag angibt, dass es ungespeicherte Eingaben gibt, wird
	eine Warnung ausgegeben, ob die Seite verlassen werden soll. Das erfolgt
	über einen Standardmechanismus. Deswegen ist der Text der zurückgegeben
	wird in einem Textblock des Browser eingebettet. Der Haken daran ist, dass
	dabei die Spracheinstellungen des Anwenders respektiert werden. Es kann also
	sein, dass englischer Text von der deutschen Standardwarnung umrahmt ist.
	*/
	window.onbeforeunload=function unloadAlert() {
		if(changed) {
			// wird Text zurückgegeben, wird dieser in der Standardwarnung eingebettet.
			// ehrlicherweise wird alles was diese Methode zurückgibt als String geparst.
			return 'Sie haben nicht gespeicherte Eingaben auf dieser Seite. Wenn Sie OK clicken gehen diese Informationen verloren.';
		}
		// ausser null.
		return null;
	}
</script>
</html>

Allerdings erschlägt das wohl zu viel. Nämlich auch diese Fälle
Ignoriert wird
a) Browser schliessen und
b) (manuell) eine andere URL eingeben.
Das macht der Benutzer mit Absicht und hat es dann nicht anders gewollt.

Wenn Du so etwas machen möchtest, musst Du tatsächlich einen eigenen Navigation Handler realisieren. Zu dem müsstest Du das JavaScript-Flag transportieren, was vielleicht über a4j:jsFunction ginge. Müsste ich nochmal drüber nachdenken.
 
Zurück