From e8469aed4a92e83f98fdc8df6a9b79c5df69502b Mon Sep 17 00:00:00 2001 From: KatriHaapalinna Date: Mon, 7 May 2018 15:36:33 +0300 Subject: [PATCH] Update V7 Docs community articles (#10637) --- .../articles/AccessControlForViews.asciidoc | 206 +++++++++++++++ .../ConfiguringGridColumnWidths.asciidoc | 4 +- ...eApplicationWithBackButtonSupport.asciidoc | 146 ++++++++++ .../CreatingAComponentExtension.asciidoc | 98 +++++++ .../CreatingASimpleComponent.asciidoc | 146 ++++++++++ ...CreatingASimpleComponentContainer.asciidoc | 249 ++++++++++++++++++ .../articles/CreatingAThemeUsingSass.asciidoc | 201 ++++++++++++++ .../articles/CreatingAUIExtension.asciidoc | 178 +++++++++++++ ...CustomizingComponentThemeWithSass.asciidoc | 191 ++++++++++++++ ...zingTheStartupPageInAnApplication.asciidoc | 120 +++++++++ ...ExposingServerSideAPIToJavaScript.asciidoc | 140 ++++++++++ .../articles/FormattingDataInGrid.asciidoc | 4 +- .../IntegratingAJavaScriptComponent.asciidoc | 116 ++++++++ ...ngAJavaScriptLibraryAsAnExtension.asciidoc | 96 +++++++ .../MigratingFromVaadin6ToVaadin7.asciidoc | 121 ++++----- ...MigratingFromVaadin7.0ToVaadin7.1.asciidoc | 6 +- .../articles/OptimizingTheWidgetSet.asciidoc | 193 ++++++++++++++ ...sFromTheClientToTheServerUsingRPC.asciidoc | 145 ++++++++++ .../ShowingExtraDataForGridRows.asciidoc | 4 +- .../SimplifiedRPCusingJavaScript.asciidoc | 19 +- ...riptLibraryOrAStyleSheetInAnAddOn.asciidoc | 61 +++++ ...singBeanValidationToValidateInput.asciidoc | 59 +++++ .../articles/UsingGridWithInlineData.asciidoc | 3 +- .../UsingParametersWithViews.asciidoc | 118 +++++++++ .../articles/UsingRPCFromJavaScript.asciidoc | 114 ++++++++ .../UsingRPCToSendEventsToTheClient.asciidoc | 154 +++++++++++ ...pleWithLiferayOrganizationService.asciidoc | 2 +- .../articles/ViewChangeConfirmations.asciidoc | 226 ++++++++++++++++ .../WidgetStylingUsingOnlyCSS.asciidoc | 178 +++++++++++++ documentation/articles/contents.asciidoc | 31 ++- 30 files changed, 3246 insertions(+), 83 deletions(-) create mode 100644 documentation/articles/AccessControlForViews.asciidoc create mode 100644 documentation/articles/CreatingABookmarkableApplicationWithBackButtonSupport.asciidoc create mode 100644 documentation/articles/CreatingAComponentExtension.asciidoc create mode 100644 documentation/articles/CreatingASimpleComponent.asciidoc create mode 100644 documentation/articles/CreatingASimpleComponentContainer.asciidoc create mode 100644 documentation/articles/CreatingAThemeUsingSass.asciidoc create mode 100644 documentation/articles/CreatingAUIExtension.asciidoc create mode 100644 documentation/articles/CustomizingComponentThemeWithSass.asciidoc create mode 100644 documentation/articles/CustomizingTheStartupPageInAnApplication.asciidoc create mode 100644 documentation/articles/ExposingServerSideAPIToJavaScript.asciidoc create mode 100644 documentation/articles/IntegratingAJavaScriptComponent.asciidoc create mode 100644 documentation/articles/IntegratingAJavaScriptLibraryAsAnExtension.asciidoc create mode 100644 documentation/articles/OptimizingTheWidgetSet.asciidoc create mode 100644 documentation/articles/SendingEventsFromTheClientToTheServerUsingRPC.asciidoc create mode 100644 documentation/articles/UsingAJavaScriptLibraryOrAStyleSheetInAnAddOn.asciidoc create mode 100644 documentation/articles/UsingBeanValidationToValidateInput.asciidoc create mode 100644 documentation/articles/UsingParametersWithViews.asciidoc create mode 100644 documentation/articles/UsingRPCFromJavaScript.asciidoc create mode 100644 documentation/articles/UsingRPCToSendEventsToTheClient.asciidoc create mode 100644 documentation/articles/ViewChangeConfirmations.asciidoc create mode 100644 documentation/articles/WidgetStylingUsingOnlyCSS.asciidoc diff --git a/documentation/articles/AccessControlForViews.asciidoc b/documentation/articles/AccessControlForViews.asciidoc new file mode 100644 index 0000000000..af73fcf124 --- /dev/null +++ b/documentation/articles/AccessControlForViews.asciidoc @@ -0,0 +1,206 @@ +--- +title: Access Control For Views +order: 46 +layout: page +--- + +[[access-control-for-views]] +Access control for views +------------------------ + +The Navigator API provides a simple mechanism to allow or disallow +navigating to a View. Before a View is shown, each ViewChangeListener +that is registered with the Navigator is given the opportunity to veto +the View change. + +One can also make the View itself trigger a navigation to another View +in navigateTo(), but let's take a look at the more flexible +beforeViewChange() and afterViewChange(), that exists specifically for +this purpose. + +First, let's continue from previous examples and create a MessageView +for secret messages: + +[source,java] +.... +import com.vaadin.navigator.View; +import com.vaadin.ui.Label; + +public class SecretView extends MessageView implements View { + public static final String NAME = "secret"; + + public SecretView() { + setCaption("Private messages"); + ((Layout) getContent()).addComponent(new Label("Some private stuff.")); + } +} +.... + +As you can see, there is absolutely nothing special going on here, we +just customize the View enough to be able to distinguish from the +regular MessageView. + +Next, we'll register this new View with the Navigator, exactly as +before. At this point our SecretView is not secret at all, but let's fix +that by adding a ViewChangeListener to the Navigator: + +[source,java] +.... +navigator.addViewChangeListener(new ViewChangeListener() { + + @Override + public boolean beforeViewChange(ViewChangeEvent event) { + if (event.getNewView() instanceof SecretView && + ((NavigationtestUI)UI.getCurrent()).getLoggedInUser() == null) { + Notification.show("Permission denied", Type.ERROR_MESSAGE); + return false; + } else { + return true; + } + } + + @Override + public void afterViewChange(ViewChangeEvent event) { + } + +}); +.... + +So if we're on our way to the SecretView, but not logged in +(getLoggedInUser() == null), the View change is cancelled. Quite simple +rules in our case, but you could check anything - most probably you'll +want to call a helper method that checks the user for permission. + +Let's go ahead and add some links to the MainView again, so that we +don't have to muck with the address-bar to try it out: + +[source,java] +.... +import com.vaadin.navigator.View; +import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent; +import com.vaadin.server.ExternalResource; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Link; +import com.vaadin.ui.Panel; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; + +public class MainView extends Panel implements View { + + public static final String NAME = ""; + + public MainView() { + + VerticalLayout layout = new VerticalLayout(); + + Link lnk = new Link("Count", new ExternalResource("#!" + CountView.NAME)); + layout.addComponent(lnk); + + lnk = new Link("Message: Hello", new ExternalResource("#!" + + MessageView.NAME + "/Hello")); + layout.addComponent(lnk); + + lnk = new Link("Message: Bye", new ExternalResource("#!" + + MessageView.NAME + "/Bye/Goodbye")); + layout.addComponent(lnk); + + lnk = new Link("Private message: Secret", new ExternalResource("#!" + + SecretView.NAME + "/Secret")); + layout.addComponent(lnk); + + lnk = new Link("Private message: Topsecret", new ExternalResource("#!" + + SecretView.NAME + "/Topsecret")); + layout.addComponent(lnk); + + // login/logout toggle so we can test this + Button logInOut = new Button("Toggle login", + new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + Object user = ((NavigationtestUI)UI.getCurrent()).getLoggedInUser(); + ((NavigationtestUI)UI.getCurrent()).setLoggedInUser( + user == null ? "Smee" : null); + } + }); + layout.addComponent(logInOut); + setContent(layout); + } + + @Override + public void enter(ViewChangeEvent event) { + } +} +.... + +Instead of just showing a notification and leaving the user wondering, +we should obviously allow the user to log in and continue. We'll do just +that in the separate tutorial about Handling login, but for now we just +add a button that toggles our logged in/out state. + +Meanwhile, here is the the full source for the UI so far: + +[source,java] +.... +import com.vaadin.navigator.Navigator; +import com.vaadin.navigator.ViewChangeListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.Notification; +import com.vaadin.ui.Notification.Type; +import com.vaadin.ui.UI; + +public class NavigationtestUI extends UI { + + Navigator navigator; + + String loggedInUser; + + @Override + public void init(VaadinRequest request) { + // Create Navigator, make it control the ViewDisplay + navigator = new Navigator(this, this); + + // Add some Views + navigator.addView(MainView.NAME, new MainView()); // no fragment + + // #!count will be a new instance each time we navigate to it, counts: + navigator.addView(CountView.NAME, CountView.class); + + // #!message adds a label with whatever it receives as a parameter + navigator.addView(MessageView.NAME, new MessageView()); + + // #!secret works as #!message, but you need to be logged in + navigator.addView(SecretView.NAME, new SecretView()); + + // we'll handle permissions with a listener here, you could also do + // that in the View itself. + + navigator.addViewChangeListener(new ViewChangeListener() { + + @Override + public boolean beforeViewChange(ViewChangeEvent event) { + if (event.getNewView() instanceof SecretView + && ((NavigationtestUI)UI.getCurrent()).getLoggedInUser() == null) { + Notification.show("Permission denied", Type.ERROR_MESSAGE); + return false; + } else { + return true; + } + } + + @Override + public void afterViewChange(ViewChangeEvent event) { + System.out.println("After view change"); + } + + }); + } + + public String getLoggedInUser(){ + return loggedInUser; + } + + public void setLoggedInUser(String user){ + loggedInUser = user; + } +} +.... \ No newline at end of file diff --git a/documentation/articles/ConfiguringGridColumnWidths.asciidoc b/documentation/articles/ConfiguringGridColumnWidths.asciidoc index d6be0e4f0b..5f3fdd7c81 100644 --- a/documentation/articles/ConfiguringGridColumnWidths.asciidoc +++ b/documentation/articles/ConfiguringGridColumnWidths.asciidoc @@ -9,8 +9,8 @@ layout: page To try out how the widths of Grid columns work in different situations, we'll use the same base implementation as in the -link:UsingGridWithAContainer.asciidoc[Using Grid with a Container] -example. +<> example. Grid does by default check the widths of all cells on the first pageful of data and allocate column widths based on that. If there's room to diff --git a/documentation/articles/CreatingABookmarkableApplicationWithBackButtonSupport.asciidoc b/documentation/articles/CreatingABookmarkableApplicationWithBackButtonSupport.asciidoc new file mode 100644 index 0000000000..1e35a4c63c --- /dev/null +++ b/documentation/articles/CreatingABookmarkableApplicationWithBackButtonSupport.asciidoc @@ -0,0 +1,146 @@ +--- +title: Creating A Bookmarkable Application With Back Button Support +order: 55 +layout: page +--- + +[[creating-a-bookmarkable-application-with-back-button-support]] +Creating a bookmarkable application with back button support +------------------------------------------------------------ + +Vaadin 7 comes with a new set of APIs to aid creation of navigation +within your application. The main concepts are *Navigator* and *View*, +and using these you can easily create an application that supports the +standard browser methods for navigation; bookmarking, history, back- and +forward navigation using browser buttons. This is (usually) done using +browser "fragments" (the stuff after the #-character in the URI). + +At the same time, the API provides a natural way of partitioning your +application into views - something most applications did previously +anyway, but previously without framework 'guidance'. + +Let's start by making a View that counts the times it has been created. +This is a simple example, but will later shed some light on when Views +are created, but let's not worry about that just yet: + +[source,java] +.... +import com.vaadin.navigator.View; +import com.vaadin.ui.Label; +import com.vaadin.ui.Panel; + +public class CountView extends Panel implements View { + public static final String NAME = "count"; + + private static int count = 1; + + public CountView() { + setContent(new Label("Created: " + count++)); + } + + public void enter(ViewChangeEvent event) { + } +} +.... + +We'll extend Panel as a convenient base, and add a Label to that in the +constructor, updating the static count. The _enter()_ -method comes from +View, and is called when our View is activated, but we'll do nothing +about that in our simplistic View. + +Note the _static final NAME_: we'll use it instead of a 'magic' string +when we register the View with the Navigator later. Feel free to use any +method you like to keep track of your View-names (e.g Enum, simpleName +of the View's class, and so on…) + +In order to do any navigating, we'll need at least two views, so let's +create a main view that has a link to the counting view we just created. + +[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() { + Link lnk = new Link("Count", new ExternalResource("#!" + + CountView.NAME)); + setContent(lnk); + } + + public void enter(ViewChangeEvent event) { + } +} +.... + +Note the empty string used as _NAME_. This is because we want this to be +our main ("home") View, displayed before any navigation is done. + +In this example we use a Link and let the browser do the navigating. We +could just as easily use a Button and tell the Navigator where we want +to go when the button's ClickListener is invoked. Note that we're using +_CountView.NAME_, and what we're actually doing is using the "fragment" +part of the application URI to indicate the view. The resulting URI will +look something like http://.../application#!count . + +Ok, one last thing: we need to set up a UI with a Navigator, and +register our views: + +[source,java] +.... +import com.vaadin.navigator.Navigator; +import com.vaadin.navigator.Navigator.SimpleViewDisplay; +import com.vaadin.server.Page; +import com.vaadin.server.WrappedRequest; +import com.vaadin.ui.UI; + +public class NavigationtestUI extends UI { + @Override + public void init(VaadinRequest request) { + // Create Navigator, use the UI content layout to display the views + Navigator navigator = new Navigator(this, this); + + // Add some Views + navigator.addView(MainView.NAME, new MainView()); // no fragment + + // #!count will be a new instance each time we navigate to it, counts: + navigator.addView(CountView.NAME, CountView.class); + + // The Navigator attached to the UI will automatically navigate to the initial fragment once + // the UI has been initialized. + } +} +.... + +There are advanced ways to use the Navigator API, and there are simple +ways. Most applications will do fine with the simple ways, and the +Navigator constructor we used is written that in mind. It simply takes +any ComponentContainer, assumes that all our Views are also Components, +and on a view change sets the given view as the ComponentContainer's +only child. Internally, it uses a _ViewDisplay_ subclass called +ComponentContainerViewDisplay to do this. If we had more advanced +requirements, we could write our own ViewDisplay subclass to show our +views in whatever fashion we'd like. + +The Navigator finds out about URI fragment changes through the Page, and +directs the ViewDisplay accordingly. We register our Views using +_addView()_ so that the Navigator knows how to connect fragments with +Views. Again notice how we use the static NAME instead of +_addView("name", view)_ - but feel free to use other approaches. + +In order to illustrate how the two differ, we register an _instance_ of +the MainView, but _CountView.class_. As a result, the MainView is +created once, when the UI is created, and lives as long as the UI lives. +On the other hand, a new CountView instance will be created each time we +navigate to it (but no earlier). You can try navigating back-and-forth +and see how the count is updated - try registering it using new +CountView() instead… + +It's also good to keep in mind that a new UI is created each time you +press reload in the browser, unless you use the @PreserveOnRefresh +annotation on the UI. \ No newline at end of file diff --git a/documentation/articles/CreatingAComponentExtension.asciidoc b/documentation/articles/CreatingAComponentExtension.asciidoc new file mode 100644 index 0000000000..764fbeafb9 --- /dev/null +++ b/documentation/articles/CreatingAComponentExtension.asciidoc @@ -0,0 +1,98 @@ +--- +title: Creating A Component Extension +order: 51 +layout: page +--- + +[[creating-a-component-extension]] +Creating a component extension +------------------------------ + +In this tutorial we create a simple extension that can be attached to a +`PasswordField`, displaying a floating notification if the user's Caps +Lock seems to be enabled. We assume the reader is already familiar with +the <> +tutorial. + +This extension has almost no server-side functionality; the whole Extension +class is as follows: + +[source,java] +.... +public class CapsLockWarning extends AbstractExtension { + protected CapsLockWarning(PasswordField field) { + // Non-public constructor to discourage direct instantiation + extend(field); + } + + public static CapsLockWarning warnFor(PasswordField field) { + return new CapsLockWarning(field); + } +} +.... + +When there's nothing to configure for the extension, users just want to +enable it for some component and be done with it. By defining a static +factory method, the user only needs to do something like +`CapsLockWarning.warnFor(myPasswordField);` to make `myPasswordField` +get the new functionality. + +The client side is not overly complicated, either. We override the +`extend` method, called by the framework when the client-side extension +connector is attached to its target the client-side counterpart of the +connector to which the server-side extension instance is attached in +this case, `PasswordFieldConnector`. + +We add a key press handler to the password widget, checking if the input +looks like Caps Lock might be enabled. The Caps Lock state cannot be +directly queried in GWT/JavaScript, so we use a trick: check if either + +* the shift key was not held but the entered character was uppercase, or +* the shift key _was_ held but the entered character was lowercase. + +If this is the case, we show a warning in the form of a floating widget +(`VOverlay`). This demonstrates how an extension may make use of UI +elements even though it is not a part of the layout hierarchy. A +frequent use case for extensions is showing different types of floating +overlay elements that are temporary in character. + +[source,java] +.... + +@Connect(CapsLockWarning.class) +public class CapsLockWarningConnector extends AbstractExtensionConnector { + @Override + protected void extend(ServerConnector target) { + final Widget passwordWidget = ((ComponentConnector) target).getWidget(); + + final VOverlay warning = new VOverlay(); + warning.setOwner(passwordWidget); + warning.add(new HTML("Caps Lock is enabled!")); + + passwordWidget.addDomHandler(new KeyPressHandler() { + @Override + public void onKeyPress(KeyPressEvent event) { + if (isEnabled() && isCapsLockOn(event)) { + warning.showRelativeTo(passwordWidget); + } else { + warning.hide(); + } + } + }, KeyPressEvent.getType()); + } + + private boolean isCapsLockOn(KeyPressEvent e) { + return e.isShiftKeyDown() ^ Character.isUpperCase(e.getCharCode()); + } +} +.... + +To use the Caps Lock warning, compile your widgetset and extend a +PasswordField with something like this + +[source,java] +.... +PasswordField field = new PasswordField("Enter your password"); +CapsLockWarning.warnFor(field); +addComponent(field); +.... \ No newline at end of file diff --git a/documentation/articles/CreatingASimpleComponent.asciidoc b/documentation/articles/CreatingASimpleComponent.asciidoc new file mode 100644 index 0000000000..f801e5675b --- /dev/null +++ b/documentation/articles/CreatingASimpleComponent.asciidoc @@ -0,0 +1,146 @@ +--- +title: Creating A Simple Component +order: 48 +layout: page +--- + +[[creating-a-simple-component]] +Creating a simple component +--------------------------- + +To make a component with a new client-side widget (as opposed to making +a server-side composite), you will need to make three things: the +_server-side component_ you'll actually use in your application (let's +call it *MyComponent*), the corresponding _client-side (GWT) widget_ +that will render your component in the browser (*MyComponentWidget*) and +a _Connector_ that handles the communication between the two +(*MyComponentConnector*). (Note that although MyComponentWidget could in +principle be a Connector as well, in practice it's a good idea to +separate the two.) + +At this point the basic MyComponent has no functionality except +inherited basic component features (we'll add functionality in following +articles): + +[source,java] +.... +package com.example.mycomponent; + +import com.vaadin.ui.AbstractComponent; + +public class MyComponent extends AbstractComponent { + +} +.... + +The main thing to notice here is that it inherits `AbstractComponent`, +which is the most common case (unless it will contain other components, +see separate article about component containers). The component will +automatically have the basic component features, such as size and +caption. + +At this point our basic client-side widget will just statically render +some text: + +[source,java] +.... +package com.example.mycomponent.client; + +import com.google.gwt.user.client.ui.Label; + +public class MyComponentWidget extends Label { + + public static final String CLASSNAME = "mycomponent"; + + public MyComponentWidget() { + setText("This is MyComponent"); + setStyleName(CLASSNAME); + } +} +.... + +Notice that this is actually a plain GWT widget that can be used as any +other GWT widget. It's a good idea to set a style name from the start, +so that the component can be styled. + +Now all we have to do is connect the component to the widget using a +Connector: + +[source,java] +.... +package com.example.mycomponent.client; + +import com.example.mycomponent.MyComponent; +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.Connect; + +@Connect(com.example.mycomponent.MyComponent.class) +public class MyComponentConnector extends AbstractComponentConnector { + @Override + protected Widget createWidget() { + return GWT.create(MyComponentWidget.class); + } +} +.... + +The *crucial Connect annotation* is what actually tells the framework +what is connected where - do this first, since it's easy to forget. + +In `createWidget()` use `GWT.create()` instead of `new` whenever possible, +since it allows for some flexibility that might come in handy later on. + +Though this is optional, you might also want to override getWidget() so +that you can narrow it's return type from Widget to your actual +implementation class: + +[source,java] +.... +@Override +public MyComponentWidget getWidget() { + return (MyComponentWidget) super.getWidget(); +} +.... + +The package structure usually looks something like this: + +* com.example.mycomponent +** MyComponent.java +** MyComponentWidgetset.gwt.xml +* com.example.mycomponent.client +** MyComponentConnector.java +** MyComponentWidget.java + +Finally, compile the widgetset, and *make sure the widgetset is defined with the @Widgetset annotation in the UI class*: + +[source,java] +.... +@Widgetset("com.example.mycomponent.MyComponentWidgetset") +class MyUI extends UI { +.... + +If you are using web.xml, it should contain the widgetset parameter: + +[source,xml] +.... + + My Vaadin App + com.vaadin.server.VaadinServlet + + Vaadin UI + UI + com.example.myexampleproject.MyApplicationUI + + + widgetset + com.example.mycomponent.MyComponentWidgetset + + +.... + +Add MyComponent to your application, and it should render a label saying +"This is MyComponent". + +Next have a look at the articles covering shared state and RPC, to learn +how to add more functionality to the component. \ No newline at end of file diff --git a/documentation/articles/CreatingASimpleComponentContainer.asciidoc b/documentation/articles/CreatingASimpleComponentContainer.asciidoc new file mode 100644 index 0000000000..01537314af --- /dev/null +++ b/documentation/articles/CreatingASimpleComponentContainer.asciidoc @@ -0,0 +1,249 @@ +--- +title: Creating A Simple Component Container +order: 49 +layout: page +--- + +[[creating-a-simple-component-container]] +Creating a simple component container +------------------------------------- + +Components in Vaadin can be roughly split into two groups, `Component`{empty}s +and `ComponentContainer`{empty}s. ComponentContainers are Components in +themselves which can also contain other components. If you are about to +implement a component that contains other components, then you'll get a +headstart by extending Vaadin's `ComponentContainer`. The biggest feature +is in tranferring the list of server side components from your component +to the client. Here's how you do it. + +[[server-side]] +Server Side +^^^^^^^^^^^ + +To start of we implement our server side component. For this we extend +the ready made abstract implementation `AbstractComponentContainer`. This +requires us to implement `addComponent(Component)`, +`removeComponent(Component)`, `replaceComponent(Component, Component)`, +`getComponentCount` and `getComponentIterator()`. + +[source,java] +.... +package com.example.widgetcontainer; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.vaadin.ui.AbstractComponentContainer; +import com.vaadin.ui.Component; + +public class WidgetContainer extends AbstractComponentContainer { + + List children = new ArrayList(); + + @Override + public void addComponent(Component c) { + children.add(c); + super.addComponent(c); + markAsDirty(); + } + + @Override + public void removeComponent(Component c) { + children.remove(c); + super.removeComponent(c); + markAsDirty(); + } + + public void replaceComponent(Component oldComponent, Component newComponent) { + int index = children.indexOf(oldComponent); + if (index != -1) { + children.remove(index); + children.add(index, newComponent); + fireComponentDetachEvent(oldComponent); + fireComponentAttachEvent(newComponent); + markAsDirty(); + } + } + + public int getComponentCount() { + return children.size(); + } + + public Iterator iterator() { + return children.iterator(); + } +} +.... + +Add, remove and replace are quite straightforward. In the class we +upkeep a list of children internally, and these three methods modify +them. Add and remove have ready made methods in the super class for +notifying all event handlers that the children have changed and because +of that we should make calls to the super methods after we have updated +the list. In `replaceComponent` we have to call +`fireComponentDetachEvent(Component)` and +`fireComponentAttachEvent(Component)` to manually trigger these events. In +all three methods we should also call `markAsDirty` as a last step to +notify the client side that the children have changed. + +The methods `getComponentCount()` and `iterator()` takes care of providing +the required information that we need to the client side. Here they are +simple delegate methods to the List's `size()` and `iterator()`. + +[[client-side]] +Client Side +^^^^^^^^^^^ + +Next up, we want to set up a standard GWT widget which will be our +component container's client side widget. GWT in itself has a bunch of +component containers in it. In GWT, these are called Panels. For this +case I will start with a `VerticalPanel`. It is roughly the same as +`VerticalLayout` in Vaadin. Down the road you want to edit this file to +add features or even extend Widget to create a complete custom widget. +For now extending `VerticalPanel` is enough and we'll use that as-is. + +[source,java] +.... +package com.example.widgetcontainer.client.ui; + +import com.google.gwt.user.client.ui.VerticalPanel; + +public class VWidgetContainer extends VerticalPanel { + public static final String CLASSNAME = "v-widgetcontainer"; + + public VWidgetContainer() { + setStyleName(CLASSNAME); + } +} +.... + +[[connector]] +Connector +^^^^^^^^^ + +Your widget's Connector will transfer the components from the server +side as child widgets to our widget. The connector will feed the +children to the panel trough it's standard API, namely `add(Widget)`, +`remove(Widget)` and `clear();` + +Instead of going the standard route of extending +`AbstractComponentConnector` as your connector, here we can take use of +Vaadin's internal features and extend +`AbstractComponentContainerConnector`. Additionally to implementing the +`getWidget()` -method from `AbstractComponentConnector`, we also have to +supply the class with an implementation to a method called +`updateCaption(ComponentConnector)`. This method is there if we want the +container to take care of the captions for all the components. We don't +need to take care of these captions in this example so we can leave the +implementation empty. + +The real benefit of extending `AbstractComponentContainerConnector` is +that we can now extend a method called +`onConnectorHierarchyChange(ConnectorHierarchyChangeEvent)`. This method +will be called every time that the server side calls `markAsDirty()` if +the component hierarchy has been changed. From within it, we can call on +`getChildComponents` to get a list of all the child components, and +populate our widget with those. + +[source,java] +.... +package com.example.widgetcontainer.client.ui; + +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.example.widgetcontainer.WidgetContainer; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorHierarchyChangeEvent; +import com.vaadin.client.ui.AbstractComponentContainerConnector; +import com.vaadin.client.ui.Connect; + +@Connect(WidgetContainer.class) +public class WidgetContainerConnector extends + AbstractComponentContainerConnector { + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + List children = getChildComponents(); + VWidgetContainer widget = getWidget(); + widget.clear(); + for (ComponentConnector connector : children) { + widget.add(connector.getWidget()); + } + } + + @Override + public VWidgetContainer getWidget() { + return (VWidgetContainer) super.getWidget(); + } + + public void updateCaption(ComponentConnector connector) { + } +} +.... + +This implementation removes all the component's in the widget and adds +all that are returned from `getChildComponents`. An obvious optimization +to these is to compare what is already in the widget and only +add/remove/move those widgets that have changed. + +[[example-usage]] +Example Usage +^^^^^^^^^^^^^ + +Nothing left but to use the component! Compile the widgetset and check +that the widgetset is defined with the @WidgetSet annotation in the UI class. +Here is a little stand-alone application that uses this component: + +[source,java] +.... +package com.example.widgetcontainer; + +import java.util.Random; + +import com.vaadin.annotations.Widgetset; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.Component; +import com.vaadin.ui.Label; +import com.vaadin.ui.UI; + +@Widgetset("com.example.widgetcontainer.Widgetset") +public class WidgetcontainerUI extends UI { + @Override + public void init(VaadinRequest request) { + VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + setContent(layout); + + Label label = new Label("Hello Vaadin user"); + layout.addComponent(label); + final WidgetContainer widgetContainer = new WidgetContainer(); + layout.addComponent(widgetContainer); + widgetContainer.addComponent(new Label( + "Click the button to add components to the WidgetContainer.")); + Button button = new Button("Add more components", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + Random randomGenerator = new Random(); + int random = randomGenerator.nextInt(3); + Component component; + if (random % 3 == 0) { + component = new Label("A new label"); + } else if (random % 3 == 1) { + component = new Button("A button!"); + } else { + component = new CheckBox("A textfield"); + } + widgetContainer.addComponent(component); + } + }); + layout.addComponent(button); + } +} +.... \ No newline at end of file diff --git a/documentation/articles/CreatingAThemeUsingSass.asciidoc b/documentation/articles/CreatingAThemeUsingSass.asciidoc new file mode 100644 index 0000000000..fe39df5e19 --- /dev/null +++ b/documentation/articles/CreatingAThemeUsingSass.asciidoc @@ -0,0 +1,201 @@ +--- +title: Creating A Theme Using SASS +order: 53 +layout: page +--- + +[[creating-a-theme-using-sass]] +Creating a theme using SASS +--------------------------- + +Vaadin 7 comes with built in support for Sass, which can be thought of +as a preprocessor for CSS. From the Sass homepage: + +_Sass makes CSS fun again. Sass is an extension of CSS3, adding nested +rules, variables, mixins, selector inheritance, and more._ + +Sass looks like CSS with some added features, and is compiled into CSS +before being sent to the browser. The compilation is either done +beforehand, or (during development) on-the-fly by the servlet. + +In Vaadin 7 you can make use of Sass in any of your CSS, and as usual +there are more than one way to arrange this. The recommended way if you +do not have a specific reason not to do so, is to compile your theme +into one CSS file (that is: without any CSS @include), but we'll start +with the getting-your-feet-wet approach that looks exactly as +before.It’s worth noting that you can continue to use CSS without Sass +just as before, if you prefer. + +[[getting-your-feet-wet]] +Getting your feet wet +^^^^^^^^^^^^^^^^^^^^^ + +In Vaadin 7 you set the theme in use by specifying the `@Theme` annotation +on your UI, e.g `@Theme(“themename”)`. Ignoring Sass for a second, you +would then create a `mytheme/styles.css` that typically `@import` the +Reindeer theme (in case you forgot, your theme should be located in +`WebContent/VAADIN/themes//styles.css`). You can start using +Sass with this approach, by renaming your `styles.css` to `styles.scss` and +importing `legacy-styles.css` instead of `styles.css` - the resulting CSS +will be exactly as the same as before, BUT now you're free to use Sass +in your theme: + +[source,scss] +.... +@import url(../reindeer/legacy-styles.css); +$color : green; +.v-button-caption { + color: $color; +} +.... + +Here we just define a Sass variable to use as color for button captions. + +*NOTE* that this way (using legacy-styles) you still lose one important +new feature: you can't have multiple themes on the same page when using +the legacy-styles.css -approach. To gain this feature, which is crucial +if you intend to run multiple applications with different themes +embedded in the same page (e.g portals), you must use Sass. + +[[compiling]] +Compiling +^^^^^^^^^ + +Provided you’re in development mode (not production), the scss will +automatically be translated into CSS. You can also compile the scss +manually (and MUST do so for production). To do this you should run +`com.vaadin.sass.SassCompiler` with the Vaadin jars on the classpath and +give it your scss file and output file as arguments. If you have the +jars readily available, you could do something like this in the command +line: + +[source,bash] +.... +> java -cp '../../../WEB-INF/lib/*' com.vaadin.sass.SassCompiler styles.scss styles.css +.... + +Another way would be to save the auto-compiled styles.css from the +browser. + +Support has been added to the Eclipse plugin through the _Compile Vaadin +Theme_ button . + +NOTE that if you're using Ivy (the default if you're using the Eclipse +plugin), you must make sure to get the appropriate dependencies on your +classpath some other way (since they are not present in `WEB-INF/lib`). In +Eclipse, use the Run -dialog to inherit the classpath from your project. + +You'll notice that the resulting theme still uses `@import` to 'extend' +the Reindeer theme: + +[source,scss] +.... +@import url(../reindeer/legacy-styles.css); +.... + +This approach is an easy way to get started with Sass, but will cause +two requests (one for our theme, one for Reindeer). Let’s have a look at +the recommended approach next. + +[[going-deeper]] +Going deeper +^^^^^^^^^^^^ + +Instead of using CSS `@import` to base your application theme on, you can +(and probably should) use Sass `@import` to make a monolithic theme (one +CSS file, one request when using the application). Just `@import reindeer.scss`, and `@include` it: + +[source,scss] +.... +// mytheme.scss +@import "../reindeer/reindeer.scss"; + +.mytheme { + @include reindeer; + + $color : yellow; + .v-button-caption { + color: $color; + } +} +.... + +This produces a styles.css that contains all the styles for Reindeer as +well as your custom styles (note that this makes your final CSS quite +big to scroll trough, so you might not want to do this when just +learning the Sass syntax). There is no `@import` in the compiled CSS, so +it will not cause additional requests. Additionally, due to the way +Vaadin Sass is structured, this opens up for many possibilities to +customize, mix-and-match themes, and leave unused stuff out. + +One important thing to notice, is that we wrapped everything in +`.themename {}`, in this case `.mytheme {}`. This is the magic sauce that +makes it possible to have multiple themes on one page. _It is crucial +that the name matches your themename, or your styles will not be +applied._ + +Some of the nice features you get with Sass include variables, selector +nesting, mixins (optionally with paramaters), selector inheritance. For +more information of what you can do with Sass, you should refer to the +official documentation at http://sass-lang.com + +Please note that the Vaadin Sass compiler only supports the “SCSS”, +which is the “new main syntax” (the original Sass also supports another, +older syntax).The Vaadin version aims to be completely compatible, +though initially there will be some limitations (and actually some added +functionality). Please let us know if you find something is not working +as expected. + +[[one-more-thing-recommended-structure]] +One more thing: Recommended structure +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In Vaadin 7, all Vaadin core themes are using Sass. The +_reindeer/styles.css_ we included first, is the compiled Reindeer theme, +including the stuff from the Base theme that Reindeer extends. The Sass +for the Reindeer theme is in _reindeer/reindeer.scss_, and contains one +big mixin that will include the whole theme, unless you specifically +tell it to leave out some parts. The themes are further divided into +smaller parts, that can be left out, or separately included and renamed +- providing a powerful way to customize and mix-and-match themes. + +*It is recommended* that you go ahead an divide your own theme into at +least two files as well: *styles.scss* and *themename.scss* (where +'themename' is the name of your theme). This is will make your theme +extendable, and also has the nice benefit that file you usually edit is +uniquely named (themename.scss) instead of a generic styles.scss that +you might have many of. + +For a theme named 'mytheme', this would look as follows: + +`mytheme/styles.scss:` + +[source,scss] +.... +@import "mytheme.scss"; +.mytheme { + @include mytheme; +} +.... + +`mytheme/mytheme.scss`: + +[source,scss] +.... +@import "../reindeer/reindeer.scss"; + +@mixin mytheme { + + // your styles go here + + @include reindeer; +} +.... + +This is the exact structure Vaadin core themes are using, and the way +the Eclipse plugin will set things up for you (not yet in beta 10). + +Of course, you're still free to arrange your theme in another way if you +prefer. + +Upcoming tutorials will address specific use-cases! \ No newline at end of file diff --git a/documentation/articles/CreatingAUIExtension.asciidoc b/documentation/articles/CreatingAUIExtension.asciidoc new file mode 100644 index 0000000000..f8f38427e2 --- /dev/null +++ b/documentation/articles/CreatingAUIExtension.asciidoc @@ -0,0 +1,178 @@ +--- +title: Creating A UI Extension +order: 50 +layout: page +--- + +[[creating-a-ui-extension]] +Creating a UI extension +----------------------- + +An *Extension* is an entity that is not a full-fledged UI component, but +is instead used to enhance or extend the functionality of an existing +component (or connector, more generally.) Unlike components, extensions +cannot be detached and reattached once they are attached to their +target. + +Extensions usually consist of a pair of `Connector`{empty}s like components do. +Hence, they can use the regular shared state and RPC mechanisms to +communicate between the client and the server. Extensions may or may not +have a UI. They can create and display widgets on the client side, but +are not part of the regular layout hierarchy. + +We will rewrite the +https://vaadin.com/directory/component/refresher[Refresher] add-on as an +extension. The Refresher causes the client to "ping" the server at +regular intervals, allowing the server to keep the client up-to-date if +the application state is changed eg. by a background thread (because of +the way Vaadin works, the server cannot itself initiate communication.) + +We start by writing the barebones server-side class for our extension: + +[source,java] +.... +public class Refresher extends AbstractExtension { + public Refresher(UI ui) { + extend(target); + } +} +.... + +Two things to note: + +* If we were writing a component, we would probably want to inherit from +`AbstractComponent`. Here, we inherit from `AbstractExtension` instead. +* The connector that should be extended is passed to the constructor, +which then uses the protected `extend(Connector)` method to attach +itself to the target connector. In this case it does not make much sense +attached to individual components, so the constructor only accepts `UI`. + +Next, the Refresher needs an RPC interface to ping the server and a +shared state to keep track of the interval. These are rather trivial: + +[source,java] +.... +public interface RefresherRpc extends ServerRpc { + public void refresh(); +} +.... + +[source,java] +.... +public class RefresherState extends SharedState { + public int interval; +} +.... + +The client-side connector is just like a component connector except that +we inherit from `AbstractExtensionConnector`, not +`AbstractComponentConnector`. We do not write a client-side widget at +all, because the Refresher does not have a UI. + +We create a `Timer` instance that calls the `refresh` RPC method when +run. In `onStateChange()`, we know that either the interval, enabled +state, or both have changed, so we always cancel a possible +currently-running timer and schedule a new one if we're enabled. We also +remember to cancel the timer when the extension is detached. + +[source,java] +.... +@Connect(Refresher.class) +public class RefresherConnector extends AbstractExtensionConnector { + + private Timer timer = new Timer() { + @Override + public void run() { + getRpcProxy(RefresherRpc.class).refresh(); + } + }; + + @Override + public void onStateChanged(StateChangeEvent event) { + super.onStateChanged(event); + timer.cancel(); + if (isEnabled()) { + timer.scheduleRepeating(getState().interval); + } + } + + @Override + public void onUnregister() { + timer.cancel(); + } + + @Override + protected void extend(ServerConnector target) { + // Nothing for refresher to do here as it does not need to access the + // connector it extends + } + + @Override + public RefresherState getState() { + return (RefresherState) super.getState(); + } +} +.... + +Finally, we add an event listener interface and some accessor methods to +`Refresher`. There is nothing extension-specific in the following code: + +[source,java] +.... +public interface RefreshListener { + static Method METHOD = ReflectTools.findMethod(RefreshListener.class, + "refresh", RefreshEvent.class); + + public void refresh(RefreshEvent refreshEvent); +} + +public class RefreshEvent extends EventObject { + public RefreshEvent(Refresher refresher) { + super(refresher); + } + + public Refresher getRefresher() { + return (Refresher) getSource(); + } +} + +public Refresher(UI ui) { + registerRpc(new RefresherRpc() { + @Override + public void refresh() { + fireEvent(new RefreshEvent(Refresher.this)); + } + }); + extend(ui); +} + +@Override +public RefresherState getState() { + return (RefresherState) super.getState(); +} + +public void setInterval(int millis) { + getState().interval = millis; +} + +public int getInterval() { + return getState().interval; +} + +public void setEnabled(boolean enabled) { + getState().enabled = enabled; +} + +public boolean isEnabled() { + return getState().enabled; +} + +public void addRefreshListener(RefreshListener listener) { + super.addListener(RefreshEvent.class, listener, RefreshListener.METHOD); +} + +public void removeRefreshListener(RefreshListener listener) { + super.removeListener(RefreshEvent.class, listener, + RefreshListener.METHOD); +} +.... \ No newline at end of file diff --git a/documentation/articles/CustomizingComponentThemeWithSass.asciidoc b/documentation/articles/CustomizingComponentThemeWithSass.asciidoc new file mode 100644 index 0000000000..7013ca7f21 --- /dev/null +++ b/documentation/articles/CustomizingComponentThemeWithSass.asciidoc @@ -0,0 +1,191 @@ +--- +title: Customizing Component Theme With SASS +order: 47 +layout: page +--- + +[[customizing-component-theme-with-sass]] +Customizing component theme with SASS +------------------------------------- + +In addition to the general benefits Sass brings to the world of CSS in +Vaadin 7, the way themes are set up allows us to quite easily accomplish +some things that were previously hard. + +Let’s start from the top, without Sass, and continue from there. We'll +use the new _setPrimaryStyleName()_ to do some things previously not +possible. + +We’ll work on a small example with buttons that we want to customize: + +[source,java] +.... +@Theme("sassy") +public class SassyUI extends UI { + @Override + public void init(VaadinRequest request) { + Button b = new Button("Reindeer"); + Layout layout = new VerticalLayout(); + layout.addComponent(b); + setContent(layout); + } +} +.... + +And our basic (mostly empty at this point) “sassy” theme, based on +Reindeer, looks like this (assuming you're using the recommended +styles.scss+themename.scss structure as introduced in the previous +tutorial): + +[source,scss] +.... +@import "../reindeer/reindeer.scss"; +@mixin sassy { + @include reindeer; + // your styles go here +} +.... + +And the result is a basic Reindeer-looking button. We can change the +color of the caption like this: + +[source,scss] +.... +.v-button-caption { + color: red; +} +.... + +…but this changes ALL buttons. We just want some of the buttons to stand +out: + +[source,java] +.... +b = new Button("important"); +b.addStyleName("important"); +layout.addComponent(b); +.... + +css: + +[source,scss] +.... +.important .v-button-caption { + color: red; +} +.... + +Ok, this is all fine - but we realize our important button should +actually not look at all like a Reindeer button. + +Since Reindeer adds quite a few styles, this requires quite a lot of +customization with this approach. Enter _setPrimaryStyleName()_: + +[source,java] +.... +b = new Button("More important"); +b.setPrimaryStyleName("my-button"); +addComponent(b); +.... + +Now everything that was previously _.v-button_ in the browser DOM is all +of a sudden _.my-button_, and we have a completely unstyled button, but +with the DOM-structure and functionality of a regular button. We can +easily style this without interference from theme styles: + +[source,scss] +.... +.my-button { + color: red; +} +.... + +However, in our case we realize we still want it to look like a button, +just not with so much decorations as a Reindeer button. Let’s apply Base +styles: + +[source,scss] +.... +@include base-button($primaryStyleName: my-button); +.my-button { + color: red; +} +.... + +What? We now have a basic button with red text, but how? + +We have @included base-button and renamed it’s selectors to “my-button” +(instead of the default “v-button”). This makes the rules match our +button perfectly (we used setPrimaryStyleName() to rename it) - in +effect we apply base-button to our “my-button”. + +Now we have a good starting-point. Note that this might not be such a +big deal for small things, like buttons, but imagine something like +Table witout _any_ styles. Yikes. + +Here are the full sources (using distinct colors for each button for +clarity): + +[source,java] +.... +package com.example.sassy; + +import com.vaadin.annotations.Theme; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.Button; +import com.vaadin.ui.Layout; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; + +@Theme("sassy") +public class SassyUI extends UI { + @Override + public void init(VaadinRequest request) { + Button b = new Button("Reindeer"); + Layout layout = new VerticalLayout(); + layout.addComponent(b); + + b = new Button("important"); + b.addStyleName("important"); + layout.addComponent(b); + + b = new Button("More important"); + b.setPrimaryStyleName("my-button"); + layout.addComponent(b); + + setContent(layout); + } +} +.... + +[source,scss] +.... +// sassy/styles.scss +@import "sassy.scss"; +.sassy { + @include sassy; +} +.... + +[source,scss] +.... +// sassy/sassy.scss +@import "../reindeer/reindeer.scss"; + +@mixin sassy { + @include reindeer; + + .v-button-caption { + color: red; + } + + .important .v-button-caption { + color: green; + } + + @include base-button($name: my-button); + .my-button { + color: blue; + } +} +.... \ No newline at end of file diff --git a/documentation/articles/CustomizingTheStartupPageInAnApplication.asciidoc b/documentation/articles/CustomizingTheStartupPageInAnApplication.asciidoc new file mode 100644 index 0000000000..f846609719 --- /dev/null +++ b/documentation/articles/CustomizingTheStartupPageInAnApplication.asciidoc @@ -0,0 +1,120 @@ +--- +title: Customizing The Startup Page In An Application +order: 43 +layout: page +--- + +[[customizing-the-startup-page-in-an-application]] +Customizing the startup page in an application +---------------------------------------------- + +In Vaadin 6, the startup page - used to bootstrap a new Vaadin UI +instance in the browser - was generated as a monolithic chunk of HTML +and was not easily customizable. In Vaadin 7, we added a new facility +for registering special _bootstrap listeners_ that are invoked before +the bootstrap response is sent. In addition, instead of bare HTML in a +string, the response is now built as a DOM tree that is easy to +manipulate programmatically. + +Here's an example of a simple bootstrap listener: + +[source,java] +.... +import org.jsoup.nodes.Comment; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.parser.Tag; + +// ... + +new BootstrapListener() { + @Override + public void modifyBootstrapPage(BootstrapPageResponse response) { + response.getDocument().body().appendChild(new Comment("Powered by Vaadin!", "")); + } + + @Override + public void modifyBootstrapFragment(BootstrapFragmentResponse response) { + // Wrap the fragment in a custom div element + Element myDiv = new Element(Tag.valueOf("div"), ""); + List nodes = response.getFragmentNodes(); + for(Node node : nodes) { + myDiv.appendChild(node); + } + nodes.clear(); + nodes.add(myDiv); + } +} +.... + +The HTML library we use is http://jsoup.org/[jsoup]. It provides a very +convenient API for traversing, manipulating and extracting data from a +DOM, and is HTML5 compliant. + +The `BootstrapListener` interface contains two methods, one of which is +usually left empty. This is because a Vaadin application can be either +stand-alone, in which case it "owns" the whole page its UI resides in, +or embedded, such as a portlet, in which case it does not control the +content of the page it is embedded in. + +The `modifyBootstrapFragment` method is called in both cases. It +receives a `BootstrapFragmentResponse` that represents the HTML fragment +that is inserted in the host page, whether the page is controlled by +Vaadin or not. Hence, you only need to implement this method if you do +not care about the host page, whether your application is embedded or +standalone. + +The `modifyBootstrapPage` method is called with a +`BootstrapPageResponse` argument that represents the whole bootstrap +page, including the fragment mentioned above. Thus, it is only invoked +when the application is standalone and actually responsible for +generating the page. This method allows you to, for instance, add things +to the `head` element. The `BootstrapPageResponse` class also allows +setting arbitrary HTTP response headers: + +[source,java] +.... +public void modifyBootstrapPage(BootstrapPageResponse response) { + response.setHeader("X-Powered-By", "Vaadin 7"); +} +.... + +But how and where should the bootstrap listeners be registered? It +should be only once per session, and right in the beginning, so that +they are already added when the first response is sent. + +To do that you should write a custom servlet that extends +`VaadinServlet`, or a custom portlet extending `VaadinPortlet`, and a +session init listener that adds the bootstrap listener to the new +session. + +[source,java] +.... +class MyVaadinServlet extends VaadinServlet { + @Override + protected void servletInitialized() throws ServletException { + super.servletInitialized(); + getService().addSessionInitListener(new SessionInitListener() { + @Override + public void sessionInit(SessionInitEvent event) { + event.getSession().addBootstrapListener(listener); + } + }); + } +} + +// Or... + +class MyVaadinPortlet extends VaadinPortlet { + @Override + protected void portletInitialized() throws PortletException { + super.portletInitialized(); + getService().addSessionInitListener(new SessionInitListener() { + @Override + public void sessionInit(SessionInitEvent event) { + event.getSession().addBootstrapListener(listener); + } + }); + } +} +.... \ No newline at end of file diff --git a/documentation/articles/ExposingServerSideAPIToJavaScript.asciidoc b/documentation/articles/ExposingServerSideAPIToJavaScript.asciidoc new file mode 100644 index 0000000000..fde40cce11 --- /dev/null +++ b/documentation/articles/ExposingServerSideAPIToJavaScript.asciidoc @@ -0,0 +1,140 @@ +--- +title: Exposing Server Side API To JavaScript +order: 41 +layout: page +--- + +[[exposing-server-side-api-to-javascript]] +Exposing server-side API to JavaScript +-------------------------------------- + +The new JavaScript integration functionality will allow you to easily +publish methods that can be called with JavaScript on the client side. +In effect, you can publish a JavaScript API for your application. +Although you will probably not find yourself using this very often, it +can be useful when integrating with JavaScript frameworks or embedding +within legacy sites. + +Exposing a `notify()` method that takes a message and displays that as a +notification can be done in one simple block in e.g `UI.init()`: + +[source,java] +.... +JavaScript.getCurrent().addFunction("notify", new JavaScriptFunction() { + public void call(JSONArray arguments) throws JSONException { + Notification.show(arguments.getString(0)); + } +}); +.... + +This will expose the `notify()`{empty}-method globally in the window object. +Technically it's thus `window.notify()`, but you can call it by simply +by `notify()`. Try entering `notify("Hey!")` into the Firebug or +Developler Tools console, or `javascript:notify("Hey!")` into the +address bar. + +You'll notice that this assumes there is a String in the first position +of the array. Also, this will clutter the global namespace, which is +generally not a good idea, unless you really have a specific need for +that. + +Let's make a complete example with two arguments, some simple error +handling, and namespacing: + +[source,java] +.... +JavaScript.getCurrent().addFunction("com.example.api.notify", + new JavaScriptFunction() { + public void call(JSONArray arguments) throws JSONException { + try { + String caption = arguments.getString(0); + if (arguments.length() == 1) { + // only caption + Notification.show(caption); + } else { + // type should be in [1] + Notification.show(caption, + Type.values()[arguments.getInt(1)]); + } + } catch (JSONException e) { + // We'll log in the console, you might not want to + JavaScript.getCurrent().execute( + "console.error('" + e.getMessage() + "')"); + } + } + }); +} +.... + +Using the dotted notation for the method will automatically create those +objects in the browser; you'll call this method like so: +`com.example.api.notify("Hey!")`. You do not have to use a long name +like this, though - it's up to you and your use-case. + +The second thing to notice is that we now wrapped the code in a +try-catch, so that the wrong number or wrong types of arguments does not +cause an ugly stacktrace in our server logs. Again, how you should react +to erroneous use of your exposed API depends on your use-case. We'll log +an error message to the browser console as an example. + +We're now accepting a second (integer) argument, and using that as +_type_ for the `Notification`. + +Finally, we'll add a link that will call the function, and work as a +_Bookmarklet_. You can drag the link to your bookmarks bar, and when you +invoke it when viewing the application with our exposed `notify()`{empty}-method, you will be prompted for a message that will then be sent to +the method. Here is the plain HTML code for creating such a link: + +[source,html] +.... +Send message +.... + +Here is the full source for our application: + +[source,java] +.... +import org.json.JSONArray; +import org.json.JSONException; +import com.vaadin.server.ExternalResource; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.JavaScript; +import com.vaadin.ui.JavaScriptFunction; +import com.vaadin.ui.Link; +import com.vaadin.ui.Notification; +import com.vaadin.ui.Notification.Type; +import com.vaadin.ui.UI; + +public class JSAPIUI extends UI { + @Override + public void init(VaadinRequest request) { + + JavaScript.getCurrent().addFunction("com.example.api.notify", + new JavaScriptFunction() { + public void call(JSONArray arguments) throws JSONException { + try { + String caption = arguments.getString(0); + if (arguments.length() == 1) { + // only caption + Notification.show(caption); + } else { + // type should be in [1] + Notification.show(caption, + Type.values()[arguments.getInt(1)]); + } + } catch (JSONException e) { + // We'll log in the console, you might not want to + JavaScript.getCurrent().execute( + "console.error('" + e.getMessage() + "')"); + } + } + }); + + + setContent(new Link( + "Send message", + new ExternalResource( + "javascript:(function(){com.example.api.notify(prompt('Message'),2);})();"))); + } +} +.... \ No newline at end of file diff --git a/documentation/articles/FormattingDataInGrid.asciidoc b/documentation/articles/FormattingDataInGrid.asciidoc index 35a41e8225..f1151aa9f9 100644 --- a/documentation/articles/FormattingDataInGrid.asciidoc +++ b/documentation/articles/FormattingDataInGrid.asciidoc @@ -114,8 +114,8 @@ Full example ^^^^^^^^^^^^ Putting all these pieces together, we end up with this class that uses -the same data as in the link:UsingGridWithAContainer.asciidoc[Using -Grid with a Container] example. +the same data as in the <> example. [source,java] .... diff --git a/documentation/articles/IntegratingAJavaScriptComponent.asciidoc b/documentation/articles/IntegratingAJavaScriptComponent.asciidoc new file mode 100644 index 0000000000..ae46677dd6 --- /dev/null +++ b/documentation/articles/IntegratingAJavaScriptComponent.asciidoc @@ -0,0 +1,116 @@ +--- +title: Integrating A JavaScript Component +order: 38 +layout: page +--- + +[[integrating-a-javascript-component]] +Integrating a JavaScript component +---------------------------------- + +You can use an existing JavaScript component as a component in Vaadin by +creating a server-side API for the component as well as writing the +JavaScript code that connects the server-side API to the actual +JavaScript component. Because of the dynamic nature of JavaScript, you +don't need to use GWT development mode or recompile the widgetset while +making client-side changes. + +The server-side component should extend `AbstractJavaScriptComponent` and +provide the API that the developer uses to interact with the component. +The class should also have a `@JavaScript` annotation that defines the +required JavaScript libraries in the order they should be loaded. This +example uses the Flot graph library from http://code.google.com/p/flot/. +Float requires jQuery which is loaded using +https://developers.google.com/speed/libraries/[Google Libraries API]. + +[source,java] +.... +import com.vaadin.annotations.*; + +@JavaScript({"https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js", "jquery.flot.js", "flot_connector.js"}) +public class Flot extends AbstractJavaScriptComponent { + public void addSeries(double... points) { + List> pointList = new ArrayList>(); + for (int i = 0; i < points.length; i++) { + pointList.add(Arrays.asList(Double.valueOf(i), + Double.valueOf(points[i]))); + } + + getState().series.add(pointList); + } + + @Override + public FlotState getState() { + return (FlotState) super.getState(); + } +} +.... + +The shared state class will not be used by any GWT code so you don't +have to put it in the widgetset's client package. The state class should +extend `JavaScriptComponentState` but is otherwise similar to the shared +state of a normal GWT component. + +[source,java] +.... +public class FlotState extends JavaScriptComponentState { + public List>> series = new ArrayList>>(); +} +.... + +The only remaining code is the client-side JavaScript connector in +`flot_connector.js`. The connector defines a global initializer function +named based on the fully qualified name of the server-side `Component` +class with dots replaced with underscores. In this example the +server-side `Component` is `com.example.Flot` which means that the function +name should be `com_example_Flot`. + +This initializer function should initialize the JavaScript part of the +component. It is called by the framework with `this` pointing to a +connector wrapper providing integration to the framework. For full +information about the services provided by the connector wrapper, please +read the Javadoc for the `AbstractJavaScriptComponent` class. + +In this example, the initializer first initializes the `element` +variable with a jQuery object for the DOM element of the component. +Next, a state change listener is defined by assigning a function to the +`onStateChange` field of the connector wrapper. This function will be +called whenever the shared state is changed from the server-side code. +In the state change listener, the Flot API is used to initialize a graph +with the data series from the shared state into the DOM element. + +The format of the series property in the `FlotState` Java class has been +chosen with the Flot API in mind. Flot expects an array of data series +where each item is an array of data points where each data point is an +array with the x value followed by the y value. This is defined in Java +as `List>>` and then the framework takes care of the +conversion between server-side Java values and client-side JavaScript +values. `double[][][]` in Java would give the same JavaScript structure, +but it was not used here as it gives less flexibility in the Java code. + +[source,javascript] +.... +window.com_example_Flot = function() { + var element = $(this.getElement()); + + this.onStateChange = function() { + $.plot(element, this.getState().series); + } +} +.... + +By implementing a server-side Java class extending +`AbstractJavaScriptConnector` and a client-side JavaScript connector +initialization function, existing JavaScript component libraries can +easily be integrated to Vaadin. The server-side code is almost similar +to the code required for a component based on GWT and the client-side +code is quite similar to a `ComponentConnector` implemented using GWT. + +[WARNING] +.Security Warning +==== +Do note that third-party JavaScript code could be dangerous +(https://www.owasp.org/index.php/3rd_Party_Javascript_Management_Cheat_Sheet), +and you should take into account the security risks of using such. + +==== diff --git a/documentation/articles/IntegratingAJavaScriptLibraryAsAnExtension.asciidoc b/documentation/articles/IntegratingAJavaScriptLibraryAsAnExtension.asciidoc new file mode 100644 index 0000000000..090c91209e --- /dev/null +++ b/documentation/articles/IntegratingAJavaScriptLibraryAsAnExtension.asciidoc @@ -0,0 +1,96 @@ +--- +title: Integrating A JavaScript Library As An Extension +order: 39 +layout: page +--- + +[[integrating-a-javascript-library-as-an-extension]] +Integrating a JavaScript library as an extension +------------------------------------------------ + +JavaScript can also be used for creating Extensions e.g. for integrating +existing JavaScript libraries. See <> for general information about Extensions. The main +difference when using JavaScript is that you extend +`AbstractJavaScriptExtension`, that your shared state class should +extend `JavaScriptExtensionState` and then of course that your +client-side implementation is written in JavaScript. See +<> for basic information about how to use +JavaScript for your client-side logic. + +This tutorial will create a simple Extension for integrating +https://developers.google.com/analytics/devguides/collection/gajs/[Google +Analytics]. Because the Analytics API just uses the same `_gaq.push` +function with different arguments, the JavaScript connector logic can be +equally simple. Aside from asynchronously loading ga.js, the client-side +code just adds a callback that the server-side code can use to push new +commands. + +[source,javascript] +.... +window._gaq = window._gaq || []; + +(function() { + var ga = document.createElement('script'); + ga.type = 'text/javascript'; + ga.async = true; + ga.src = ('https:' == document.location.protocol ? + 'https://ssl' : 'http://www') + + '.google-analytics.com/ga.js'; + var s = document.getElementsByTagName('script')[0]; + s.parentNode.insertBefore(ga, s); +})(); + +window.com_example_Analytics = function() { + this.pushCommand = function(command) { + _gaq.push(command); + } +} +.... + +The server-side Extension class provides the common Extension API for +extending a UI instance as well as API for some Analytics features. All +the Analytics features are based on the `pushCommand` method that +invokes the corresponding client-side callback. + +The Analytics API used in this example has nothing that warrants using +shared state, but you can of course use shared state in your own +JavaScript Extension if you want to as long as your state class extends +`JavaScriptExtensionState`. + +[source,java] +.... +@JavaScript("analytics_connector.js") +public class Analytics extends AbstractJavaScriptExtension { + public Analytics(UI ui, String account) { + extend(ui); + pushCommand("_setAccount", account); + } + + public void trackPageview(String name) { + pushCommand("_trackPageview", name); + } + + private void pushCommand(Object... commandAndArguments) { + // Cast to Object to use Object[] commandAndArguments as the first + // varargs argument instead of as the full varargs argument array. + callFunction("pushCommand", (Object) commandAndArguments); + } +} +.... + +Extensions are suitable for integrating many existing JavaScript +libraries that do not provide a component that is added to a layout. By +using a client-side JavaScript connector for integrating the JavaScript +library, you can eliminate GWT from the equation to give you slightly +less code to maintain. + +[WARNING] +.Security Warning +==== +Do note that third-party JavaScript code can be dangerous +(https://www.owasp.org/index.php/3rd_Party_Javascript_Management_Cheat_Sheet), +and you should take into account the security risks of using such. + +==== \ No newline at end of file diff --git a/documentation/articles/MigratingFromVaadin6ToVaadin7.asciidoc b/documentation/articles/MigratingFromVaadin6ToVaadin7.asciidoc index de3e9f5d24..f6ac6aae37 100644 --- a/documentation/articles/MigratingFromVaadin6ToVaadin7.asciidoc +++ b/documentation/articles/MigratingFromVaadin6ToVaadin7.asciidoc @@ -8,8 +8,8 @@ layout: page = Migrating from Vaadin 6 to Vaadin 7 For migration to Vaadin 7.1,  see -link:MigratingFromVaadin7.0ToVaadin7.1.asciidoc[Migrating -from Vaadin 7.0 to Vaadin 7.1] +<> [[getting-started]] Getting Started @@ -71,7 +71,7 @@ public class V6tm1Application extends Application { Label label = new Label("Hello Vaadin!"); mainWindow.addComponent(label); setMainWindow(mainWindow); - setTheme(“mytheme”); + setTheme("mytheme"); } } .... @@ -180,9 +180,9 @@ In Vaadin 6, Window, Panel and some other components had a *default layout* and addComponent() etc. As this often caused confusion and caused layout problems when unaware of the implicit layout or forgetting to set its layout parameters, Vaadin 7 now requires *explicitly setting -the content*. See See e.g. -link:CreatingABasicApplication.asciidoc[Creating -a basic application] +the content*. See e.g. +<> If you want to minimize the impact of this on the look and theme of an old application, you can reproduce the *old structure* simply by setting @@ -213,8 +213,9 @@ of synchronizing to the Application instance - see the javadoc for To customize the creation of UIs - for instance to create different UIs for mobile and desktop devices - -*link:CreatingAnApplicationWithDifferentFeaturesForDifferentClients.asciidoc[a -custom UIProvider]* can be used. +<> can be used. [[forms-and-data-binding]] Forms and Data Binding @@ -231,21 +232,24 @@ data binding works mostly as is, version 7 brings something better: * *FieldGroup* supporting *automated data binding*, whether for a hand-designed form or -link:AutoGeneratingAFormBasedOnABeanVaadin6StyleForm.asciidoc[creating -the fields automatically] +<> -* *link:CreatingATextFieldForIntegerOnlyInputUsingADataSource.asciidoc[typed -fields and properties]* +* *<>* -* *link:CreatingYourOwnConverterForString.asciidoc[converters]*, +* *<>*, both -link:ChangingTheDefaultConvertersForAnApplication.asciidoc[automatic -via ConverterFactory] and -link:CreatingATextFieldForIntegerOnlyInputWhenNotUsingADataSource.asciidoc[explicitly set] +<> and +<> * improved *validation* (performed on data model values after conversion) - see e.g. -link:UsingBeanValidationToValidateInput.asciidoc[bean validation example] +<> * and more @@ -305,8 +309,7 @@ optional additional optional parameters before the module name. If you have optimized your widgetset to limit what components to load initially, see -link:OptimizingTheWidgetSet.asciidoc[this -tutorial] and the +<> and the https://vaadin.com/directory/component/widget-set-optimizer[WidgetSet Optimizer add-on]. @@ -332,18 +335,18 @@ fully migrating your themes to the SCSS format with a theme name selector. To take advantage of the new features, see -link:CreatingAThemeUsingSass.asciidoc[Creating -a theme using sass] and -link:CustomizingComponentThemeWithSass.asciidoc[Customizing -component theme with Sass]. +<> +and +<>. Note that the SCSS theme needs to be *compiled* to CSS before use - in development mode, this takes place automatically on the fly whenever the theme is loaded, but when moving to production mode, you need to run the theme compiler on it to produce a pre-compiled static theme. -link:WidgetStylingUsingOnlyCSS.asciidoc[CSS -can be used to style components] somewhat more freely than in Vaadin 6. +<> somewhat more freely than in Vaadin 6. The DOM structure of several layouts has changed, which might require changes to themes for layouts. See also the section on layouts below. @@ -359,14 +362,12 @@ navigate to them. The best way to get acquainted with the new navigation features is to check the tutorials on -link:CreatingABookmarkableApplicationWithBackButtonSupport.asciidoc[creating -a bookmarkable application], -link:UsingParametersWithViews.asciidoc[using -parameters with views], -link:AccessControlForViews.asciidoc[access -control for views] and -link:ViewChangeConfirmations.asciidoc[view -change confirmations]. +<>, +<>, +<> and +<>. When logging out a user, you can use *Page.setLocation()* to redirect the user to a suitable page. @@ -380,8 +381,8 @@ As ApplicationServlet moved to history and is replaced by The most common customizations: -* link:CustomizingTheStartupPageInAnApplication.asciidoc[Customizing -the bootstrap page]: JavaScript, headers, ... +* <>: JavaScript, headers, ... * Add-ons using customized servlets for other purposes (e.g. customizing communication between client and server) probably need more extensive rework @@ -413,20 +414,21 @@ server side component Label, but the communication part has been split off into LabelConnector. The annotations linking the client side and the server side have also changed, now the LabelConnector has an *@Connect* annotation linking it to the server side component Label. -https://vaadin.com/book/vaadin7/-/page/architecture.client-side.html[the +https://vaadin.com/book/vaadin7/-/page/architecture.client-side.html[The book] provides some background and the tutorial on -link:CreatingASimpleComponent.asciidoc[creating -a simple component] shows an example. +<> shows an example. The connector communicates with the server primarily via shared state from the server to the client and **RPC -calls **link:SendingEventsFromTheClientToTheServerUsingRPC.asciidoc[from -client to server] and -link:UsingRPCToSendEventsToTheClient.asciidoc[from -server to client], with a larger set of supported data types. For +calls **<> and +<>, with a larger set of supported data types. For component containers, -link:CreatingASimpleComponentContainer.asciidoc[the -hierarchy of the contained components is sent separately]. +<>. The old mechanism with UIDL, *paintContent()* and *changeVariables()* is still there for a while to ease migration, but it is recommended to @@ -436,15 +438,16 @@ in much cleaner code. Using the old mechanisms requires implementing There are also new features such as support for *Extensions* (components which -link:CreatingAUIExtension.asciidoc[extend -the UI] or -link:CreatingAComponentExtension.asciidoc[other -components] without having a widget in a layout) and -link:UsingAJavaScriptLibraryOrAStyleSheetInAnAddOn.asciidoc[support -for JavaScript], also for -link:IntegratingAJavaScriptComponent.asciidoc[implementing -components] and -link:IntegratingAJavaScriptLibraryAsAnExtension.asciidoc[extensions], +<> or +<> without having a widget in a layout) and +<>, also for +<> and +<>, which might simplify the implementation of some components. Shared state and RPC can also be used from JavaScript, and there are other techniques for client-server communication. @@ -550,8 +553,8 @@ side component containers, but a few can also affect other developers. Among the changes affecting others than layout developers, *CssLayout* now consists of a single DIV instead of three nested elements, and -link:WidgetStylingUsingOnlyCSS.asciidoc[CSS -can be used to do more customization] than in previous Vaadin versions. +<> than in previous Vaadin versions. Also other layouts have changed in terms of their *DOM structure* on the client, which might require changes to themes. The interface *MarginHandler* is now only implemented by layouts that actually support @@ -565,10 +568,10 @@ of *ComponentContainer*. For those implementing new component containers or layouts, see the related tutorials -link:CreatingASimpleComponentContainer.asciidoc[Creating -a simple component container] and -link:WidgetStylingUsingOnlyCSS.asciidoc[Widget -styling using only CSS]. +<> and +<>. [[migration-steps-for-componentcontainers]] Migration steps for ComponentContainers diff --git a/documentation/articles/MigratingFromVaadin7.0ToVaadin7.1.asciidoc b/documentation/articles/MigratingFromVaadin7.0ToVaadin7.1.asciidoc index ecd244a675..43d91a8f67 100644 --- a/documentation/articles/MigratingFromVaadin7.0ToVaadin7.1.asciidoc +++ b/documentation/articles/MigratingFromVaadin7.0ToVaadin7.1.asciidoc @@ -13,9 +13,9 @@ This guide describes how to migrate from earlier versions to Vaadin 7.1. Migrating from Vaadin 6 ~~~~~~~~~~~~~~~~~~~~~~~ -When migrating from Vaadin 6, first review -link:MigratingFromVaadin6ToVaadin7.asciidoc[Migrating -from Vaadin 6 to Vaadin 7], then continue with the rest of this guide. +When migrating from Vaadin 6, first review +<>, then continue with the rest of this guide. [[migrating-from-vaadin-7.0]] Migrating from Vaadin 7.0 diff --git a/documentation/articles/OptimizingTheWidgetSet.asciidoc b/documentation/articles/OptimizingTheWidgetSet.asciidoc new file mode 100644 index 0000000000..1254e53e5f --- /dev/null +++ b/documentation/articles/OptimizingTheWidgetSet.asciidoc @@ -0,0 +1,193 @@ +--- +title: Optimizing The Widget Set +order: 44 +layout: page +--- + +[[optimizing-the-widget-set]] +Optimizing the widget set +------------------------- + +Vaadin contains a lot of components and most of those components +contains a client side part which is executed in the browser. Together +all the client side implementations sum up to a big amount of data the +enduser needs to download to the browser even if he might never use all +the components. + +For that reason Vaadin uses three strategies for downloading the +components: + +[[eager]] +Eager ++++++ + +What eager means is that the client implementation for the component is +included in the payload that is initially downloaded when the +application starts. The more components that is made eager the more will +need to be downloaded before the initial view of the application is +shown. By default Vaadin puts most components here since Vaadin does not +know which components will be used in the first view and cannot thus +optimize any further. You would have noticed this if you ever made a +Hello World type of application and wondered why Vaadin needed to +download so much for such a simple application. + +[[deferred]] +Deferred +++++++++ + +When marking a component as deferred it means that its client side +implementation will be downloaded right after the initial rendering of +the application is done. This can be useful if you know for instance +that a component will soon be used in that application but is not +displayed in the first view. + +[[lazy]] +Lazy +++++ + +Lazy components client side implementation doesn't get downloaded until +the component is shown. This will sometimes mean that a view might take +a bit longer to render if several components client side implementation +needs to first be downloaded. This strategy is useful for components +that are rarely used in the application which everybody might not see. +`RichTextArea` and `ColorPicker` are examples of components in Vaadin that by +default are Lazy. + +[[optimizing-the-loading-of-the-widgets]] +Optimizing the loading of the widgets +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Now that we know what Vaadin provides, lets see how we can modify how a +component is loaded to provide the best experience for our application. + +Lets say we want to build a HelloWorld application which only needs a +few components. Specifically these components will be shown on the +screen: + +* UI - The UI of the application. +* VerticalLayout - The Vertical layout inside the UI where the message +is shown +* Label - A label with the text "Hello World" + +All other Vaadin components provided by Vaadin we don't want to load. To +do this we are going to mark those three components as Eager (initially +loaded) and all the rest as Lazy. + +To do that we need to implement our own `ConnectorBundleLoaderFactory`. +Here is my example one: + +[source,java] +.... +public class MyConnectorBundleLoaderFactory extends + ConnectorBundleLoaderFactory { + private static final List eagerComponents = new + LinkedList(); + + static { + eagerComponents.add(UI.class); + eagerComponents.add(VerticalLayout.class); + eagerComponents.add(Label.class); + } + + @Override protected LoadStyle getLoadStyle(JClassType connectorType){ + Connect annotation = connectorType.getAnnotation(Connect.class); + Class componentClass = annotation.value(); + + // Load eagerly marked connectors eagerly + if(eagerComponents.contains(componentClass)) { + return LoadStyle.EAGER; + } + + //All other components should be lazy + return LoadStyle.LAZY; + } +} +.... + +We also need to add our factory to the widgetset by adding the following +to our .gwt.xml: + +[source,xml] +.... + + + +.... + +If you are using the Eclipse Plugin to compile the widgetset you will +also want to add the following meta data for the compiler so it does not +overwrite our generator setting: + +[source,xml] +.... + +.... + +If you have used the Maven archetype for setting up your project, you +might need to add vaadin-client-compiler as a dependency in your project +as it is by default only used when actually starting the widgetset +compiler. See http://dev.vaadin.com/ticket/11533 for more details. + +Finally, here is my simple test application UI for which I have +optimized the widgetset: + +[source,java] +.... +public class HelloWorldUI extends UI { + + @Override + protected void init(VaadinRequest request) { + VerticalLayout layout = new VerticalLayout(); + layout.addComponent(new Label("Hello world")); + setContent(layout); + } +} +.... + +Now, all I have to do is recompile the widgetset for the new load +strategy to take effect. + +If you now check the network traffic when you load the application you +will notice a *huge difference*. Using the default widgetset with the +default loading strategy our Hello World application will load over *1 +Mb* of widgetset data. If you then switch to using our own widgetset +with our own custom loader factory the widgetset will only be about *375 +kb*. That is over *60% less!* + +Using your own custom widgetset loader factory is highly recommended in +all projects. + +[[finding-out-which-components-are-loaded-by-a-view]] +Finding out which components are loaded by a View +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +So you want to start optimizing your widgetset but how do you find out +which components are needed for the initial view so you can make them +eager while keeping everything else deferred or lazy? Fortunately there +is an addon +https://vaadin.com/directory#addon/widget-set-optimizer[WidgetSetOptimizer] +for doing just this. + +To use it you download this addon and add it to your project. + +Add the following to the .gwt.xml: + +[source,xml] +.... + +.... + +You will also need to add the following to your UI classes init method + +[source,java] +.... +new WidgetSetOptimizer().extend(this); +.... + +Finally compile the widgetset and run the application with the &debug +parameter. In the debug window there will be a new button "OWS" which by +pressing you will get the Generator class automatically generated for +you. The generated generator class will mark the currently displayed +components as Eager while loading everything else as Deferred. More +information about the addon and its usage can be found on the Addon page +in the directory. \ No newline at end of file diff --git a/documentation/articles/SendingEventsFromTheClientToTheServerUsingRPC.asciidoc b/documentation/articles/SendingEventsFromTheClientToTheServerUsingRPC.asciidoc new file mode 100644 index 0000000000..06ccef3af4 --- /dev/null +++ b/documentation/articles/SendingEventsFromTheClientToTheServerUsingRPC.asciidoc @@ -0,0 +1,145 @@ +--- +title: Sending Events From The Client To The Server Using RPC +order: 37 +layout: page +--- + +[[sending-events-from-the-client-to-the-server-using-RPC]] +Sending events from the client to the server using RPC +------------------------------------------------------ +An RPC mechanism can be used to communicate from the client to the +server. In effect, the client can call methods that are executed by the +server component. The server component can then take appropriate action +- e.g updating the shared state or calling event listeners. + +To set up client-server RPC we need to create one interface defining the +RPC methods, and then make use of that interface on both the client and +the server. Place the `MyComponentServerRpc` interface in the client +package: + +[source,java] +.... +package com.example.mycomponent.client; + +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; + +public interface MyComponentServerRpc extends ServerRpc { + public void clicked(MouseEventDetails mouseDetails); +} +.... + +Note that the RPC methods can not have return values. In this example, +we pass `MouseEventDetails` to get a more complete example, but you +could pass almost any (or no) parameters. + +In the server side `MyComponent` we need to implement the interface, and +register it for use: + +[source,java] +.... +package com.example.mycomponent; + +import com.example.mycomponent.client.MyComponentServerRpc; +import com.example.mycomponent.client.MyComponentState; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.ui.AbstractComponent; + +public class MyComponent extends AbstractComponent { + + private int clickCount = 0; + + private MyComponentServerRpc rpc = new MyComponentServerRpc() { + public void clicked(MouseEventDetails mouseDetails) { + clickCount++; + setText("You have clicked " + clickCount + " times"); + } + }; + + public MyComponent() { + registerRpc(rpc); + } + +/* Previous code commented out for clarity: + @Override + public MyComponentState getState() { + return (MyComponentState) super.getState(); + } + public void setText(String text) { + getState().text = text; + } + public String getText() { + return getState().text; + } +*/ +} +.... + +Here we react to the RPC call by incrementing a counter. We do not make +use of the `MouseEventDetails` (yet). Notice the *important call to +`registerRpc()`* in the added constructor. + +In the client side `MyComponentConnector`, we use `RpcProxy` to get an +implementation of the RPC interface, and call the `clicked()` method +when the widget is clicked: + +[source,java] +.... +package com.example.mycomponent.client; + +// imports removed for clarity +import com.vaadin.terminal.gwt.client.communication.RpcProxy; + +@Connect(MyComponent.class) +public class MyComponentConnector extends AbstractComponentConnector { + + MyComponentServerRpc rpc = RpcProxy + .create(MyComponentServerRpc.class, this); + + public MyComponentConnector() { + getWidget().addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + + final MouseEventDetails mouseDetails = MouseEventDetailsBuilder + .buildMouseEventDetails(event.getNativeEvent(), + getWidget().getElement()); + + rpc.clicked(mouseDetails); + } + }); + } + +/* Previous code commented out for clarity: + @Override + protected Widget createWidget() { + return GWT.create(MyComponentWidget.class); + } + @Override + public MyComponentWidget getWidget() { + return (MyComponentWidget) super.getWidget(); + } + @Override + public MyComponentState getState() { + return (MyComponentState) super.getState(); + } + @OnStateChange("text") + void updateText() { + getWidget().setText(getState().text); + } +*/ +} +.... + +Notice that most of the code is for attaching the click handler and +creating the `MouseEventDetails`, the code for the actual RPC is quite +minimal. + +Compile the widgetset, and the label text should be updated with the +click count whenever you click it (remember that the counting is done on +the server-side). + +Finally, note that you can use multiple RPC interfaces in one component, +allowing for better code separation and reuse. + +You can do the same thing in the other direction, see the article about +server to client RPC for details. \ No newline at end of file diff --git a/documentation/articles/ShowingExtraDataForGridRows.asciidoc b/documentation/articles/ShowingExtraDataForGridRows.asciidoc index a5c12fa995..5be882cfd8 100644 --- a/documentation/articles/ShowingExtraDataForGridRows.asciidoc +++ b/documentation/articles/ShowingExtraDataForGridRows.asciidoc @@ -18,8 +18,8 @@ expanded, and then you need to hook up the events for actually expanding a row. This example uses the same data as in the -link:UsingGridWithAContainer.asciidoc[Using Grid with a Container] -example. +<> example. [[detailsgenerator]] DetailsGenerator diff --git a/documentation/articles/SimplifiedRPCusingJavaScript.asciidoc b/documentation/articles/SimplifiedRPCusingJavaScript.asciidoc index 64d3ea5b1c..e53d79b7e8 100644 --- a/documentation/articles/SimplifiedRPCusingJavaScript.asciidoc +++ b/documentation/articles/SimplifiedRPCusingJavaScript.asciidoc @@ -8,16 +8,17 @@ layout: page = Simplified RPC using JavaScript This tutorial continues where -link:IntegratingAJavaScriptComponent.asciidoc[Integrating a JavaScript -component] ended. We will now add RPC functionality to the JavaScript -Flot component. RPC can be used in the same way as with ordinary GWT -components as described in link:UsingRPCFromJavaScript.asciidoc[Using -RPC from JavaScript]. This tutorial describes a simplified way that is +<> ended. We will now add RPC +functionality to the JavaScript Flot component. RPC can be used in the +same way as with ordinary GWT components as described in +<>. This tutorial describes a simplified way that is based on the same concepts as in -link:ExposingServerSideAPIToJavaScript.asciidoc[Exposing server -side API to JavaScript]. This way of doing RPC is less rigorous and is -intended for simple cases and for developers appreciating the dynamic -nature of JavaScript. +<>. This way of doing RPC is less +rigorous and is intended for simple cases and for developers appreciating +the dynamic nature of JavaScript. The simplified way is based on single callback functions instead of interfaces containing multiple methods. We will invoke a server-side diff --git a/documentation/articles/UsingAJavaScriptLibraryOrAStyleSheetInAnAddOn.asciidoc b/documentation/articles/UsingAJavaScriptLibraryOrAStyleSheetInAnAddOn.asciidoc new file mode 100644 index 0000000000..243875a95a --- /dev/null +++ b/documentation/articles/UsingAJavaScriptLibraryOrAStyleSheetInAnAddOn.asciidoc @@ -0,0 +1,61 @@ +--- +title: Using A JavaScript Library Or A Style Sheet In An Add On +order: 40 +layout: page +--- + +[[using-a-javascript-library-or-a-style-sheet-in-an-addon]] +Using a JavaScript library or a style sheet in an add-on +-------------------------------------------------------- + +Including style sheets or JavaScript files in your add-ons or as a part +of your application can now be done by adding a `@StyleSheet` or +`@JavaScript` annotation to a `Component` or `Extension` class. Each +annotation takes a list of strings with URLs to the resources that +should be loaded on the page before the framework initializes the +client-side `Component` or `Extension`. + +The URLs can either be complete absolute urls (e.g. https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js) or +relative URLs (e.g. _redbutton.css_). A relative URL is converted to a +special URL that will download the file from the Java package where the +defining class is located. This means that e.g. +`@StyleSheet({"redbutton.css"})` on the class `com.example.RedButton` will +cause the file `com/example/redbutton.css` on the classpath to be loaded +in the browser. `@JavaScript` works in exactly the same way - see +<> for a practical example. + +[source,java] +.... +@StyleSheet("redbutton.css") +public class RedButton extends NativeButton { + public RedButton(String caption) { + super(caption); + addStyleName("redButton"); + } +} +.... + +In this simple example, the `RedButton` component just adds a `redButton` +style name to a normal `NativeButton`. _redbutton.css_ is located in the +same folder as _RedButton.java_ and has this content: + +[source,css] +.... +.redButton { + background-color: red; +} +.... + +This new mechanism makes it very easy to include style sheet or +JavaScript files with add-ons and automatically load them in the browser +when the add-on is used. + +[WARNING] +.Security Warning +==== +Do note that third-party JavaScript code can be dangerous +(https://www.owasp.org/index.php/3rd_Party_Javascript_Management_Cheat_Sheet), +and you should take into account the security risks of using such. + +==== diff --git a/documentation/articles/UsingBeanValidationToValidateInput.asciidoc b/documentation/articles/UsingBeanValidationToValidateInput.asciidoc new file mode 100644 index 0000000000..685dfa668b --- /dev/null +++ b/documentation/articles/UsingBeanValidationToValidateInput.asciidoc @@ -0,0 +1,59 @@ +--- +title: Using Bean Validation To Validate Input +order: 45 +layout: page +--- + +[[using-bean-validation-to-validate-input]] +Using Bean Validation to validate input +--------------------------------------- + +Before you get started with Bean Validation you need to download a Bean +Validation implementation and add it to your project. You can find one +for instance at http://bval.apache.org/downloads.html. Just add the jars +from the lib folder to your project. + +Bean Validation works as a normal validator. If you have a bean with +Bean Validation annotations, such as: + +[source,java] +.... +public class Person { + + @Size(min = 5, max = 50) + private String name; + + @Min(0) + @Max(100) + private int age; + // + constructor + setters + getters +} +.... + +You can create a field for the name field as you always would: + +[source,java] +.... +Person person = new Person("John", 26); +BeanItem item = new BeanItem(person); + +TextField firstName = new TextField("First name", + item.getItemProperty("name")); +firstName.setImmediate(true); +setContent(firstName); +.... + +and add the bean validation as a normal validator: + +[source,java] +.... +firstName.addValidator(new BeanValidator(Person.class, "name")); +.... + +Your `firstName` field is now automatically validated based on the +annotations in your bean class. You can do the same thing for the `age` +field and you won't be able to set a value outside the valid 0-100 +range. + +A Bean Validation tutorial is available here: +http://docs.oracle.com/javaee/6/tutorial/doc/gircz.html \ No newline at end of file diff --git a/documentation/articles/UsingGridWithInlineData.asciidoc b/documentation/articles/UsingGridWithInlineData.asciidoc index 2bc08a9910..de2486508f 100644 --- a/documentation/articles/UsingGridWithInlineData.asciidoc +++ b/documentation/articles/UsingGridWithInlineData.asciidoc @@ -8,7 +8,8 @@ layout: page = Using Grid with inline data Instead of using a Vaadin Container as explained in -link:UsingGridWithAContainer.asciidoc[Using Grid with a Container], +<>, you can also directly add simple inline data to Grid without directly using a Container. diff --git a/documentation/articles/UsingParametersWithViews.asciidoc b/documentation/articles/UsingParametersWithViews.asciidoc new file mode 100644 index 0000000000..51c63acf28 --- /dev/null +++ b/documentation/articles/UsingParametersWithViews.asciidoc @@ -0,0 +1,118 @@ +--- +title: Using Parameters With Views +order: 36 +layout: page +--- + +[[using-parameters-with-views]] +Using parameters with Views +--------------------------- + +When the Navigator API is in use, one can pass "parameters" to Views in +the URI fragment. + +The remainder of the fragment that is left after the (longest) view name +matched is removed, is considered to be "fragment parameters". These are +passed to the View in question, which can then handle the parameter(s). +Basically: `#!viewname/parameters`. + +Continuing from the basic navigation example, let's make a View that +displays a message passed as a fragment parameter: + +[source,java] +.... +import com.vaadin.navigator.View; +import com.vaadin.ui.Label; +import com.vaadin.ui.Panel; + +public class MessageView extends Panel implements View { + public static final String NAME = "message"; + + public MessageView() { + super(new VerticalLayout()); + setCaption("Messages"); + } + + @Override + public void enter(ViewChangeEvent event) { + if(event.getParameters() != null){ + // split at "/", add each part as a label + String[] msgs = event.getParameters().split("/"); + for (String msg : msgs) { + ((Layout)getContent()).addComponent(new Label(msg)); + } + } + } +} +.... + +Let's register `MessageView` along with the other Views: + +[source,java] +.... +import com.vaadin.navigator.Navigator; +import com.vaadin.navigator.Navigator.SimpleViewDisplay; +import com.vaadin.server.Page; +import com.vaadin.server.WrappedRequest; +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); + + // Add some Views + navigator.addView(MainView.NAME, new MainView()); // no fragment + + // #!count will be a new instance each time we navigate to it, counts: + navigator.addView(CountView.NAME, CountView.class); + + // #!message adds a label with whatever it receives as a parameter + navigator.addView(MessageView.NAME, new MessageView()); + } +} +.... + +Finally, we'll add two labels to the MainView so we don't have to type +in the browsers address-bar to try it out: + +[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() { + + VerticalLayout layout = new VerticalLayout(); + + Link lnk = new Link("Count", new ExternalResource("#!" + CountView.NAME)); + layout.addComponent(lnk); + + lnk = new Link("Message: Hello", new ExternalResource("#!" + + MessageView.NAME + "/Hello")); + layout.addComponent(lnk); + + lnk = new Link("Message: Bye", new ExternalResource("#!" + + MessageView.NAME + "/Bye/Goodbye")); + layout.addComponent(lnk); + setContent(layout); + } + + @Override + public void enter(ViewChangeEvent event) { + + } +} +.... + +Simple! Let's just conclude by noting that it's usually a good idea to +make sure the parameters are URI encoded, or the browser might +disapprove. \ No newline at end of file diff --git a/documentation/articles/UsingRPCFromJavaScript.asciidoc b/documentation/articles/UsingRPCFromJavaScript.asciidoc new file mode 100644 index 0000000000..24a774bc0a --- /dev/null +++ b/documentation/articles/UsingRPCFromJavaScript.asciidoc @@ -0,0 +1,114 @@ +--- +title: Using RPC From JavaScript +order: 42 +layout: page +--- + +[[using-rpc-from-javascript]] +Using RPC from JavaScript +------------------------- + +This tutorial continues where +<> ended. We will now add RPC +functionality to the JavaScript Flot component. RPC can be used in the +same way as with ordinary GWT components. + +We will add RPC from the client to the server when the user clicks a +data point in the graph and RPC from server to client for highlighting a +data point in the graph. For each of these, we define an RPC interface. +Each interface has one method that takes a data series index and the +index of a point in that series. As with the shared state, the GWT code +doesn't need to know about these interfaces and it's thus not required +to put them in the widgetset's client package and to recompile the +widgetset after making changes. + +[source,java] +.... +public interface FlotClickRpc extends ServerRpc { + public void onPlotClick(int seriesIndex, int dataIndex); +} + +public interface FlotHighlightRpc extends ClientRpc { + public void highlight(int seriesIndex, int dataIndex); +} +.... + +The server side code for this looks the same as if the client-side +connector was implemented using GWT. An RPC implementation is registered +in the constructor. + +[source,java] +.... +public Flot() { + registerRpc(new FlotClickRpc() { + public void onPlotClick(int seriesIndex, int dataIndex) { + Notification.show("Clicked on [" + seriesIndex + ", " + + dataIndex + "]"); + } + }); +} +.... + +Highlighting is implemented by getting an RPC proxy object and invoking +the method. + +[source,java] +.... +public void highlight(int seriesIndex, int dataIndex) { + getRpcProxy(FlotHighlightRpc.class).highlight(seriesIndex, dataIndex); +} +.... + +The JavaScript connector uses similar functions from the connector +wrapper: `this.getRpcProxy()` for getting an object with functions that +will call the server-side counterpart and `this.registerRpc()` for +registering an object with functions that will be called from the +server. Because of the dynamic nature of JavaScript, you don't need to +use the interface names if you don't want to - all methods from all RPC +interfaces registered for the connector on the server will be available +in the RPC proxy object and any RPC method invoked from the server will +be called if present in the RPC object you registered. If a connector +uses multiple RPC interfaces that define methods with conflicting names, +you can still use the interface names to distinguish between interfaces. + +We need to make some small adjustments to the connector JavaScript to +make it work with the way Flot processes events. Because a new Flot +object is created each time the `onStateChange` function is called, we +need to store a reference to the current object that we can use for +applying the highlight. We also need to pass a third parameter to +`$.plot` to make the graph area clickable. Aside from those changes, we +just call the function on the RPC proxy in a click listener and register +an RPC implementation with a function that highlights a point. + +[source,javascript] +.... +window.com_example_Flot = function() { + var element = $(this.getElement()); + var rpcProxy = this.getRpcProxy(); + var flot; + + this.onStateChange = function() { + flot = $.plot(element, this.getState().series, {grid: {clickable: true}}); + } + + element.bind('plotclick', function(event, point, item) { + if (item) { + rpcProxy.onPlotClick(item.seriesIndex, item.dataIndex); + } + }); + + this.registerRpc({ + highlight: function(seriesIndex, dataIndex) { + if (flot) { + flot.highlight(seriesIndex, dataIndex); + } + } + }); +} +.... + +When the normal Vaadin RPC is used with JavaScript connectors, you can +use the same server-side code that you would use with a GWT connector +and the client-side code uses the same concepts as for GWT connectors, +just translated to fit into the world of JavaScript. \ No newline at end of file diff --git a/documentation/articles/UsingRPCToSendEventsToTheClient.asciidoc b/documentation/articles/UsingRPCToSendEventsToTheClient.asciidoc new file mode 100644 index 0000000000..9cd920e0d0 --- /dev/null +++ b/documentation/articles/UsingRPCToSendEventsToTheClient.asciidoc @@ -0,0 +1,154 @@ +--- +title: Using RPC To Send Events To The Client +order: 56 +layout: page +--- + +[[using-rpc-to-send-events-to-the-client]] +Using RPC to send events to the client +-------------------------------------- + +An RPC mechanism can be used to communicate from the server to the +client. In effect, the server-side component can call methods that are +executed by the client-side connector. As opposed to shared state +(discussed in a separate article), no information is automatically +re-transmitted when the client-side state is lost (e.g when a browser +reload is invoked). + +Whether shared state or RPC is appropriate depends on the nature of the +data being transmitted, but if the information transmitted needs to be +retained on the client over a page refresh, you should probably use +shared state. You'll probably find shared state more appropriate in most +cases, and server-client RPC extremely useful in a few cases. + +To set up server-client RPC, we need to create an interface extending +`ClientRpc` for the RPC methods, then register an implementation of the +RPC interface in the client-side connector, and call the method(s) via a +proxy on the server. This is the reverse of the server-client RPC +described in a separate article. + +We'll create *MyComponentClientRpc* in the client package: + +[source,java] +.... +package com.example.mycomponent.client; + +import com.vaadin.client.communication.ClientRpc; + +public interface MyComponentClientRpc extends ClientRpc { + public void alert(String message); +} +.... + +Again, note that the RPC methods can not return anything, but can take +multiple arguments. + +In *MyComponentConnector* we register the RPC implementation in the +constructor. This time we'll create the implementation inline: + +[source,java] +.... +package com.example.mycomponent.client; + +// imports removed for clarity + +@Connect(MyComponent.class) +public class MyComponentConnector extends AbstractComponentConnector { + + MyComponentServerRpc rpc = RpcProxy + .create(MyComponentServerRpc.class, this); + + public MyComponentConnector() { + registerRpc(MyComponentClientRpc.class, new MyComponentClientRpc() { + public void alert(String message) { + Window.alert(message); + } + }); + +/* The rest of the code remains unchanged: + + getWidget().addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + final MouseEventDetails mouseDetails = MouseEventDetailsBuilder + .buildMouseEventDetails(event.getNativeEvent(), + getWidget().getElement()); + rpc.clicked(mouseDetails); + } + }); + } + @Override + protected Widget createWidget() { + return GWT.create(MyComponentWidget.class); + } + @Override + public MyComponentWidget getWidget() { + return (MyComponentWidget) super.getWidget(); + } + @Override + public MyComponentState getState() { + return (MyComponentState) super.getState(); + } + @OnStateChange("text") + void updateText() { + getWidget().setText(getState().text); + } +*/ +} +.... + +(`MyComponentServerRpc` is introduced in +<>. `Window` here is +`com.google.gwt.user.client.Window`, _not_ `com.vaadin.ui.Window`.) + +Finally, in *MyComponent* we use the RPC via a proxy: + +[source,java] +.... +import com.vaadin.ui.AbstractComponent; + +public class MyComponent extends AbstractComponent { + + private int clickCount = 0; + + private MyComponentServerRpc rpc = new MyComponentServerRpc() { + public void clicked(MouseEventDetails mouseDetails) { + clickCount++; + + // nag every 5:th click + if (clickCount % 5 == 0) { + getRpcProxy(MyComponentClientRpc.class).alert( + "Ok, that's enough!"); + } + + setText("You have clicked " + clickCount + " times"); + } + }; + +/* Unchanged code follows: + public MyComponent() { + registerRpc(rpc); + } + + @Override + public MyComponentState getState() { + return (MyComponentState) super.getState(); + } + + public void setText(String text) { + getState().text = text; + } + + public String getText() { + return getState().text; + } +*/ +} +.... + +That is: every fifth time the label is clicked, we get the RPC proxy by +calling `getRpcProxy()` and call our `alert()` method with a message to +send to the client. + +Compile the widgetset, and you're all set to try out server-client RPC. \ No newline at end of file diff --git a/documentation/articles/Vaadin7HierarchicalContainerAndTreeComponentExampleWithLiferayOrganizationService.asciidoc b/documentation/articles/Vaadin7HierarchicalContainerAndTreeComponentExampleWithLiferayOrganizationService.asciidoc index db607c9552..cf450a3792 100644 --- a/documentation/articles/Vaadin7HierarchicalContainerAndTreeComponentExampleWithLiferayOrganizationService.asciidoc +++ b/documentation/articles/Vaadin7HierarchicalContainerAndTreeComponentExampleWithLiferayOrganizationService.asciidoc @@ -12,7 +12,7 @@ user belongs to in a Hierarchical Tree.  I used Vaadin's tree and hierarchical container components along with information from Vaadin's book of examples to create the code below (http://demo.vaadin.com/book-examples-vaadin7/book#component.tree.itemstylegenerator). -See link:img/DmoOrgTreeUI.java[DmoOrgTreeUI.java] for full source code. +See <> for full source code. [source,java] .... diff --git a/documentation/articles/ViewChangeConfirmations.asciidoc b/documentation/articles/ViewChangeConfirmations.asciidoc new file mode 100644 index 0000000000..f78911bf4a --- /dev/null +++ b/documentation/articles/ViewChangeConfirmations.asciidoc @@ -0,0 +1,226 @@ +--- +title: View Change Confirmations +order: 54 +layout: page +--- + +[[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(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. \ No newline at end of file diff --git a/documentation/articles/WidgetStylingUsingOnlyCSS.asciidoc b/documentation/articles/WidgetStylingUsingOnlyCSS.asciidoc new file mode 100644 index 0000000000..b52187e23f --- /dev/null +++ b/documentation/articles/WidgetStylingUsingOnlyCSS.asciidoc @@ -0,0 +1,178 @@ +--- +title: Widget Styling Using Only CSS +order: 52 +layout: page +--- + +[[widget-styling-using-only-css]] +Widget styling using only CSS +----------------------------- + +The preferred way of styling your widget is to only use static CSS +included in the theme's styles.css file. For information on how to +create custom themes, please refer to +https://vaadin.com/book/-/page/themes.creating.html. Your component can +be styled using the CSS class names that are defined to the widget using +`setStyleName`. + +Normal styling of components works in the same way as any styling using +CSS, but there are some special features to pay attention to when Vaadin +7 is used. + +[[some-sizing-theory]] +Some sizing theory +~~~~~~~~~~~~~~~~~~ + +All Vaadin 7 components get the CSS class v-widget which sets the +box-sizing to border-box. This causes borders and paddings to be +considered when the browser calculates the component's size. This means +that e.g. a component with padding: 5px and width: 100% inside a 200px +wide slot will fill the slot without any overflow because the inner +width of the component will be calculated to 190px by the browser. + +The Vaadin 7 server side API allows setting the size of the component in +three different ways: + +* Undefined size, set e.g. using `setWidth(null)`, means that the +component's size should have it's default size that might vary depending +on its content. +* Relative size, set e.g. using `setWidth("100%")` means that the +component's size is determined by the size and settings of the +component's parent. +* Fixed size, set e.g. using `setWidth("250px")` or `setWidth("10em")` means +that the component's size is fixed, so that the parent doesn't affect +the size and neither does the component's content. + +The three different ways of setting the size means that a component +should both support having its own natural size and filling the +allocated space depending on how the size is set. This usually means +that the main area of the component should adjust based on the settings. + +[[a-simple-sample]] +A simple sample +~~~~~~~~~~~~~~~ + +Consider e.g. a simple date picker component with a text field where a +date can be entered and where the currently selected is displayed and a +button that is used to open a calendar view where a date can be picked +using the mouse. + +[source,java] +.... +public class MyPickerWidget extends ComplexPanel { + public static final String CLASSNAME = "mypicker"; + + private final TextBox textBox = new TextBox(); + private final PushButton button = new PushButton("..."); + + public MyPickerWidget() { + setElement(Document.get().createDivElement()); + setStylePrimaryName(CLASSNAME); + + textBox.setStylePrimaryName(CLASSNAME + "-field"); + button.setStylePrimaryName(CLASSNAME + "-button"); + + add(textBox, getElement()); + add(button, getElement()); + + button.addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + Window.alert("Calendar picker not yet supported!"); + } + }); + } +} +.... + +We then add this basic styling to the theme CSS file + +[source,scss] +.... +.mypicker { + white-space: nowrap; +} +.mypicker-button { + display: inline-block; + border: 1px solid black; + padding: 3px; + width: 15px; + text-align: center; +} +.... + +`display: inline-block` makes the button continue on the same line as the +text field, placing it to the right of the field. We also add +`white-space: nowrap` to the main div element to ensure the button is not +wrapped to the next row. Finally, there is some padding and a border to +make the button look more like a real button. + +[[using-available-space]] +Using available space +^^^^^^^^^^^^^^^^^^^^^ + +This simple layout works well as long as the component's has it's +default undefined width. Changing the width from the server does however +not have any visible effect because only the size of the main div is +changed. If the component is made smaller, the contents goes beyond the +size of the element (this can be verified by adding `overflow: hidden;` to +`.mypicker`) and if it gets larger the extra space is just left as empty +space to the right of the button. + +The first step towards making the size adjust is to make the text field +adjust to the main div element's width whenever the width is something +else then than undefined. In these situations, Vaadin 7 adds a +`v-has-width` class to the component's main element (`v-has-height` added +when the height is not undefined). + +[source,scss] +.... +.mypicker.v-has-width > .mypicker-field { + width: 100%; +} +.... + +With this additional CSS, the text field directly inside a picker that +has a defined width gets full width. + +[[making-room-for-the-button-again]] +Making room for the button again +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We're however not done yet. Setting the width of the text field to 100% +makes it as wide as the main div is, which means that the button goes +outside the main div. This can be verified using the DOM inspector in +your browser or by setting `overflow: hidden` to the style for `mypicker`. +To reserve space for the button, we can add some padding to the right +edge of the main div element. This does however cause the padding space +to remain unused if the component size is undefined. To compensate for +this, negative margin can be added to the right edge of the button, +effectively reducing its occupied size to 0px. + +Finally, we need to use `box-sizing: border-box` to make the field's +borders and paddings be included in the 100% width. + +The full CSS for the sample component: + +[source,scss] +.... +.mypicker { + white-space: nowrap; + padding-right: 23px; +} +.mypicker-button { + display: inline-block; + border: 1px solid black; + padding: 3px; + width: 15px; + text-align: center; + margin-right: -23px; +} +.mypicker.v-has-width > .mypicker-field { + width: 100%; +} +.mypicker-field { + -moz-box-sizing: border-box; + -webkit-boz-sizing: border-box; + box-sizing: border-box; +} +.... \ No newline at end of file diff --git a/documentation/articles/contents.asciidoc b/documentation/articles/contents.asciidoc index 5f3830e4a3..a8a4f8f6f4 100644 --- a/documentation/articles/contents.asciidoc +++ b/documentation/articles/contents.asciidoc @@ -1,11 +1,11 @@ ---- +-- title: Contents order: 1 layout: page ---- - +-- + = Community articles for Vaadin 7 - + [discrete] == Articles - <> @@ -42,3 +42,26 @@ layout: page - <> - <> - <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> +- <> + + \ No newline at end of file -- 2.39.5