From fa7a1dfc62ac38c164bef4d9d99f1ae8b76f77e5 Mon Sep 17 00:00:00 2001
From: Erik Lumme <erik@vaadin.com>
Date: Wed, 13 Sep 2017 14:20:22 +0300
Subject: Migrate ViewChangeConfirmations

---
 .../articles/ViewChangeConfirmations.asciidoc      | 220 +++++++++++++++++++++
 1 file changed, 220 insertions(+)
 create mode 100644 documentation/articles/ViewChangeConfirmations.asciidoc

(limited to 'documentation/articles/ViewChangeConfirmations.asciidoc')

diff --git a/documentation/articles/ViewChangeConfirmations.asciidoc b/documentation/articles/ViewChangeConfirmations.asciidoc
new file mode 100644
index 0000000000..5beda623eb
--- /dev/null
+++ b/documentation/articles/ViewChangeConfirmations.asciidoc
@@ -0,0 +1,220 @@
+[[view-change-confirmations]]
+View change confirmations
+-------------------------
+
+The `Navigator` API provides ways to prevent the user from navigating away
+from a view in some cases, usually when the view has some unsaved
+changes. We'll make a simple example that does just that (and only
+that).
+
+We'll create our simple `SettingsView` later, because it has the actual
+meat of this example. Let's set up the basic stuff first, our UI and our
+`MainView`.
+
+UI:
+
+[source,java]
+....
+import com.vaadin.navigator.Navigator;
+import com.vaadin.navigator.ViewChangeListener;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.ui.UI;
+
+public class NavigationtestUI extends UI {
+  @Override
+  public void init(VaadinRequest request) {
+    // Create Navigator, make it control the ViewDisplay
+     Navigator navigator = new Navigator(this, this);
+
+    // no fragment for main view
+    navigator.addView(MainView.NAME, new MainView(navigator));
+
+    // #settings
+    navigator.addView(SettingsView.NAME, new SettingsView(navigator));
+  }
+}
+....
+
+Minimalistic. The only thing to notice is that we pass the `Navigator` to
+the `SettingsView`, so that it can attach a listener and trigger
+navigation. More on that when we actually create the `SettingsView`.
+
+Let's do the `MainView`:
+
+[source,java]
+....
+import com.vaadin.navigator.View;
+import com.vaadin.server.ExternalResource;
+import com.vaadin.ui.Link;
+import com.vaadin.ui.Panel;
+
+public class MainView extends Panel implements View {
+
+  public static final String NAME = "";
+
+  public MainView(final Navigator navigator) {
+    Link lnk = new Link("Settings", new ExternalResource("#!"
+        + SettingsView.NAME));
+    setContent(lnk);
+  }
+
+  @Override
+  public void enter(ViewChangeEvent event) {
+  }
+}
+....
+
+Yeah, really nothing to see here - we just create this so we can
+navigate back and forth when trying it out.
+
+Now let's do the SettingsView, which has some more things going on in
+order to make it fairly complete:
+
+[source,java]
+....
+import java.util.Date;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.navigator.Navigator;
+import com.vaadin.navigator.View;
+import com.vaadin.navigator.ViewChangeListener;
+import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.DateField;
+import com.vaadin.ui.InlineDateField;
+import com.vaadin.ui.Layout;
+import com.vaadin.ui.Notification;
+import com.vaadin.ui.Notification.Type;
+import com.vaadin.ui.Panel;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.themes.Reindeer;
+
+public class SettingsView extends Panel implements View {
+
+  public static String NAME = "settings";
+
+  Navigator navigator;
+  DateField date;
+  Button apply;
+  Button cancel;
+
+  String pendingViewAndParameters = null;
+
+  public SettingsView(final Navigator navigator) {
+    this.navigator = navigator;
+    Layout layout = new VerticalLayout();
+
+    date = new InlineDateField("Birth date");
+    date.setImmediate(true);
+    layout.addComponent(date);
+    // pretend we have a datasource:
+    date.setPropertyDataSource(new ObjectProperty<Date>(new Date()));
+    date.setBuffered(true);
+    // show buttons when date is changed
+    date.addValueChangeListener(new ValueChangeListener() {
+      public void valueChange(ValueChangeEvent event) {
+        hideOrShowButtons();
+        pendingViewAndParameters = null;
+      }
+    });
+
+    // commit the TextField changes when "Save" is clicked
+    apply = new Button("Apply", new Button.ClickListener() {
+      public void buttonClick(ClickEvent event) {
+        date.commit();
+        hideOrShowButtons();
+        processPendingView();
+      }
+    });
+    layout.addComponent(apply);
+
+    // Discard the TextField changes when "Cancel" is clicked
+    cancel = new Button("Cancel", new Button.ClickListener() {
+      public void buttonClick(ClickEvent event) {
+        date.discard();
+        hideOrShowButtons();
+        processPendingView();
+      }
+    });
+    cancel.setStyleName(Reindeer.BUTTON_LINK);
+    layout.addComponent(cancel);
+
+    // attach a listener so that we'll get asked isViewChangeAllowed?
+    navigator.addViewChangeListener(new ViewChangeListener() {
+      public boolean beforeViewChange(ViewChangeEvent event) {
+        if (event.getOldView() == SettingsView.this
+            && date.isModified()) {
+
+          // save the View where the user intended to go
+          pendingViewAndParameters = event.getViewName();
+          if (event.getParameters() != null) {
+            pendingViewAndParameters += "/";
+            pendingViewAndParameters += event
+                .getParameters();
+          }
+
+          // Prompt the user to save or cancel if the name is changed
+          Notification.show("Please apply or cancel your changes",
+              Type.WARNING_MESSAGE);
+
+          return false;
+        } else {
+          return true;
+        }
+      }
+
+      public void afterViewChange(ViewChangeEvent event) {
+        pendingViewAndParameters = null;
+      }
+    });
+
+    setContent(layout);
+  }
+
+  // Hide or show buttons depending on whether date is modified or not
+  private void hideOrShowButtons() {
+    apply.setVisible(date.isModified());
+    cancel.setVisible(date.isModified());
+  }
+
+  // if there is a pending view change, do it now
+  private void processPendingView() {
+    if (pendingViewAndParameters != null) {
+      navigator.navigateTo(pendingViewAndParameters);
+      pendingViewAndParameters = null;
+    }
+  }
+
+  @Override
+  public void enter(ViewChangeEvent event) {
+    hideOrShowButtons();
+  }
+}
+....
+
+First we set up a `DateField` with buffering and a (dummy) datasource to
+make this work more as a real application would. With buffering on, the
+value (date in this case) can be changed, but it will not be written to
+the datasource before we `commit()`, which is what the Save -button does.
+The Cancel -button does `discard()` on the DateField, which returns the
+field to its unmodified state.
+
+The buttons do not need to be shown if nothing has changed, so we add a
+`ValueChangeListener` to the `DateField` for that purpose.
+
+But the main thing that we're trying to demonstrate here happens in the
+`ViewChangeListener` that we attach to the `Navigator`. There, if we're
+about to change _away_ from our settings _and_ the date is changed but
+_not_ saved, we'll make note of where the user wanted to go, but cancel
+that navigation and prompt the user to save or cancel the changes.
+
+When the user saves or cancels changes, we also check if the user
+previously tried to navigate away form the page, and sends him on his
+way if that is the case.
+
+That is basically all there is to this. You'll notice we try to
+carefully clear or set the 'pending view' and hide/show the buttons at
+the right places to make the user happy, other than that this is pretty
+straightforward.
-- 
cgit v1.2.3

