diff options
115 files changed, 8839 insertions, 6 deletions
diff --git a/documentation/articles/AccessingWebPageAndBrowserInformation.asciidoc b/documentation/articles/AccessingWebPageAndBrowserInformation.asciidoc new file mode 100644 index 0000000000..2aea7b8d1a --- /dev/null +++ b/documentation/articles/AccessingWebPageAndBrowserInformation.asciidoc @@ -0,0 +1,50 @@ +[[accessing-web-page-and-browser-information]] +Accessing web page and browser information +------------------------------------------ + +Vaadin 7 includes a new *Page* class offering access to various +client-side information and events concerning the web page and browser +window in which the Vaadin UI resides. The Page instance corresponding +to a given UI is accessed via the `getPage()` method of the UI or using +a static method `Page.getCurrent()`. + +You can access the browser window size and add size change listeners: + +[source,java] +.... +Page page = someUI.getPage(); +page.addBrowserWindowResizeListener(new BrowserWindowResizeListener() { + public void browserWindowResized(BrowserWindowResizeEvent event) { + Notification.show("Window width=" + event.getWidth() + ", height=" + event.getHeight()); + } +}); +.... + +You can access the optional fragment part of the location URI and add +fragment change listeners: + +[source,java] +.... +page.setUriFragment(page.getUriFragment() + "foo"); +page.addUriFragmentChangedListener(new UriFragmentChangedListener() { + public void uriFragmentChanged(UriFragmentChangedEvent event) { + Notification.show("Fragment=" + event.getUriFragment()); + } +}); +.... + +You can access client browser details: + +[source,java] +.... +Notification.show("IP" + browser.getAddress() + + "User-Agent:" + browser.getBrowserApplication() + + "Linux: " + browser.isLinux()); +.... + +https://demo.vaadin.com/sampler/#foundation/advanced/browser-information[Live +Demo] + +Note: If you are using a reverse proxy, you must get the value +`X-Forwarded-For` from request headers. You cannot get a browser name, +but you can check which browser are using. diff --git a/documentation/articles/AddingASplashScreen.asciidoc b/documentation/articles/AddingASplashScreen.asciidoc new file mode 100644 index 0000000000..400241433e --- /dev/null +++ b/documentation/articles/AddingASplashScreen.asciidoc @@ -0,0 +1,53 @@ +[[adding-a-splash-sreen]] +Adding a splash screen +---------------------- + +When a Vaadin application is loading a loading indicator is +automatically shown so the user knows something is happening. But what +if we want to show something else, like a custom splash screen for our +product? Or just customize the loading indicator? Let's have a look. + +During the Vaadin bootstrap there are a couple of div elements added to +the page which we can use for our custom splash screen. The DOM for a +servlet application looks the like the following when starting up: + +[source,html] +.... +<body scroll="auto" class="v-generated-body"> + <div id="splash-895866265" class="v-app splash"> + <div class="v-app-loading"></div> + <noscript> + You have to enable javascript in your browser to use an application built with Vaadin. + </noscript> + </div> +.... + +In this example _splash_ is the name of our theme. The _v-app-loading_ +div is meant for styling the loading screen. It will only exist in the +DOM until the widget set has been loaded and the application starts. At +that point the _v-app-loading_ div is removed and replaced by the actual +components for the application. + +Styling the loading screen is then as simple as adding some rules for +_v-app-loading_, for example: + +[source,scss] +.... +.v-generated-body &.v-app .v-app-loading { + height: 100%; + width: 100%; + background-image: url(http://archive.eclipse.org/eclipse/downloads/drops/R-3.3-200706251500/whatsnew/images/splash.png); + background-repeat: no-repeat; + background-position: 50%; +} +.... + +This will use an Eclipse logo as your splash screen - change the +background image to your own splash screen or redesign the css +completely to your liking. + +*Note that Vaadin 7.0 incorrectly does not add the theme name during +bootstrap. You must therefore use a rule without the theme name, e.g. +`.v-generated-body .v-app .v-app-loading` and move it out of the +@mixin. You also need to ensure the v-app div has a height using +`.v-app {height:100%;}`* diff --git a/documentation/articles/BroadcastingMessagesToOtherUsers.asciidoc b/documentation/articles/BroadcastingMessagesToOtherUsers.asciidoc new file mode 100644 index 0000000000..a61d3caed9 --- /dev/null +++ b/documentation/articles/BroadcastingMessagesToOtherUsers.asciidoc @@ -0,0 +1,129 @@ +[[broadcasting-messages-to-other-users]] +Broadcasting messages to other users +------------------------------------ + +In this tutorial we will create an application where any user can send a +broadcast message to all other active users. We will start from a +project where push has been enabled (see link:EnablingServerPush.asciidoc[Enabling +server push] for details). + +For simplicity, we will use a static `Broadcaster` which is shared between +all users and all sessions. Each UI will register itself to this +broadcast when initialized and unregister when detached. The broadcaster +will take care of sending events to the registered UI:s as needed. In a +real world scenario you probably want to use something else than a +shared static class (e.g. JMS or some other messaging system) but the +same ideas apply. + +So, let’s start from a simple `Broadcaster` class and a listener +interface. We need the possibility to register and unregister listeners +and to broadcast a message to the listeners so we end up with the +following class: + +[source,java] +.... +public class Broadcaster { + + private static final List<BroadcastListener> listeners = new CopyOnWriteArrayList<BroadcastListener>(); + + public static void register(BroadcastListener listener) { + listeners.add(listener); + } + + public static void unregister(BroadcastListener listener) { + listeners.remove(listener); + } + + public static void broadcast(final String message) { + for (BroadcastListener listener : listeners) { + listener.receiveBroadcast(message); + } + } + + public interface BroadcastListener { + public void receiveBroadcast(String message); + } +} +.... + +As Broadcast will be used by many threads simultaneously, we need to +ensure that it is thread-safe. We will do it here by using the +thread-safe `CopyOnWriteArrayList` class for keeping track of the +listeners. + +Now that we have the `Broadcaster` implemented we can use it in our UI for +instance as follows: + +[source,java] +.... +@Push +public class BroadcasterUI extends UI implements BroadcastListener { + + @Override + protected void init(VaadinRequest request) { + [...] + // Register broadcast listener + Broadcaster.register(this); + } + + @Override + public void detach() { + Broadcaster.unregister(this); + super.detach(); + } + + @Override + public void receiveBroadcast(final String message) { + access(new Runnable() { + @Override + public void run() { + Notification n = new Notification("Message received", + message, Type.TRAY_NOTIFICATION); + n.show(getPage()); + } + }); + } +.... + +We register the UI in the init method and unregister it in the detach +method to avoid receiving messages for UIs no longer in use (and +ensuring that the detached UI can be garbage collected). + +When we receive a broadcast message we need to use the access method as +this call comes from a thread where the UI is not locked. +`access(Runnable)` will take care of locking the UI for us so we can +update it. In the wrapped run method we can do whatever we like with the +received message, for instance show it as a tray notification as done +here. + +To send a broadcast message we can create a simple user interface in our +UI init method: + +[source,java] +.... +protected void init(VaadinRequest request) { + final VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + setContent(layout); + + final TextArea message = new TextArea("", + "The system is going down for maintenance in 10 minutes"); + layout.addComponent(message); + + final Button button = new Button("Broadcast"); + layout.addComponent(button); + button.addClickListener(new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + Broadcaster.broadcast(message.getValue()); + } + }); + + // Register broadcast listener + Broadcaster.register(this); +} +.... + +Now if you deploy the application and open it in a couple of browser +tabs or separate browsers you are able to send messages between the +instances. diff --git a/documentation/articles/ChangingThemeOnTheFly.asciidoc b/documentation/articles/ChangingThemeOnTheFly.asciidoc new file mode 100644 index 0000000000..9f0722f383 --- /dev/null +++ b/documentation/articles/ChangingThemeOnTheFly.asciidoc @@ -0,0 +1,35 @@ +[[changing-theme-on-the-fly]] +Changing theme on the fly +------------------------- + +Starting from Vaadin 7.3, you can change themes in the application +without reloading the page. To do this, simply use the +`UI.setTheme(String)` method. + +[source,java] +.... +public class ThemeChangeUI extends UI { + + private String[] themes = { "valo", "reindeer", "runo", "chameleon" }; + + @Override + protected void init(VaadinRequest request) { + ComboBox themePicker = new ComboBox("Theme", Arrays.asList(themes)); + themePicker.setValue(getTheme()); + + themePicker.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange(ValueChangeEvent event) { + String theme = (String) event.getProperty().getValue(); + setTheme(theme); + } + }); + + setContent(themePicker); + } +} +.... + +In this way, you can let your users choose the look of your application. +The functionality also makes it easier to create applications that are +branded for different customers. diff --git a/documentation/articles/ChooseInputFieldComponentsWisely.asciidoc b/documentation/articles/ChooseInputFieldComponentsWisely.asciidoc new file mode 100644 index 0000000000..2d3e4c92e9 --- /dev/null +++ b/documentation/articles/ChooseInputFieldComponentsWisely.asciidoc @@ -0,0 +1,92 @@ +[[choosing-input-field-components-wisely]] +Choosing input field components wisely +-------------------------------------- + +The core Vaadin framework has more than ten different input field +components. Choosing the right one can improve your application’s +usability significantly. Which component is the best fit in a certain +situation depends on so many factors that it’s pretty much impossible to +set down any specific rules. Nevertheless, here are some helpful +pointers that might guide you in the right direction. + +[[textfield-or-selection-component]] +Textfield or selection component? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**TextField**s are of course the most obvious choice in most cases. +However, their inherent lack of restrictions requires that the user +understands what kind of values are acceptable. They don't present any +options to choose from, and, although you can restrict them to certain +kinds of values e.g. by using **Converter**s, these restrictions are not +immediately obvious to the user. Furthermore, if the UI in question is +mostly mouse-operated, a *TextField* forces the user to move her hands +to the keyboard to enter a value. Thus, a selection component can in +many cases be more convenient for your users. + +[[optiongroup-and-checkbox]] +OptionGroup and Checkbox +^^^^^^^^^^^^^^^^^^^^^^^^ + +**OptionGroup**s are handy for very small sets of options (typically +2-4). You can toggle between single-select (radiobuttons) and +multi-select (checkboxes) with the *setMultiSelect(boolean)* method. +Using an OptionGroups to present purely numerical options might be +somewhat strange, though. + +image:img/optiongroup.png[OptionGroup] + +For binary either/or, yes/no options, a *CheckBox* is a natural choice. +However, if the meaning of the "no" option (i.e. unchecked) is not +completely obvious, or the "yes" (i.e. checked) option could be +confusing by itself, a two-item single-select *OptionGroup* might be +better, since it clearly states what both options represent. + +image:img/checkbox-vs-og.png[Checkbox vs OptionGroup] + +[[nativeselect]] +NativeSelect +^^^^^^^^^^^^ + +The often-overlooked *NativeSelect* component is also convenient for +small sets of options (up to 10 or so), and works great both for named +items and small sets of numbers. + +image:img/nativeselect.png[NativeSelect] + +[[combobox]] +ComboBox +^^^^^^^^ + +If the set of options is larger than about 10 items, a *ComboBox* might +be a better choice, since it allows the user to search for a specific +value to typing part of it, and supports lazy loading. + +image:img/combo.png[ComboBox] + +[[slider]] +Slider +^^^^^^ + +The *Slider* is great for _quickly_ selecting a value from a wide range +of values, _if precision is not that important_, such as audio volume, +brightness, or any approximate value. (If precision is important it's +much better to allow the user to manually enter the value, such as with +a *TextField*.) + +image:img/slider.png[Slider] + +[[datefields]] +DateFields +^^^^^^^^^^ + +The *PopupDateField* component, which opens a calendar popup for date +selection, is great for entering dates and times. Remember to set the +datefield's _resolution_ to something appropriate: seconds are hardly +relevant in most cases, and sometimes all you need is the date. + +image:img/datefield.png[DateField] + +There’s also the *InlineDateField*, which is really just the +*PopupDateField*’s datepicker without a textfield. It’s probably a bit +too big to use in most forms, but if picking dates is one of the few +tasks in a form, give it a try if you have the space. diff --git a/documentation/articles/ComponentAddonProjectSetupHOWTO.asciidoc b/documentation/articles/ComponentAddonProjectSetupHOWTO.asciidoc new file mode 100644 index 0000000000..3d633b82fa --- /dev/null +++ b/documentation/articles/ComponentAddonProjectSetupHOWTO.asciidoc @@ -0,0 +1,174 @@ +[[component-add-on-project-setup-howto]] +Component add-on project setup how-to +------------------------------------ + +This how-to walks you through a complete setup for a project for +developing, building and publishing your own Vaadin UI component +add-ons. The goal here is not to teach how to write an add-on, but to +make the process of setting up your project environment as smooth as +possible. I hope this encourages you to try building and publishing your +own add-ons :) + +[[goals-for-the-project-environment]] +Goals for the project environment +--------------------------------- + +* Fully automated build with Maven +* Allow anyone to re-build your project easily regardless of the IDE:s +* Almost instant save-build-deploy-try cycle +* Simple, but complete, project setup +* Project publishing to GitHub +* Easy publishing of the results to Vaadin Directory + +[[install-toolchain]] +Install toolchain +----------------- + +If you do not already have the following tools in use, install them: + +* Eclipse IDE for Java EE developers from http://www.eclipse.org (Indigo +Service Release 1 was used in this how-to) +* Google Chrome browser from https://www.google.com/chrome/ (other +browsers will do, but Chrome is recommended) +* Eclipse plugins: m4e-wtp, vaadin, egit (optional) and jrebel +(optional) from Marketplace (just select Help->Marketplace... from the +menu) + +[[create-a-new-widget-project]] +Create a new widget project +--------------------------- + +Start project creation wizard: File -> New -> Other... -> "Maven +Project" + +Give a proper name for your project and save it under workspace. For +this example I am building a list widget and name it MyList. + +Ensure that your Maven archetype catalogs contain +http://repo1.maven.org/maven2/archetype-catalog.xml as remote catalog +and select it. + +Select vaadin-archetype-widget from the list. + +Give a proper name for the project. I use "org.vaadin" as group id as it +can be used by anyone who wants to contribute non-commercial widgets to +Vaadin project and name of the widget as artifact id in this case i use +"mylist" as example. For a package name use "org.vaadin.mylist". + +Observe that pom.xml shows two errors. This is because m2e does not +directly support gwt and vaadin -plugins. To fix the problem, choose the +problems one by one and choose "ignore" quick fix. Then edit the pom.xml +by changing all `<ignore></ignore>` tags to `<execute></execute>` to get the +plugins to execute. Finally, clear the remaining "project configuration +needs update" error with quickfix (that not surprisingly updates project +configuration). In the end, pom.xml should look like +https://raw.github.com/jojule/MyList/56ac906f9cc6442e0817eb0cc945eee023ff9001/pom.xml[this]. + +Refactor the name of the component you are building. + +* Instead of using `MyComponent` and `VMyComponent`, use your own name. In +this example I use `MyList` and `VMyList`. +* Also change the theme directory name from +`src/main/java/org/vaadin/mylist/gwt/public/mywidget` to +`src/main/java/org/vaadin/mylist/gwt/public/mylist` +* and update the reference in `MyWidgetSet.gwt.xml`. +* Also rename `MyWidgetSet.gwt.xml` to `MyListWidgetSet.gwt.xml` +* and update references in `pom.xml` and `web.xml`. + +Test that the project compiles and runs by running (Run -> Run as ... -> +Maven Build...) maven goal "package jetty:run". If everything compiles +fine and Jetty server starts, you can access the application at +http://localhost:8080/mylist/. You should see "It works!" on the web +page. Do not worry that the build takes a lot of time, we'll get back to +it in a minute. + +Finally, if you prefer to use Git, create a repository for the project. +You could simply choose "Share Project..." from the Project's Team menu. +Choose "Use or create repository in parent folder" and click "Create +Repository". Then, add project resources to commit. Choose pom.xml and +src directory from Navigator view and select Team -> Add to Index. Then +add the rest of the files (.settings, .project, .classpath and target) +to .gitignore with Team -> Ignore. Finally, just do Team -> Commit. + +At this point - or later whenevery you are ready for it - you can +publish the project to GitHub. Just go to github.com and create a new +repository. Use MyList as the name for the repository. Then follow the +instructions on the screen. In my case, I executed the following command +line commands: `cd /Users/phoenix/Documents/workspace/mylist; git remote +add origin git@github.com:jojule/MyList.git; git push -u origin master`. +You can see the results +https://github.com/jojule/MyList/tree/56ac906f9cc6442e0817eb0cc945eee023ff9001[at +GitHub]. + +[[save---build---deploy---try]] +Save - Build - Deploy - Try +--------------------------- + +If it takes minutes each time from code change to seeing that change on +the screen, you are not going to get your component ready anytime soon. +To solve the issue, we use two tools: 1) Google GWT Developer Mode and +2) JRebel. The first one is more important here as the GWT compilation +step is the really slow step, but JRebel also helps as it gives you +instant redeploy for the server-side changes. + +To enable JRebel, open project popup menu and choose JRebel -> Generate +rebel.xml in `src/main/java`. Then click "Enable JRebel" on the JRebel tab +for Maven run configuration for "jetty:run". Now when you make any +changes to server-side code - for example to `WidgetTestApplication.java` +- hit save and reload the browser pointing to +http://localhost:8080/mylist/?restartApplication, the changes are +appliead immediately. Even better - you can start the project with Debug +As and add break points to the application. + +Client-side changes are more tricky as they are compiled from Java to +JavaScript by GWT. To make those changes immediately you, must be +running a GWT Devepment Mode. This is done by running Maven goal gwt:run +instead of just pointing your web browser to the running application. +Note that must be running both jetty:run and gwt:run concurrently. +gwt:run starts application called "GWT Development Mode". From there you +can launch your browser - or cut-n-paste URL to Chrome - if that is not +your default browser. When the application is started, add +`&restartApplication` parameter to the end of the URL to ensure that the +server-side of the application is reloaded each time you reload the +page. In this case, the full url is +http://127.0.0.1:8080/mylist/?gwt.codesvr=127.0.0.1:9997&restartApplication. +Try making a change to the client-side code (for example `VMyList.java`), +hitting save and reloading the page to see how everything works +together. You can also run gwt:run in Debug As to debug the client-side +code. + +Now the "save - build - deploy - try" cycle has been reduced to almost +instant for both client-side as well as server-side changes. Let the +real development begin. + +[[developing-a-new-component-for-vaadin]] +Developing a new component for Vaadin +------------------------------------- + +Wait for an amazing idea, code like crazy, enjoy and POOOF, there it is +- your own brand new component. + +If you need guidance with this, +https://vaadin.com/book/-/page/gwt.html[Book of Vaadin] is a recommended +reading :) + +For this example, I implemented a trivial list component. Take a look of +1.0.0 version +https://github.com/jojule/MyList/tree/496a8bdf629154a4da7b83c4a11979272959aa96[at +GitHub], but do not expect too much :) To try it out just do: `git clone +git@github.com:jojule/MyList.git; mvn package; mvn jetty:run` and point +your web browser to http://localhost:8080/mylist/. + +[[packaging-and-submitting-the-widget-to-directory]] +Packaging and submitting the widget to directory +------------------------------------------------ + +Set the version number in pom.xml + +Run Maven target "package" and you'll have a ready made package at +target/mylist-1.0.0.jar ready for upload to vaadin directory. + +Go to https://vaadin.com/directory/my-components, select UI Component and +click upload. + +Fill the form, preview and publish. diff --git a/documentation/articles/ConfigureInputFieldsToGuideDataEntry.asciidoc b/documentation/articles/ConfigureInputFieldsToGuideDataEntry.asciidoc new file mode 100644 index 0000000000..628af4ec1c --- /dev/null +++ b/documentation/articles/ConfigureInputFieldsToGuideDataEntry.asciidoc @@ -0,0 +1,119 @@ +[[configure-input-fields-to-guide-data-entry]] +Configure input fields to guide data entry +------------------------------------------ + +[[field-size]] +Field size +^^^^^^^^^^ + +There are a number of tricks we can use to help users understand what +kind of values we expect them to enter into a field, without resorting +to tooltips, footnotes or excessively long captions. Consider the form +below: + +image:img/form1.png[Basic form] + +The shape, size and layout of these fields don’t really correspond to +the kinds of values expected to be entered in them, or to the relations +between different fields. For instance, while the homepage url is +probably between 25 and 55 characters long, european postal codes are +only about 6 digits long, and age maxes out at three digits. By setting +the widths of these fields to be approximately proportional to the +lengths of expected values, the fields become easier to identify, and +the entire form feels more friendly and less monotonous: + +image:img/form2.png[Form with different input widths] + +Please note that there is no point in trying to _match_ the _exact_ +lengths of expected values – the positive effect here relies more on the +sizes of fields _relative to each other_ than on specific widths. Also +note that it’s important not to make the shortest fields too short just +to distinguish them from longer ones. An address field that can only +display 10 characters at a time would be really annoying. + +An easy way to set field widths to approximately a certain number of +characters is to set the component’s widths using +http://en.wikipedia.org/wiki/Em_(typography)[Em units], found in +Vaadin’s *Sizeable.Unit* enum. One *Em* is approximately the width of +(actually usually slightly wider than) an uppercase M. + +[source,java] +.... +TextField tfPostalCode = new TextField("Postal code"); +tfPostalCode.setWidth(6, Unit.EM); +.... + +This was all about the _width_ of input fields, since most input fields +are single-line. Fields for which you expect more than a few words of +text should of course be multi-line *TextAreas*. While the height of a +*TextArea* might not be as important as the width of a *TextField* +(since it has scrollbars), it’s still a good idea to set both the width +and the height of a *TextArea* to roughly match the amount of text you +expect people to enter. A bigger *TextArea* encourages the user to enter +more text into it, while a smaller area suggests that perhaps a few +carefully chosen words is enough, thank you very much. + +[[component-grouping]] +Component grouping +^^^^^^^^^^^^^^^^^^ + +Some of the fields in the above example clearly “belong together”. First +and last name. Street address, postal code, city and country. By +changing the layout slightly, moving the most closely related fields to +the same line and adding a little spacing between groups of related +fields the fields become even easier to identify, and the form easier to +understand in a single glance: + +image:img/form3.png[Form with grouped inputs] + +(See layout component sections in Tutorial and Book of Vaadin to see how +this is done in Vaadin.) + +[[input-prompts]] +Input prompts +^^^^^^^^^^^^^ + +An input prompt is a placeholder text inside an input field that +disappears as soon as a value is entered into the field. Many input +components in Vaadin have a *setInputPrompt()* method for this: + +[source,java] +.... +TextField tfSearch = new TextField(); +tfSearch.setInputPrompt(“Search by keywords”); +.... + +One use for input prompts is as an alternative to a separate *caption*. +This can be useful especially if there is very little space for the +field, or if you wish to reduce the visual clutter of your UI. A common +example is a search or text-filter field with an input prompt like +_“Search by keywords”_ or _“Filter by name”_ : + +image:img/searchfield.png[Search field with prompt] + +The decision to use input prompts as captions should not be taken +lightly, however. Keep in mind that the input prompt disappears as soon +as any text is entered into the field, so the user will have to remember +what the field was for, or be able to deduce its identity from its +current value. In the example above, with just a single search field, or +even in a login form with username and password fields, this is +acceptable. In a significantly longer form, however, especially one that +the user might wish to read through before submitting to check that +everything has been correctly entered, this approach quickly turns into +a problem. + +Another way to use input prompts is for displaying *additional +information* about the field, such as the expected *format*, or the +*types of values that are accepted*. This is quite similar to +*tooltips*, with the difference that the input prompt must be kept short +enough to fit into the field itself, and that it is immediately visible, +without hovering over the field with the mouse pointer. For this reason, +input prompts are useful on touch screens, as opposed to tooltips. + +A good example of indicating the types of values a fields accepts is a +_Location_ field where you can enter a street address, a postal code or +just the name of the city. These details are probably too long to fit in +the field’s caption, and might not be discoverable enough in a tooltip. +An input prompt briefly explaining the options is an excellent solution: + +image:img/locationfield.png[Input field with prompt] diff --git a/documentation/articles/ConfiguringPushForYourEnviroment.asciidoc b/documentation/articles/ConfiguringPushForYourEnvironment.asciidoc index dd837e5093..ee9c363da8 100644 --- a/documentation/articles/ConfiguringPushForYourEnviroment.asciidoc +++ b/documentation/articles/ConfiguringPushForYourEnvironment.asciidoc @@ -1,6 +1,6 @@ [[configuring-push-for-your-environment]] -Configuring Push For Your Enviroment ------------------------------------- +Configuring push for your environment +------------------------------------- Server push and especially websockets are emerging technologies and not all servers and browsers handle them correctly (or even close to @@ -37,7 +37,7 @@ Tomcat 6 + Streaming For Tomcat 6, falling back to streaming always results in an error message such as [source] .... -Failed using comet support: org.atmosphere.container.TomcatCometSupport, error: Tomcat failed to detect this is a Comet application because context.xml is missing or the Http11NioProtocol Connector is not enabled.If that's not the case, you can also remove META-INF/context.xml and WEB-INF/lib/atmosphere-compat-tomcat.jar Is the Nio or Apr Connector enabled?WARNING: Using org.atmosphere.container.BlockingIOCometSupport` +Failed using comet support: org.atmosphere.container.TomcatCometSupport, error: Tomcat failed to detect this is a Comet application because context.xml is missing or the Http11NioProtocol Connector is not enabled.If that's not the case, you can also remove META-INF/context.xml and WEB-INF/lib/atmosphere-compat-tomcat.jar Is the Nio or Apr Connector enabled?WARNING: Using org.atmosphere.container.BlockingIOCometSupport .... Atmosphere is expecting the Servlet to implement Tomcat's proprietary interface https://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/CometProcessor.html[CometProcessor]. (See https://github.com/Atmosphere/atmosphere/blob/atmosphere-project-1.0.14/modules/cpr/src/main/java/org/atmosphere/cpr/AtmosphereServlet.java[AtmosphereServlet]) @@ -116,10 +116,10 @@ Glassfish 3 + Websockets As a rule of thumb, don't do this. The Grizzly version shipped with Glassfish 3.1.2.2 contains a -https://java.net/jira/browse/GRIZZLY-1289[fatal bug] which prevents +https://github.com/javaee/grizzly/issues/1289[fatal bug] which prevents Vaadin from working. Replace *glassfish/modules/grizzly-websockets.jar* with -https://java.net/jira/secure/attachment/50681/grizzly-websockets-1.9.50-fix.jar +http://central.maven.org/maven2/com/sun/grizzly/grizzly-websockets/1.9.56/grizzly-websockets-1.9.56.jar to get websockets working (with Vaadin 7.3). *This version is actually also broken in many ways, so you may or may not get it to work. If you want websockets, you should upgrade to Glassfish 4.* diff --git a/documentation/articles/CreatingABookmarkableApplicationWithBackButtonSupport.asciidoc b/documentation/articles/CreatingABookmarkableApplicationWithBackButtonSupport.asciidoc new file mode 100644 index 0000000000..e01ea78bc7 --- /dev/null +++ b/documentation/articles/CreatingABookmarkableApplicationWithBackButtonSupport.asciidoc @@ -0,0 +1,140 @@ +[[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. diff --git a/documentation/articles/CreatingAComponentExtension.asciidoc b/documentation/articles/CreatingAComponentExtension.asciidoc new file mode 100644 index 0000000000..92a673f013 --- /dev/null +++ b/documentation/articles/CreatingAComponentExtension.asciidoc @@ -0,0 +1,92 @@ +[[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 link:CreatingAUIExtension.asciidoc[Creating a UI extension] +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); +.... diff --git a/documentation/articles/CreatingAServlet3.0Application.asciidoc b/documentation/articles/CreatingAServlet3.0Application.asciidoc new file mode 100644 index 0000000000..18e6a1c8dd --- /dev/null +++ b/documentation/articles/CreatingAServlet3.0Application.asciidoc @@ -0,0 +1,62 @@ +[[creating-a-servlet-3.0-application]] +Creating a servlet 3.0 application +---------------------------------- + +Servlet 3.0 introduces a `@WebServlet` annotation which can be used to +replace the traditional web.xml. The straightforward approach to create +a Vaadin application using servlet 3.0 annotations is to simply move +whatever is in web.xml to a custom servlet class (extends `VaadinServlet`) +and annotate it using `@WebServlet` and add `@WebInitParams` as needed. You +will end up with something like + +[source,java] +.... +@WebServlet(value = "/*", asyncSupported = true, initParams = { + @WebInitParam(name = "ui", value = "com.example.MyUI"), + @WebInitParam(name = "productionMode", value = "false") +}) +public class MyServlet extends VaadinServlet { +} +.... + +The problem you will face sooner or later with both this approach as +well as using web.xml is that you will misspell some parameter name or +class name. Maybe you change the UI class name and the init parameter is +not automatically updated - and the head scratching and debugging +starts. + +Vaadin 7.1 introduces two features which makes this a lot easier, +`@VaadinServletConfiguration` and automatic UI finding. + +`@VaadinServletConfiguration` is a type safe, Vaadin version of +`@WebInitParam` which provides you with the option to select UI by +referring the UI class directly, toggle `productionMode` using a boolean +and more. The above example rewritten using `@VaadinServletConfiguration` +looks like: + +[source,java] +.... +@WebServlet(value = "/*", asyncSupported = true) +@VaadinServletConfiguration(productionMode = false, ui = MYUI.class) +public class MyServlet extends VaadinServlet { +} +.... + +Automatic UI finding takes this even one step further and allows you to +leave out `@VaadinServletConfiguration` completely if you define your +servlet class as a static inner class to your UI class. The above +example could therefore also be written as + +[source,java] +.... +public class MYUI extends UI { + @WebServlet(value = "/*", asyncSupported = true) + public static class Servlet extends VaadinServlet { + } +.... + +For clarity the variant with `@VaadinServletConfiguration` is likely the +better option. Please do note that `@VaadinServletConfiguration` comes +with defaults for some parameters, most importantly +`legacyPropertyToStringMode`, which might be important if you are +migrating an older application. diff --git a/documentation/articles/CreatingASimpleComponent.asciidoc b/documentation/articles/CreatingASimpleComponent.asciidoc new file mode 100644 index 0000000000..2a17eb7e4c --- /dev/null +++ b/documentation/articles/CreatingASimpleComponent.asciidoc @@ -0,0 +1,133 @@ +[[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 web.xml contains the +widgetset parameter:* + +[source,xml] +.... +<servlet> + <servlet-name>My Vaadin App</servlet-name> + <servlet-class>com.vaadin.server.VaadinServlet</servlet-class> + <init-param> + <description>Vaadin UI</description> + <param-name>UI</param-name> + <param-value>com.example.myexampleproject.MyApplicationUI</param-value> + </init-param> + <init-param> + <param-name>widgetset</param-name> + <param-value>com.example.mycomponent.MyComponentWidgetset</param-value> + </init-param> +</servlet> +.... + +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. diff --git a/documentation/articles/CreatingASimpleComponentContainer.asciidoc b/documentation/articles/CreatingASimpleComponentContainer.asciidoc new file mode 100644 index 0000000000..1d3cb9c372 --- /dev/null +++ b/documentation/articles/CreatingASimpleComponentContainer.asciidoc @@ -0,0 +1,241 @@ +[[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<Component> children = new ArrayList<Component>(); + + @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<Component> iterator() { + return children.iterator(); + } +} +.... + +Add, remove and replace are quite straight forward. 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<ComponentConnector> 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 in use in your web.xml. Here is a little +stand-alone application that uses this component: + +[source,java] +.... +package com.example.widgetcontainer; + +import java.util.Random; + +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; + +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); + } +} +.... diff --git a/documentation/articles/CreatingAThemeUsingSass.asciidoc b/documentation/articles/CreatingAThemeUsingSass.asciidoc new file mode 100644 index 0000000000..6b0b519325 --- /dev/null +++ b/documentation/articles/CreatingAThemeUsingSass.asciidoc @@ -0,0 +1,195 @@ +[[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/<themename>/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! diff --git a/documentation/articles/CreatingAUIExtension.asciidoc b/documentation/articles/CreatingAUIExtension.asciidoc new file mode 100644 index 0000000000..8c0fcd8fc2 --- /dev/null +++ b/documentation/articles/CreatingAUIExtension.asciidoc @@ -0,0 +1,172 @@ +[[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); +} +.... diff --git a/documentation/articles/CreatingAnEclipseProject.asciidoc b/documentation/articles/CreatingAnEclipseProject.asciidoc new file mode 100644 index 0000000000..f42bd095a5 --- /dev/null +++ b/documentation/articles/CreatingAnEclipseProject.asciidoc @@ -0,0 +1,98 @@ +[[creating-an-eclipse-project]] +Creating an Eclipse project +--------------------------- + +At the moment you have two options: + +* Creating a Maven project in Eclipse +* Using the Vaadin plug-in to create an Ivy project in Eclipse + +[[create-a-maven-project-in-eclipse]] +Create a Maven project in Eclipse +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To create a Maven project in Eclipse you will need to have to Maven +plugin installed. To install the plugin in Eclipse select "Help->Eclipse +Marketplace" and search for "Maven Integration for Eclipse". If you do +not have it installed install it before continuing. You will need to +restart Eclipse after installation is complete. When Eclipse has +restarted it will start the Maven repository indexer which will take a +while to finish so wait patiently for it to finish before trying to +create a new project, else the new project archetype will not be found. + +To create a new Vaadin maven project in Eclipse, start by selecting "File +-> New -> Project..." and select "Maven Project". When clicking "Next" +two times you will see a list of available archetypes. Write "vaadin" in +the filter field and select "vaadin-archetype-application". This will +create a Vaadin 7 application for you. + +Now you still need to enter some project info for your Maven project +such as group id, artifact id and version. Once you are done, click +"Finish". The project structure should now be ready. + +Next you will need to create a launch configuration for compiling the +widget set. This can be done by right clicking on the project in the +Project Explorer and select Run As.. -> Run Configurations... Then right +click on Maven Build and select New. You will be presented by the +following dialog: + +image:img/maven-compile-widgetset-launch.png[Run configuration: Compile Widgetset] + +Click run and the widget set will compile. You should do this whenever +you add new addons or client side classes to the project. + +To run the project we will need to create a similar launch script. So, +start by creating another Maven Build launch configuration just like we +did above but instead of using the `vaadin:compile` target we will use the +`jetty:run` target. Here is my ready made configuration for that: + +image:img/maven-run-project.png[Maven run configuration] + +Now, click Run and your project should be running on +http://localhost:8080 + +[[using-the-vaadin-plug-in-to-create-an-ivy-project-in-eclipse]] +Using the Vaadin plug-in to create an Ivy project in Eclipse +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Vaadin Plug-in is based on a Dynamic Web Project and therefore uses +Ivy instead of Maven for handling the dependencies. + +So lets start by adding the update sites to our eclipse installation. +This can be done by selecting "Help->Install New Software" in eclipse. +In the dialog which opens we need to add the Vaadin update site. + +Click "Add..." and add the following and click OK. + +.... +Name: Vaadin Eclipse Plugin +Location: http://vaadin.com/eclipse +.... + +Once that is done select the Vaadin plugin like have been done below. + +image:img/eclipse-plugin-install.png[Eclipse Vaadin plugin installation] + +Select "Next->Next->Finish" and the plugin will be installed. You will +need to restart eclipse for the plugin to take effect. + +Once the plugin is installed you can create a new project by selecting +File -> New -> Project... -> Vaadin 7 Project. In the dialog you will be +presented you will need to provide a name for the project at least. You +can also select which version of Vaadin should be used. By default the +latest version is automatically selected. Click finish and the project +is created. + +Once the project has been created you will still need to configure the +project to use Ivy. To do this right click on the ivy.xml file in the +newly created project and select "Add Ivy Library...". In the dialog +that appears select Finish. + +One final thing needs to be done to ensure that the Ivy managed jars are +deployed to your server and that is to add the project properties to the +deployment assembly. This can be done by right clicking on the project +and selecting Properties. In the properties dialog find the tab named +"Deployment Assembly" and select "Add.." -> "Java Build Path Entries" +and choose `ivy.xml[*]`. Click OK and your are done. + +Congratulations, you have just created a new Vaadin project! diff --git a/documentation/articles/CreatingMultiTabApplications.asciidoc b/documentation/articles/CreatingMultiTabApplications.asciidoc new file mode 100644 index 0000000000..1c04f77d81 --- /dev/null +++ b/documentation/articles/CreatingMultiTabApplications.asciidoc @@ -0,0 +1,29 @@ +[[creating-multi-tab-applications]] +Creating multi-tab applications +------------------------------- + +Every new request to the server gets a new session and UI instance. +Having the application open in separate tabs or windows means that the +instances are totally separate from each others, and thus won't conflict +and cause any out of sync or similar issues. + +Opening the new tab will open the application into it's start page. Use +URI fragments if you want to open a specific view into the secondary +tab. You can read the URI fragments in the `UI.init()` and decide if you +want to show the main view or a specialized view: + +[source,java] +.... +public void init(VaadinRequest request) { + String person = request.getParameter("editPerson"); + if (person == null) { + setContent(new MainView()); + } else { + setContent(new EditPersonView(person)); + } +} +.... + +More examples on URI fragments and parameters can be found at: + +* link:UsingURIFragments.asciidoc[Using URI fragments] diff --git a/documentation/articles/CreatingSecureVaadinApplicationsUsingJEE6.asciidoc b/documentation/articles/CreatingSecureVaadinApplicationsUsingJEE6.asciidoc new file mode 100644 index 0000000000..5d71d30b0e --- /dev/null +++ b/documentation/articles/CreatingSecureVaadinApplicationsUsingJEE6.asciidoc @@ -0,0 +1,646 @@ +[[creating-secure-vaadin-applications-using-jee6]] +Creating secure Vaadin applications using JEE6 +---------------------------------------------- + +by Petter Holmström + +[[introduction]] +Introduction +~~~~~~~~~~~~ + +In this article, we are going to look at how the security features of +JEE6 and GlassFish 3 can be used to create a secure Vaadin application. +You should be familiar with the security features of JEE6. If +not, skim through Part VII of the +http://docs.sun.com/app/docs/doc/820-7627[The Java EE 6 Tutorial, Volume +I] before continuing. + +[[architecture]] +Architecture +^^^^^^^^^^^^ + +The example system we are going to discuss has the following +architecture: + +image:img/architecture.png[System architecture] + +It is a typical JEE web application consisting of several layers. At the +top, there is the client layer, i.e. the users' web browsers. Then comes +the Internet (or any other network for that matter) through which the +data travels between the client layer and the application server. On the +server side, there are the web (or presentation) layer that contains the +user interface - in this case our Vaadin application - and the +enterprise (or application) layer that contains all our business logic +in the form of Enterprise Java Beans (EJB). Finally, the domain layer +contains the system data in the form of persistent entity objects stored +in some relational database - in this case JavaDB - by some Object +Relational Mapping (ORM) solution - in this case EclipseLink 2.0. + +In this article, we are going to secure all the layers, with the +exception of the client layer. This means that once the data has reached +the client it may be stored unencrypted in the browser's memory or +downloaded to a disk, but that is a risk we are willing to take in this +case ;-). + +[[getting-the-example-source-code]] +Getting the Example Source Code +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This article is based around an example application that maintains +employee records. Please note that the example system bears little +resemblance to a similar real-world system in order to keep it simple +and easy to understand. + +The source code can be downloaded +https://github.com/eriklumme/doc-attachments/blob/master/attachments/SecureVaadinApplicationDemo.zip[here] +and it has been packaged as a NetBeans project. If you do not have +NetBeans, go to http://www.netbeans.org[the NetBeans web site] and +download the latest version (6.8 at the time of writing). Remember to +select the version that includes GlassFish v3. + +Once NetBeans and GlassFish are installed, you can unzip the source code +archive and open it in NetBeans. The opened project should look +something like this: + +image:img/nbscrshot1.png[Netbeans screenshot 1] + +However, before you can try out the application, you have to create a +new JavaDB for the test data. This can be done from the Services tab +inside NetBeans: + +image:img/nbscrshot2.png[Netbeans screenshot 2] + +When this is done, you should update the _setup/sun-resources.xml_ file +accordingly, so that the correct username/password is used to connect to +the database and the correct JNDI-resources are registered when the +application is deployed. + +[[securing-the-domain-layer]] +Securing the Domain Layer +~~~~~~~~~~~~~~~~~~~~~~~~~ + +In this section we are not going to cover data encryption or Access +Control Lists (ACL), so if that is what you need, you unfortunately have +to look elsewhere for the time being. Instead, we are going to point out +some things that need to be taken into account when designing the domain +model. + +In a simple application where all users have read access to all data and +some users have write access, the domain model can be designed basically +in any way possible. However, if there are groups of users that should +be given read access to only some parts of the data, things get a little +more complicated. + +In the case of the example system written for this article, the system +contains information that is not intended for everyone such as salaries, +competences or individual career development plans (not included in the +example code). The system will be used by different types of users +(roles): + +* A payroll assistant will need read access to the employees' salaries +in order to be able to calculate the paychecks. Write access should be +prohibited. +* A project manager will need read access to the employees' competences +in order to be able to select the right people to his or her team. +However, he or she has nothing to do with how much each employee is +getting paid. +* A director will need full access to the system in order to add new +employees, change salaries, etc. + +To keep our lives simple, we should design the domain model in such a +way that if a part of an object graph is readable by a certain role, +then the entire graph should also be readable by the role: + +image:img/domain.png[Domain model] + +Here, all roles have read access to the `Employee` domain class. Please +note that no associations point out from this class. Therefore, if we +get an instance of `Employee`, we cannot accidentally (or intentionally) +access restricted information by traversing through the object graph. + +If we take a look at the `SalaryInfo` domain class, we note that it has +a unidirectional association to the `Employee` class. Thus, if we get an +instance of `SalaryInfo`, we can also access all the information in the +corresponding `Employee`. However, this is perfectly valid as the +Payroll Assistant role has read access to both domain classes. + +Now it is time to move on to the enterprise layer, where the security +constraints will actually be enforced. + +[[securing-the-enterprise-layer]] +Securing the Enterprise Layer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Securing the enterprise layer is actually the easiest part, as this is +no different from securing EJBs in an ordinary JEE application. In this +example, role-based security is sufficient so we can use annotations to +secure the EJBs: + +[source,java] +.... +@Stateless +@TransactionManagement +public class EmployeeBean { + // Implementations omitted + + @PersistenceContext + private EntityManager entityManager; + + @RolesAllowed(UserRoles.ROLE_DIRECTOR) + @TransactionAttribute(TransactionAttributeType.REQUIRED) + public void insertEmployee(Employee employee) { + ... + } + + @RolesAllowed(UserRoles.ROLE_DIRECTOR) + @TransactionAttribute(TransactionAttributeType.REQUIRED) + public Employee updateEmployee(Employee employee) { + ... + } + + @RolesAllowed(UserRoles.ROLE_DIRECTOR) + @TransactionAttribute(TransactionAttributeType.REQUIRED) + public void deleteEmployee(Employee employee) { + ... + } + + @RolesAllowed({UserRoles.ROLE_DIRECTOR, + UserRoles.ROLE_PAYROLL_ASSISTANT, + UserRoles.ROLE_PROJECT_MANAGER}) + public Employee getEmployeeByPersonNumber(String personNumber) { + ... + } + + @RolesAllowed({UserRoles.ROLE_DIRECTOR, UserRoles.ROLE_PAYROLL_ASSISTANT}) + public SalaryInfo getSalaryInfo(Employee employee) { + ... + } + + + @RolesAllowed({UserRoles.ROLE_DIRECTOR}) + @TransactionAttribute(TransactionAttributeType.REQUIRED) + public SalaryInfo saveSalaryInfo(SalaryInfo salaryInfo) { + ... + } + + @RolesAllowed({UserRoles.ROLE_DIRECTOR, UserRoles.ROLE_PROJECT_MANAGER}) + public EmployeeCompetences getCompetences(Employee employee) { + ... + } + + @RolesAllowed({UserRoles.ROLE_DIRECTOR, UserRoles.ROLE_PROJECT_MANAGER}) + @TransactionAttribute(TransactionAttributeType.REQUIRED) + public EmployeeCompetences saveCompetences(EmployeeCompetences competences) { + ... + } +} +.... + +The `UserRoles` class is a helper class that defines constants for all +the role names: + +[source,java] +.... +public class UserRoles { + public static final String ROLE_DIRECTOR = "DIRECTOR"; + public static final String ROLE_PAYROLL_ASSISTANT = "PAYROLL_ASSISTANT"; + public static final String ROLE_PROJECT_MANAGER = "PROJECT_MANAGER"; +} +.... + +This is actually all there is to it - the container will take care of +the rest. Note, that there are separate lookup methods for basic +employee information and salary information, and that the methods +require different roles. This is how the security constraints discussed +in the previous section are enforced in practice. + +[[securing-the-web-layer]] +Securing the Web Layer +~~~~~~~~~~~~~~~~~~~~~~ + +As all of the application's data and logic should now be protected +inside the enterprise layer, securing the web layer really comes down to +two basic tasks: handling user authentication and disabling the +restricted parts of your user interface. In the example application, the +user interface has not been restricted in order to make it possible to +test the security of the enterprise layer, e.g. what happens when a +restriction actions is attempted. + +As the Vaadin application runs entirely on the server, this can be done +inside the application in the same manner as in a Swing desktop +application. However, an (arguably) better approach is to rely on +standard JEE web layer security. + +To keep things simple, a Vaadin application should be designed in such a +way that when the application starts, the user is already authenticated +and when the user logs out, the application is closed. In this way, the +JEE container handles the authentication and it is even possible to move +from e.g. form-based authentication to certificate-based authentication +without having to change a single line of code inside the Vaadin +application. + +[[the-vaadin-application-servlet]] +The Vaadin Application Servlet +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Here is the code for the application servlet: + +[source,java] +.... +@WebServlet(urlPatterns={"/ui/*", "/VAADIN/*"}) +public class DemoAppServlet extends AbstractApplicationServlet { + + @Inject Instance<DemoApp> application; + + @Override + protected Class<? extends Application> getApplicationClass() throws + ClassNotFoundException { + return DemoApp.class; + } + + @Override + protected Application getNewApplication(HttpServletRequest request) throws + ServletException { + DemoApp app = application.get(); + Principal principal = request.getUserPrincipal(); + if (principal == null) { + throw new ServletException("Access denied"); + } + + // In this example, a user can be in one role only + if (request.isUserInRole(UserRoles.ROLE_DIRECTOR)) { + app.setUserRole(UserRoles.ROLE_DIRECTOR); + } else if (request.isUserInRole(UserRoles.ROLE_PAYROLL_ASSISTANT)) { + app.setUserRole(UserRoles.ROLE_PAYROLL_ASSISTANT); + } else if (request.isUserInRole(UserRoles.ROLE_PROJECT_MANAGER)) { + app.setUserRole(UserRoles.ROLE_PROJECT_MANAGER); + } else { + throw new ServletException("Access denied"); + } + + app.setUser(principal); + app.setLogoutURL(request.getContextPath() + "/logout.jsp"); + return app; + } +} +.... + +Please note the URL patterns that this servlet handles. The URL for the +Vaadin application will be _$CONTEXT_PATH/ui_. However, the servlet also +has to handle requests to _$CONTEXT_PATH/VAADIN/*_, as the widgetsets +and themes will not load otherwise. + +Next, in the `getNewApplication(..)` method, the user principal is +fetched from the request and passed to the Vaadin application using the +`setUser(..)` method (this is not a requirement, but is useful if the +Vaadin application needs to know the identity of the current user). If +the application will act differently depending on the user's roles, +these have to be passed in as well - in this case using a custom setter +defined in the `DemoApp` class. Finally, the logout URL is set to point +to a custom JSP which we will look at in a moment. + +[[the-deployment-descriptor]] +The Deployment Descriptor +^^^^^^^^^^^^^^^^^^^^^^^^^ + +To make sure the user is authenticated when the Vaadin application is +started, all requests to the Vaadin application should require +authentication. In this example we are going to use form-based +authentication using ordinary JSPs for the login, logout and error +screens, but we could just as well use some other form of authentication +such as certificates. In order to achieve this, we add the following to +the `web.xml` deployment descriptor: + +[source,xml] +.... +<web-app> + ... + <security-constraint> + <display-name>SecureApplicationConstraint</display-name> + <web-resource-collection> + <web-resource-name>Vaadin application</web-resource-name> + <description>The entire Vaadin application is protected</description> + <url-pattern>/ui/*</url-pattern> + </web-resource-collection> + <auth-constraint> + <description>Only valid users are allowed</description> + <role-name>DIRECTOR</role-name> + <role-name>PAYROLL_ASSISTANT</role-name> + <role-name>PROJECT_MANAGER</role-name> + </auth-constraint> + </security-constraint> + <login-config> + <auth-method>FORM</auth-method> + <realm-name>file</realm-name> + <form-login-config> + <form-login-page>/login.jsp</form-login-page> + <form-error-page>/loginError.jsp</form-error-page> + </form-login-config> + </login-config> + <security-role> + <description/> + <role-name>DIRECTOR</role-name> + </security-role> + <security-role> + <description/> + <role-name>PAYROLL_ASSISTANT</role-name> + </security-role> + <security-role> + <description/> + <role-name>PROJECT_MANAGER</role-name> + </security-role> + ... +</web-app> +.... + +Basically, this file tells the container that this web application: + +* uses the roles DIRECTOR, PAYROLL_ASSISTANT and PROJECT_MANAGER, +* requires the user to be in any of these roles when accessing the +Vaadin application, +* requires users to be in the _file_ realm (a built-in realm manageable +from the GlassFish administration console), and +* uses form-based authentication with a JSP for displaying the login +form and another for displaying login errors. + +For more information about configuring security for JEE web +applications, please see the JEE6 documentation. + +[[the-jsps]] +The JSPs +^^^^^^^^ + +Now we are going to write the JSPs that will be used for logging users +in and out. These files are well covered in the JEE6 documentation, so +we are just going to list them here without further commenting. First up +is _login.jsp_: + +[source,html] +.... +<%@page contentType="text/html" pageEncoding="UTF-8"%> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>Secure Vaadin Application Demo Login</title> + </head> + <body> + <h1>Please login</h1> + <form method="post" action="j_security_check"> + <p> + Username: <input type="text" name="j_username"/> + </p> + <p> + Password: <input type="password" name="j_password"/> + </p> + <p> + <input type="submit" value="Login"/> + </p> + </form> + </body> +</html> +.... + +Then we move on to _loginError.jsp_: + +[source,html] +.... +<%@page contentType="text/html" pageEncoding="UTF-8"%> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>Secure Vaadin Application Demo Login Failure</title> + </head> + <body> + <h1>Login Failed!</h1> + <p> + Please <a href="login.jsp">try again</a>. + </p> + </body> +</html> +.... + +Coming up next is _logout.jsp_: + +[source,html] +.... +<%@page contentType="text/html" pageEncoding="UTF-8"%> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>Secure Vaadin Application Demo</title> + </head> + <body> + <h1>You have been logged out</h1> + <p> + <a href="login.jsp">Log in</a> again. + </p> + </body> +</html> +<% + session.invalidate(); +%> +.... + +Please note that this file contains a single line of code at the end +that invalidates the session, effectively logging the user out. + +Finally, an _index.jsp_ file is needed in order to make sure that any +requests to the context path are redirected to the Vaadin application: + +[source,html] +.... +<% + response.sendRedirect("ui/"); +%> +.... + +There! Now the login and logout mechanisms are in place. + +[[securing-the-transport-layer]] +Securing the Transport Layer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Even though both the web layer and the enterprise layer are now secured, +the data still has to travel across the Internet to reach the client +layer and, as we know, the Internet is full of people with questionable +intentions. Therefore, we need to make sure that the data reaches its +destination undisclosed and unmodified. In other words, we need SSL. + +Provided that the application server has been properly configured to use +SSL (GlassFish v3 should be out of the box, though with a self-signed +certificate), it is very easy to force a web application to use SSL. We +just have to add the following security constraint to the _web.xml_ +file: + +[source,xml] +.... +<security-constraint> + <display-name>SecureChannelConstraint</display-name> + <web-resource-collection> + <web-resource-name>Entire site</web-resource-name> + <description/> + <url-pattern>/*</url-pattern> + </web-resource-collection> + <user-data-constraint> + <description>Require encrypted channel</description> + <transport-guarantee>CONFIDENTIAL</transport-guarantee> + </user-data-constraint> +</security-constraint> +.... + +This will force all requests to the application to go over an encrypted +SSL link. + +[[configuring-glassfish]] +Configuring GlassFish +~~~~~~~~~~~~~~~~~~~~~ + +As we are going to let GlassFish handle the user database, we have to do +some additional configuration before the application can be deployed. +Users created using the GlassFish administration console are assigned to +groups, which in turn can be mapped to application roles. It is possible +to configure GlassFish to automatically map a group name to a role with +the same name, but in this case we are going to define the mapping +manually by adding the following definitions to the _sun-web.xml_ file: + +[source,xml] +.... +<security-role-mapping> + <role-name>DIRECTOR</role-name> + <group-name>Directors</group-name> +</security-role-mapping> +<security-role-mapping> + <role-name>PAYROLL_ASSISTANT</role-name> + <group-name>Payroll Assistants</group-name> +</security-role-mapping> +<security-role-mapping> + <role-name>PROJECT_MANAGER</role-name> + <group-name>Project Managers</group-name> +</security-role-mapping> +.... + +These definitions tell GlassFish that all users that belong to the +_Directors_ group should hold the `DIRECTOR` role, etc. + +The application is now secured. However, in order to try it out we need +to add some users to the _file_ realm using the GlassFish Administration +Console: + +image:img/glassfish_console1.png[Glassfish console 1] + +image:img/glassfish_console2.png[Glassfish console 2] + +Now, we can deploy the application, login with different users and +explore what happens. + +[[adding-auditing]] +Adding Auditing +~~~~~~~~~~~~~~~ + +Although the application is now protected from unauthorized users, it +has not yet been protected from illegal use by authorized users. As the +application deals with sensitive personal information, it should be +possible to see what the users have done with the data while using the +system. + +GlassFish has an auditing system that, when turned on, automatically +records access decisions (such as successful or failed logins). However, +in this case we need some more fine-grained auditing. One way of +accomplishing this is to use CDI and interceptors (go +http://docs.jboss.org/webbeans/reference/1.0.0.PREVIEW1/en-US/html/interceptors.html[here] +for more information). + +We begin by defining the annotation that will be used to annotate the +methods that are to be subject to auditing: + +[source,java] +.... +@InterceptorBinding +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuditLog { +} +.... + +Next, we implement the actual interceptor: + +[source,java] +.... +@AuditLog +@Interceptor +public class AuditLogInterceptor { + @Resource + SessionContext sessionContext; + + @EJB + AuditService auditService; + + @AroundInvoke + public Object recordAuditLogEntry(InvocationContext ctx) throws Exception { + Object result = ctx.proceed(); + StringBuilder sb = new StringBuilder(); + sb.append(ctx.getMethod().getName()); + sb.append("("); + for (Object p : ctx.getParameters()) { + sb.append(p); + sb.append(","); + } + sb.append(")"); + String userName = sessionContext.getCallerPrincipal().getName(); + auditService.recordEntry(userName, sb.toString()); + return result; + } +} +.... + +Before we can use the interceptor, we have to activate it by adding the +following to the _beans.xml_ file: + +[source,xml] +.... +<interceptors> + <class>demoapp.security.AuditLogInterceptor</class> +</interceptors> +.... + +Finally, we annotate the enterprise methods that should be subject to +auditing: + +[source,java] +.... +@Stateless +@TransactionManagement +public class EmployeeBean { + ... + + @RolesAllowed(UserRoles.ROLE_DIRECTOR) + @TransactionAttribute(TransactionAttributeType.REQUIRED) + @AuditLog + public void insertEmployee(Employee employee) { + ... + } + + @RolesAllowed(UserRoles.ROLE_DIRECTOR) + @TransactionAttribute(TransactionAttributeType.REQUIRED) + @AuditLog + public Employee updateEmployee(Employee employee) { + ... + } + ... +} +.... + +There! Now, every time a method annotated with `@AuditLog` is +successfully invoked, it will be recorded together with a timestamp and +the name of the user who invoked it. + +[[summary]] +Summary +~~~~~~~ + +In this article, we have discussed how a typical Vaadin/JEE6 application +can be secured. We have secured the enterprise layer using annotations, +secured the web and channel layers by declaring security constraints in +the deployment descriptor and shown how Vaadin can be used together with +form-based authentication. Finally, we have looked at a way of +implementing auditing using interceptors. diff --git a/documentation/articles/CustomizingComponentThemeWithSass.asciidoc b/documentation/articles/CustomizingComponentThemeWithSass.asciidoc new file mode 100644 index 0000000000..b4728c14d1 --- /dev/null +++ b/documentation/articles/CustomizingComponentThemeWithSass.asciidoc @@ -0,0 +1,188 @@ +[[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. + +*_NOTE_* _in beta10, the $primaryStyleName functionality is still only +available for Base and Reindeer. This will change in the near future._ + +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; + } +} +.... diff --git a/documentation/articles/DynamicallyInjectingCSS.asciidoc b/documentation/articles/DynamicallyInjectingCSS.asciidoc new file mode 100644 index 0000000000..d379ec6c4d --- /dev/null +++ b/documentation/articles/DynamicallyInjectingCSS.asciidoc @@ -0,0 +1,307 @@ +[[dynamically-injecting-css]] +Dynamically injecting CSS +------------------------- + +In most cases you will style your components using SASS or CSS and +create a theme for the application which you include with the `@Theme` +annotation. This is always the preferred way of theming your +application. But in some cases this is not enough. Sometimes you will +want your user to be able to change some property on-the-fly without +providing pre-made class names. To do this you can use CSS style +injection. In this example I am going to show you how you can use CSS +injection to create an editor which you can use to modify text visually +with, a WYSIWYG of sorts. Here is an image of the final component I am +going to create: + +image:img/theme-editor.png[Theme editor] + +First lets start by defining the UI of the editor component, it looks +like this: + +[source,java] +.... +private Component createEditor(String text) { + Panel editor = new Panel("Text Editor"); + editor.setWidth("580px"); + VerticalLayout panelContent = new VerticalLayout(); + panelContent.setSpacing(true); + panelContent.setMargin(new MarginInfo(true, false, false, false)); + editor.setContent(panelContent); + // Create the toolbar + HorizontalLayout toolbar = new HorizontalLayout(); + toolbar.setSpacing(true); + toolbar.setMargin(new MarginInfo(false, false, false, true)); + // Create the font family selector + toolbar.addComponent(createFontSelect()); + // Create the font size selector + toolbar.addComponent(createFontSizeSelect()); + // Create the text color selector + toolbar.addComponent(createTextColorSelect()); + // Create the background color selector + toolbar.addComponent(createBackgroundColorSelect()); + panelContent.addComponent(toolbar); + panelContent.setComponentAlignment(toolbar, Alignment.MIDDLE_LEFT); + // Spacer between toolbar and text + panelContent.addComponent(new Label("<hr />", ContentMode.HTML)); + // The text to edit + TextArea textLabel = new TextArea(null, text); + textLabel.setWidth("100%"); + textLabel.setHeight("200px"); + // IMPORTANT: We are here setting the style name of the label, we are going to use this in our injected styles to target the label + textLabel.setStyleName("text-label"); + panelContent.addComponent(textLabel); + return editor; +} +.... + +Basically the editor component is a Panel with a text area and some +buttons which you can use to modify the text area text with. The +important thing here is that we give the text area a style name +"text-label". With this style name we will be able to inject CSS styles +targeted at that text area and modify colors and fonts of it. Lets next +take a look at how the controls in the toolbar is implemented. They are +all pretty similar but lets first take a look at how the Font selector +was made: + +[source,java] +.... +private Component createFontSelect() { + final ComboBox select = new ComboBox(null, + Arrays.asList("Arial", "Helvetica", "Verdana", "Courier", "Times", "sans-serif")); + select.setValue("Arial"); + select.setWidth("200px"); + select.setInputPrompt("Font"); + select.setDescription("Font"); + select.setImmediate(true); + select.setNullSelectionAllowed(false); + select.setNewItemsAllowed(false); + select.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange( ValueChangeEvent event ) { + // Get the new font family + String fontFamily = select.getValue().toString(); + // Get the stylesheet of the page + Styles styles = Page.getCurrent().getStyles(); + // inject the new font size as a style. We need .v-app to override Vaadin's default styles here + styles.add(".v-app .v-textarea.text-label { font-family:" + fontFamily + "; }"); + } + }); + return select; +} +.... + +The important part here is what is inside the `ValueChangeListener`. Once +we get the value from the ComboBox we are ready to inject it to the page +so the user can visually see what have changed. To do this we fetch the +StyleSheet for the current Page by calling `Page.getCurrent()`. Once we +have the current Page we can get its StyleSheet by calling +`Page.getstyleSheet()`. Once we got the StyleSheet we are free to inject +any CSS string into the page by using `StyleSheet.inject(String css)`. As +you see here we use the style name we gave to the TextArea as the +selector and apply the font-family attribute to change the font family +to the one the user has selected. For the sake of clarity, lets look at +how another one, the text color selector, was implemented: + +[source,java] +.... +private Component createTextColorSelect( ) { + // Colorpicker for changing text color + ColorPicker textColor = new ColorPicker("Color", Color.BLACK); + textColor.setWidth("110px"); + textColor.setCaption("Color"); + textColor.addColorChangeListener(new ColorChangeListener() { + @Override + public void colorChanged( ColorChangeEvent event ) { + // Get the new text color + Color color = event.getColor(); + // Get the stylesheet of the page + Styles styles = Page.getCurrent().getStyles(); + // inject the new color as a style + styles.add(".v-app .v-textarea.text-label { color:" + color.getCSS() + "; }"); + } + }); + return textColor; +} +.... + +Again, the important part in this method is in the `ColorChangeListener`. +Basically here we do the exactly same thing as we did with the font +family except here we are dealing with a color. To change the color of +the text we just simply apply the 'color' attribute for text area. The +`ColorPicker.Color` even provides us with a convenient method of directly +converting the received Color object into a CSS color. And finally, for +completeness, here is the full example code which will produce the demo +application in the picture above for you to try out: + +[source,java] +.... +/** + * Imports and package definition omitted + */ +public class CSSInjectWithColorpicker extends UI { + @Override + protected void init( VaadinRequest request ) { // Create a text editor + Component editor = + createEditor("Lorem ipsum dolor sit amet, lacus pharetra sed, sit a " + + "tortor. Id aliquam lorem pede, orci ut enim metus, diam nulla mi " + + "suspendisse tempor tortor. Eleifend lorem proin, morbi vel diam ut. " + + "Tempor est tellus vitae, pretium condimentum facilisis sit. Sagittis " + + "quam, ac urna eros est cras id cras, eleifend eu mattis nec." + + "Lorem ipsum dolor sit amet, lacus pharetra sed, sit a " + + "tortor. Id aliquam lorem pede, orci ut enim metus, diam nulla mi " + + "suspendisse tempor tortor. Eleifend lorem proin, morbi vel diam ut. " + + "Tempor est tellus vitae, pretium condimentum facilisis sit. Sagittis " + + "quam, ac urna eros est cras id cras, eleifend eu mattis nec." + + "Lorem ipsum dolor sit amet, lacus pharetra sed, sit a " + + "tortor. Id aliquam lorem pede, orci ut enim metus, diam nulla mi " + + "suspendisse tempor tortor. Eleifend lorem proin, morbi vel diam ut. " + + "Tempor est tellus vitae, pretium condimentum facilisis sit. Sagittis " + + "quam, ac urna eros est cras id cras, eleifend eu mattis nec." + + "Lorem ipsum dolor sit amet, lacus pharetra sed, sit a " + + "tortor. Id aliquam lorem pede, orci ut enim metus, diam nulla mi " + + "suspendisse tempor tortor. Eleifend lorem proin, morbi vel diam ut. " + + "Tempor est tellus vitae, pretium condimentum facilisis sit. Sagittis " + + "quam, ac urna eros est cras id cras, eleifend eu mattis nec."); + VerticalLayout content = new VerticalLayout(editor); + content.setMargin(true); + setContent(content); + } + + /** + * Creates a text editor for visually editing text + * + * @param text The text editor + * @return + */ + private Component createEditor( String text ) { + Panel editor = new Panel("Text Editor"); + editor.setWidth("580px"); + VerticalLayout panelContent = new VerticalLayout(); + panelContent.setSpacing(true); + panelContent.setMargin(new MarginInfo(true, false, false, false)); + editor.setContent(panelContent); + // Create the toolbar + HorizontalLayout toolbar = new HorizontalLayout(); + toolbar.setSpacing(true); + toolbar.setMargin(new MarginInfo(false, false, false, true)); + // Create the font family selector + toolbar.addComponent(createFontSelect()); + // Create the font size selector + toolbar.addComponent(createFontSizeSelect()); + // Create the text color selector + toolbar.addComponent(createTextColorSelect()); + // Create the background color selector + toolbar.addComponent(createBackgroundColorSelect()); + panelContent.addComponent(toolbar); + panelContent.setComponentAlignment(toolbar, Alignment.MIDDLE_LEFT); + // Spacer between toolbar and text + panelContent.addComponent(new Label("<hr />", ContentMode.HTML)); + // The text to edit + TextArea textLabel = new TextArea(null, text); + textLabel.setWidth("100%"); + textLabel.setHeight("200px"); + // IMPORTANT: We are here setting the style name of the label, we are going to use this in our injected styles to + // target the label + textLabel.setStyleName("text-label"); + panelContent.addComponent(textLabel); + return editor; + } + + /** + * Creates a background color select dialog + */ + private Component createBackgroundColorSelect( ) { + ColorPicker bgColor = new ColorPicker("Background", Color.WHITE); + bgColor.setWidth("110px"); + bgColor.setCaption("Background"); + bgColor.addColorChangeListener(new ColorChangeListener() { + @Override + public void colorChanged( ColorChangeEvent event ) { + // Get the new background color + Color color = event.getColor(); + // Get the stylesheet of the page + Styles styles = Page.getCurrent().getStyles(); + // inject the new background color + styles.add(".v-app .v-textarea.text-label { background-color:" + color.getCSS() + "; }"); + } + }); + return bgColor; + } + + /** + * Create a text color selection dialog + */ + private Component createTextColorSelect( ) { + // Colorpicker for changing text color + ColorPicker textColor = new ColorPicker("Color", Color.BLACK); + textColor.setWidth("110px"); + textColor.setCaption("Color"); + textColor.addColorChangeListener(new ColorChangeListener() { + + @Override + public void colorChanged( ColorChangeEvent event ) { + // Get the new text color + Color color = event.getColor(); + // Get the stylesheet of the page + Styles styles = Page.getCurrent().getStyles(); + // inject the new color as a style + styles.add(".v-app .v-textarea.text-label { color:" + color.getCSS() + "; }"); + } + }); + return textColor; + } + + /** + * Creates a font family selection dialog + */ + private Component createFontSelect( ) { + final ComboBox select = + new ComboBox(null, Arrays.asList("Arial", "Helvetica", "Verdana", "Courier", "Times", "sans-serif")); + select.setValue("Arial"); + select.setWidth("200px"); + select.setInputPrompt("Font"); + select.setDescription("Font"); + select.setImmediate(true); + select.setNullSelectionAllowed(false); + select.setNewItemsAllowed(false); + select.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange( ValueChangeEvent event ) { + // Get the new font family + String fontFamily = select.getValue().toString(); + // Get the stylesheet of the page + Styles styles = Page.getCurrent().getStyles(); + // inject the new font size as a style. We need .v-app to override Vaadin's default styles here + styles.add(".v-app .v-textarea.text-label { font-family:" + fontFamily + "; }"); + } + }); + return select; + } + + /** + * Creates a font size selection control + */ + private Component createFontSizeSelect( ) { + final ComboBox select = new ComboBox(null, Arrays.asList(8, 9, 10, 12, 14, 16, 20, 25, 30, 40, 50)); + select.setWidth("100px"); + select.setValue(12); + select.setInputPrompt("Font size"); + select.setDescription("Font size"); + select.setImmediate(true); + select.setNullSelectionAllowed(false); + select.setNewItemsAllowed(false); + select.addValueChangeListener(new ValueChangeListener() { + @Override + public void valueChange( ValueChangeEvent event ) { + // Get the new font size + Integer fontSize = (Integer) select.getValue(); + // Get the stylesheet of the page + Styles styles = Page.getCurrent().getStyles(); + // inject the new font size as a style. We need .v-app to override Vaadin's default styles here + styles.add(".v-app .v-textarea.text-label { font-size:" + String.valueOf(fontSize) + "px; }"); + } + }); + return select; + } +} +.... diff --git a/documentation/articles/DynamicallyUpdatingStateBeforeSendingChangesToClient.asciidoc b/documentation/articles/DynamicallyUpdatingStateBeforeSendingChangesToClient.asciidoc new file mode 100644 index 0000000000..ee8fc7967e --- /dev/null +++ b/documentation/articles/DynamicallyUpdatingStateBeforeSendingChangesToClient.asciidoc @@ -0,0 +1,85 @@ +[[dynamically-updating-state-before-sending-changes-to-client]] +Dynamically updating state before sending changes to client +----------------------------------------------------------- + +There are some cases where a server-side implementation must delay some +work until right before data is about to be sent to the client. Some +examples of this: + +* An expensive operation that should be done only once and not every +time some input for the calculation changes. +* Anything that depends on the component (or extension) being attached +to the component hierarchy. + +Vaadin provides the `ClientConnector.beforeClientResponse(boolean +initial)` method, which a server-side component or extension can override +if it wants to make some final adjustments to its shared state or send +some RPC right before data is being sent to the client. Because the +method is called just before the data will be sent, there are some +special considerations: + +* You should remember to call `super.beforeClientResponse(initial)` +because e.g. `AbstractComponent` relies on the method for performing its +own last minute changes to the state. +* The component hierarchy may not be modified in the +`beforeClientResponse` method, doing so might cause undesirable side +effects. +* `markAsDirty()` has no effect - changes will only be sent for connectors +that were marked as dirty before `beforeClientResponse` was called. + +Please note that `beforeClientResponse` will only be called for components +that the framework thinks might have changes, e.g. because they have +recently been attached, their `getState()` method has been called or they +have been marked as dirty using `markAsDirty()`. + +This shows a simple example where two terms are summed together only +once even if the terms are changed multiple times before a response is +sent to the client. + +[source,java] +.... +public class Addition extends AbstractComponent { + private int term1; + private int term2; + private boolean needsRecalculation = false; + + public void setTerm1(int value1) { + this.term1 = value1; + needsRecalculation = true; + + //Mark the component as dirty to ensure beforeClientResponse will be invoked + markAsDirty(); + } + + public void setTerm2(int value2) { + this.term2 = value2; + needsRecalculation = true; + + //Mark the component as dirty to ensure beforeClientResponse will be invoked + markAsDirty(); + } + + private int calculateSum() { + return term1 + term2; + } + + @Override + public void beforeClientResponse(boolean initial) { + super.beforeClientResponse(initial); + if (needsRecalculation) { + needsRecalculation = false; + // This could be an expensive operation that we don't want to do every time setTerm1 or setTerm2 is invoked. + getState().sum = calculateSum(); + } + } + + @Override + protected AddResultState getState() { + return (AddResultState) super.getState(); + } +} + +class AddResultState extends ComponentState { + public int sum; +} +.... diff --git a/documentation/articles/EnableAndDisableButtonsToIndicateState.asciidoc b/documentation/articles/EnableAndDisableButtonsToIndicateState.asciidoc new file mode 100644 index 0000000000..713fdc3a9e --- /dev/null +++ b/documentation/articles/EnableAndDisableButtonsToIndicateState.asciidoc @@ -0,0 +1,183 @@ +[[enable-and-disable-buttons-to-indicate-state]] +Enable and disable buttons to indicate state +-------------------------------------------- + +Most user interfaces have actions that can only be performed if certain +conditions are met. In other cases, the actions can be performed at any +time in principle, but don’t really make any sense to in certain +situations. And quite often, there are actions that really need to be +performed, e.g. to prevent data loss. + +A good example of this is a typical CRUD form for entering items into a +database, with buttons for saving, reverting (i.e. discarding changes) +and deleting items: + +image:img/potus1.png[POTUS Database CRUD example] + +The above image illustrates a typical UI for adding, modifying and +deleting data: A table listing the available items above, and a form for +editing the selected item below. The same form is also used to enter new +items. The _Add new_ button prepares the form for entering a new item. +Clicking a table row selects the corresponding item for editing. + +[[disabling-actions-to-prevent-errors]] +Disabling actions to prevent errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Naturally, the Save action in the UI depicted above can only be +performed if an existing item has been selected, or if the _“Add new”_ +button has been clicked to create a new item. Assuming there are +required fields (which there nearly always are), the _Save_ action can +only be successfully performed when all these have been properly filled +in. Let’s call these two requirements the *_technical criteria_* for +performing the _Save_ action. + +Similarly, the _Delete_ action can only be performed if an existing, +previously saved item is selected. Attempting to delete a nonexistent +(yet to be saved) item would result in an error. Thus, selection of an +existing item is a technical criterion for the _Delete_ action. + +So how do we handle these criteria in our code? An unfortunately common +solution is to display a pop-up error message explaining the situation. +The problem with this approach is that the user’s time is wasted +invoking an unperformable action and in being forced to dismiss an +annoying pop-up window (usually by clicking “OK” or something to that +effect). Also, users tend to ignore popups and just click them away +without reading the message, so they might not even be aware that the +action wasn’t performed. + +A clearly superior approach is to simply *disable actions until their +criteria are fulfilled*. By disabling actions that cannot be currently +performed, the user gets a clear visual indication of this situation, is +spared the trouble of attempting in vain to perform the action, and the +nuisance of an error message. + +image:img/potus2.png[Save and Revert actions disabled when they cannot be +succesfully +performed.] + +[[disablingenabling-actions-to-indicate-state]] +Disabling/enabling actions to indicate state +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The action criteria discussed so far are only the purely _technical_ +criteria for performing the _Save_ and _Delete_ actions. They are simply +there to prevent an exception from being thrown or a database constraint +being violated. Looking beyond the _technical_ requirements, neither the +_Save_ action or the _Revert_ action actually _do_ anything unless there +are *unsaved changes* in the form, so it doesn’t really make sense do +perform them at that time, even though they wouldn't result in an error. +We could call the existence of unsaved changes the *_logical criteria_* +for the _Save_ and _Revert_ actions. + +On the other hand, if there _are_ unsaved changes, then either _Save_ or +_Revert_ should be performed to either save the changes or revert the +fields to their original values, and you definitely want your users to +be aware of this state. + +It might seem unlikely that a user would be unaware of the state of the +form he or she is currently filling in, but out in The Real World, your +users will be constantly distracted by co-workers, incoming emails, +internet porn, coffee breaks and shiny things. They probably have “a +hunch” about whether they already clicked _Save_ or not, but even then +they might have some doubts about whether that action was _successfully +performed_. In the end, any uncertainty about whether their precious +data is safely stored is a tiny source of unnecessary stress for your +users. + +The following graphic illustrates a UI that does not, in any way, +indicate the current state of the form: + +image:img/disabled-before.png[UI without form state indication] + +Thus, both of these states (unsaved changes or not) should be indicated +to the user somehow. The solution, again, is *disabling and enabling* +the corresponding actions: The _Save/Cancel_ buttons are *disabled* +until any change is made in the form. As soon as changes are detected, +and the new values have been validated, the _Save/Cancel_ buttons are +*enabled*. When either one is clicked, both are *disabled* again to +indicate that the action was successfully performed. + +With this approach we add even more information about the current state +of the application to the buttons themselves. Not only are we indicating +when actions *_technically can_* be performed, but we also indicate when +they *_logically make sense_* to perform, and, in cases like the +_Save/Cancel_ actions in the example above, we also notify the user +about actions that *_probably should_* be performed to prevent data +loss. This is a great deal of information being *_subtly_* and +*_non-intrusively_* conveyed to the user, without resorting to annoying +popups, simply by enabling and disabling buttons. + +image:img/disabled-after.png[UI with form state indication] + +[[how-to-do-this-in-a-vaadin-application]] +How to do this in a Vaadin application +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To implement the above functionality, we need to be able to trigger the +button-toggling code for changes in the following states: + +* Item selection +* Field validation +* Unsaved changes + +The first one, whether or not an item has been selected and loaded into +the form is quite trivial of course. You can check for that in the same +code that handles item selection. + +The second one is really easy if you’ve bound the fields with a +*FieldGroup*, since in that case you can use the *isValid()* method on +the *FieldGroup* to check if all fields are valid or not. Empty required +fields cause this to return false, as do any validators you’ve +explicitly added. + +The third one is a bit trickier, since a change listener has to be added +to each field separately, and the type of listener you need to add +depends on the type of field. For most field components, a +*ValueChangeListener* is fine, since it triggers a notification when the +field’s value changes, such as when a different item is selected in a +*ComboBox*. However, for the various text field components (*TextField, +TextArea and PasswordField*) you’ll be better off with a +*TextChangeListener*, since you’ll want to trigger the button-toggling +code as soon as any change is made to the field’s text content, and a +*ValueChangeListener* won’t do that. + +Luckily, adding the change listeners can be done in a fairly simple loop +over the components in a layout, or the fields bound through a +*FieldGroup*. The appropriate type of listener can be chosen based on +whether the component implements the *FieldEvents.TextChangeNotifier* +interface: + +[source,java] +.... +TextChangeListener textListener = new TextChangeListener() { + @Override + public void textChange(TextChangeEvent event) { + formHasChanged(); + } +}; + +ValueChangeListener valueListener = new ValueChangeListener() { + @Override + public void valueChange(ValueChangeEvent event) { + formHasChanged(); + } +}; + +for (Field f : fieldGroup.getFields()) { + if (f instanceof TextChangeNotifier) { + ((TextChangeNotifier) f).addTextChangeListener(textListener); + } else { + f.addValueChangeListener(valueListener); + } +} +.... + +[source,java] +.... +public void formHasChanged() { + btnRevert.setEnabled(true); + boolean allFieldsValid = fieldGroup.isValid(); + btnSave.setEnabled(allFieldsValid); +} +.... diff --git a/documentation/articles/ExposingServerSideAPIToJavaScript.asciidoc b/documentation/articles/ExposingServerSideAPIToJavaScript.asciidoc new file mode 100644 index 0000000000..2f41d453a1 --- /dev/null +++ b/documentation/articles/ExposingServerSideAPIToJavaScript.asciidoc @@ -0,0 +1,134 @@ +[[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] +.... +<a href="javascript:(function(){com.example.api.notify(prompt('Message'),2);})();">Send message</a> +.... + +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);})();"))); + } +} +.... diff --git a/documentation/articles/GeneratingDynamicResourcesBasedOnURIOrParameters.asciidoc b/documentation/articles/GeneratingDynamicResourcesBasedOnURIOrParameters.asciidoc new file mode 100644 index 0000000000..9cfc756bc7 --- /dev/null +++ b/documentation/articles/GeneratingDynamicResourcesBasedOnURIOrParameters.asciidoc @@ -0,0 +1,61 @@ +[[generating-dynamic-resources-based-on-uri-or-parameters]] +Generating dynamic resources based on URI or parameters +------------------------------------------------------- + +You can dynamically generate responses based on e.g. query parameters by +creating your own `RequestHandler` and registering it with the session. + +In this way, you can for instance create an image that draws a text, +given as a parameter to the image. This has been done in the example +below: + +[source,java] +.... +public class DynamicImageUI extends UI { + public static final String IMAGE_URL = "myimage.png"; + + private final RequestHandler requestHandler = new RequestHandler() { + @Override + public boolean handleRequest(VaadinSession session, + VaadinRequest request, VaadinResponse response) + throws IOException { + if (("/" + IMAGE_URL).equals(request.getPathInfo())) { + // Create an image, draw the "text" parameter to it and output + // it to the browser. + String text = request.getParameter("text"); + BufferedImage bi = new BufferedImage(100, 30, + BufferedImage.TYPE_3BYTE_BGR); + bi.getGraphics().drawChars(text.toCharArray(), 0, + text.length(), 10, 20); + response.setContentType("image/png"); + ImageIO.write(bi, "png", response.getOutputStream()); + + return true; + } + // If the URL did not match our image URL, let the other request + // handlers handle it + return false; + } + }; + + @Override + public void init(VaadinRequest request) { + Resource resource = new ExternalResource(IMAGE_URL + "?text=Hello!"); + + getSession().addRequestHandler(requestHandler); + + // Add an image using the resource + Image image = new Image("A dynamically generated image", resource); + + setContent(image); + } + + @Override + public void detach() { + super.detach(); + + // Clean up + getSession().removeRequestHandler(requestHandler); + } +} +.... diff --git a/documentation/articles/GettingStartedOnNetBeans.asciidoc b/documentation/articles/GettingStartedOnNetBeans.asciidoc new file mode 100644 index 0000000000..aa656f6e17 --- /dev/null +++ b/documentation/articles/GettingStartedOnNetBeans.asciidoc @@ -0,0 +1,162 @@ +[[getting-started-on-netbeans]] +Getting started on NetBeans +--------------------------- + +*This page is for old NetBeans version. Take a look at +http://wiki.netbeans.org/VaadinPlugin1.0.0[New plugin in NetBeans wiki]* + +[[your-first-project-with-vaadin-in-netbeans-ide-6.7]] +Your First Project with Vaadin in NetBeans IDE 6.7 +-------------------------------------------------- + +Eclipse users have access to the http://vaadin.com/eclipse[Vaadin Eclipse +plugin] which +is probably the easiest way to get started with the Vaadin framework. But +if you preferNetBeans IDE, this is the article for you. And don't worry, +it's almost as easy to get started with NetBeans also. + +This tutorial assumes you have downloaded and installed a bundle of +http://www.netbeans.org[NetBeans 6.7] that +includes the Apache Tomcat server (the "Java Web & EE" support) and you +have the latest +http://vaadin.com/download[Vaadin] JAR +package at hand. + +[[creating-the-project]] +Creating the Project +~~~~~~~~~~~~~~~~~~~~ + +image:img/netbeans_new_project.png[NetBeans new project] + +Launch your NetBeans IDE and perform the following steps to create a new +web project. + +* Select `File -> New Project` to open the New Project dialog +window. +* Select `Java Web` from the categories and `Web Application` +from the project selection and click `Next` to proceed. +* Type in a name and location for your project (I use `HelloVaadin` as +the name and my default project folder) and click `Next`. +* Select the `Apache Tomcat 6.0.18` server and type in preferred +context path or use the default (the project name). The context path +will define the URL of your application (for example +`http://localhost:8084/HelloVaadin`). Click `Finish` to create the +project. +* You can close and ignore the index.jsp, which is opened to the editor +by default after the project has been created. + +[[importing-vaadin-libraries]] +Importing Vaadin Libraries +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Next you need to import the Vaadin library JAR package to the project +you just created. + +* Right-click the `Libraries` node on your project and select `Add +JAR/Folder...`. +* Locate your copy of the Vaadin JAR file in the opening file dialog and +click `Open`. +* Now you should see the JAR file under the Libraries node. + +[[writing-the-code]] +Writing the Code +~~~~~~~~~~~~~~~~ + +Next we create the application class for our simple example application. + +* Select `New -> Java Class` on your project to open the New Java +Class dialog. +* Type in your a name and package for your class (I use a class name of +`HelloVaadin` and a package `com.vaadin.netbeans.tutorial`). +* Select `Finish` to create the Java class. + +This class will be the main application class of our Vaadin +application.Therefore it must extend the abstract +`com.vaadin.Application` class and implement the `init()` method. + +Type in or copy-paste the following code to the newly created file: + +[source,java] +.... +package com.vaadin.netbeans.tutorial; + +import com.vaadin.Application; +import com.vaadin.ui.*; + +public class HelloVaadin extends Application { + @Override + public void init() { + Window mainWindow = new Window("HelloVaadin"); + Label label = new Label("Hello Vaadin user"); + mainWindow.addComponent(label); + setMainWindow(mainWindow); + } +} +.... + +[[defining-deployment-descriptor]] +Defining Deployment Descriptor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To run your application you must define a deployment descriptor for it. +Open `Web Pages -> WEB-INF -> web.xml` file on your project. By +default the file is opened in a graphical editor but you can select the +XML tab to edit the XML file directly. Type in or copy-paste the +following to the contents of the file. + +[source,xml] +.... +<?xml version="1.0" encoding="UTF-8"?> +<web-app version="2.5" + xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> + <display-name>HelloVaadin</display-name> + <context-param> + <param-name>productionMode</param-name> + <param-value>false</param-value> + <description>Vaadin production mode</description> + </context-param> + + <servlet> + <servlet-name>HelloVaadin</servlet-name> + <servlet-class>com.vaadin.terminal.gwt.server.ApplicationServlet</servlet-class> + <init-param> + <param-name>application</param-name> + <param-value>com.vaadin.netbeans.tutorial.HelloVaadin</param-value> + <description>Vaadin application class to start</description> + </init-param> + </servlet> + + <servlet-mapping> + <servlet-name>HelloVaadin</servlet-name> + <url-pattern>/*</url-pattern> + </servlet-mapping> +</web-app> +.... + +[[running-your-application]] +Running Your Application +~~~~~~~~~~~~~~~~~~~~~~~~ + +Now we can run (or debug) the application by simply selecting `Run -> +Run Main Project` (or `Run -> Debug Main Project`).This starts the +Apache Tomcat server and opens up your application in your default +browser. + +image:img/netbeans_hello_vaadin.png[NetBeans "Hello Vaadin"] + +[[what-next]] +What Next? +~~~~~~~~~~ + +Now that you have your environment setup, you probably want to explore +more of the features of the Vaadin framework. I would suggest that +you head to the http://vaadin.com/tutorial[Vaadin tutorial]. +Have fun with Vaadin! + +[[update]] +Update +~~~~~~ + +* http://vaadin.com/netbeans[Vaadin Plugin for NetBeans 6.8] diff --git a/documentation/articles/IBGettingStartedWithVaadinSpringWithoutSpringBoot.asciidoc b/documentation/articles/IBGettingStartedWithVaadinSpringWithoutSpringBoot.asciidoc new file mode 100644 index 0000000000..3196f2c12c --- /dev/null +++ b/documentation/articles/IBGettingStartedWithVaadinSpringWithoutSpringBoot.asciidoc @@ -0,0 +1,223 @@ +[[i-b-getting-started-with-vaadin-spring-without-spring-boot]] +I b - Getting started with Vaadin Spring without Spring Boot +------------------------------------------------------------ + +Note: this tutorial applies to *Vaadin Spring 1.0.0 and later* + +During this tutorial we will create a new Vaadin project, add Spring and +Vaadin Spring as dependencies, create a simple Spring UI and deploy the +application to a server. + +*Note that this tutorial is for using Vaadin Spring without Spring Boot. +Using Spring Boot is the recommended approach for getting started +quickly when creating a new project. For more information about setting +up a project both with and without Spring Boot - +see https://vaadin.github.io/spring-tutorial/[the Vaadin Spring Boot tutorial].* + +[[creating-a-vaadin-project]] +Creating a Vaadin project +~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you've created Vaadin projects before, there's nothing new here. +File→New→Project... then select Maven Project. This will take you to the +project creation wizard. You can also create the project using Vaadin +Plug-in for Eclipse if you prefer. + +image:img/project-creation.png[Project creation] + +then select the Vaadin archetype +(`com.vaadin:vaadin-archetype-application:<newest Vaadin version>`). +Set your group and artefact ids and your package to your liking. (I used +`com.vaadin`, `spring.tutorial` and `com.vaadin.spring.tutorial` respectively) + +[[adding-vaadin-spring-as-a-dependency]] +Adding Vaadin Spring as a dependency +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Open pom.xml and add + +[source,xml] +.... +<dependency> + <groupId>com.vaadin</groupId> + <artifactId>vaadin-spring</artifactId> + <version>1.0.0</version> +</dependency> +.... + +in the dependencies. Also replace the dependency "vaadin-client" with +"vaadin-client-compiled" and remove the scope line for it as we are +using a pre-compiled widgetset. If your project does not use the Vaadin +add-on Maven repository yet, add it to the POM: + +[source,xml] +.... +<repositories> + <repository> + <id>vaadin-addons</id> + <url>http://maven.vaadin.com/vaadin-addons</url> + </repository> +</repositories> +.... + +Then save and update project. + +[[creating-the-ui]] +Creating the UI +~~~~~~~~~~~~~~~ + +The project wizard created a UI for us that we'll use as a starting +point for building our application. There are some unnecessary things +we'll take out and some things we'll need to add. Start of by deleting +the AppWidgetSet.gwt.xml, as we won't need a custom widgetset in these +tutorials. We'll also have to make a few changes to the UI to make it +work with Vaadin Spring. + +Here's the UI's original source: + +[source,java] +.... +@Theme("mytheme") +@SuppressWarnings("serial") +public class MyVaadinUI extends UI { + + @WebServlet(value = "/*", asyncSupported = true) + @VaadinServletConfiguration(productionMode = false, ui = MyVaadinUI.class, widgetset = "com.vaadin.spring.tutorial.AppWidgetSet") + public static class Servlet extends VaadinServlet { + } + + @Override + protected void init(VaadinRequest request) { + final VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + setContent(layout); + + Button button = new Button("Click Me"); + button.addClickListener(new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + layout.addComponent(new Label("Thank you for clicking")); + } + }); + layout.addComponent(button); + } +} + +.... + +To allow Vaadin Spring to use the UI you'll need to add the following +annotation to the UI: + +[source,java] +.... +@SpringUI +.... + +The servlet configuration needs to be updated to initialize a Spring +application context, and the servlet should inherit from +`SpringVaadinServlet`. In this tutorial, a `ContextLoaderListener` is used +to initialize Spring. + +Finally, as we do not need a custom theme in the application, the theme +annotation is updated to use "valo" and the custom theme in the project +can be deleted. + +The resulting UI should be something like this: + +[source,java] +.... +package com.vaadin.spring.tutorial; + +import javax.servlet.annotation.WebListener; +import javax.servlet.annotation.WebServlet; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.context.ContextLoaderListener; + +import com.vaadin.annotations.Theme; +import com.vaadin.server.VaadinRequest; +import com.vaadin.spring.annotation.EnableVaadin; +import com.vaadin.spring.annotation.SpringUI; +import com.vaadin.spring.server.SpringVaadinServlet; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Label; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; + +@Theme("valo") +@SpringUI +@SuppressWarnings("serial") +public class MyVaadinUI extends UI { + + @WebServlet(value = "/*", asyncSupported = true) + public static class Servlet extends SpringVaadinServlet { + } + + @WebListener + public static class MyContextLoaderListener extends ContextLoaderListener { + } + + @Configuration + @EnableVaadin + public static class MyConfiguration { + } + + @Override + protected void init(VaadinRequest request) { + final VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + setContent(layout); + + Button button = new Button("Click Me"); + button.addClickListener(new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + layout.addComponent(new Label("Thank you for clicking")); + } + }); + layout.addComponent(button); + } +} +.... + +With the `@SpringUI` annotation the Vaadin Spring plugin will know to +inject the UI rather than directly instantiating it. With injected beans +we can use all of the usual Spring features such as autowiring. More on +that in later tutorials. + +In addition to these changes, when not using Spring Boot, create the +following Spring context file at +src/main/webapp/WEB-INF/applicationContext.xml : + +[source,xml] +.... +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-4.1.xsd"> + + <bean class="com.vaadin.spring.tutorial.MyVaadinUI.MyConfiguration" /> + <context:component-scan base-package="com.vaadin.spring.tutorial" /> +</beans> +.... + +A full description of alternative approaches to configuring Spring is +outside the context of this tutorial and you should consult Spring +documentation for them, but a brief introduction to them is given in +https://vaadin.github.io/spring-tutorial/[this +tutorial]. + +[[deployment]] +Deployment +~~~~~~~~~~ + +Once the UI is done we'll deploy it to our server by Run→Run as→Run on +Server. Select your server runtime (Tomcat in our case) and click +Finish. + +Eclipse should automatically open an embedded browser directed at your +development server. + +Congratulations! You've deployed your first Spring application. diff --git a/documentation/articles/IIInjectionAndScopes.asciidoc b/documentation/articles/IIInjectionAndScopes.asciidoc new file mode 100644 index 0000000000..618b816283 --- /dev/null +++ b/documentation/articles/IIInjectionAndScopes.asciidoc @@ -0,0 +1,253 @@ +[[ii-injection-and-scopes]] +II - Injection and scopes +------------------------- + +In this tutorial we'll take a closer look at the @CDIUI annotation and +use CDI to inject some beans to our application. + +[[cdiui]] +@CDIUI +~~~~~~ + +The @CDIUI annotation is the way in which you let the Vaadin CDI plugin +know which UI's should be accessible to the user and how they should be +mapped. It accepts one optional String parameter indicating the UI path. +If an explicit path is not provided the class name of the UI will be +used to construct a pathname by the following convention: any trailing +"UI" will be truncated and camelcase will be converted to hyphenated +lowercase. Some examples of the convention: + +.... +HelloWorldUI → hello-world +ExampleUI → example +VisualEditor → visual-editor +.... + +Passing an empty String as the path will cause the UI to be mapped to +the root of the deployment. Most single UI applications will probably +want to do this. + +[[injecting-beans]] +Injecting beans +~~~~~~~~~~~~~~~ + +Now that the UI itself has been injected, we can use the @Inject +annotation to further inject beans to it. Let's create something for us +to actually inject. + +We'll define the following interface, and an implementation for it. + +[source,java] +.... +package com.vaadin.cdi.tutorial; + +public interface Greeting { + public String getText(); +} +.... + +[source,java] +.... +package com.vaadin.cdi.tutorial; + +import java.io.Serializable; + +public class SimpleGreetingImpl implements Greeting, Serializable { + + @Override + public String getText() { + return "Hello, World!"; + } +} +.... + +So far so good, now we'll inject it into our UI. You'll need to add the +CDI API as a dependency in your pom.xml. (Group id: javax.enterprise, +artefact id: cdi-api, version: 1.2) + +[source,java] +.... +package com.vaadin.cdi.tutorial; + +import javax.inject.Inject; + +import com.vaadin.annotations.Theme; +import com.vaadin.cdi.CDIUI; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Label; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; + +@SuppressWarnings("serial") +@CDIUI("") +@Theme("valo") +public class HelloWorldUI extends UI { + + @Inject + private Greeting greeting; + + @Override + protected void init(VaadinRequest request) { + final VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + setContent(layout); + + Button button = new Button("Click Me"); + button.addClickListener(new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + layout.addComponent(new Label(greeting.getText())); + } + }); + layout.addComponent(button); + } +} +.... + +Let's run that and see. + +image:img/hello-world.png[Injection seems to be working] + +So far so good. Suppose we want to say hello to the user by name. We'll +create a class to store our user data in. For now it's a simple POJO +just for storing the name in a single string. + +[source,java] +.... +package com.vaadin.cdi.tutorial; + +import java.io.Serializable; + +public class UserInfo implements Serializable { + private String name; + + public UserInfo() { + this.name = "stranger"; + } + + public UserInfo(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} +.... + +We'll inject that to the UI and assign the user some name during +initalization (for now) + +[source,java] +.... +@Inject +private UserInfo user; + +@Override +protected void init(VaadinRequest request) { + ... + user.setName("Ernest"); +} +.... + +Then we'll create a new implementation of the Greeting and inject the +user there as well. + +[source,java] +.... +package com.vaadin.cdi.tutorial; + +import javax.inject.Inject; + +public class UserGreetingImpl implements Greeting { + + @Inject + private UserInfo user; + + @Override + public String getText() { + return "Hello, " + user.getName() + "!"; + } +} +.... + +Now, it would be easy to think that that's all you need but we're not +quite there. There are two issues with this that need to be addressed. +The first one will become immediately obvious when you try to deploy the +application. The deployment will fail as the injection in HelloWorldUI +is ambiguous, that is CDI doesn't know which implementation of Greeting +we want. + +There are three annotations that are useful in situations like this: +@Default, @Alternative and @Specializes. Giving a bean any of these +annotations will affect it's preference order when the CDI container is +looking for which implementation to inject. Unless otherwise specified, +beans will be considered to have the @Default annotation. Beans with the +@Alternative annotation will only be injected if that particular bean is +named in the beans.xml file (TODO: add link). Beans with the +@Specializes annotation will be considered a drop-in replacement for +it's superclass, and will be used over any implementations it extends. + +In our case, we'll want to give SimpleGreetingImpl the @Default +annotation and UserGreetingImpl the @Alternative annotation. + +[source,java] +.... +@Default +public class SimpleGreetingImpl implements Greeting { +.... + +[source,java] +.... +@Alternative +public class UserGreetingImpl implements Greeting { +.... + +After that the application could actually be deployed. To tell CDI we'll +want to use our alternative implementation we need to create the +beans.xml in our WEB-INF folder, and add the following declaration to +it: + +[source,xml] +.... +<beans> + <alternatives> + <class>com.vaadin.cdi.tutorial.UserGreetingImpl</class> + </alternatives> +</beans> +.... + +Let's try that out: + +image:img/hello-stranger.png[Something's not right] + +Better, but not quite there yet. We're getting the wrong username, +despite the fact that we set the username to "Earnest" in the UI. The +problem here is the scope of the bean. If you don't specify a scope for +your bean either in the bean class itself or at the injection point, +it'll default to using the dependent scope. Every time you inject a +dependent bean you'll get a new instance, which is clearly not what we +want here. Let's go back to our UserInfo class and assign it an explicit +scope. + +[source,java] +.... +import com.vaadin.cdi.UIScoped; + +@UIScoped +public class UserInfo { +... +.... + +The @UIScoped annotation is specific to Vaadin CDI. Anything injected +with that annotation will get the same instance while within the same +UI. Load a different UI and you'll get a different instance. If the +session expires or the UI is closed the instances will be cleaned up. + +Let's see if it worked. + +image:img/hello-earnest.png[Something IS right] + +Looks like we're making progress. diff --git a/documentation/articles/IntegratingAJavaScriptComponent.asciidoc b/documentation/articles/IntegratingAJavaScriptComponent.asciidoc new file mode 100644 index 0000000000..88cb679cc7 --- /dev/null +++ b/documentation/articles/IntegratingAJavaScriptComponent.asciidoc @@ -0,0 +1,101 @@ +[[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<List<Double>> pointList = new ArrayList<List<Double>>(); + 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<List<List<Double>>> series = new ArrayList<List<List<Double>>>(); +} +.... + +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<List<List<Double>>>` 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. diff --git a/documentation/articles/IntegratingAJavaScriptLibraryAsAnExtension.asciidoc b/documentation/articles/IntegratingAJavaScriptLibraryAsAnExtension.asciidoc new file mode 100644 index 0000000000..c9184be047 --- /dev/null +++ b/documentation/articles/IntegratingAJavaScriptLibraryAsAnExtension.asciidoc @@ -0,0 +1,79 @@ +[[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 link:CreatingAUIExtension.asciidoc[Creating a UI extension] 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 link:IntegratingAJavaScriptComponent.asciidoc[Integrating a +JavaScript component] 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. diff --git a/documentation/articles/IntegratingAnExistingGWTWidget.asciidoc b/documentation/articles/IntegratingAnExistingGWTWidget.asciidoc new file mode 100644 index 0000000000..53fd51256c --- /dev/null +++ b/documentation/articles/IntegratingAnExistingGWTWidget.asciidoc @@ -0,0 +1,179 @@ +[[integrating-an-existing-gwt-widget]] +Integrating an existing GWT widget +---------------------------------- + +Integrating an existing, third party GWT widget usually just involves +creating a regular Vaadin component with a client-side connector that +uses the third-party widget, probably using a shared state, and possibly +also with RPC - just as described in separate articles on these topics. +Usually, the only addition is the need to modify your widgetset +declaration to inherit the third-party widget's GWT module. + +In the following, we'll integrate the SimplePlot widget from the +http://code.google.com/p/gflot/[GFlot library] (which in turn is a GWT +adaptation of the pure JavaScript plotting library Flot) to create a +simple line plot component. We'll start with modifying our widgetset's +`gwt.xml` to inherit the GFlot GWT module, so if you're familiar with the +rest of the process, you can basically stop once that is done. + +But first a note on package structure: this particular example uses the +`com.example` package domain, and is set up to be a add-on project, with +the actual component in the `addon` package, and the demo that uses the +component in the `vaadingflot` package. The `addon` package contains the +`widgetset gwt.xml` definition, the server-side component (LinePlot), as +well as all the code related to the client-side in the `client.ui` +subpackage. + +Once you have a working project, go ahead and +http://code.google.com/p/gflot/downloads/list[download the GFlot jar], +and add it to `WEB-INF/lib`, then update the widgetset `gwt.xml` as follows: + +[source,xml] +.... +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.7.0//EN" + "http://google-web-toolkit.googlecode.com/svn/tags/1.7.0/distro-source/core/src/gwt-module.dtd"> +<module> + <inherits name="com.vaadin.DefaultWidgetSet" /> + <inherits name="ca.nanometrics.gflot.GFlot" /> +</module> +.... + +It inherits the default Vaadin widgetset as well as the GFlot GWT +module. + +Now we're ready to integrate the SimplePlot widget from the GFlot +package. Since we're familiar with the GFlot API, we know we want to add +'series' to the plot - we'll create a shared state for this purpose, and +a `DataSeries` class to represent the actual series within the state: + +[source,java] +.... +package com.example.addon.client.ui; + +import java.util.ArrayList; +import java.util.List; + +import com.vaadin.client.ComponentState; + +public class LinePlotState extends AbstractComponentState { + + public List<DataSeries> series = new ArrayList<DataSeries>(); + + public static class DataSeries { + public String label; + public String color; + public List<Float> data; + } +} +.... + +Lets make the server-side component next: + +[source,java] +.... +package com.example.addon; + +import java.util.Arrays; + +import com.example.addon.client.ui.LinePlotState; +import com.example.addon.client.ui.LinePlotState.DataSeries; +import com.vaadin.ui.AbstractComponent; + +public class LinePlot extends AbstractComponent { + + public LinePlot() { + } + + @Override + public LinePlotState getState() { + return (LinePlotState) super.getState(); + } + + public void addSeries(String label, String color, Float[] fs) { + DataSeries ds = new DataSeries(); + ds.label = label; + ds.color = color; + ds.data = Arrays.asList(fs); + getState().series.add(ds); + } +} +.... + +We override `getState()` in order to narrow the return type to our own +`LinePlotState`, and then implement a simple `addSeries()` that creates a +`DataSeries` instance and adds it to the state. The state will be +automatically transmitted to the client when needed, so the plots will +remain intact over browser reloads for instance.The API for our +component could obviously be expanded, but lets leave it like this for +this example. + +Since the GWT widget we're going to use is already made for us (in the +GFlot library), the only thing left for us to do is implement the +client-side connector: + +[source,java] +.... +package com.example.addon.client.ui; + +import ca.nanometrics.gflot.client.DataPoint; +import ca.nanometrics.gflot.client.SeriesHandler; +import ca.nanometrics.gflot.client.SimplePlot; + +import com.example.addon.LinePlot; +import com.example.addon.client.ui.LinePlotState.DataSeries; +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.Connect; + +@Connect(LinePlot.class) +public class LinePlotConnector extends AbstractComponentConnector { + + @Override + public LinePlotState getState() { + return (LinePlotState) super.getState(); + } + + @Override + public SimplePlot getWidget() { + return (SimplePlot) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(SimplePlot.class); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().getModel().clear(); + for (DataSeries ds : getState().series) { + + SeriesHandler s = getWidget().getModel().addSeries(ds.label, + ds.color); + for (int i = 0; i < ds.data.size(); i++) { + s.add(new DataPoint(i, ds.data.get(i))); + } + } + getWidget().redraw(); + } +} +.... + +We override both `getState()` and `getWidget()` to narrow the return type to +our liking, then make `createWidget()` return an instance of the GFlot +widget we're going to use, `SimplePlot`. + +Last, we override `onStateChange()` which is called whenever the shared +state has been changed. Here we make use of the `SimplePlot` API to add +the series contained in the shared state (for simplicity, we clear the +`SimplePlot` first, then add all the series in our state). + +That's it! The full source is available as an attachment to this +article. + +link:img/vaadingflot.zip[Attachment vaadingflot.zip] diff --git a/documentation/articles/IntegrationExperiences.asciidoc b/documentation/articles/IntegrationExperiences.asciidoc new file mode 100644 index 0000000000..ac6a660a02 --- /dev/null +++ b/documentation/articles/IntegrationExperiences.asciidoc @@ -0,0 +1,414 @@ +[[integrating-vaadin-applications-with-other-technologies]] +Integrating Vaadin Applications with other technologies +------------------------------------------------------- + +[[preface]] +Preface +~~~~~~~ + +The intention of this article is not to show how to integrate +http://vaadin.com/[Vaadin] with some other technology, but to tell +user-stories from both easy and hard customer cases where Vaadin has +been combined with something else. It should also give you a pointer to +what is possible and what is not. + +Please see the http://vaadin.com/wiki[wiki] for more extensive technical +articles on various subjects. If you are interested in learning more +from our developers, please contact us through +http://vaadin.com/forum[our forums]. The forum messages are usually +answered very rapidly (read: within a few hours during business hours). + +[[user-stories]] +User stories +~~~~~~~~~~~~ + +[[cloud-computing-and-vaadin]] +Cloud computing and Vaadin +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +Amazon's EC2 really takes creating and serving Vaadin applications to +a new level. You can literally have a new server running with server, +portal and application in couple of minutes and it all just works. Of +course this not only applies to Vaadin applications, but together they +are a perfect fit! -*Joonas Lehtinen* +=============================== + +link:https://aws.amazon.com/ec2/[Amazon EC2] + +[[cms-ala-liferay-and-vaadin]] +CMS (ala Liferay) and Vaadin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +We wanted to be able to show some pop-up application windows that were +part of our Vaadin application and that the customer themselves could +write the links in their CMS without hassle. What we did was that we +created an invisible Vaadin application that listened to the URL and +whenever it changed due to a click on a link, it launched a new pop-up +window. The solution was also highly scalable as 30.000+ users are using +it daily already. -*Johannes Tuikkala* +=============================== + +[[custom-ui-components-to-browser-with-java]] +Custom UI components to browser with Java +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +Creating your own widget for Vaadin is very easy thanks to the new +eclipse plugin. All you need to do is select "Create new Widget" in +eclipse and all the server and client side code is automatically +generated for you. Then you'll just create the widget using Java and +GWT. Using the eclipse plugin it is also much faster to set up a new +Vaadin project. -*Matti Tahvonen* +=============================== + +[[drools-and-vaadin]] +Drools and Vaadin +^^^^^^^^^^^^^^^^^ + +=============================== +We required a flexible way to validate internal dependencies in highly +dynamic data structures. We solved our need by integrating the Drools +rule engine in our application, and with it we got a powerful +content-triggered autocompletion mechanism as a freebie. We wrapped the +rule engine behind a general API in a EJB, so the rule mechanism can be +used by all components of the application architecture. -*Henri +Muurimaa* +=============================== + +http://jboss.org/drools[Drools] + +[[extending-existing-components]] +Extending existing components +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +We had a real business need for few important widgets that just were +not available in Vaadin. So we extended the current table and came up +with hierarchical table aka treetable. Another simple but important +widget was multilingual text field. It acts just like normal textfield +but when opened, you get the java field opened with multiple locale +instances, it has nice UI too including user friendly flags. Great for +handling i18n based data in your UI. We got multiple customercases that +enjoy these new widgets. -*Jani Laakso* +=============================== + + +[[flex-and-vaadin]] +Flex and Vaadin +^^^^^^^^^^^^^^^ + +=============================== +Flex is really excellent for animations and flashy things and in this +way is an ideal candidate for creating something really good looking +together with Vaadin. I created a proof-of-concept flex-Vaadin +application that communicates through GWT by wrapping the flex component +and implementing Vaadin's paintable interface. If you know flex from +before or have teams that know flex and others that know Java then this +is an ideal combination. -*Kim Leppänen* +=============================== + +http://demo.vaadin.com/coverflow/[Coverflow example with Flex+Vaadin] + +[[google-analytics-integration]] +Google Analytics Integration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +For the Sampler application, we wanted to be able to see which +'samples' are interesting to the users. GWT makes it easy to integrate +with JavaScript APIs, and thus making a Google Analytics component was a +breeze, even without using existing GWT/Java efforts. -*Marc Englund* +=============================== + +http://demo.vaadin.com/sampler/[Sampler] + +[[graphics-with-jfreechart]] +Graphics with JFreeChart +^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +It turned out surprisingly easy to add various charts to reports +generated by a web application. All we needed to do is create the +JFreeChart chart object and an Vaadin component automatically displays +it as SVG or PNG graphics depending on the browser used. -*Henri Sara* +=============================== + +http://dev.vaadin.com/browser/incubator/JFreeChartComponent[Vaadin:JFreeChart +Component] + +http://www.jfree.org/jfreechart/[JFreeChart] + +[[gwt-components-to-vaadin]] +GWT Components to Vaadin +^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +I wanted to create a Vaadin component out of Google's !GoogleMaps GWT +Widget. In order to do this you needed to create a widgetset that +includes the component and implement two methods that are required for +communication between the client and the server. Quite straight forward. +See the code in the incubator (link below). -*Henri Muurimaa* +=============================== + +https://vaadin.com/directory/component/googlemaps-add-on[GoogleMaps +add-on] + +[[javascript-libraries-and-vaadin]] +Javascript libraries and Vaadin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +For the Sampler application, I made a code formatting and colorizing +component, which makes use of the "google-code-prettify" JavaScript +library. I've found existing JavaScript libraries are easy to use with +GWT on the client-side. -*Marc Englund* +=============================== + +http://demo.vaadin.com/sampler/[Sampler] + +http://code.google.com/p/google-code-prettify/[Prettify] + +[[jquery-dojo-and-prototype-with-vaadin]] +JQuery, Dojo and Prototype with Vaadin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +Combining Vaadin applications and client-side javascript libraries is +a very interesting use-case and a very easy one to implement as well. +The easiest way to accomplish this is to use a !CustomLayout with the +Javascript you want. See a short demo I made below. -*Joonas Lehtinen* +=============================== + +http://vaadin.com/forum/-/message_boards/message/18611[Discussions on +the forum] + +http://jole.virtuallypreinstalled.com/JQTest/[Live example of +JQuery and Vaadin collaboration] + +[[latex-and-vaadin]] +LaTeX and Vaadin +^^^^^^^^^^^^^^^^ + +=============================== +This was no doubt a very exotic combination. Our customer wanted to be +able to create books that could be sent to printing based on huge +amounts of data. We wrote an advanced parser (using DOM and SAX parsers) +that created a file that LaTeX could interpret. LaTeX then created a PDF +that was saved in a database and a link showed up in the webshop. +Whenever you clicked on the link, you got finalized PDF that could be +sent to printing and as a result you got a full several hundred pages +long book. -*Jani Laakso* +=============================== + +http://www.latex-project.org/[LaTeX] + +[[leveraging-existing-java-libraries-in-the-browser]] +Leveraging existing Java libraries in the browser +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +Recently, while making a gesture recognition component, I was able to +use the Levensthein Distance algorithm implementation from the Jakarta +Commons project, without any modification.The use of GWT makes it +possible to leverage existing Java code on the client side - within own +projects, or by making use of the vast amount of libraries available for +Java. -*Marc Englund* +=============================== + +[[moss-and-vaadin-through-iframe]] +MOSS and Vaadin through IFrame +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +The goal was to get a Vaadin application running inside the Microsoft +Office Sharepoint Server (MOSS) as a portlet. What we did was that we +wrote our Vaadin application and deployed it on a JBoss portal. We then +took the application's URL and used MOSS' !PageViewer Webpart to have it +include the application inside an IFrame. It was actually much easier +that I thought. -*Johannes Tuikkala* +=============================== + +http://blogs.technet.com/josebda/archive/2007/04/05/integrating-sharepoint-with-other-portals-and-web-applications.aspx[Integrating +Sharepoint with other portals and web applications] + +[[moss-and-vaadin-with-sharepoint-sso]] +MOSS and Vaadin with Sharepoint SSO +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +The goal was to integrate a Vaadin application to MOSS as portal. +Priority was to use SSO by Sharepoint. This was accomplished doing +custom integration Webpart with C# that transfered authentication +information to Vaadin before it was opened with-in IFRAME. -*Mauno +Haukila* +=============================== + +[[n-tier-jee-architecture-with-vaadin]] +N-tier JEE architecture with Vaadin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +We have found in several larger customer cases that the best possible +architecture is to separate the UI-layer from the rest. Never mix your +business logic inside UI logic, there's no need to. This way the UI can +be replaced in the future and also when updating old systems to use +Vaadin (e.g. SWING applications that are written this way) it has been a +breeze when the only thing that needs to be rewritten is the UI. Another +very imporant factor is security, trust your data layer, do not trust +your clients, using enterprise beans with JNDI helps you. Scalability +and high-availability are also easier to cope with. Scale up with +multiple cheap Tomcat machines acting as Vaadin clients, few powerful +data servers as JBoss cluster and a good SQL server. -*Jani +Laakso* +=============================== + +image:img/n-tier.png[N-tier] + +[[offline-standalone-applications]] +Offline / standalone applications +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +I've made offline / standalone Vaadin applications by integrating an +embedded servlet container (Jetty and Winstone so far) with Xulrunner. +This is actually a quite straightforward process, requiring only a few +lines of code for the basic functionality. It's really the distribution +(making installers and such), and the actual application that's the hard +part. -*Marc Englund* +=============================== + +[[opensource-stack-for-customer-projects]] +Opensource stack for customer projects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +One of our customers asked us to help them with selecting an open +source stack for their open source product portfolio. What we came up +with was based on our own experiences and is by no means the only +possibility. What it shows though, is how Vaadin is only part of the UI +layer and everything beneath it can be almost anything. -*Ville +Ingman* +=============================== + +image:img/OSStack.png[OS Stack] + +link:img/OSStack.pdf[OS Stack PDF] + +[[pentaho-bi-suite-and-vaadin]] +Pentaho BI Suite and Vaadin +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +Yet another way to create PDF files, but this time really tightly +integrated with Vaadin. We had some data in a Vaadin table that we +wanted to print as a PDF file. As each component in Vaadin has a +container, the most natural way was to take the data from the container, +send it through a wrapper and give it to Pentaho. Pentaho then created +the PDF files without any problems. So whenever somebody wanted to print +the data they were viewing in a scrollable table, they just clicked a +button and everything was taken care of in the background. -*Jani +Laakso* +=============================== + +http://www.pentaho.com/[Pentaho BI Suite] + +[[portlets-with-vaadin]] +Portlets with Vaadin +^^^^^^^^^^^^^^^^^^^^ + +=============================== +The only thing you have to do in practice when you want to write a +portlet is add the portlet.xml file, where you define Vaadin's +!ApplicationPortlet as the entry point. After this your application will +work inside the portal. The only situation where you need to edit the +Java code as well is when you want to use some portal/portlet specific +features. This gives you the possibility to first develop a standard +Vaadin application and then later, by just adding the portlet.xml file +you have it working inside a portal. -*Jonas Granvik* +=============================== + +http://dev.vaadin.com/wiki/Articles/PortalTools[Portal Tools] + +[[pdfs-fopitext-and-vaadin]] +PDFs (FOP+iText) and Vaadin +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +When creating my printing service I used Apache FOP and iText to +create PDF files from multiple XML/XSLT/PDF files just as you would +normally in any Java application. The thing I found the most powerful +however was the combination with these two products together with +Vaadin's Embedded component. I just created the object, setSource() to +my byte array and the PDF showed up in my browser together with all the +buttons and widgets I also wanted on the screen. The user experience was +significantly enhanced compared to having to download the PDF and click +on some button in an external window. -*Fredrik Rönnlund* +=============================== + +http://vaadin.com/book/-/page/components.embedded.html[Vaadin:Embedded] + +http://xmlgraphics.apache.org/fop/[Apache FOP] + +http://www.lowagie.com/iText/[iText] + +[[saas-with-vaadin]] +SAAS with Vaadin +^^^^^^^^^^^^^^^^ + +=============================== +When writing our application as a service we had to do some serious +planning beforehand as the application was going to be used by so many +people. What we did was that we had one database for all users and then +inserted the instance ID in all HQL/SQL clauses to be sure we were +always accessing the correct instance. The instance was select at login. +This didn't require any specific things from the UI part, which was +created using Vaadin. -*Jani Laakso* +=============================== + +[[single-sign-on-sso-to-liferay]] +Single Sign On (SSO) to Liferay +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +Writing a Single Sign On portlet was really easy. A Vaadin application +by default only requires to be packaged as a portlet in order to work +inside a portal and then by using the provided API to the portal you can +easily retrieve all login information. This way the user doesn't need to +login several times and context handling is also done easily. -*Jani +Laakso* +=============================== + +[[unit-testing-vaadin-applications]] +Unit testing Vaadin applications +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +Unit testing Vaadin applications is no different from unit testing any +java application. What we have done however is separate the GUI from the +!BusinessLogic (even so far that they are running on physically +different servers). This way our most extensive testing is done on the +business logic, but also on our Controller in the GUI. Having Unit tests +in general has helped us build more solid applications, on all +layers. -*Kim Leppänen* +=============================== + +[[web-2.0-apis-and-vaadin]] +Web 2.0 APIs and Vaadin +^^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +Many "Web 2.0" APIs already have GWT bindings, making it trivial to +use these. Even without existing bindings, it's easy to use JavaScript +APIs from GWT, or REST/JSON/XML/etc APIs from the server-side.Google +Maps, Google Analytics, Flickr, Youtube - these are some "Web 2.0" APIs +that have been used. -*Marc Englund* +=============================== + +[[webservices-and-vaadin]] +Webservices and Vaadin +^^^^^^^^^^^^^^^^^^^^^^ + +=============================== +Integrating a webservice with Vaadin was really a no brainer. What we +had was a UI that had to check something through a service-provider's +Webservice. So when ever the UI launched the check, we called the +webservice (with Pojos created by the WSDL file that we got from the +service provider) and got some other Pojos as a reply. The reply then +dictated what the UI would look like. -*Johannes Tuikkala* +=============================== diff --git a/documentation/articles/LabelButtonsExpressively.asciidoc b/documentation/articles/LabelButtonsExpressively.asciidoc new file mode 100644 index 0000000000..d22ff13649 --- /dev/null +++ b/documentation/articles/LabelButtonsExpressively.asciidoc @@ -0,0 +1,51 @@ +[[label-buttons-expressively]] +Label buttons expressively +-------------------------- + +People don’t read dialog box messages. They just click _“OK”_ or _“Yes”_ +or whichever button that seems like the right choice to make the dialog +go away, without reading, or at least without really thinking about what +it's trying to tell them. + +What this means for UI design is that it’s important to label buttons in +a way that clearly indicates the result of pressing them, in order to +prevent users from making the wrong choice with potentially disastrous +results. + +Most desktop applications display a dialog like this when you attempt to +close them without first saving the document you were working on: + +image:img/save%20changes%201.png[Save changes dialog] + +Sometimes, however, the same question is expressed in a subtly different +way: + +image:img/save%20changes%202.png[Save changes "Are you sure..."] + +Needless to say, the risk of choosing the wrong answer is quite +substantial, unless you actually take the time to read through the +message (which most users do not). Of course it helps to stick to the +most common way of asking these kinds of questions, and being consistent +at least within your own applications. But there is room for improvement +here. + +If the options were labelled *_“Save”_ and _“Discard”_* or *_“Save +first”_ and _“Quit without saving”_*, instead of a simple _“Yes”_ and +_“No”_, all ambiguity suddenly disappears. Even if the user doesn’t read +a word of the message itself, the effects of these options is quite +clear: + +image:img/save%20changes%203.png[Save changes button] + +This principle doesn’t just apply to buttons in dialog boxes, though. +Even in other areas of a user interface, expressive labelling of actions +reduces ambiguity and makes users more confident in their work. +Consider, for instance, *_“Post”_* or even *_“Post comment”_* instead of +the typical _“Submit”_ for posting a comment on a blog. If your +_“Cancel”_ button doesn’t actually cancel anything, but rather resets a +form or reverts some changes, consider labelling it *_“Revert”_* or +*_“Reset form”_* instead. + +Also try to keep the label as short as possible, and consider starting +with a verb. That way the user probably picks the right choice even if +he only reads the first word of the label. diff --git a/documentation/articles/LoadTestingWithGatling.asciidoc b/documentation/articles/LoadTestingWithGatling.asciidoc new file mode 100644 index 0000000000..ec5bc80525 --- /dev/null +++ b/documentation/articles/LoadTestingWithGatling.asciidoc @@ -0,0 +1,267 @@ +[[loading-testing-with-gatling]] +Load testing with Gatling +------------------------- + +http://gatling.io[Gatling] is a powerful tool for load testing. Compared +to WebDriver/Selenium/TestBench, it doesn't render the actual content, +but just simulates messages clients send to the server. The server +doesn't know if the test tool actually does something with the +responses, so it gives you perfectly valid numbers for your applications +performance - on the server side. It scales very well, so you don' t +need huge army of nodes to bombard your application under test. It can +be used with Vaadin as such, but with these tips you hopefully get +started easier. + +[[vaadin-tips-to-make-tests-more-stable-and-easier-to-create]] +Vaadin tips to make tests more stable and easier to create +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Gatling works with Vaadin "out of the box", but there are some obstacles +you might face if you don't understand a bit how Vaadin communication +works. E.g. plain recordings from standard Vaadin apps will not work +properly. + +The communication that Vaadin's "thin client" in browser does with the +server side has couple of checks that it does to improve robustness and +security. One can simulate these with Gatling as well, by e.g. +https://github.com/mstahv/v-quiz/blob/master/src/test/scala/loadtest/WebSocketVaadinSimulation.scala#L84[reading the XSRF preventation key into a variable] and passing the value +https://github.com/mstahv/v-quiz/blob/master/src/test/scala/loadtest/WebSocketVaadinSimulation.scala#L95[in +each response]. However, these setting can be disabled during load +testing to make it easier to write and maintain your application. The +effect for the scalability, when disabling or configuring these, should +be negligible. Feel free to do these, but also remember to remove these +"hacks" when building your production war file. Consider e.g. using +separate maven profile and inject different parameters with it. + +[[disabling-xsrf-presentation-key]] +Disabling XSRF presentation key +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The XSRF preventation can be disabled with following servlet parameter +(or similar servlet 3 style parameter). NOTE, do not leave this for +public apps in production. + +[source,xml] +.... +<context-param> + <param-name>disable-xsrf-protection</param-name> + <param-value>true</param-value> +</context-param> +.... + +[[disabling-syncid-happens-with-similar-parameter]] +Disabling syncId happens with similar parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +[source,xml] +.... +<context-param> + <param-name>syncIdCheck</param-name> + <param-value>false</param-value> +</context-param> +.... + +If you want to do the above with Java Servlet 3.0 annotations, use the +following: + +[source,java] +.... +initParams = { + @WebInitParam(name = "disable-xsrf-protection", value = "true"), + @WebInitParam(name = "syncIdCheck", value = "false")} +.... + +[[using-debug-ids-in-communication]] +Using debug ids in communication +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you want to minimize the effort needed for maintaining your +scalability tests, you probably want to do a small hack to the Vaadin +communication mechanism. Normally Vaadin uses a incrementing session +wide identifier to connect components to their "client side +counterparts". Thus, if you add a one single component to your login +screen, the whole load test simulation might be broken. + +You can set "id" for each component, but in recent Vaadin versions this +id is no more used in the communication, but only assigned to +client dom. This can still be enforced with a specially crafted +extension to VaadinServlet. An implementation for the "competing tool" JMeter can be +found at <<jmeter-vaadin-servlet-extension>>. This implementation works for Gatling users +as well. Note that, it is suggested to do this only for load testing, and NOT +for the production. + +[[ignoring-obsolete-static-file-requests]] +Ignoring "obsolete" static file requests +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +One of the most simplest and cheapest method to improve your apps +scalability is to serve static files form a separate server or from a +CDN provider. Thus it might make sense to leave loading those files away +from your test script. If you do the script manually, just don't add +requests for static files (js/css/images/...). If you recorded you test +case, just remove these form the script. Check out the example project +that only uses the required files. + +[[testing-with-websockets]] +Testing with WebSockets +~~~~~~~~~~~~~~~~~~~~~~~ + +If your want to load test your application with the most advanced +communication channel, WebSockets, you can do that with Gatling as well. +Using the recorder in this case doesn't work, but handcrafting the test +case isn't that hard once you get started. The example app has a branch +with WebSocket test case. With WebSocket communication it might also be +handy to disable xsrf preventation and the so called "syncid". + +First two request are just normal http requests. The first gets the +"host page" and also the initial state request is done with normal XHR. +The difference to normal Vaadin communication is that it is to be sent +to "/PUSH" address. + +After the initial state request you start to use the special WebSocket +API in Gatling. There are lot of things to keep in mind with this +fundamentally totally different kind of communication mechanism. Check +out Gatling's generic websocket help for basic info. + +When you start handcrafting the WebSocket simulation, the easiest tool +is probably Chrome's dev tools. With that you can open normal browser +session and "sniff" the traffic that is sent to the server and also the +messages that are received. An easy option is just to copy paste the +payloads and possibly add some verification to ensure proper answers are +received. The websocket example is built with special variable to work +without disabling xsrf verification. + +If you are using random input in your load tests, something that is +highly suggested for realistic numbers, you might end up in small +problems. The message format, by Atmosphere, has a weird number and "|" +in front of each message. That number tells the message length and it +must really match the real message length. Create a simple helper +function to calculate that if your input data length varies. + +[source,javascript] +.... +import io.gatling.core.session._ +import io.gatling.core.session.el._ + +def atmoMessage(message: Expression[String]) = message.map(m => m.length + '|' + m) + +.sendText(atmoMessage("SomeMessage")) +.... + +If (and when) you probably want to close the websocket connection +cleanly, you need to notify the server with an extra xhr with a +identifier given by the atmosphere framework. The key is the first +message that the server sends when you connect to it. + +Check out this script for +https://github.com/mstahv/v-quiz/blob/master/src/test/scala/loadtest/WebSocketVaadinSimulation.scala[an +example using WebSocket] communication. It also saves XSRF preventation +key to variable, so it don't need it to be disabled from the server. + +[[configuring-gatling-to-the-web-app-build]] +Configuring Gatling to the Web app build +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is a good habit to keep your tests in the same projects as your +actual application. Then you can easily verify your application still +scales, after you have for example written a cryptic SQL query. + +Even better if you can make a Gatling script to be executed during +builds to make. Gatling has http://gatling.io/docs/current/extensions/maven_plugin/[a +maven plugin] that can do exactly this thing. +https://github.com/mstahv/gatling-vaadin-example[The example project +setup] executes a test during basic "mvn install". With similar setup in +a real project, your CI server most likely saves results stored under +target directory. This way it is easy to check it out afterwards how the +performance of your application has evolved during its development. + +[[jmeter-vaadin-servlet-extension]] +JMeter Vaadin Servlet extension +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The implementation referred to in <<using-debug-ids-in-communication>> + +[source,java] +.... +package com.example.vaadin7jmeterservlet; + +import com.vaadin.server.ClientConnector; +import com.vaadin.server.DeploymentConfiguration; +import com.vaadin.server.ServiceException; +import com.vaadin.server.VaadinRequest; +import com.vaadin.server.VaadinService; +import com.vaadin.server.VaadinServlet; +import com.vaadin.server.VaadinServletService; +import com.vaadin.server.VaadinSession; +import com.vaadin.ui.Component; + +/** + * @author Marcus Hellberg (marcus@vaadin.com) + * Further modified by Johannes Tuikkala (johannes@vaadin.com) + */ +public class JMeterServlet extends VaadinServlet { + private static final long serialVersionUID = 898354532369443197L; + + public JMeterServlet() { + System.setProperty(getPackageName() + "." + "disable-xsrf-protection", + "true"); + } + + @Override + protected VaadinServletService createServletService( + DeploymentConfiguration deploymentConfiguration) + throws ServiceException { + JMeterService service = new JMeterService(this, deploymentConfiguration); + service.init(); + + return service; + } + + private String getPackageName() { + String pkgName; + final Package pkg = this.getClass().getPackage(); + if (pkg != null) { + pkgName = pkg.getName(); + } else { + final String className = this.getClass().getName(); + pkgName = new String(className.toCharArray(), 0, + className.lastIndexOf('.')); + } + return pkgName; + } + + public static class JMeterService extends VaadinServletService { + private static final long serialVersionUID = -5874716650679865909L; + + public JMeterService(VaadinServlet servlet, + DeploymentConfiguration deploymentConfiguration) + throws ServiceException { + super(servlet, deploymentConfiguration); + } + + @Override + protected VaadinSession createVaadinSession(VaadinRequest request) + throws ServiceException { + return new JMeterSession(this); + } + } + + public static class JMeterSession extends VaadinSession { + private static final long serialVersionUID = 4596901275146146127L; + + public JMeterSession(VaadinService service) { + super(service); + } + + @Override + public String createConnectorId(ClientConnector connector) { + if (connector instanceof Component) { + Component component = (Component) connector; + return component.getId() == null ? super + .createConnectorId(connector) : component.getId(); + } + return super.createConnectorId(connector); + } + } +} +.... diff --git a/documentation/articles/MarkRequiredFieldsAsSuch.asciidoc b/documentation/articles/MarkRequiredFieldsAsSuch.asciidoc new file mode 100644 index 0000000000..c80132db2c --- /dev/null +++ b/documentation/articles/MarkRequiredFieldsAsSuch.asciidoc @@ -0,0 +1,40 @@ +[[mark-required-fields-as-such]] +Mark required fields as such +---------------------------- + +Don’t make your users guess which fields in your form are required. Mark +them as such: + +[source,java] +.... +TextField tfFirstName = new TextField("First name"); +tfFirstName.setRequired(true); +tfFirstName.setRequiredError("First name must be filled in!"); +.... + +Required fields get a small *asterisk* after the caption (or after the +field itself, if it doesn’t have a caption), which is quite universally +understood to mean “required”. Of course, it certainly doesn’t hurt to +have “footnote” somewhere in your form that explains it anyway. + +image:img/reqfield.png[Required field with asterisk] + +Marking a field as required also implicitly adds a non-empty validator +to the field, preventing the form from being submitted unless a value +has been entered. The error message associated with that validator can +be set with the *setRequiredError()* method: + +[source,java] +.... +TextField tfFirstName = new TextField("First name"); +tfFirstName.setRequired(true); +tfFirstName.setRequiredError("First name must be filled in!"); +.... + +image:img/errortooltip.png[Required field with error] + +Think carefully about which fields really are required, though. For +instance, asking unnecessary questions in a sign-up form has a tendency +to make people either cancel signing up, or enter nonsense information +in fields they deem nonessential. Only mark as required fields that you +really need the user to fill in, right there and then. diff --git a/documentation/articles/OpeningAUIInAPopupWindow.asciidoc b/documentation/articles/OpeningAUIInAPopupWindow.asciidoc new file mode 100644 index 0000000000..e9cfc5ccb9 --- /dev/null +++ b/documentation/articles/OpeningAUIInAPopupWindow.asciidoc @@ -0,0 +1,54 @@ +[[opening-a-ui-in-a-popup-window]] +Opening a UI in a popup window +------------------------------ + +To open a new popup window in the browser showing another part of your +application, you can use the new `BrowserWindowOpener` extension. Any +component that you extend with `BrowserWindowOpener` will make clicking +the component open a new browser window with some options for the +content. Opening a popup this way helps you bypass most popup blockers. + +One of the things that `BrowserWindowOpener` can be configured to open +in the new window is a new instance of a Vaadin UI class. + +[source,java] +.... +public class OpeningUIInPopup extends UI { + @Override + protected void init(VaadinRequest request) { + Button popupButton = new Button("Open popup with MyPopupUI"); + + BrowserWindowOpener popupOpener = new BrowserWindowOpener(MyPopupUI.class); + popupOpener.setFeatures("height=300,width=300"); + popupOpener.extend(popupButton); + + // Add a parameter + popupOpener.setParameter("foo", "bar"); + + // Set a fragment + popupOpener.setUriFragment("myfragment"); + + setContent(popupButton); + } +} + +public class MyPopupUI extends UI { + @Override + protected void init(VaadinRequest request) { + setContent(new Label("This is MyPopupUI where parameter foo=" + + request.getParameter("foo") + " and fragment is set to " + + getPage().getUriFragment())); + } +} +.... + +In this example, a `Button` is created and extended with a +`BrowserWindowOpener`. Furthermore, the size of the popup window is +defined. See +https://developer.mozilla.org/en-US/docs/DOM/window.open#Position_and_size_features +for a description of the commonly supported features. If you don't +define any features, the browser will use its default setting which +usually makes it open a new tab instead of opening a popup window. + +The `BrowserWindowOpener` can also be used to open a `Resource` or any +URL that you define using different constructors. diff --git a/documentation/articles/OptimizingTheWidgetSet.asciidoc b/documentation/articles/OptimizingTheWidgetSet.asciidoc new file mode 100644 index 0000000000..a8920c95f9 --- /dev/null +++ b/documentation/articles/OptimizingTheWidgetSet.asciidoc @@ -0,0 +1,187 @@ +[[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<Class> eagerComponents = new + LinkedList<Class>(); + + 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 <widgetset>.gwt.xml: + +[source,xml] +.... +<generate-with class="com.example.widgetsetoptimization.MyConnectorBundleLoaderFactory"> + <when-type-assignable class="com.vaadin.client.metadata.ConnectorBundleLoader" /> +</generate-with> +.... + +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] +.... +<!-- WS Compiler: manually edited --> +.... + +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 <widgetset>.gwt.xml: + +[source,xml] +.... +<inherits name="org.vaadin.artur.widgetsetoptimizer.WidgetSetOptimizerWidgetSet" /> +.... + +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. diff --git a/documentation/articles/PackagingSCSSOrCSSinAnAddon.asciidoc b/documentation/articles/PackagingSCSSOrCSSinAnAddon.asciidoc new file mode 100644 index 0000000000..19e461f6f0 --- /dev/null +++ b/documentation/articles/PackagingSCSSOrCSSinAnAddon.asciidoc @@ -0,0 +1,159 @@ +[[packaging-scss-or-css-in-an-add-on]] +Packaging SCSS or CSS in an add-on +---------------------------------- + +The add-on architecture of Vaadin enables you to easily create reusable +components and share them with the world. The add-on can be of various +types: widgets, themes, server side components etc. Most of them contain +Java code and classes, themes provide CSS and SCSS which can be used as +the application theme but how should package add-ons which are not +themes but still include CSS or SCSS? Let’s explore the options by +creating a fancy `DangerButton` component (a native button which should +have a red background). + +If we have an application (as opposed to an add-on) and want to create a +red native button we have many options: + +*1. Use NativeButton with a style name and define the style in the +application theme* + +[source,java] +.... +NativeButton dangerButton = new NativeButton("Don’t click me"); +dangerButton.addStyleName("danger"); +.... + +In the application theme: + +[source,css] +.... +.danger { + background-color: red; +} +.... + +In our application we would of course encapsulate this in a +`DangerButton` (`extends NativeButton`) which always adds the “danger” +style name. + +*2. Use NativeButton with a style name and @Stylesheet to include the +style* + +Do as above but create a custom css file which we place in the same +folder as DangerButton.java and annotate DangerButton with +@Stylesheet(“dangerbutton.css”). The @Stylesheet annotation will take +care of publishing the css file and make the browser load it. + +*3. Create a custom connector and define the style inline* + +Create a custom component + connector by creating `DangerButton extends +NativeButton` and `DangerButtonConnector +extends NativeButtonConnector`. We can then manage to do this in the +connector e.g. by setting an inline style when creating the widget. The +upside of this is that it will work without a custom theme, the downside +is that tuning the exact red color becomes cumbersome as you need to +recompile the widget set (or use dev mode) for each update. + +*4. Include the style sheet in a widget set* + +Include the style sheet in a widget set by adding +`<stylesheet src="dangerbutton-widgetset.css"/>` to the widget set +gwt.xml file and adding the css file to the “public” folder inside the +widget set folder (containing the gwt.xml file) so it will be included +in the compiled widget set. + +*5. Dynamically inject the styles* + +Use NativeButton with a style name and dynamically inject the CSS +needed, e.g. + +[source,java] +.... +NativeButton dangerButton = new NativeButton("Don’t click me"); +dangerButton.addStyleName("injectedDanger"); +getPage().getStyles().add(".injectedDanger {background-color: red;}"); +.... + +This approach does not require widget set compilation or a custom theme +but does not support scss and injecting a lot of css this way can +quickly add up to a lot of style tags in the page. + +[[what-about-the-add-on]] +What about the add-on +^^^^^^^^^^^^^^^^^^^^^ + +Returning to add-on discussion, which of the options above are viable +for an add-on? What should we really use for our DangerButton add-on to +keep it simple and avoid possible future problems? + +The first option which includes the application theme is not viable for +an add-on as such except if we are fine with documenting that you should +copy paste the css into your application theme. + +Option 2 seems like a good option but the day we add images or other +resources to the style sheet we will run into problems as @Stylesheet +only publishes the css file and nothing else. Including other style +sheets (even when they are published with @Stylesheet also) does not +work as we do not know the actual URL they will be available from at +runtime and including images will also cause issues as we have no easy +way of making them available. Also we cannot use SCSS with this +approach. + +Option 3 and 4 with the custom widget set usually work out fine, you can +even use SCSS for these (although it won’t be included in the +application theme, e.g. prefixed correctly). However, if we want to keep +things simple we do not want to make the add-on user compile widget set. + +We also do not want to go with option 5 as it does not support SCSS and +have potential issues when overused, especially in older Internet +Explorers. + +[[so-what-about-the-add-on..]] +So, what about the add-on..? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A sometimes overlooked feature of Vaadin is the VAADIN folder used to +serve themes and widget sets to the browser. As a matter of fact all +files inside the VAADIN folder are published directly as-is to the web, +also when the VAADIN folder is in an add-on jar. So by placing our style +sheet inside a “/VAADIN/addons/dangerbutton/” folder it will be +available for the browser and any images or related style sheets will +also be available (and relative paths will work). The only question +remaining is how we can include the style sheet automatically for any +user of our add-on and this can be done using `@StyleSheet` with the +special `vaadin://` protocol, e.g. +`@StyleSheet(“vaadin://addons/dangerbutton/styles.css”)`. + +[[that-does-not-work-with-scss]] +That does not work with SCSS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +One of the nice features of SCSS is that the theme creator can define +variables which you can use in your own scss file. If we are doing +standalone compilation of the scss file to css we are not able to take +advantage of these features. Also our styles will not be restricted to +the application theme (using theme name prefixing) if it is not compiled +at the same time as the application theme. Finally by including the +styles in the application theme, all possible minifiers and similar can +operate on the add-on styles also. + +To take full advantage of SCSS we can, instead of using the +`@Stylesheet` annotation to include a css file directly, define in the +add-on metadata that the addon jar contains a scss (or css) file which +should be included when compiling the application theme. This is done +with the `Vaadin-Stylesheets` attribute, for instance for our +`DangerButton` as: + +.... +Vaadin-Stylesheets: VAADIN/addons/dangerbutton/dangerbutton.scss +.... + +A small gotcha here is that the `Vaadin-Stylesheet` attribute refers to +the full path inside the add-on jar (i.e. starts with /VAADIN) whereas +the /VAADIN part is included in the `vaadin://` protocol when using +`@Stylesheet`. + +Now when you add the dangerbutton add-on jar to a project you will see +that the addons.scss file gets automatically updated to include the +dangerbutton css and it is compiled together with the rest of the +application theme. diff --git a/documentation/articles/ReadOnlyVsDisabledFields.asciidoc b/documentation/articles/ReadOnlyVsDisabledFields.asciidoc new file mode 100644 index 0000000000..eb92325877 --- /dev/null +++ b/documentation/articles/ReadOnlyVsDisabledFields.asciidoc @@ -0,0 +1,134 @@ +[[read-only-vs-disabled-fields]] +Read-only vs Disabled fields +---------------------------- + +Vaadin input field components have both a _disabled_ and a _read-only_ +state (which can be set through the *setEnabled(false)* and +*setReadOnly(true)* methods respectively). While at first glance these +seem to be interchangeable, there is a distinct difference in their +effects and proper usage. + +image:img/disabledvsreadonly.png[Disabled vs read-only] + +As you can see in the image above, the visual effects of the two states +are clearly different. The _disabled_ ComboBox is “grayed out” but looks +otherwise identical to its normal state. The _read-only_ ComboBox, +meanwhile, doesn’t look like a ComboBox at all, but more like a *Label*. +This visual difference is the key to understanding when to use which +state. + +[[disabled-fields]] +Disabled fields +^^^^^^^^^^^^^^^ + +An input field is disabled to indicate that it cannot _currently_ be +used. This might be because it is _not applicable_, e.g. due to the +value of some other field. Let’s look at the following piece of a +questionnaire form as an example: + +image:img/disabled1.png[Disabled example] + +The “years” dropdown above is _disabled_ unless the _Yes_ radio button +is selected, since it naturally _is not applicable_ otherwise. Another +reason for disabling a field might be that the user lacks the required +permissions to set it, such as in the following example: + +image:img/disabled3.png[Disabled example 2] + +In both cases, there is no need to _read_ the value of the field, since +it cannot have a value or simply isn’t applicable or relevant in the +current context. This is why disabled fields are grayed out with a +reduced opacity effect in Vaadin built-in themes. + +[[read-only-fields]] +Read-only fields +^^^^^^^^^^^^^^^^ + +_Read-only_, on the other hand, is used when a field is currently only +used to _display_ a value, without providing any means of changing it. +In this case, it is important that the field is presented in a +_readable_ way, which is why Vaadin generally renders them like labels, +without the unnecessary component chrome. A very common example is a +form that can be toggled between viewing and editing modes: + +image:img/viewmode-readonly.png[Viewing vs editing] + +Using read-only fields in viewing mode means that you don’t have to swap +between labels and input fields in your UI code when view/edit mode is +toggled. Instead, you just iterate through your fields set read-only +mode on or off: + +[source,java] +.... +Iterator<Component> i = someLayout.getComponentIterator(); +while (i.hasNext()) { + Component c = i.next(); + if (c instanceof com.vaadin.ui.AbstractField) { + AbstractField field = (AbstractField)c; + field.setReadOnly(true); + } +} +.... + +Even better, if your fields are databound through a *FieldGroup*, their +read-only states can be collectively toggled through the *FieldGroup*: + +[source,java] +.... +FieldGroup fieldGrp = new FieldGroup(dataItem); +TextField tfName = new TextField(“Name”); +fieldGrp.bind(tfName, “name”); +TextField tfAge = new TextField(“Age”); +fieldGrp.bind(tfAge, “age”); +fieldGrp.setReadOnly(true); +.... + +(Unfortunately, setting a Vaadin component container, like a layout, +_read-only_ does not set all its components read-only recursively, as +one might expect. Doing the same with _disabled does_, though.) + +One caveat regarding read-only fields is that if the text is longer than +the field, it will be clipped, as opposed to a Label, which instead will +wrap the text to a new line. Thus, in certain situations, switching to +Labels may be preferrable. + +It’s probably best to mention here that *setReadOnly(true)* also has a +certain side-effect in Vaadin that *setEnabled(false)* does not: You +cannot set the value of a _read-only_ field even in server-side code. +The following code would throw a *ReadOnlyException*: + +[source,java] +.... +TextField tfFoo = new TextField(); +tfFoo.setReadOnly(true); +tfFoo.setValue(“foo”); +.... + +[[why-this-is-important]] +Why this is important +^^^^^^^^^^^^^^^^^^^^^ + +Understanding the difference between _disabled_ and _read-only_ is +important because using them wrong tends to harm usability. First, let’s +see what happens if you use _disabled_ instead _read-only_ in the +view/edit example form above: + +image:img/viewmode-disabled.png[Viewing with disabled] + +Not very readable, is it? Looks kind of awful, doesn’t it? The reduced +opacity and unnecessary component chrome really make _reading_ the form +rather painful, which kind of defeats the entire "view" mode. + +Okay, so the other way around, what if we use _read-only_ instead of +_disabled_ in the wrong situation? The field will look like a *Label*, +and that’s not so bad, right? Well, if the field is _empty_, as is often +the case with disabled fields, then in read-only mode it would simply be +_invisible_, save for the caption (if it has one): + +image:img/readonly-wrong.png[Readonly wrong use] + +Admittedly, not as bad as using disabled for read-only forms, but bad +enough. Also, even if the field does have a value, setting it +_read-only_ gives the user the impression that it cannot be changed at +all, whereas a grayed out _disabled_ field indicates that it could be +changed, if only the circumstances were different... diff --git a/documentation/articles/RightAlignComparableNumericalFields.asciidoc b/documentation/articles/RightAlignComparableNumericalFields.asciidoc new file mode 100644 index 0000000000..4f835ead59 --- /dev/null +++ b/documentation/articles/RightAlignComparableNumericalFields.asciidoc @@ -0,0 +1,52 @@ +[[right-align-comparable-numeric-fields]] +Right-align comparable numeric fields +------------------------------------- + +In the table below, the various numerical fields are kind of hard to +compare and easy misinterpret, because of the alignment of the values: + +image:img/table1.png[Table with left alignment] + +By right-aligning numerical fields, the values become easier to compare +and less prone to misinterpretation, since the least significant digits +are vertically aligned: + +[source,java] +.... +Table tblExpenses = new Table(); +tblExpenses.setContainerDataSource(expensesData); +tblExpenses.setColumnAlignment("cost", Table.Align.RIGHT); +tblExpenses.setColumnAlignment("vat", Table.Align.RIGHT); +.... + +image:img/table2.png[Table with right alignment] + +This effect is of course ruined if the formats of these fields are +different, such as different number of decimals. Make sure that you’re +using the same format for fields that should be easy to compare. + +Even though this example was of a table, the same principles also apply +to input fields. Right-aligning a text field indicates to the user that +a numerical value is expected, and makes multiple numerical fields in +the same form easier to compare. To do this, you’ll need to set up a +custom theme for your application (which you’ll probably end up doing at +some point anyway), set a custom stylename to the field, such as +_“numerical”_ or _“right-aligned”_... + +[source,java] +.... +TextField tfExpensesLodging = new TextField(“Lodging”); +tfExpensesLodging.addStyleName(“numerical”); +.... + +...and get your hands dirty with a bit of CSS: + +[source,css] +.... +.v-textfield.numerical { + text-align: right; +} +.... + +Note that right-aligning non-comparable numerical fields, such as +product IDs or ISBNs is kind of pointless, and probably best to avoid. diff --git a/documentation/articles/UseTooltipsToClarifyFunctions.asciidoc b/documentation/articles/UseTooltipsToClarifyFunctions.asciidoc new file mode 100644 index 0000000000..af871a6332 --- /dev/null +++ b/documentation/articles/UseTooltipsToClarifyFunctions.asciidoc @@ -0,0 +1,32 @@ +[[use-tooltips-to-clarify-functions]] +Use tooltips to clarify functions +--------------------------------- + +Even when clearly labelled, the action performed by a button might not +be clear enough to the user. This is especially common when space or +design constraints force you to keep the label very short, or use an +icon instead of a label. This is where tooltips come in handy. + +image:img/tooltip.png[A tooltip] + +Tooltips are very easy to add to Vaadin components, although the method +used to set them is the somewhat misleadingly named *setDescription()*: + +[source,java] +.... +Button btnAttach = new Button(); +btnAttach.setIcon(new ThemeResource("icons/attach.png")); +btnAttach.setDescription("Attach a file"); +.... + +Of course, tooltips can be used for all kinds of UI components, not just +buttons. An input field or even a read-only indicator can benefit from a +tooltip. While the entire point of tooltips is to convey more +information that would fit in the component’s caption or label, it’s +still a good idea of try to keep the tooltip’s text as short as +possible. Big tooltip balloons get in the way of using the UI and become +annoying distractions instead of helpful aids. + +Keep in mind, however, that tooltips cannot be seen on a touch screen, +since there is no mouse pointer to hover over the component. For input +fields, consider using an *input prompt* instead. diff --git a/documentation/articles/UsingAJavaScriptLibraryOrAStyleSheetInAnAddOn.asciidoc b/documentation/articles/UsingAJavaScriptLibraryOrAStyleSheetInAnAddOn.asciidoc new file mode 100644 index 0000000000..ee2123b204 --- /dev/null +++ b/documentation/articles/UsingAJavaScriptLibraryOrAStyleSheetInAnAddOn.asciidoc @@ -0,0 +1,46 @@ +[[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 +link:IntegratingAJavaScriptComponent.asciidoc[Integrating a JavaScript component] +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. diff --git a/documentation/articles/UsingBeanValidationToValidateInput.asciidoc b/documentation/articles/UsingBeanValidationToValidateInput.asciidoc new file mode 100644 index 0000000000..5071a25a3a --- /dev/null +++ b/documentation/articles/UsingBeanValidationToValidateInput.asciidoc @@ -0,0 +1,53 @@ +[[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<Person> item = new BeanItem<Person>(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 diff --git a/documentation/articles/UsingDeclarativeServices.asciidoc b/documentation/articles/UsingDeclarativeServices.asciidoc new file mode 100644 index 0000000000..adcdc3426c --- /dev/null +++ b/documentation/articles/UsingDeclarativeServices.asciidoc @@ -0,0 +1,252 @@ +[[using-declarative-services]] +Using declarative services +-------------------------- + +Declarative Services (DS) are very common to define OSGi services. The +DS bundle scans all bundles (extender pattern), parses the component +definition xml-file and provides services based on that information. DS +may also be used to define references to other services. For instance +service A may require 0:n services of type B. The DS runtime will +ensure, that they are properly injected into the service A. References +to other services also influence the lifecycle of a service. For +instance service A requires 1:1 of service C. Thus service A will not be +activated before service C is injected properly. This short overview of +OSGi-DS is enough to understand the example defined below. + +[[setup-example]] +Setup example +~~~~~~~~~~~~~ + +To follow my explanation + +* clone repository from +https://github.com/lunifera/lunifera-Vaadin-examples.git +* use Import -> "Existing Maven Projects" in Eclipse IDE (Make sure that +m2e is installed) +* expand `org.lunifera.example.Vaadin.osgi.bootstrap.ds/setup` and set +`targetDS.target` +* `open targetDS.target` +** wait until resolved +** if error, then select all repository in target and press update +button on the right side +** wait until resolved +** press "set as target platform" +* Now there should be no problems. +* To build the project use `mvn clean verify` + +You will recognize that the bundle does not contain an `Activator`. Thats +not necessary since we use OSGi services managed by OSGi-DS. The +component runtime of DS manages the lifecycle of the services. Instead +of an activator we are using the class `ServiceComponent`. It contains all +logic to wire things together properly. + +[[servicecomponent]] +ServiceComponent +~~~~~~~~~~~~~~~~ + +The service component will become instantiated by OSGi DS and DS +controls its lifecycle. If the bundle containing the class is stopped, +the service will be deactivated by invoking `deactivate()`. If mandatory +references can be resolved, the service will be activated automatically. The `bindService` and `unbindService` are invoked by DS, +if a http service becomes available or unavailable. We do not need to +use a `ServiceTracker` anymore to get notified about the +`HttpService`-lifecycle. All that stuff is handled by OSGi-DS. + +[source,java] +.... +package org.lunifera.example.Vaadin.osgi.bootstrap.ds; + +import javax.servlet.ServletException; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleListener; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.http.HttpService; +import org.osgi.service.http.NamespaceException; + +/** + * The service will look for the HttpService and registers the Vaadin servlet at + * it. + */ +public class ServiceComponent implements BundleListener { + + private HttpService httpService; + private ResourceProvider resourceProvider; + + /** + * Called by OSGi DS if the component is activated. + * + * @param context + */ + protected void activate(ComponentContext context) { + handleStartedBundles(context); + context.getBundleContext().addBundleListener(this); + } + + /** + * Called by OSGi DS if the component is deactivated. + * + * @param context + */ + protected void deactivate(ComponentContext context) { + context.getBundleContext().removeBundleListener(this); + resourceProvider = null; + } + + /** + * Binds the http service to this component. Called by OSGi-DS. + * + * @param service + */ + protected void bindHttpService(HttpService service) { + httpService = service; + + try { + // register the servlet at the http service + httpService.registerServlet("/", new SimpleVaadinServlet(), null, + getResourceProvider()); + } catch (ServletException e) { + e.printStackTrace(); + } catch (NamespaceException e) { + e.printStackTrace(); + } + } + + /** + * Unbinds the http service from this component. Called by OSGi-DS. + * + * @param service + */ + protected void unbindHttpService(HttpService service) { + // unregister the servlet from the http service + httpService.unregister("/"); + } +.... + +If a http service is available, it becomes injected and will be used to +register the Vaadin servlet at it. If it becomes unbound (bundle +containing the http service stopped), the servlet will be unregistered. + +[[usecase-study]] +Usecase study +~~~~~~~~~~~~~ + +Imagine the following usecase. There are 2 bundle providing http +services. + +* `org.abc.http.jetty` +* `org.abc.http.tomcat` (can be achieved using virgo for instance) + +[[what-you-may-do...]] +What you may do... +^^^^^^^^^^^^^^^^^^ + +* Start the jetty bundle → then jetty-httpService will be bound to our +service component and Vaadin is running on a jetty +* Start the tomcat bundle → nothing will happen so far (service +component requires 0:1 http services - see below) +* Stop the jetty bundle → The jetty-httpService will become unbound and +Vaadin stops +* Some milliseconds later the tomcat-httpService will be bound +automatically → Vaadin will become installed to the tomcat +* Update the jetty bundle in the running OSGi environment (new bundle +with latest version is installed and old uninstalled) +* Start the jetty bundle (with the new version) again +* Stop tomcat bundle → The tomcat-httpService will become unbound and +Vaadin stops +* Some milliseconds later the jetty-httpService will be bound +automatically → Vaadin will become available at jetty + +That’s real modularity... Give it a try and play around. Indeed, you +won't write your own http services. But there are a lot of other use +cases too. I will blog about them later when I am talking about "Content +Provider by OSGi DS". + +[[servicecomponent-definition]] +ServiceComponent-Definition +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The service component definition is the description about the service. +It defines the implementation class, the provided services and the +referenced (required) services. Eclipse PDE comes with an editor to +define them. Expand the `OSGI-INF` folder in the bundle and double click +`VaadinComponent.xml`. Now you see the definition of the service +component. + +[source,xml] +.... +<?xml version="1.0" encoding="UTF-8"?> +<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.lunifera.example.Vaadin.osgi.bootstrap.ds"> + <implementation class="org.lunifera.example.Vaadin.osgi.bootstrap.ds.ServiceComponent"/> + <reference bind="bindHttpService" cardinality="0..1" interface="org.osgi.service.http.HttpService" + name="HttpService" policy="dynamic" unbind="unbindHttpService"/> +</scr:component> +.... + +* Line 2 defines the name of the service. Feel free to insert a unique +name +* Line 3 defines the class name of the service class that needs to +become instantiated +* Line 4 defines a reference to a required service - the HttpService +* *bind* means the method that is called to bind the HttpService +instance to the service instance +* *unbind* means the method that is called to unbind the HttpService +instance from the service instance +* *cardinality* defines how many services may / must be bound - 0..1, +1..1, 0..n, 1..n +* *interface* is the name of the service that should be bound + +A *very important* issue is an entry in the `MANIFEST.mf`. Using the +manifest header `Service-Component: OSGI-INF/*.xml` all xml files from +OSGI-INF are registered as component definitions to the DS runtime. If +you miss to add this statement, DS will never resolve your service! + +[[run-example]] +Run example +~~~~~~~~~~~ + +To run the example, we need to prepare an OSGi-launch-configuration. The +following bundles are required to run the example properly. In +difference to part 1, the `org.eclipse.equinox.ds` and +`org.eclipse.equinox.util` bundles are required. Otherwise OSGi-DS will +not become started. + +[cols=",,",options="header",] +|============================================================ +|bundle |start level |autostart +|org.lunifera.example.Vaadin.osgi.bootstrap.ds |default |true +|com.Vaadin.client-compiled |default |false +|com.Vaadin.server |default |false +|com.Vaadin.shared |default |false +|com.Vaadin.shared.deps |default |false +|com.Vaadin.themes |default |false +|javax.annotation |default |false +|javax.servlet |default |false +|org.apache.felix.gogo.command |default |false +|org.apache.felix.gogo.runtime |default |false +|org.apache.felix.gogo.shell |default |false +|org.eclipse.equinox.console |default |false +|org.eclipse.equinox.ds |1 |false +|org.eclipse.equinox.http.jetty |default |false +|org.eclipse.equinox.http.servlet |default |false +|org.eclipse.equinox.util |default |false +|org.eclipse.jetty.continuation |default |false +|org.eclipse.jetty.http |default |false +|org.eclipse.jetty.io |default |false +|org.eclipse.jetty.security |default |false +|org.eclipse.jetty.server |default |false +|org.eclipse.jetty.servlet |default |false +|org.eclipse.jetty.util |default |false +|org.eclipse.osgi |default |false +|org.eclipse.osgi.services |default |false +|org.json |default |false +|org.jsoup |default |false +|============================================================ + +To start a jetty server on a proper port, use the VM argument: +`-Dorg.osgi.service.http.port=8082` in your launch configuration. Now +you can access the Vaadin page under http://localhost:8082. Have fun! + +By http://de.gravatar.com/florianpi[Florian Pirchner] - based on +lunifera.org - OSGi components for business applications diff --git a/documentation/articles/UsingFontIcons.asciidoc b/documentation/articles/UsingFontIcons.asciidoc new file mode 100644 index 0000000000..81bd3ea075 --- /dev/null +++ b/documentation/articles/UsingFontIcons.asciidoc @@ -0,0 +1,188 @@ +[[using-font-icons-in-vaadin-7.2]] +Using font icons in Vaadin 7.2 +------------------------------ + +A “font icon” is an icon that is a glyph (essentially a character) from +a font. A font that is made for this purpose (containing only icons) is +called an “icon font”. Font icons scale well (being vectors), so you do +not have to make icons for a specific pixel size. Icon fonts are also +typically very light-weight (in bytes), and easy to use once set up. + +Vaadin 7.2 comes with generic support for font icons, and has the +popular http://fortawesome.github.io/Font-Awesome/[FontAwesome] icon font +built-in. The support is ‘generic’, in the sense that it does not +include some of the advanced styling options specifically available in +FontAwesome (such as spinning icons). + +FontAwesome is included in the theme by default, but the fonts are only +loaded by the browser when used. If needed, you can also remove the CSS, +minimize the font, or make a custom font. + +A demo application showing the result is +https://github.com/Porotype/FontIconDemo[available onGitHub] +(https://github.com/Porotype/FontIconDemo/tree/master/src/com/example/fonticondemo[java], +https://github.com/Porotype/FontIconDemo/blob/master/WebContent/VAADIN/themes/fonticondemo/fonticondemo.scss[scss]) + +[[using-fontawesome]] +Using FontAwesome +~~~~~~~~~~~~~~~~~ + +You can use font icons with the familiar `setIcon()` method. It looks like +this, when using the built-in FontAwesome: + +[source,java] +.... +button.setIcon(FontAwesome.BOOKMARK); +.... + +You can also easily embed an icon in any place that allows HTML: + +[source,java] +.... +label.setContentMode(ContentMode.HTML); +label.setValue("Press " + FontAwesome.BOOKMARK.getHtml() + " to bookmark"); +.... + +[[making-a-custom-set-of-icons]] +Making a custom set of icons +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are many tools for making icons, and icon fonts. One of our +favourite tools is http://icomoon.io/app[IcoMoon], which is an excellent +online application where you can pick icons from a vast library of +ready-made icon fonts, to compose your own set of icons. When you’re +done, you can download a zip that contains your font files - just copy +the ‘fonts’ folder to your theme. + +1. Browse to http://icomoon.io/app +2. _Add icons from library_ (the first time, some icons sets are added +automatically) +3. _Add_ the icon set(s) you fancy (notice the licenses) +4. Mark the icons you want in your font +5. _Download_ the font + +In IcoMoon you can also produce a customised version of FontAwesome, if +you for instance want to remove unused icons, or swap individual icons. +You can also re-upload your custom icon font, if you want to make +changes or additions to it later. + +1. _Import icons_ +2. Add/delete/swap icons +3. _Download_ the new font + +When using ready-made icons, please pay attention to the licenses the +different icon sets have. + +[[using-a-custom-icon-font]] +Using a custom icon font +~~~~~~~~~~~~~~~~~~~~~~~~ + +To use your own icon set, you need to do four things: + +1. Make an icon font +2. Make it easily usable from Java and +3. Load the font in your theme +4. Use the icons in your application. + +(You can skip #1 if you already have an icon font you want to use.) + +*1. Compose an icon font* in e.g IcoMoon, download, and copy the “fonts” +folder from the zip to your theme. + +*2. Add the following to your styles.scss* OUTSIDE of the `.mytheme{}` block: + +[source,scss] +.... +@include font(IcoMoon, '../../fonticondemo/fonts/icomoon'); +.... + +The first parameter, “IcoMoon”, is the font-family - i.e the name you +want to use for your font. You can choose anything, as long as you use +the same name in Java later. + +The second parameter is the filename base; in this case the files are +called icomoon.ttf, icomoon.svg, etc... + +*3. Make use of the icons in Java;* you can make an anonymous FontIcon +instance, but in practice you will probably want to make an enum or some +sort of icon factory. The FontAwesome implementation uses an enum, in +this manner: + +[source,java] +.... + private final int codepoint; + + IcoMoon(int codepoint) { + this.codepoint = codepoint; + } + + @Override + public String getFontFamily() { + // This must match the name you use in your (S)CSS + return "IcoMoon"; + } + + @Override + public int getCodepoint() { + return codepoint; + } + + @Override + public String getHtml() { + return "<span class=\"v-icon IcoMoon\">&#x" + + Integer.toHexString(codepoint) + ";</span>"; + } + + @Override + public String getMIMEType() { + throw new UnsupportedOperationException("Not supported for FontIcon"); + } +.... + +(Note that you can easily generate the enum from the list of icons in +the zip downloaded from IcoMoon.) + +*4. Now you can use your icon:* + +[source,java] +.... + button.setIcon(IcoMoon.RIBBON); +.... + +[[styling-font-icons]] +Styling font icons +~~~~~~~~~~~~~~~~~~ + +You can not generally set style names on the icon itself, instead you +apply styles to the _component_ where the icon is used, much in the same +way you would style anything else in a component. + +Given a button with a font icon and a style name: + +[source,java] +.... + button.addStyleName("redicon"); + button.setIcon(FontAwesome.SAVE); +.... + +…you can then style the icon by applying styles to the .v-icon element: + +[source,css] +.... + .redicon .v-icon { + color: red; + font-size: 20px; + } +.... + +Note that the icon is actually text, so you style it in much the same +way you style text. + +A font icon also gets an additional `.<font-family>` stylename, so you can +apply styles to only font icons (not ‘regular’ image icons): + +[source,css] +.... +.v-button .FontAwesome { + color: blue; +} +.... diff --git a/documentation/articles/UsingRPCFromJavaScript.asciidoc b/documentation/articles/UsingRPCFromJavaScript.asciidoc new file mode 100644 index 0000000000..c4bfea9bb9 --- /dev/null +++ b/documentation/articles/UsingRPCFromJavaScript.asciidoc @@ -0,0 +1,108 @@ +[[using-rpc-from-javascript]] +Using RPC from 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. + +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. diff --git a/documentation/articles/UsingRPCToSendEventsToTheClient.asciidoc b/documentation/articles/UsingRPCToSendEventsToTheClient.asciidoc new file mode 100644 index 0000000000..478986c374 --- /dev/null +++ b/documentation/articles/UsingRPCToSendEventsToTheClient.asciidoc @@ -0,0 +1,147 @@ +[[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 +link:SendingEventsFromTheClientToTheServerUsingRPC.asciidoc[Sending +events from the client to the server using RPC]. `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. diff --git a/documentation/articles/UsingServerInitiatedEvents.asciidoc b/documentation/articles/UsingServerInitiatedEvents.asciidoc new file mode 100644 index 0000000000..26221e9ac2 --- /dev/null +++ b/documentation/articles/UsingServerInitiatedEvents.asciidoc @@ -0,0 +1,130 @@ +[[using-server-initiated-events]] +Using server-initiated events +----------------------------- + +The traditional way of communicating with the server is always client +initiated. Whenever the user interacts with the application so a server +visit is needed, the browser connects to the server, sends a request and +waits for the response. The response is handled when received by the +browser and the UI is updated accordingly. For basic applications this +is all that is needed but there are also many cases when you want the +server to be able to initiate events: updating the progress of a long +running application, notifying you of changes in the back end, providing +interaction between users and so on. + +Starting from Vaadin 7.1 you have a couple of ways you can implement +server initiated events without requiring any external add-ons: polling +and push. Polling is based on the client actively polling the server, +asking “are there any news for me?” whereas push is based on keeping a +connection constantly open to the server, allowing the server to, +whenever it likes, send something to the client. Which you want to use +is dependent on your use case. If you have events which occur seldom but +you want to deliver them immediately to the user, then push might serve +you better. If you want to avoid constant server connections and only +need to check now and then if something has happened, polling may work +out well for you. For any use case you should consider the two options +and which one will better suit your needs. + +An important part to consider when using server initiated events is +locking. Vaadin is built around the fact that only one thread can access +any VaadinSession instance at any time. This is to prevent +inconsistencies from occuring and needs to be taken into account when +using background threads to update a UI (which lives inside a +VaadinSession). To do proper locking when you are updating the UI you +use the UI.access method: + +[source,java] +.... +theUI.access(new Runnable() { + @Override + public void run() { + theUI.setContent(new Label("This is the real content")); + } +}); +.... + +The access method ensure your update is safe to do (you have exclusive +access to the `VaadinSession`), it ensures that threadlocals work also +inside the `run()`` method (e.g. `UI.getCurrent()``) and finally it deals with +some typical deadlock situations. To be able to do all this it does not +guarantee that the `Runnable` is executed immediately - on the contrary +it will be run as soon as it is safe to run it without causing deadlocks +by locking multiple `VaadinSessions` at once. + +A typical use case is the one-shot background operation which does some +heavy lifting, updates the `UI` based on the result (which is then +fetched by the browser by polling or pushed to the browser) and then +goes away. This can be implemented as a `Runnable`, e.g. + +[source,java] +.... +class AntCounter implements Runnable { + @Override + public void run() { + // Do some heavy work + final int result = calculateNumberOfAntsInTheWorld(); + + // Wrap UI updates in access to properly deal with locking + theUI.access(new Runnable() { + @Override + public void run() { + theUI.setContent(new Label("The result is " + result)); + } + }); + } +} +.... + +The Runnable is typically run in a background thread using e.g. an +`ExecutorService`. + +The other typical case is a long running background task which checks +for some event and, in case that events happens, alerts the user. The +event can originate from another user, from a change in the database or +from somewhere else. Similarly to before, this can be implemented as a +`Runnable`: + +[source,java] +.... +class AntTracker implements Runnable { + @Override + public void run() { + // Do some initial work + int antPopulation = calculateNumberOfAntsInTheWorld(); + + while (true) { + // Loop forever and ever + final int antPopulationNow = calculateNumberOfAntsInTheWorld(); + if (antPopulationNow != antPopulation) { + antPopulation = antPopulationNow; + // Wrap UI updates in access to properly deal with locking + theUI.access(new Runnable() { + @Override + public void run() { + new Notification("Ant population is now " + + antPopulationNow, Type.TRAY_NOTIFICATION) + .show(theUI.getPage()); + } + }); + } + // Wait for a while before calculating again + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + return; + } + } + } + + private int calculateNumberOfAntsInTheWorld() { + return (int) (new Date().getTime()/1500); + } +} +.... + +The same thing can of course be implemented without the `while(true)`` loop +by using for instance a `ScheduledExecutorService` instead to make the +executor service handle the iteration and interval (e.g. `Executors.newScheduledThreadPool(1).scheduleAtFixedRate(...)`). + +For more information on how to enable push or polling in your +application, see link:EnablingServerPush.asciidoc[Enabling server push] or link:UsingPolling.asciidoc[Using polling]. diff --git a/documentation/articles/UsingURIFragments.asciidoc b/documentation/articles/UsingURIFragments.asciidoc new file mode 100644 index 0000000000..eebdf78303 --- /dev/null +++ b/documentation/articles/UsingURIFragments.asciidoc @@ -0,0 +1,87 @@ +[[using-uri-fragments]] +Using URI fragments +------------------- + +[[reading-fragment-when-initializing-ui]] +Reading Fragment when Initializing UI +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +URI fragments can either be used for initializing the UI and/or read or +modified in any listener. In the UI.init method you get a "VaadinRequest +request" parameter. The UI's Page contains a information about the HTTP +request used to initialize the application and you can get the URI +fragment using + +.... +getPage().geUriFragment() +.... + +A simple init that depends on the URI fragment is thus: + +[source,java] +.... +public class MyUI extends UI { + @Override + protected void init(VaadinRequest request) { + layout = new VerticalLayout(); + layout.setMargin(true); + setContent(layout); + + Label label = new Label("Hello, your fragment is " + + getPage().getUriFragment()); + layout.addComponent(label); + } +} +.... + +[[reading-fragment-changes]] +Reading Fragment Changes +~~~~~~~~~~~~~~~~~~~~~~~~ + +The URI fragment can be changed also when the application is running, +either manually in the location bar in browser or from user code. These +changes can be caught with a **FragmentChangedListener**. Notice, +however, that there is no event fired for the initial URL fragment. The +easiest way to handle both cases in the same way is to call the same +method from the FragmentChangedListener and the init method: + +[source,java] +.... +public class MyUI extends UI { + // ... + + // React to fragment changes + getPage().addUriFragmentChangedListener(new UriFragmentChangedListener() { + @Override + public void uriFragmentChanged(UriFragmentChangedEvent source) { + handleFragment(source.getUriFragment()); + } + }); + + // Handle the fragment received in the initial request + handleFragment(getPage().getUriFragment()); + + addComponent(new Button("Show and set fragment", new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + handleFragment(getPage().getUriFragment()); + getPage().setUriFragment("customFragment"); + } + })); +.... + +[[reading-and-writing-the-fragment]] +Reading and Writing the Fragment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To later on read the fragment you can use + +.... +Page.getCurrent().getUriFragment(); +.... + +and to set the fragment + +.... +Page.getCurrent().setUriFragment(String fragment); +.... diff --git a/documentation/articles/UsingVaadinCDIWithJAASAuthentication.asciidoc b/documentation/articles/UsingVaadinCDIWithJAASAuthentication.asciidoc new file mode 100644 index 0000000000..7fdf82b930 --- /dev/null +++ b/documentation/articles/UsingVaadinCDIWithJAASAuthentication.asciidoc @@ -0,0 +1,375 @@ +[[using-vaadin-cdi-with-jaas-authentication]] +Using Vaadin CDI with JAAS authentication +----------------------------------------- + +Servlet 3.0 is awesome, so is CDI. They work well and are a joy to set +up. Even adding the Vaadin Navigator to the mix isn't an issue, since +you can use the CDIViewProvider to maintain the injection chains. +Everything works nicely with annotations, and you don't need to mess +around with nasty XML. But what if you want to use JAAS? Recently I +stumbled upon a small dilemma in a customer project where the customer +wanted to use JAAS in combination with CDI. How in the world was I going +to combine the two? + +For this example I am using JBoss 7.1 for out-of-the-box support for CDI +and JAAS. I also tried this on Wildfly (the newest JBoss), but ran into +a bug that Matti has made +https://github.com/mstahv/vaadin-cdi-jaas-jbossas-example/tree/workaround[a +bit hacky workaround here]. I use a Postgres database, because a +database-based mechanism is more in the real world then the simplest +cases. But you can use any authentication mechanism you want, like LDAP, +AD and so on; you’ll find tutorials for integrating these into the JAAS +modules of almost any application server. + +There is a little bit of configuration involved because of the DB +connection, but don't be scared. It really is quite easy stuff. OK, +everyone ready? Lets dive in. + +The problem is that you need to define a secure mapping for a servlet in +web.xml, but maintaining the automatic scanning of Servlet annotations. +Luckily, we can use both. The servlet mapping (or UI mapping, in our +case) will take care of combining the two. I am not actually defining a +Servlet anywhere, but using the Vaadin-CDI addon for this. I simply +annotate my UIs with the @CDIUI annotation, and they will be picked up +automatically. The idea is to have an unsecured root UI mapped to /, a +secured area mapped to /secure, and a login page mapped to /login. For +the root UI, it looks like this: + +[source,java] +.... +@CDIUI +public class UnsecureUI extends UI { + + @Override + protected void init(VaadinRequest request) { + final VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + setContent(layout); + + layout.addComponent(new Label("unsecure UI")); + + Button b = new Button("Go to secure part"); + b.addClickListener(new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + String currentURI = getPage().getLocation().toString(); + getPage().setLocation(currentURI + "secure"); + } + }); + layout.addComponent(b); + } +} +.... + +The CDI addon (more exactly, the CDIUIProvider) will find the UI, and +will automatically deploy it. You can then start injecting things into +the UI class, such as a CDIViewProvider for the Navigator: + +[source,java] +.... +@Inject +private CDIViewProvider provider; + +@Override +protected void init(VaadinRequest request) { + Navigator n = new Navigator(this, this); + n.addProvider(provider); +.... + +Please note that you can configure the Servlet in a multitude of ways; +you can map the Servlet in your web.xml file as well. Leave out any UI +definitions, and put this in instead: + +[source,xml] +.... +<init-param> + <param-name>UIProvider</param-name> + <param-value>com.vaadin.cdi.CDIUIProvider</param-value> +</init-param> +.... + +This snippet tells Vaadin to use the same classpath scanning as it would +with Servlet annotations. + +So, thats it for the CDI part. What about JAAS? Well, we need to put +this in web.xml, so lets create the file and add some security +configuration: + +[source,xml] +.... +<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee + http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> + + <security-constraint> + <display-name>SecureApplicationConstraint</display-name> + <web-resource-collection> + <web-resource-name>SecureUI</web-resource-name> + <description>Only this UI is protected</description> + <url-pattern>/secure/*</url-pattern> + </web-resource-collection> + <auth-constraint> + <description>Only valid users are allowed</description> + <role-name>viewer</role-name> + </auth-constraint> + </security-constraint> + <login-config> + <auth-method>FORM</auth-method> + <realm-name>ApplicationRealm</realm-name> + <form-login-config> + <form-login-page>/login</form-login-page> + <form-error-page>/login</form-error-page> + </form-login-config> + </login-config> + <security-role> + <role-name>viewer</role-name> + </security-role> + +</web-app> +.... + +What happens is that the root of our application is not secured; here we +have an unsecured part of our application. When the user tries to access +the secure area under /secure, they get redirected to our login UI under +/login. Any attempt to go to /secure/ without logging in will be blocked +by the server. We also restrict that any user who tries to access the +application needs to have the 'viewer' role, or they won't get in. + +The secure UI and the login UI are added below: + +SecureUI: + +[source,java] +.... +@CDIUI("secure") +public class SecureUI extends UI { + + @Override + protected void init(VaadinRequest request) { + final VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + setContent(layout); + + layout.addComponent(new Label("This is a secure UI! Username is " + + request.getUserPrincipal().getName())); + } +} +.... + +The secure page isn’t anything special, but you can see how we access +the user name from the JAAS security context. + +LoginUI: + +[source,java] +.... +@CDIUI("login") +public class LoginUI extends UI { + + @Override + protected void init(VaadinRequest request) { + final VerticalLayout layout = new VerticalLayout(); + layout.setMargin(true); + setContent(layout); + + Button login = new Button("login"); + login.addClickListener(new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + try { + JaasAccessControl.login("demo", "demo"); + Page page = Page.getCurrent(); + page.setLocation(page.getLocation()); + } catch (ServletException e) { + // TODO handle exception + e.printStackTrace(); + } + } + }); + layout.addComponent(login); + } +} +.... + +The interesting parts are these: + +[source,java] +.... +JaasAccessControl.login("demo", "demo"); +Page page = Page.getCurrent(); +page.setLocation(page.getLocation()); +.... + +JaasAccessControl is a utility class from the Vaadin-CDI addon; we use +it to perform programmatic login. If the login succeeds, we refresh the +page the user is on. Why do we need to do this? Well, let’s consider why +the login page is visible. The user has tried to access /secure, but +isn’t logged in. Under the hood, the server realizes this, and serves +our login page instead without doing a proper redirect. This means the +users URL doesn’t change; it still says /secure. We refresh the page, +and since we are logged in, we get the real content of the secure UI. + +Now, we could do login with other technologies as well. If you have a +single-sign-on of some sort, you might want to use the JaasAccessControl +class to integrate that into your app. You can also do form-based JSP +login, as you would do in the olden days. The possibilities are truly +many here. If you do decide on using JSP, here are a couple of helpers +for you: + +Add the following into your login.jsp: + +[source,html] +.... +<!-- Vaadin-Refresh --> +.... + +Why is this line needed? To answer this I need to tell you what happens +when an application session times out. When Vaadin requests something +from the server, the server replies with something else. Typically +(without JAAS), it is a simple error message saying the session is +invalid. If we are using JAAS, however, what we get in the response from +the server is the login page HTML. Vaadin doesn't handle this too well; +it adds the HTML response to the red notification popup. To fix this, we +have added a feature to Vaadin that checks the HTML for a specific +keyword (you guessed it, 'Vaadin-Refresh'), and if it finds it, simply +reloads the complete page. You can also define a redirect url if you +want to, but we won't need it here since JAAS will redirect for us. So, +we add the comment to the JSP so that when a session timeouts, we want +to be redirected to the login page. + +The second thing (still in login.jsp) is this: + +[source,html] +.... +<meta http-equiv="refresh" content="${pageContext.session.maxInactiveInterval}"> +.... + +We add this line so that the login page itself doesn't timeout. Session +timeouts are active from the first access to the servlet; in our case +loading the login page. If the user doesn't fill in anything, and the +timer runs out, the user will get an ugly error message from the server. +To fix that we simply reload the page, extending the session (or +creating a new one). + +OK, with us so far? We still need a couple of things for JBoss to +understand what we want to do: + +I have a jboss-web.xml inside WEB-INF that tells JBoss which settings to +use: + +[source,xml] +.... +<jboss-web> + <security-domain>DBAuth</security-domain> +</jboss-web> +.... + +Then in the JBoss standalone.xml configuration file, I add the security +domain params: + +[source,xml] +.... +<security-domain name="DBAuth"> + <authentication> + <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required"> + <module-option name="dsJndiName" value="java:jboss/datasources/myappdb"/> + <module-option name="principalsQuery" value="select password from PRINCIPLES where principal_id=?"/> + <module-option name="rolesQuery" value="select user_role, 'Roles' from ROLES where principal_id=?"/> + </login-module> + </authentication> +</security-domain> +.... + +The domain that we specify tells the server where to find users and +passwords. In our case, they can be found in the PRINCIPLES table, with +roles added to the ROLES table. As you can see, you specify the SQL for +the query, so you have a lot of freedom in how you do this. Note that we +are not using any encryption or hashing for the passwords; please don't +use this configuration for real applications. Instead, you should use a +custom Login Module class that can compare hashes instead of pure +strings, and store salted hashes in your database. Implement your class +by extending the DatabaseServerLoginModule class and change the code +attribute in the login-module tag to point to your class instead. + +Then we need the data source (still in standalone.xml): + +[source,xml] +.... +<datasources> + <datasource jta="true" jndi-name="java:jboss/datasources/myappdb" pool-name="java:jboss/datasources/myappdb_pool" + enabled="true" use-java-context="true" use-ccm="true"> + <connection-url>jdbc:postgresql://localhost:5432/myappdb</connection-url> + <driver-class>org.postgresql.Driver</driver-class> + <driver>postgresql-jdbc4</driver> + <pool> + <min-pool-size>2</min-pool-size> + <max-pool-size>20</max-pool-size> + <prefill>true</prefill> + </pool> + <security> + <user-name>demo</user-name> + <password>demo</password> + </security> + <validation> + <check-valid-connection-sql>SELECT 1</check-valid-connection-sql> + <validate-on-match>false</validate-on-match> + <background-validation>false</background-validation> + <use-fast-fail>false</use-fast-fail> + </validation> + </datasource> + <drivers> + <driver name="postgresql-jdbc4" module="org.postgresql"/> + </drivers> +</datasources> +.... + +As you can see, I'm using a Postgres database. You will need the +postgres JDBC driver installed under the Wildfly modules directory for +this to work. And, of course an actual Postgres server with the +specified database created. In our application we use Hibernate with +container managed transactions to handle persistence; as this isn't a +JPA tutorial, so I'll leave that for another day. + +But, for completeness sake, here is a short SQL script for the DB. +Create a database named ‘myappdb’, and run this: + +[source,sql] +.... +CREATE USER demo WITH PASSWORD 'demo'; + +CREATE TABLE PRINCIPLES ( principal_id VARCHAR(64) primary key,password VARCHAR(64)); +CREATE TABLE ROLES ( role_item_id integer, principal_id VARCHAR(64),user_role VARCHAR(64)); + +Grant all privileges on table roles to demo; +Grant all privileges on table principles to demo; + +--Initial data +Insert into principles values ('demo', 'demo'); +insert into roles values (1, 'demo', 'viewer'); +.... + +The only thing left is to get the username and roles from inside your +Vaadin app: + +[source,java] +.... +@Override +protected void init(VaadinRequest request) { + String username = request.getUserPrincipal().toString(); + if (request.isUserInRole("viewer")) { + // Add admin view to menu + } +.... + +If you are using the CDI-based navigator, you can also use the +@RolesAllowed annotation on your views to automatically constrain +visibility of your views. + +That's it, your app will now use database authentication with JAAS and +CDI. The provided configuration isn't complete, and there are small +pieces I didn't really cover, but it will work for basic cases. Feel +free to add comments below. + +You might also check out +https://github.com/mstahv/vaadin-cdi-jaas-jbossas-example/[a related +full app example], that uses built in "FileRealm" in JBoss. diff --git a/documentation/articles/Vaadin7SpringSecurityBaseAuthentification.asciidoc b/documentation/articles/Vaadin7SpringSecurityBaseAuthentification.asciidoc new file mode 100644 index 0000000000..b43540bc29 --- /dev/null +++ b/documentation/articles/Vaadin7SpringSecurityBaseAuthentification.asciidoc @@ -0,0 +1,65 @@ +[[vaadin-7-spring-security-base-authentication]] +Vaadin 7 + Spring Security (base authentication) +------------------------------------------------ + +Vaadin 7 is easy to integrate with Spring Security. You should configure only +2 files. First - web.xml and second one spring-security.xml (user +credentials and security settings). This is a small example on how to use +base form for authentification. + +[[web.xml-configuration]] +web.xml Configuration +^^^^^^^^^^^^^^^^^^^^^ + +[source,xml] +.... +<?xml version="1.0" encoding="UTF-8"?> +<web-app xmlns:xsi="[[http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"id="WebApp_ID|http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"id="WebApp_ID]]" version="3.0"> + <display-name>Vaadin7SpringSecurity</display-name> + <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring/spring-security.xml </param-value></context-param> + <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener> + <!-- filter declaration for Spring Security --> + <filter> + <filter-name>springSecurityFilterChain</filter-name> + <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> + </filter> + <filter-mapping> + <filter-name>springSecurityFilterChain</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> +</web-app> +.... + +[[spring-security.xml]] +spring-security.xml +^^^^^^^^^^^^^^^^^^^ + +[source,xml] +.... +<beans:beans xmlns="http://www.springframework.org/schema/security" +xmlns:beans="http://www.springframework.org/schema/beans" +xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.2.xsd + http://www.springframework.org/schema/security + http://www.springframework.org/schema/security/spring-security-3.1.xsd"> + +<http auto-config='true'> + <intercept-url pattern="/*" access="ROLE_USER" /> +</http> + +<authentication-manager> + <authentication-provider> + <user-service> + <user name="user" password="password" authorities="ROLE_USER" /> + </user-service> + </authentication-provider> +</authentication-manager> + +</beans:beans> +.... + +For more details, how to extend *spring-security.xml* configuration you +can use +http://docs.spring.io/autorepo/docs/spring-security/3.0.x/reference/ns-config.html[Spring +resources]. diff --git a/documentation/articles/VaadinCDI.asciidoc b/documentation/articles/VaadinCDI.asciidoc new file mode 100644 index 0000000000..f8b7cf4c4b --- /dev/null +++ b/documentation/articles/VaadinCDI.asciidoc @@ -0,0 +1,68 @@ +[[vaadin-cdi]] +Vaadin CDI +---------- + +During these tutorials we will be solving a number of common problems +when using the https://vaadin.com/directory/component/vaadin-cdi[Vaadin CDI plugin]. +The principal question we will be addressing is "How do I gain access to +CDI features in a Vaadin project?" + +At the end of these tutorials you will have learned how to + +1. Set up a Vaadin CDI project + +2. Create UI's and Views with Vaadin CDI + +3. Use injection with standard and Vaadin CDI scopes + +4. Pass events between various parts of your application + +5. Set up access control for your application + +We will assume familiarity with Vaadin 7 and common CDI concepts. As a +reference development environment we'll be using +http://www.eclipse.org/downloads/[Eclipse] Luna with the +http://marketplace.eclipse.org/content/vaadin-plugin-eclipse[Vaadin +plugin], Maven and http://tomee.apache.org/apache-tomee.html[TomEE]. +Installation and configuration of said environment is outside the scope +of these tutorials. + +The tutorials will build off one another, each starting where the +previous left off. If you wish to jump in at a later point feel free to +get a copy of the project here: https://github.com/Vaadin/cdi-tutorial. +The repository has tags for each tutorial's starting point, called +tutorial-1 to tutorial-5. + +[[vaadin-cdi-for-the-impatient]] +Vaadin CDI for the impatient +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are eager to get started right away without following the +tutorial, feel free to do so. There are still some *practical details* +that are good to keep in mind if you're not familiar with +https://vaadin.com/directory/component/vaadin-cdi[Vaadin CDI features] or CDI in +general. + +* *Use http://tomee.apache.org/downloads.html[TomEE Web Profile]* or +some other JavaEE 6+ *server*. Tomcat or Jetty won't work out of the +box. +** *Add the http://mvnrepository.com/artifact/javax/javaee-api[JavaEE +API] to your classpath* if e.g. the @Inject annotation can't be +found. Depending on how your project is configured, this might not be +necessary and might in some cases even cause conflicts. + +* *Objects must be injected* and managed by the CDI implementation in +order to use CDI features. +** *Use @Inject on an instance field* to make the CDI implementation +inject a managed intstance of the corresponding type. +** *Annotate your UI class with @CDIUI("")* to let Vaadin CDI know that +it should inject and use instances of that class when the application is +opened in the browser. +** *Remove any existing VaadinServlet* from your project (look for +servlets with @WebServlet or defined in web.xml). Vaadin CDI has its own +VaadinCDIServlet that will be deployed automatically as long as no other +Vaadin servlets are present. +** *Initialize objects in a method annotated with @PostConstruct.* CDI +features such as @Inject are not yet functional when the constructor is +run. + +[[related-pages]] +Related pages +~~~~~~~~~~~~~ + +link:IIInjectionAndScopes.asciidoc[II - Injection and scopes] diff --git a/documentation/articles/VaadinOnGrailsCreateProjectInIntelliJIDEA.asciidoc b/documentation/articles/VaadinOnGrailsCreateProjectInIntelliJIDEA.asciidoc new file mode 100644 index 0000000000..a63200fa5e --- /dev/null +++ b/documentation/articles/VaadinOnGrailsCreateProjectInIntelliJIDEA.asciidoc @@ -0,0 +1,108 @@ +[[vaadin-on-grails-create-project-in-intellij-idea]] +Vaadin on grails - Create project in IntelliJ IDEA +-------------------------------------------------- + +_Versions used in this tutorial: Grails 2.3.x, Vaadin 7.1.x. News and +updates about Vaadin on Grails are available on +https://twitter.com/VaadinOnGrails[VaadinOnGrails]_ + +In this tutorial we will show how to create and setup +http://grails.org/doc/latest/guide/single.html[Grails] project together +with https://vaadin.com/learn[Vaadin], in IntelliJ IDEA. These two +frameworks Grails and Vaadin are integrated together with this +http://grails.org/plugin/vaadin[plugin]. + +First we need to setup Grails with IDE, +http://www.jetbrains.com/idea/[IntelliJ IDEA 13] in this tutorial. Or we +could get https://spring.io/tools/ggts[Groovy/Grails Tool Suite] +which is without any fees. + +[[setup-ide]] +Setup IDE ++++++++++ + +1. Go to http://grails.org/download and get the latest version of +Grails +2. Unpack it on your local computer and start up IntelliJ IDEA +3. Open the *New Project* window and select Grails from the list. Click +on *Create...* button and then select the root of your unpacked Grails +archive. + +image:http://vaadinongrails.com/img/setup-idea.png[Setup IDEA] + +[[create-new-project]] +Create New Project +++++++++++++++++++ + +Fill in the name of the project and choose the latest version of Grails. + +image:http://vaadinongrails.com/img/new-project.png[Create new project] + +Click on *Finish* and on the next dialog, choose **Run 'create +app'** + +image:http://vaadinongrails.com/img/create-app-idea.png[Create app IDEA] + +Open file `BuildConfig.groovy` and add new plugin as follows: +`compile ":vaadin:7.1.11"`. The latest version of the plugin is always +available on http://grails.org/plugin/vaadin + +image:http://vaadinongrails.com/img/build-config-idea.png[Build config IDEA] + +We have to disable Grails to take control over the URLs, so Vaadin can +do it. Open `UrlMappings.groovy` file and remove the mapping, so the +content of the file is the following: + +.... +class UrlMappings { + static mappings = {} +} +.... + +If you see this error, run grails again and compile it again. There an +issue with grails and it is not possible to compile the project in the +first run. + +.... +target/work/plugins/vaadin-7.1.11/scripts/_Events.groovy: 1: unable to resolve class com.vaadin.grails.VaadinConfiguration @ line 1, column 1. import com.vaadin.grails.VaadinConfiguration +.... + +Now we want Vaadin plugin to generate the mandatory files in order to be +able to run Vaadin application. On the right side in the menu, there is +a launch button, click on that one. + +image:http://vaadinongrails.com/img/run-app-idea.png[Run app IDEA] + +You can also press `Alt+Cmd+G` on Mac OS or `Ctrl+Alt+G` on Windows and +type `run-app` command there. + +Mark `grails-app/vaadin` folder as a source folder in IDE. + +image:http://vaadinongrails.com/img/source-folder-idea.png[Source folder IDEA] + +Run the application again and a Vaadin application with a single *Home* +label will be available on http://localhost:8080/ria-app in your +browser. + +image:http://vaadinongrails.com/img/first-run.png[First run] + +[[developing]] +Developing +++++++++++ + +All the Vaadin code should be placed in `grails-app/vaadin` folder. All +the other classes that are not UI related can be put to `src/groovy` or +`src/java`. + +Open the generated `MyUI.groovy` file, edit the content a bit and save +the file. See that the class has been recompiled and when you refresh +the page, it gets automatically updated. You don't have to restart the +application or use JRebel for small changes. But if we change the +application context, add a new bean, change method signature and other +'big' changes, we have to restart the application. + +image:http://vaadinongrails.com/img/recompile-idea.png[Recompile IDEA] + +Now you can continue with +link:VaadinOnGrailsDatabaseAccess.asciidoc[Vaadin +on Grails - Database access] diff --git a/documentation/articles/VaadinOnGrailsDatabaseAccess.asciidoc b/documentation/articles/VaadinOnGrailsDatabaseAccess.asciidoc new file mode 100644 index 0000000000..db3c5ca9bb --- /dev/null +++ b/documentation/articles/VaadinOnGrailsDatabaseAccess.asciidoc @@ -0,0 +1,153 @@ +[[vaadin-on-grails-database-access]] +Vaadin on grails - Database access +---------------------------------- + +_Versions used in this tutorial: Grails 2.3.x, Vaadin 7.1.x. News and +updates about Vaadin on Grails are available on +https://twitter.com/VaadinOnGrails[VaadinOnGrails]. This is continuation +of link:VaadinOnGrailsCreateProjectInIntelliJIDEA.asciidoc[Vaadin +on Grails - Create project in IntelliJ IDEA]_ + +We are going to create persistence a domain class that is automatically +mapped into a database through Hibernate. GORM from Grails framework +will do most of the automatic stuff there. + +[[create-persistent-layer]] +Create persistent layer ++++++++++++++++++++++++ + +1. Right click on the domain folder and select Create Domain +image:http://vaadinongrails.com/img/idea-new-domain.png[IDEA create domain class] +2. Fill in the full class name. If we put there only `Author` then the +package name will be automatically added based on the name of the +project. +image:http://vaadinongrails.com/img/idea-domain-name.png[IDEA domain name] +3. Add some fields to the Author domain class. Without fields, it is +not considered as a domain object and therefore it is not mapped to the +database. Each field represents a database table column. + +.... +package com.app + +class Author { + String name + static constraints = {} +} +.... + +[[service-layer]] +Service layer ++++++++++++++ + +1. Create a new service in the same way as the domain object. +image:http://vaadinongrails.com/img/idea-service-new.png[IDEA new Grails Service] +2. Create `getAuthors` method that returns list of authors from the +database. + +.... +package com.app +import grails.transaction.Transactional + +@Transactional +class AuthorService { + List<Author> getAuthors() { + return Author.list() + } +} +.... + +[[data-for-development-in-application-bootstrap]] +Data for development in application bootstrap ++++++++++++++++++++++++++++++++++++++++++++++ + +When the application starts, we want to create few authors in the +database. So we get some initial data for easy development. Open +`BootStrap.groovy` and save few authors into the database. + +The code will be executed only in development environment. That means no +database records will be created when running application on test, +production or any other environment. + +.... +import com.app.Author +import grails.util.Environment + +class BootStrap { + + def init = { + servletContext -> if (Environment.current == + Environment.DEVELOPMENT) { + Author raymond = new Author(name: 'Raymond') + raymond.save(failOnError: true) + + Author pug = new Author(name: 'Pug') + pug.save(failOnError: true) + } + } + + def destroy = { } +} +.... + +[[using-service-in-vaadin-code]] +Using service in Vaadin code +++++++++++++++++++++++++++++ + +Now we are ready to get bean of `AuthorService` in Vaadin code and get +the list of authors from the database. Open `MyUI.groovy` and put there +the following code. + +.... +package app + +import com.app.Author +import com.app.AuthorService +import com.vaadin.ui.UI +import com.vaadin.ui.VerticalLayout +import com.vaadin.server.VaadinRequest +import com.vaadin.ui.Label +import com.vaadin.grails.Grails + +class MyUI extends UI { + @Override + protected void init(VaadinRequest request) { + VerticalLayout layout = new VerticalLayout() + layout.setMargin(true) + AuthorService authorService = Grails.get(AuthorService) + + List<Author> authors = authorService.getAuthors() + + for (Author author : authors) { + Label label = new Label(author.name) + layout.addComponent(label) + } + + setContent(layout) + } +} +.... + +Run the application and see the results fetched from the database. We +have created domain object `Author` which is mapped to automatically +created database table. Then we have created `AuthorService` that we +have got from Spring application context. We have loaded authors from +the database and displayed them in Vaadin application as labels. +image:http://vaadinongrails.com/img/data-from-service.png[Data from service] + +[[database-connection]] +Database connection ++++++++++++++++++++ + +As we have covered big part of normal work when developing an +application, created the persistance layer, maybe next questions are +coming. Where are the data stored? What database is used and how to +change it? + +Grails is using H2 in memory database by default and you can change it +to any other database that is supported by Hibernate. In order to update +database connection setup, open `DataSource.groovy`. Just add there the +proper parameters and add the dependency to, for example, MySql driver +in `BuildConfig.groovy`. +image:http://vaadinongrails.com/img/idea-datasource.png[IDEA data source] + +You can continue with link:VaadinOnGrailsMultipleUIs.asciidoc[Vaadin on Grails - Multiple UIs] diff --git a/documentation/articles/VaadinOnGrailsMultipleUIs.asciidoc b/documentation/articles/VaadinOnGrailsMultipleUIs.asciidoc new file mode 100644 index 0000000000..3e42e650e3 --- /dev/null +++ b/documentation/articles/VaadinOnGrailsMultipleUIs.asciidoc @@ -0,0 +1,52 @@ +[[vaadin-on-grails-multiple-uis]] +Vaadin on grails - multiple UIs +------------------------------- + +_Versions used in this tutorial: Grails 2.3.x, Vaadin 7.1.x. News and +updates about Vaadin on Grails are available on +https://twitter.com/VaadinOnGrails[VaadinOnGrails]. This is continuation +of link:VaadinOnGrailsDatabaseAccess.asciidoc[Vaadin on Grails - Database access]_ + +In `grails-app/conf/VaadinConfig.groovy`, we can change URL mapping to +UI. Also, we can define multiple UIs to be accessible from one Grails +application. + +Create two new UIs that we will show under different URLs. `ClientUI` +will be available on http://localhost:8080/client and `ServerUI` on +http://localhost:8080/server + +[source,java] +.... +class ClientUI extends UI { + protected void init(VaadinRequest request) { + VerticalLayout layout = new VerticalLayout() + layout.setMargin(true) + Label label = new Label("Client") + layout.addComponent(label) + setContent(layout) + } +} + +class ServerUI extends UI { + protected void init(VaadinRequest request) { + VerticalLayout layout = new VerticalLayout() + layout.setMargin(true) + Label label = new Label("Server") + layout.addComponent(label) setContent(layout) + } +} +.... + +Open `VaadinConfig.groovy` and change the mapping, so the mapping points +to the new UIs. + +.... +mapping = +[ "/client/*": "app.ClientUI", + "/server/*": "app.ServerUI"] +.... + +Now we can start up the application and access both URLs to see each is +mapped to different UI class. + +image:http://vaadinongrails.com/img/mapping-uis.png[Mapping UIs] diff --git a/documentation/articles/VaadinScalabilityTestingWithAmazonWebServices.asciidoc b/documentation/articles/VaadinScalabilityTestingWithAmazonWebServices.asciidoc new file mode 100644 index 0000000000..0f5261ecdc --- /dev/null +++ b/documentation/articles/VaadinScalabilityTestingWithAmazonWebServices.asciidoc @@ -0,0 +1,209 @@ +[[vaadin-scalability-testing-with-amazon-web-services]] +Vaadin scalability testing with Amazon Web Services +--------------------------------------------------- + +This article explains how you can test the scalability of your +application in the Amazon Web Services (AWS) cloud. The AWS services +used in this article include http://aws.amazon.com/ec2/[Amazon Elastic +Compute Cloud] (EC2) and http://aws.amazon.com/rds/[Amazon Relational +Database Service] (RDS). The use of +http://aws.amazon.com/elasticloadbalancing/[Amazon Elastic Load +Balancing] (ELB) is also briefly discussed. The application under +testing is called QuickTickets, a fictional Vaadin web application that +sells movie tickets to theaters all over the world. See also the +https://vaadin.com/blog/vaadin-scalability-study-quicktickets[blog +post about the experiment and the results]. + +To fully understand this article and follow through the steps, you +should have some basic knowledge of Amazon Web Services (AWS), +http://jakarta.apache.org/jmeter/[Apache JMeter], MySQL and Linux shell +usage. You will also need to know how to checkout the +http://dev.vaadin.com/svn/incubator/QuickTickets/trunk/[QuickTickets +project] from SVN and run http://ant.apache.org/[Ant] targets to +generate test database and to package the application as a WAR file. + +Please notice, that using the AWS services discussed here will incur +some expenses. + +[[setting-up-the-amazon-rds-database]] +1. Setting up the Amazon RDS database +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Login to http://aws.amazon.com/console/[AWS Management Console] and +select the Amazon RDS tab. + +* Click the Launch DB Instance button. + +* Select the following properties for the DB instance: +** DB Instance Class: db1.m1.large +** Multi-AZ Deployment: No +** Allocated Storage: 5 GB +** DB Instance Idenfitier: `quicktickets` +** Master User Name: `quicktickets` +** Master User Password: `<your-password>` +* Additional configuration: +** Database Name: `quicktickets` +* Management options: +** Backup Retention Period: 0 (disable backups) + +* After the DB instance is started up, connect to the database with the +MySQL client. +** If this is the first time you are using Amazon RDS, you need to setup +the DB Security Groups. +** More information about http://aws.amazon.com/rds/faqs/#31[network +access to you DB instances]. + +* Once you have connected to the DB, run the following +command: `alter database quicktickets charset=utf8;` + +* Note that the following steps might be a bit faster to do in an EC2 +instance in the same zone as the RDS database. But you can of course do +these in your local machine as well. + +* Take a checkout of the QuickTickets application project from the +http://dev.vaadin.com/svn/incubator/QuickTickets/trunk/application/QuickTickets/[SVN +repository]. + +* Create the database schema by running the +http://dev.vaadin.com/svn/incubator/QuickTickets/trunk/application/QuickTickets/db/createSchema.sql[QuickTickets/db/createSchema.sql] +file to the quicktickets +database.`mysql -uquicktickets -p<your-password> -h<db-instance-endpoint>.rds.amazonaws.com < QuickTickets/db/createSchema.sql` + +* Create a huge test data by running Ant target +`create-huge-database-script` of the +http://dev.vaadin.com/svn/incubator/QuickTickets/trunk/application/QuickTickets/build.xml[QuickTickets/build.xml] +script.`cd QuickTicketsant create-huge-database-script` + +* This target will generate a huge SQL file (500MB) into a temporary +directory containing loads of test data. The location of the file is +printed to the console by the Ant target. + +* Run the resulting `quickticketsdata.sql` file to the quicktickets +database (this will take quite a while, well over an +hour). `mysql -uquicktickets -p<your-password> -h<db-instance-endpoint>.rds.amazonaws.com < /tmp/quickticketsdata.sql` + +[[setting-up-ec2-instances-for-quicktickets]] +2. Setting up EC2 instance(s) for QuickTickets +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Login to http://aws.amazon.com/console/[AWS Management Console] and +select the Amazon EC2 tab. + +* Click the Launch Instance button. + +* Select Community AMIs tab and search an AMI with following id: +`ami-fb16f992` + +* Launch a large instance of the AMI. Consult the +http://aws.amazon.com/documentation/ec2/[Amazon EC2 documentation] for +more details on how to launch a new instance. + +* Login to the started instance as root via SSH. + +* Copy and execute the +http://dev.vaadin.com/svn/incubator/QuickTickets/trunk/installationscripts/webserver-memcached.sh[webserver-memcached.sh] +installation script as the root user. This script will setup +http://memcached.org/[Memcached] and http://tomcat.apache.org/[Apache +Tomcat]. + +* Repeat the above procedure for all the instances you want to setup. + +[[deploying-the-quicktickets-application]] +3. Deploying the QuickTickets application +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Take a checkout of the QuickTickets application project (if you +haven't already) from: +** http://dev.vaadin.com/svn/incubator/QuickTickets/trunk/application/QuickTickets/ + +* Add the *Private DNS* of all the instances you have setup to the list +of Memcached servers in the `WebContent/WEB-INF/servers.xml` file with +the default Memcached port 11211. For +example: `<!-- Memcached servers --><value>ip-11-111-11-111.ec2.internal:11211</value><value>ip-22-222-22-222.ec2.internal:11211</value>...` + +* Create a file called `build.properties` to the root directory of the +project (right next to the `QuickTickets/build.xml`). Set the +`productionMode` property to `true` and add your Amazon RDS database +configuration details to the file. For example: + +.... + +# Debug or production mode? +productionMode=true + +# Database configuration +database.url=jdbc:mysql:<db-instance-endpoint>.rds.amazonaws.com:3306/quicktickets?characterEncoding=utf8&useCompression=true +database.username=quicktickets +database.password=<your-password-here> +database.driverClassName=com.mysql.jdbc.Driver +.... + +* Run the `package-war` target of the `build.xml` to compile and package +the WAR file (resulting in the `build/ROOT.war` file).`ant package-war` + +* Deploy the WAR file into all EC2 instances you just created by copying +the `ROOT.war` into `/opt/apache-tomcat/webapps` directory. + +* Now you should be able to access the application through your web +browser by opening the following +URL: `http://<instance-public-dns>.amazonaws.com:8080/app` + +* If you did setup more than one instance, you could create an Amazon +ELB load balancer and attach all instances to that load balancer. +However, this makes the JMeter testing close to impossible as the ELB +doesn't scale to sudden increases in traffic fast enough and starts +dropping connections. +** More information: +https://forums.aws.amazon.com/thread.jspa?messageID=130622&tstart=0 +** More information: +https://wiki.apache.org/jmeter/JMeterAndAmazon + +* If you still want to try using ELB, you should add +`-Dsun.net.inetaddr.ttl=0` to the JMeter JVM args and use the following +settings with the ELB: +** Port Configuration: 80 forwarding to 8080 +** Enable Application Generated Cookie Stickiness for cookie name: +`jsessionid` +** Set the Health Check port to `8080` +** Ping Path: `/VAADIN/ticket.html` + +[[setting-up-ec2-instances-for-jmeter]] +4. Setting up EC2 instance(s) for JMeter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Launch and login to a new EC2 large instance (using the AMI +`ami-fb16f992`). See the first 5 steps of the second chapter. + +* Copy and execute the +http://dev.vaadin.com/svn/incubator/QuickTickets/trunk/installationscripts/jmeter-instance.sh[jmeter-instance.sh] +installation script as the root user. + +* Download the +http://dev.vaadin.com/svn/incubator/QuickTickets/trunk/installationscripts/jmeter-test-script.jmx[JMeter +script]. +** The script contains prerecorded ticket purchase sequence that lasts +about 2.5 minutes. + +* Open the script in JMeter and make sure you configure the following +settings to suit your test: +** HTTP Request Defaults (set the server name) +** Thread Group (thread count, ramp-up, loop count) +** Summary report (result file name) + +* Upload the test script to the JMeter instance(s). + +* When logged in as root to the JMeter server you can start the test +from command line with the following +command: `~/jakarta-jmeter-2.4/bin/jmeter.sh -n -t ~/jmeter-test-script.jmx` + +* After the run is complete you'll have `jmeter-results.jtl` file (or +the filename you used for the report) which you can open in JMeter for +analyzing the results. + +[[results]] +5. Results +^^^^^^^^^^ + +Jump directly to the results: +https://vaadin.com/blog/vaadin-scalability-study-quicktickets[blog +post about the experiment and the results]. diff --git a/documentation/articles/VaadinSpringTips.asciidoc b/documentation/articles/VaadinSpringTips.asciidoc new file mode 100644 index 0000000000..08932faae3 --- /dev/null +++ b/documentation/articles/VaadinSpringTips.asciidoc @@ -0,0 +1,20 @@ +[[vaadin-spring-tips]] +Vaadin Spring tips +------------------ + +[[vaadin-spring-boot-configuration-properties]] +Vaadin Spring (Boot) configuration properties +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Your project, if generated by `start.spring.io`, contains a configuration +file `src/main/resources/application.properties` or similar YAML +configuration file. It is a handy way to configure the VaadinServlet +that is automatically introduced by Vaadin Spring. Here are couple of +example which can be configured: + +* `vaadin.servlet.productionMode=true` +* `vaadin.servlet.heartbeatInterval=60` +* `vaadin.servlet.closeIdleSessions=true` + +For full list of available properties, see +https://github.com/vaadin/spring/blob/master/vaadin-spring-boot/src/main/java/com/vaadin/spring/boot/internal/VaadinServletConfigurationProperties.java[VaadinServletConfigurationProperties]. diff --git a/documentation/articles/ValoExamples.asciidoc b/documentation/articles/ValoExamples.asciidoc new file mode 100644 index 0000000000..d8eaa78a2c --- /dev/null +++ b/documentation/articles/ValoExamples.asciidoc @@ -0,0 +1,237 @@ +[[valo-examples]] +Valo examples +------------- + +These are some sample looks for the Valo theme. You can use these as-is, +as a starting point, or while learning Valo. + +[[blueprint]] +Blueprint +--------- + +[source,scss] +.... +$v-app-loading-text: "Blueprint Valo"; +$v-background-color: #1a61b7; +$v-focus-color: #fff; +$v-panel-background-color: $v-background-color; +$v-overlay-background-color: $v-background-color; +$valo-menu-background-color: $v-background-color; +$v-overlay-shadow: 0 0 0 1px rgba(#fff, .5); +$v-window-shadow: $v-overlay-shadow; +$v-window-modality-curtain-background-color: $v-background-color; +$v-bevel: false; +$v-gradient: false; +$v-shadow: false; +$v-textfield-bevel: false; +$v-textfield-shadow: false; +$v-border: 1px solid (v-tint 1.5); +$v-textfield-background-color: $v-background-color; +$v-font-family: sans-serif; +$v-font-size: 18px; + +@import "../valo/valo"; +.... + +[[dark]] +Dark +---- + +[source,scss] +.... +$v-app-loading-text: "Dark Valo"; +$v-background-color: #444d50; +$v-focus-color: #07a9ca; +$v-focus-style: 0 0 3px 2px $v-focus-color; +$v-bevel-depth: 40%; +$v-gradient: v-linear 12%; +$v-border-radius: 10px; +$v-font-family: Roboto, sans-serif; +$v-font-weight: 400; +$v-font-weight--header: 400; +$v-bevel: inset 0 1px 2px v-tint, inset 0 0 1px (v-tint 0.1); +$v-shadow: 0 0 0 3px rgba(0,0,0,0.32), 0 1px 0 3px rgba(255,255,255,0.14); +$v-textfield-bevel: inset 0 2px 2px v-shade; +$v-textfield-shadow: $v-shadow; +$v-unit-size: 40px; +$v-overlay-shadow: 0 0 0 3px (v-shade 8), 0 5px 10px (v-shade 4); +$v-component-group-spacing: 6px; + +@import "../valo/valo"; +.... + +[[facebook]] +Facebook +-------- + +[source,scss] +.... +$v-app-loading-text: "Facebook Valo"; +$v-background-color: #fafafa; +$v-app-background-color: #e7ebf2; +$v-panel-background-color: #fff; +$v-focus-color: #3b5998; +$v-focus-style: 0 0 1px 1px rgba($v-focus-color, .5); +$v-border-radius: 3px; +$v-textfield-border-radius: 0; +$v-font-family: Helvetica, Arial, 'lucida grande', tahoma, verdana, arial, sans-serif; +$v-font-size: 14px; +$v-font-color: #37404E; +$v-font-weight: 400; +$v-link-text-decoration: none; +$v-shadow: 0 1px 0 (v-shade 0.2); +$v-bevel: inset 0 1px 0 v-tint; +$v-unit-size: 30px; +$v-gradient: v-linear 12%; +$v-overlay-shadow: 0 3px 8px v-shade, 0 0 0 1px (v-shade 0.7); +$v-shadow-opacity: 20%; +$v-selection-overlay-padding-horizontal: 0; +$v-selection-overlay-padding-vertical: 6px; +$v-selection-item-border-radius: 0; + +@import "../valo/valo"; +.... + +[[flat]] +Flat +---- + +[source,scss] +.... +$v-app-loading-text: "Flat Valo"; + +$v-font-family: "Roboto", sans-serif; +$v-font-weight: 400; +$v-font-weight--header: 400; +$v-background-color: #fff; +$v-focus-color: rgb(150,190,90); +$v-luminance-threshold: 180; +$v-border: 2px solid v-shade; +$v-border-radius: 6px; +$v-bevel: false; +$v-gradient: false; +$v-shadow: false; +$v-textfield-bevel: false; +$v-textfield-shadow: false; +$v-link-text-decoration: false; +$v-selection-overlay-padding-horizontal: 0; +$v-selection-overlay-padding-vertical: 6px; +$v-selection-item-height: 30px; +$v-selection-item-border-radius: 0; +$valo-menu-background-color: #eee; + +@import "../valo/valo"; + +.valo-test { + .v-button-primary.v-button-primary { + background: #fff; + border-color: $v-focus-color; + color: $v-focus-color; + } + + .v-button-danger.v-button-danger { + background: #fff; + border-color: $v-error-indicator-color; + color: $v-error-indicator-color; + } + + .v-slider-base:before, + .v-slider-base:after { + border: none !important; + } +} +.... + +[[flat-dark]] +Flat Dark +--------- + +[source,scss] +.... +$v-app-loading-text: "Dark & Flat Valo"; + +$v-background-color: #000; +$v-focus-color: #ffa500; +$v-font-size: 15px; +$v-font-weight: 600; +$v-unit-size: 42px; +$v-bevel: false; +$v-shadow: false; +$v-gradient: false; +$v-textfield-bevel: false; +$v-textfield-shadow: false; +$v-border-radius: 0; +$v-border: 2px solid v-tone; +$v-overlay-shadow: 0 0 0 2px (v-tint 10); +$v-focus-style: $v-focus-color; +$v-font-family: "Lato", sans-serif; +$v-font-weight--header: 600; + +@import "../valo/valo"; +.... + +[[light]] +Light +----- + +[source,scss] +.... +$v-app-loading-text: "Light Valo"; + +$v-background-color: hsl(0, 0, 99.5%); +$v-app-background-color: #fff; +$v-focus-color: hsl(218, 80%, 60%); +$v-border: 1px solid (v-shade 0.6); +$v-border-radius: 3px; +$v-bevel: inset 0 1px 0 v-tint; +$v-textfield-bevel: false; +$v-gradient: v-linear 3%; +$v-shadow: false; +$valo-menu-background-color: hsl(218, 20%, 98%); +$v-friendly-color: hsl(163, 61%, 41%); +$v-error-indicator-color: hsl(349, 66%, 56%); + + +@import "../valo/valo"; + +.tests-valo-light .valo-menu .valo-menu-title { + background: $v-app-background-color; + color: $v-selection-color; + text-shadow: none; + border-color: first-color(valo-border($color: $v-app-background-color, $strength: 0.5)); +} +.... + +[[metro]] +Metro +----- + +[source,scss] +.... +$v-app-loading-text: "Metro Valo"; + +$v-font-family: "Source Sans Pro", sans-serif; +$v-app-background-color: #fff; +$v-background-color: #eee; +$v-focus-color: #0072C6; +$v-focus-style: 0 0 0 1px $v-focus-color; +$valo-menu-background-color: darken($v-focus-color, 10%); +$v-border: 0 solid v-shade; +$v-border-radius: 0px; +$v-bevel: false; +$v-gradient: false; +$v-shadow: false; +$v-textfield-bevel: false; +$v-textfield-shadow: false; +$v-textfield-border: 1px solid v-shade; +$v-link-text-decoration: none; +$v-overlay-shadow: 0 0 0 2px #000; +$v-overlay-border-width: 2px; // For IE8 +$v-window-shadow: $v-overlay-shadow; +$v-selection-overlay-background-color: #fff; +$v-selection-overlay-padding-horizontal: 0; +$v-selection-overlay-padding-vertical: 6px; +$v-panel-border: 2px solid v-shade; + +@import "../valo/valo"; +.... diff --git a/documentation/articles/ValoThemeGettingStarted.asciidoc b/documentation/articles/ValoThemeGettingStarted.asciidoc new file mode 100644 index 0000000000..1cf9214dff --- /dev/null +++ b/documentation/articles/ValoThemeGettingStarted.asciidoc @@ -0,0 +1,182 @@ +[[valo-theme-getting-started]] +Valo theme - Getting started +---------------------------- + +To create your own variation of the Valo theme, start by creating a new +custom theme for your project. See +the link:CreatingAThemeUsingSass.asciidoc[Creating a theme using Sass] +tutorial to get that done. + +Change your theme import and include from Reindeer to Valo: + +[source,scss] +.... +@import "../valo/valo"; + +.my-theme { + @include valo; +} +.... + +To modify the theme outlook, define any of the global Sass variables +before the import statement: + +[source,scss] +.... +$v-background-color: #777; + +@import "../valo/valo"; +... +.... + +See below for possible variables that you can adjust. There are also +component specific variables, but those are not yet documented anywhere +(at the time of writing). See the corresponding component Sass source +files in the Vaadin Git repo, they are documented at the top of the +files. + +[[main-global-variables-in-valo]] +Main global variables in Valo +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* *$v-background-color* +** The main color of the theme, which is used for computing all other +colors in the theme (you can override those computed defaults). Can be +any CSS color value. +* *$v-app-background-color* +** The background color of the application (think of the BODY element). +Used for calculating some other component background colors. Can be any +CSS color value. +* *$v-app-loading-text* +** A static text which is shown under the initial loading spinner when +the application assets are loaded by the browser. The default is an +empty string. Provide any other value using quotes, e.g. + +`$v-app-loading-text: "Loading Resources...";` + +* *$v-line-height* +** The base line-height for all text in the theme. Should be specified +as a unitless number, e.g. + +`$v-line-height: 1.6;` + +* *$v-font-size* +** The base font size for all text in the theme. Should be specified as +a pixel integer value, e.g. + +`$v-font-size: 18px;` +* *$v-font-weight* +** The base font weight for all text in the theme. Should be specified +as numeric value, e.g. + +`$v-font-weight: 400;` +* *$v-font-color* +** The base font color for all text in the theme. Can be any CSS color +value. Computed by default. +* *$v-font-family* +** The base font family for all text in the theme. The theme comes +bundled with a few web fonts that you can easily use: Open Sans +(default), Roboto, Lato, Lora and Source Sans Pro. Just specifying any +of them in the $v-font-family will load the necessary font files also, +e.g. + +`$v-font-family: "Source Sans Pro", sans-serif;` + +* *$v-unit-size***** +** The base sizing unit for various measures in the theme. The unit-size +is for instance directly mapped to the height of the buttons, as well as +to the layout margins. Must be specified as integer pixel value, e.g. + +`$v-unit-size: 40px;` +* *$v-layout-margin-top, + +$v-layout-margin-right, + +$v-layout-margin-bottom, + +$v-layout-margin-left* +** The margin size for all built-in layouts. Can be any CSS width +value. + +* *$v-layout-spacing-vertical, + +$v-layout-spacing-horizontal* +** The spacing size for all built-in layouts. Can be any CSS width +value. +* *$v-border* +** The base border definition for all components (though not all +components have a border). Must be a valid CSS border shorthand, e.g. + +`$v-border: 2px solid #ddd;` +** The color can be defined as a special keyword for Valo (v-tint, +v-shade or v-tone). See the section about color keywords for details. +* *$v-border-radius* +** The base border radius for all components. Must be specified as a +pixel integer value, e.g. + +`$v-border-radius: 8px;` +* *$v-gradient* +** The gradient style for all components. This is not a CSS gradient, +but a Valo specific syntax for specifying the gradient type and the +gradient opacity, e.g. + +`$v-gradient: v-linear 20%;` +** Only two gradient types are supported (at the time of writing): +`v-linear` and `v-linear-reverse` +** The opacity must be specified as a percentage value from 0% to 100%. +* *$v-bevel* +** The bevel style for all components. The bevel defines how the +components "lift-up" from the background of the application, as a sort +of a 3D effect. The syntax is the same as for CSS box-shadow, but only +inset shadows should be used. You can define as many bevels +(box-shadows) as you wish. E.g. `$v-bevel: inset 0 2px 2px 0 #fff, inset +0 -2px 2px 0 #ddd;` +** You can also use the color keywords (v-tint, v-shade, v-tone: see +below) in place of the colors to make the bevel adapt to any color to +which it is applied to (e.g. different colored buttons). +* *$v-bevel-depth* +** The "depth" of the bevel effect, i.e. how evident the bevel effect +is, how much contrast it applies. Must be specified as a percentage +value from 0% to 100%. Only affects the color keywords. +* *$v-shadow* +** The main shadow style for all components. Very few components specify +have a shadow by default, and overlay elements define their own specific +shadow style. The syntax is the same as for $v-bevel, but without the +inset. +** You can use the v-tint or v-shade keywords for the color of the +shadows. v-tint is substituted with white and v-shade with black. +* *$v-shadow-opacity* +** The opacity of the shadow colors. Only affects the color keywords. +* *$v-focus-color* +** The color of the focus outline/border for focusable elements in the +application. Computed by default. Can be any CSS color value. +* *$v-focus-style* +** The style of the focus outline for focusable elements in the +application. The syntax is the same as for CSS box-shadow, e.g. + +`$v-focus-style: 0 0 0 2px orange;` +** You can also specify it to just a color value, in which case only the +border color of the components is affected, and no other outline is +drawn. E.g. `$v-focus-style: orange;` +* *$v-selection-color* +** The color for any selection highlights in the application (such as +selected items in a Table or ComboBox). Defaults to $v-focus-color. Can +be any CSS color value. +* *$v-error-indicator-color* +** The color for error indicators, and any error related styles in the +application (such as the error style Notification). Can be any CSS color +value. + +[[color-keywords]] +Color Keywords +~~~~~~~~~~~~~~ + +Valo offers three custom color keywords which you can use with +$v-border, $v-bevel and $v-shadow in place of a regular CSS color value: +*v-tint*, *v-shade* and *v-tone*. The keywords work in the following +way: + +* v-tint will be lighter version of the color it is applied on +* v-shade will be a darker version of the color it is applied on +* v-tone depends on the luminance value of the color on which it is +applied on: +** If the color is dark, then the resulting color will be a lighter +version of that same color +** If the color is light, then the resulting color will be darker +version of that same color + +The keywords can optionally be weighted with additional numeric values, +if you wish to fine tune the end result. Examples: + +* `$v-border: 1px solid v-shade;` +* `$v-border: 2px solid (v-tint 2);` +* `$v-border: 1px solid (v-tone 0.5);` + +[[additional-style-names]] +Additional Style Names +~~~~~~~~~~~~~~~~~~~~~~ + +Use the `ValoTheme` Java class for additional style names for many +components. diff --git a/documentation/articles/ViewChangeConfirmations.asciidoc b/documentation/articles/ViewChangeConfirmations.asciidoc new file mode 100644 index 0000000000..5beda623eb --- /dev/null +++ b/documentation/articles/ViewChangeConfirmations.asciidoc @@ -0,0 +1,220 @@ +[[view-change-confirmations]] +View change confirmations +------------------------- + +The `Navigator` API provides ways to prevent the user from navigating away +from a view in some cases, usually when the view has some unsaved +changes. We'll make a simple example that does just that (and only +that). + +We'll create our simple `SettingsView` later, because it has the actual +meat of this example. Let's set up the basic stuff first, our UI and our +`MainView`. + +UI: + +[source,java] +.... +import com.vaadin.navigator.Navigator; +import com.vaadin.navigator.ViewChangeListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.UI; + +public class NavigationtestUI extends UI { + @Override + public void init(VaadinRequest request) { + // Create Navigator, make it control the ViewDisplay + Navigator navigator = new Navigator(this, this); + + // no fragment for main view + navigator.addView(MainView.NAME, new MainView(navigator)); + + // #settings + navigator.addView(SettingsView.NAME, new SettingsView(navigator)); + } +} +.... + +Minimalistic. The only thing to notice is that we pass the `Navigator` to +the `SettingsView`, so that it can attach a listener and trigger +navigation. More on that when we actually create the `SettingsView`. + +Let's do the `MainView`: + +[source,java] +.... +import com.vaadin.navigator.View; +import com.vaadin.server.ExternalResource; +import com.vaadin.ui.Link; +import com.vaadin.ui.Panel; + +public class MainView extends Panel implements View { + + public static final String NAME = ""; + + public MainView(final Navigator navigator) { + Link lnk = new Link("Settings", new ExternalResource("#!" + + SettingsView.NAME)); + setContent(lnk); + } + + @Override + public void enter(ViewChangeEvent event) { + } +} +.... + +Yeah, really nothing to see here - we just create this so we can +navigate back and forth when trying it out. + +Now let's do the SettingsView, which has some more things going on in +order to make it fairly complete: + +[source,java] +.... +import java.util.Date; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.data.util.ObjectProperty; +import com.vaadin.navigator.Navigator; +import com.vaadin.navigator.View; +import com.vaadin.navigator.ViewChangeListener; +import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.DateField; +import com.vaadin.ui.InlineDateField; +import com.vaadin.ui.Layout; +import com.vaadin.ui.Notification; +import com.vaadin.ui.Notification.Type; +import com.vaadin.ui.Panel; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.Reindeer; + +public class SettingsView extends Panel implements View { + + public static String NAME = "settings"; + + Navigator navigator; + DateField date; + Button apply; + Button cancel; + + String pendingViewAndParameters = null; + + public SettingsView(final Navigator navigator) { + this.navigator = navigator; + Layout layout = new VerticalLayout(); + + date = new InlineDateField("Birth date"); + date.setImmediate(true); + layout.addComponent(date); + // pretend we have a datasource: + date.setPropertyDataSource(new ObjectProperty<Date>(new Date())); + date.setBuffered(true); + // show buttons when date is changed + date.addValueChangeListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + hideOrShowButtons(); + pendingViewAndParameters = null; + } + }); + + // commit the TextField changes when "Save" is clicked + apply = new Button("Apply", new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + date.commit(); + hideOrShowButtons(); + processPendingView(); + } + }); + layout.addComponent(apply); + + // Discard the TextField changes when "Cancel" is clicked + cancel = new Button("Cancel", new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + date.discard(); + hideOrShowButtons(); + processPendingView(); + } + }); + cancel.setStyleName(Reindeer.BUTTON_LINK); + layout.addComponent(cancel); + + // attach a listener so that we'll get asked isViewChangeAllowed? + navigator.addViewChangeListener(new ViewChangeListener() { + public boolean beforeViewChange(ViewChangeEvent event) { + if (event.getOldView() == SettingsView.this + && date.isModified()) { + + // save the View where the user intended to go + pendingViewAndParameters = event.getViewName(); + if (event.getParameters() != null) { + pendingViewAndParameters += "/"; + pendingViewAndParameters += event + .getParameters(); + } + + // Prompt the user to save or cancel if the name is changed + Notification.show("Please apply or cancel your changes", + Type.WARNING_MESSAGE); + + return false; + } else { + return true; + } + } + + public void afterViewChange(ViewChangeEvent event) { + pendingViewAndParameters = null; + } + }); + + setContent(layout); + } + + // Hide or show buttons depending on whether date is modified or not + private void hideOrShowButtons() { + apply.setVisible(date.isModified()); + cancel.setVisible(date.isModified()); + } + + // if there is a pending view change, do it now + private void processPendingView() { + if (pendingViewAndParameters != null) { + navigator.navigateTo(pendingViewAndParameters); + pendingViewAndParameters = null; + } + } + + @Override + public void enter(ViewChangeEvent event) { + hideOrShowButtons(); + } +} +.... + +First we set up a `DateField` with buffering and a (dummy) datasource to +make this work more as a real application would. With buffering on, the +value (date in this case) can be changed, but it will not be written to +the datasource before we `commit()`, which is what the Save -button does. +The Cancel -button does `discard()` on the DateField, which returns the +field to its unmodified state. + +The buttons do not need to be shown if nothing has changed, so we add a +`ValueChangeListener` to the `DateField` for that purpose. + +But the main thing that we're trying to demonstrate here happens in the +`ViewChangeListener` that we attach to the `Navigator`. There, if we're +about to change _away_ from our settings _and_ the date is changed but +_not_ saved, we'll make note of where the user wanted to go, but cancel +that navigation and prompt the user to save or cancel the changes. + +When the user saves or cancels changes, we also check if the user +previously tried to navigate away form the page, and sends him on his +way if that is the case. + +That is basically all there is to this. You'll notice we try to +carefully clear or set the 'pending view' and hide/show the buttons at +the right places to make the user happy, other than that this is pretty +straightforward. diff --git a/documentation/articles/VisuallyDistinguishPrimaryActions.asciidoc b/documentation/articles/VisuallyDistinguishPrimaryActions.asciidoc new file mode 100644 index 0000000000..e7fe9ee987 --- /dev/null +++ b/documentation/articles/VisuallyDistinguishPrimaryActions.asciidoc @@ -0,0 +1,96 @@ +[[visually-distinguish-primary-actions]] +Visually distinguish primary actions +------------------------------------ + +Most forms and dialogs have at least two actions that can be performed, +such as _Submit/Cancel_, _Save/Revert_ or _Yes/No_. Quite often, there +are more, such as a login form with the actions _“Sign in”_, +_“Register”_, and _“Forgot password”_. Usually, one of these actions is +by far the most commonly used, and as such, the most likely one the user +is going to be looking for. + +If all actions are represented by identical buttons (save for the +caption), identifying the primary button can be quite slow, and the risk +of selecting the wrong action by mistake (especially when in a hurry) is +substantial: + +image:img/sign%20in%20-%20no%20distinction.png[Sign in - no distinction] + +By visually distinguishing primary actions, e.g. by color, size or +shape, the user can quickly and accurately find them even in a crowded, +cluttered UI. A typical approach is to use a stronger (more saturated) +color with greater contrast for the primary actions, and a grayer, lower +contrast color for the secondary actions: + +image:img/sign%20in%20-%20primary%20distinguished.png[Sign in - distinguished] + +Sometimes a view can have more than one primary action simultaneously +available, although usually in different parts of the view. Google +handles this quite elegantly by systematically styling _creation_ +primary buttons (such as _Compose_ in Gmail and _Create_ in Drive) in +*red*, and other primary buttons (such as search) in *blue*, while +leaving secondary buttons *gray*: + +image:img/google%20drive.png[Google drive] + +Choose colors wisely, though – red, for instance, means _“no”_, _"stop"_ +or _“danger”_ in most cultures, so using that for _“Yes”_ or _“Submit”_ +might send the user mixed signals. You might also want to take into +account the effects of color blindness (affecting approximately 10% of +men and 1% of women), especially if your user base is going to be tens +of thousands of people. + +Setting a different visual style for primary action buttons is very easy +to do in Vaadin by using the *BUTTON_DEFAULT* stylename in any of the +built-in themes like Reindeer or Chameleon: + +[source,java] +.... +Button btnSignIn = new Button("Sign in"); +btnSignIn.addStyleName(Reindeer.BUTTON_DEFAULT); +.... + +Another common approach, mainly used on the web, is to use text links +instead of buttons for secondary or tertiary actions. This has a +significantly stronger effect than color or size, and should only be +used for significantly less common actions, such as a password reset +request, not for the _“No”_ option in a _Yes/No_ dialog, for instance: + +image:img/sign%20in%20-%20all%20different.png[Sign in - all different] + +This is just as easy in Vaadin. Just use the *BUTTON_LINK* stylename +defined in the base theme (and inherited in all built in themes), and +your Button will look like a normal text-hyperlink. + +[source,java] +.... +Button btnForgotPwd = new Button("Forgot password?"); +btnForgotPwd.addStyleName(Reindeer.BUTTON_LINK); +.... + +(Note that the separate *Link* component should not be used for server +actions, since you can't bind a ClickListener to a Link.) + +[[consider-binding-the-enter-key-to-the-primary-action]] +Consider binding the Enter key to the primary action +++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Especially in short, often used forms, such as a login form, it is +usually a good idea to bind the Enter key to the primary action. This +relieves the user from having to move his hand from the keyboard to the +mouse. + +[source,java] +.... +Button btnSignIn = new Button("Sign in"); +btnSignIn.addStyleName(Reindeer.BUTTON_DEFAULT); +btnSignIn.setClickShortcut(KeyCode.ENTER); +.... + +If the primary action is something that really mustn’t be invoked by +mistake or without properly thinking about it first, however, it’s +probably better not to bind it to a keyboard shortcut, to avoid +accidental invocations. Another reason to abstain from a keyboard +shortcut is if the form contains an input field in which Enter can be +used for something, such as a multi-line text area (where Enter creates +a line break). diff --git a/documentation/articles/WidgetStylingUsingOnlyCSS.asciidoc b/documentation/articles/WidgetStylingUsingOnlyCSS.asciidoc new file mode 100644 index 0000000000..609c8a566b --- /dev/null +++ b/documentation/articles/WidgetStylingUsingOnlyCSS.asciidoc @@ -0,0 +1,172 @@ +[[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; +} +.... diff --git a/documentation/articles/contents.asciidoc b/documentation/articles/contents.asciidoc index f0dc2f97b5..84cec17eed 100644 --- a/documentation/articles/contents.asciidoc +++ b/documentation/articles/contents.asciidoc @@ -21,7 +21,7 @@ are great, too. - link:SendingEmailFromJavaApplications.asciidoc[Sending email from Java applications] - link:OptimizingSluggishUI.asciidoc[Optimizing sluggish UI] - link:UsingParametersWithViews.asciidoc[Using parameters with views] -- link:ConfiguringPushForYourEnviroment.asciidoc[Configuring push for your environment] +- link:ConfiguringPushForYourEnvironment.asciidoc[Configuring push for your environment] - link:SettingAndReadingCookies.asciidoc[Setting and reading cookies] - link:UsingPolling.asciidoc[Using polling] - link:FindingTheCurrentUIAndPageAndVaadinSession.asciidoc[Finding the current UI and page and Vaadin Session] @@ -32,3 +32,63 @@ are great, too. - link:RememberToTheSetTheLocale.asciidoc[Remember to the set the locale] - link:MVCBasicsInITMillToolkit.asciidoc[MVC Basics in IT Mill Toolkit] - link:CustomizingTheStartupPageInAnApplication.asciidoc[Customizing the startup page in an application] +- link:UsingURIFragments.asciidoc[Using URI fragments] +- link:AccessingWebPageAndBrowserInformation.asciidoc[Accessing web page and browser information] +- link:GeneratingDynamicResourcesBasedOnURIOrParameters.asciidoc[Generating dynamic resources based on URI or parameters] +- link:OptimizingTheWidgetSet.asciidoc[Optimizing the widget set] +- link:UsingServerInitiatedEvents.asciidoc[Using server-initiated events] +- link:ChooseInputFieldComponentsWisely.asciidoc[Choose input field components wisely] +- link:CreatingASimpleComponent.asciidoc[Creating a simple component] +- link:IntegratingAnExistingGWTWidget.asciidoc[Integrating an existing GWT widget] +- link:IntegrationExperiences.asciidoc[Integration experiences] +- link:VaadinOnGrailsCreateProjectInIntelliJIDEA.asciidoc[Vaadin on grails - Create project in IntelliJ IDEA] +- link:VaadinOnGrailsDatabaseAccess.asciidoc[Vaadin on grails - Database access] +- link:VaadinOnGrailsMultipleUIs.asciidoc[Vaadin on grails - Multiple UIs] +- link:IntegratingAJavaScriptComponent.asciidoc[Integrating a JavaScript component] +- link:IntegratingAJavaScriptLibraryAsAnExtension.asciidoc[Integrating a JavaScript library as an extension] +- link:UsingAJavaScriptLibraryOrAStyleSheetInAnAddOn.asciidoc[Using a JavaScript library or a style sheet in an add-on] +- link:ExposingServerSideAPIToJavaScript.asciidoc[Exposing server-side API to JavaScript] +- link:UsingRPCFromJavaScript.asciidoc[Using RPC from JavaScript] +- link:IBGettingStartedWithVaadinSpringWithoutSpringBoot.asciidoc[I b - Getting started with Vaadin Spring withoout Spring Boot] +- link:Vaadin7SpringSecurityBaseAuthentification.asciidoc[Vaadin 7 + Spring Security (base authentication)] +- link:UsingBeanValidationToValidateInput.asciidoc[Using Bean Validation to validate input] +- link:VaadinSpringTips.asciidoc[Vaadin Spring tips] +- link:VaadinCDI.asciidoc[Vaadin CDI] +- link:IIInjectionAndScopes.asciidoc[II - Injection and scopes] +- link:CreatingSecureVaadinApplicationsUsingJEE6.asciidoc[Creating secure Vaadin applications using JEE6] +- link:UsingVaadinCDIWithJAASAuthentication.asciidoc[Using Vaadin CDI with JAAS authentication] +- link:LoadTestingWithGatling.asciidoc[Load testing with Gatling] +- link:VaadinScalabilityTestingWithAmazonWebServices.asciidoc[Vaadin scalability testing with Amazon Web Services] +- link:UsingFontIcons.asciidoc[Using font icons in Vaadin 7.2] +- link:DynamicallyInjectingCSS.asciidoc[Dynamically injecting CSS] +- link:ValoExamples.asciidoc[Valo examples] +- link:ReadOnlyVsDisabledFields.asciidoc[Read-only vs Disabled fields] +- link:ValoThemeGettingStarted.asciidoc[Valo theme - Getting started] +- link:UseTooltipsToClarifyFunctions.asciidoc[Use tooltips to clarify functions] +- link:EnableAndDisableButtonsToIndicateState.asciidoc[Enable and disable buttons to indicate state] +- link:ChangingThemeOnTheFly.asciidoc[Changing theme on the fly] +- link:MarkRequiredFieldsAsSuch.asciidoc[Mark required fields as such] +- link:PackagingSCSSOrCSSinAnAddon.asciidoc[Packaging SCSS or CSS in an add-on] +- link:RightAlignComparableNumericalFields.asciidoc[Right-align comparable numerical fields] +- link:CustomizingComponentThemeWithSass.asciidoc[Customizing component theme with Sass] +- link:WidgetStylingUsingOnlyCSS.asciidoc[Widget styling using only CSS] +- link:VisuallyDistinguishPrimaryActions.asciidoc[Visually distinguish primary actions] +- link:LabelButtonsExpressively.asciidoc[Label buttons expressively] +- link:CreatingAServlet3.0Application.asciidoc[Creating a servlet 3.0 application] +- link:CreatingAnEclipseProject.asciidoc[Creating an Eclipse project] +- link:CreatingASimpleComponentContainer.asciidoc[Creating a simple component container] +- link:UsingRPCToSendEventsToTheClient.asciidoc[Using RPC to send events to the client] +- link:CreatingAComponentExtension.asciidoc[Creating a component extension] +- link:CreatingAUIExtension.asciidoc[Creating a UI extension] +- link:UsingDeclarativeServices.asciidoc[Using declarative services] +- link:DynamicallyUpdatingStateBeforeSendingChangesToClient.asciidoc[Dynamically updating state before sending changes to client] +- link:GettingStartedOnNetBeans.asciidoc[Getting started on NetBeans] +- link:ComponentAddonProjectSetupHOWTO.asciidoc[Component add-on project setup how-to] +- link:CreatingAThemeUsingSass.asciidoc[Creating a theme using Sass] +- link:OpeningAUIInAPopupWindow.asciidoc[Opening a UI in a popup window] +- link:ViewChangeConfirmations.asciidoc[View change confirmations] +- link:CreatingABookmarkableApplicationWithBackButtonSupport.asciidoc[Creating a bookmarkable application with back button support] +- link:BroadcastingMessagesToOtherUsers.asciidoc[Broadcasting messages to other users] +- link:ConfigureInputFieldsToGuideDataEntry.asciidoc[Configure input fields to guide data entry] +- link:CreatingMultiTabApplications.asciidoc[Creating multi-tab applications] +- link:AddingASplashScreen.asciidoc[Adding a splash screen] diff --git a/documentation/articles/img/OSStack.pdf b/documentation/articles/img/OSStack.pdf Binary files differnew file mode 100644 index 0000000000..ea3502ff56 --- /dev/null +++ b/documentation/articles/img/OSStack.pdf diff --git a/documentation/articles/img/OSStack.png b/documentation/articles/img/OSStack.png Binary files differnew file mode 100644 index 0000000000..3dbd52afe9 --- /dev/null +++ b/documentation/articles/img/OSStack.png diff --git a/documentation/articles/img/architecture.png b/documentation/articles/img/architecture.png Binary files differnew file mode 100644 index 0000000000..06780e0040 --- /dev/null +++ b/documentation/articles/img/architecture.png diff --git a/documentation/articles/img/checkbox-vs-og.png b/documentation/articles/img/checkbox-vs-og.png Binary files differnew file mode 100644 index 0000000000..23b6fdc70f --- /dev/null +++ b/documentation/articles/img/checkbox-vs-og.png diff --git a/documentation/articles/img/combo.png b/documentation/articles/img/combo.png Binary files differnew file mode 100644 index 0000000000..fa3cbe8995 --- /dev/null +++ b/documentation/articles/img/combo.png diff --git a/documentation/articles/img/datefield.png b/documentation/articles/img/datefield.png Binary files differnew file mode 100644 index 0000000000..8788130758 --- /dev/null +++ b/documentation/articles/img/datefield.png diff --git a/documentation/articles/img/disabled-after.png b/documentation/articles/img/disabled-after.png Binary files differnew file mode 100644 index 0000000000..dceb70d098 --- /dev/null +++ b/documentation/articles/img/disabled-after.png diff --git a/documentation/articles/img/disabled-before.png b/documentation/articles/img/disabled-before.png Binary files differnew file mode 100644 index 0000000000..cb0920eead --- /dev/null +++ b/documentation/articles/img/disabled-before.png diff --git a/documentation/articles/img/disabled1.png b/documentation/articles/img/disabled1.png Binary files differnew file mode 100644 index 0000000000..adaac11f88 --- /dev/null +++ b/documentation/articles/img/disabled1.png diff --git a/documentation/articles/img/disabled3.png b/documentation/articles/img/disabled3.png Binary files differnew file mode 100644 index 0000000000..d32013f8c3 --- /dev/null +++ b/documentation/articles/img/disabled3.png diff --git a/documentation/articles/img/disabledvsreadonly.png b/documentation/articles/img/disabledvsreadonly.png Binary files differnew file mode 100644 index 0000000000..26a57afc9f --- /dev/null +++ b/documentation/articles/img/disabledvsreadonly.png diff --git a/documentation/articles/img/domain.png b/documentation/articles/img/domain.png Binary files differnew file mode 100644 index 0000000000..5cc494d9f5 --- /dev/null +++ b/documentation/articles/img/domain.png diff --git a/documentation/articles/img/eclipse-plugin-install.png b/documentation/articles/img/eclipse-plugin-install.png Binary files differnew file mode 100644 index 0000000000..972ccb84e0 --- /dev/null +++ b/documentation/articles/img/eclipse-plugin-install.png diff --git a/documentation/articles/img/errortooltip.png b/documentation/articles/img/errortooltip.png Binary files differnew file mode 100644 index 0000000000..730058c554 --- /dev/null +++ b/documentation/articles/img/errortooltip.png diff --git a/documentation/articles/img/form1.png b/documentation/articles/img/form1.png Binary files differnew file mode 100644 index 0000000000..52ab03bb28 --- /dev/null +++ b/documentation/articles/img/form1.png diff --git a/documentation/articles/img/form2.png b/documentation/articles/img/form2.png Binary files differnew file mode 100644 index 0000000000..25f6374fdd --- /dev/null +++ b/documentation/articles/img/form2.png diff --git a/documentation/articles/img/form3.png b/documentation/articles/img/form3.png Binary files differnew file mode 100644 index 0000000000..dde67b305f --- /dev/null +++ b/documentation/articles/img/form3.png diff --git a/documentation/articles/img/glassfish_console1.png b/documentation/articles/img/glassfish_console1.png Binary files differnew file mode 100644 index 0000000000..9ff76819bd --- /dev/null +++ b/documentation/articles/img/glassfish_console1.png diff --git a/documentation/articles/img/glassfish_console2.png b/documentation/articles/img/glassfish_console2.png Binary files differnew file mode 100644 index 0000000000..4efb69c583 --- /dev/null +++ b/documentation/articles/img/glassfish_console2.png diff --git a/documentation/articles/img/google drive.png b/documentation/articles/img/google drive.png Binary files differnew file mode 100644 index 0000000000..1a8bc5ceee --- /dev/null +++ b/documentation/articles/img/google drive.png diff --git a/documentation/articles/img/hello-earnest.png b/documentation/articles/img/hello-earnest.png Binary files differnew file mode 100644 index 0000000000..1de661e134 --- /dev/null +++ b/documentation/articles/img/hello-earnest.png diff --git a/documentation/articles/img/hello-stranger.png b/documentation/articles/img/hello-stranger.png Binary files differnew file mode 100644 index 0000000000..1c2834febb --- /dev/null +++ b/documentation/articles/img/hello-stranger.png diff --git a/documentation/articles/img/hello-world.png b/documentation/articles/img/hello-world.png Binary files differnew file mode 100644 index 0000000000..3045a0268c --- /dev/null +++ b/documentation/articles/img/hello-world.png diff --git a/documentation/articles/img/locationfield.png b/documentation/articles/img/locationfield.png Binary files differnew file mode 100644 index 0000000000..e654776002 --- /dev/null +++ b/documentation/articles/img/locationfield.png diff --git a/documentation/articles/img/maven-compile-widgetset-launch.png b/documentation/articles/img/maven-compile-widgetset-launch.png Binary files differnew file mode 100644 index 0000000000..e75aa22d1a --- /dev/null +++ b/documentation/articles/img/maven-compile-widgetset-launch.png diff --git a/documentation/articles/img/maven-run-project.png b/documentation/articles/img/maven-run-project.png Binary files differnew file mode 100644 index 0000000000..1ef03d9a3f --- /dev/null +++ b/documentation/articles/img/maven-run-project.png diff --git a/documentation/articles/img/n-tier.png b/documentation/articles/img/n-tier.png Binary files differnew file mode 100644 index 0000000000..daa170da4d --- /dev/null +++ b/documentation/articles/img/n-tier.png diff --git a/documentation/articles/img/nativeselect.png b/documentation/articles/img/nativeselect.png Binary files differnew file mode 100644 index 0000000000..80843f404d --- /dev/null +++ b/documentation/articles/img/nativeselect.png diff --git a/documentation/articles/img/nbscrshot1.png b/documentation/articles/img/nbscrshot1.png Binary files differnew file mode 100644 index 0000000000..d8a8f07b50 --- /dev/null +++ b/documentation/articles/img/nbscrshot1.png diff --git a/documentation/articles/img/nbscrshot2.png b/documentation/articles/img/nbscrshot2.png Binary files differnew file mode 100644 index 0000000000..76b99efd50 --- /dev/null +++ b/documentation/articles/img/nbscrshot2.png diff --git a/documentation/articles/img/netbeans_hello_vaadin.png b/documentation/articles/img/netbeans_hello_vaadin.png Binary files differnew file mode 100644 index 0000000000..a26f062f6b --- /dev/null +++ b/documentation/articles/img/netbeans_hello_vaadin.png diff --git a/documentation/articles/img/netbeans_new_project.png b/documentation/articles/img/netbeans_new_project.png Binary files differnew file mode 100644 index 0000000000..9ab53e95f9 --- /dev/null +++ b/documentation/articles/img/netbeans_new_project.png diff --git a/documentation/articles/img/optiongroup.png b/documentation/articles/img/optiongroup.png Binary files differnew file mode 100644 index 0000000000..a22b6dcf10 --- /dev/null +++ b/documentation/articles/img/optiongroup.png diff --git a/documentation/articles/img/potus1.png b/documentation/articles/img/potus1.png Binary files differnew file mode 100644 index 0000000000..f78a27d549 --- /dev/null +++ b/documentation/articles/img/potus1.png diff --git a/documentation/articles/img/potus2.png b/documentation/articles/img/potus2.png Binary files differnew file mode 100644 index 0000000000..8c60bf638c --- /dev/null +++ b/documentation/articles/img/potus2.png diff --git a/documentation/articles/img/project-creation.png b/documentation/articles/img/project-creation.png Binary files differnew file mode 100644 index 0000000000..0671115779 --- /dev/null +++ b/documentation/articles/img/project-creation.png diff --git a/documentation/articles/img/readonly-wrong.png b/documentation/articles/img/readonly-wrong.png Binary files differnew file mode 100644 index 0000000000..3c738fade5 --- /dev/null +++ b/documentation/articles/img/readonly-wrong.png diff --git a/documentation/articles/img/reqfield.png b/documentation/articles/img/reqfield.png Binary files differnew file mode 100644 index 0000000000..498ac5df1f --- /dev/null +++ b/documentation/articles/img/reqfield.png diff --git a/documentation/articles/img/save changes 1.png b/documentation/articles/img/save changes 1.png Binary files differnew file mode 100644 index 0000000000..28d4e909b2 --- /dev/null +++ b/documentation/articles/img/save changes 1.png diff --git a/documentation/articles/img/save changes 2.png b/documentation/articles/img/save changes 2.png Binary files differnew file mode 100644 index 0000000000..5223339cf3 --- /dev/null +++ b/documentation/articles/img/save changes 2.png diff --git a/documentation/articles/img/save changes 3.png b/documentation/articles/img/save changes 3.png Binary files differnew file mode 100644 index 0000000000..cea358b1a7 --- /dev/null +++ b/documentation/articles/img/save changes 3.png diff --git a/documentation/articles/img/searchfield.png b/documentation/articles/img/searchfield.png Binary files differnew file mode 100644 index 0000000000..3690b92eaf --- /dev/null +++ b/documentation/articles/img/searchfield.png diff --git a/documentation/articles/img/sign in - all different.png b/documentation/articles/img/sign in - all different.png Binary files differnew file mode 100644 index 0000000000..792de8ff55 --- /dev/null +++ b/documentation/articles/img/sign in - all different.png diff --git a/documentation/articles/img/sign in - no distinction.png b/documentation/articles/img/sign in - no distinction.png Binary files differnew file mode 100644 index 0000000000..7cc4e5c526 --- /dev/null +++ b/documentation/articles/img/sign in - no distinction.png diff --git a/documentation/articles/img/sign in - primary distinguished.png b/documentation/articles/img/sign in - primary distinguished.png Binary files differnew file mode 100644 index 0000000000..59b4397548 --- /dev/null +++ b/documentation/articles/img/sign in - primary distinguished.png diff --git a/documentation/articles/img/slider.png b/documentation/articles/img/slider.png Binary files differnew file mode 100644 index 0000000000..01835f7686 --- /dev/null +++ b/documentation/articles/img/slider.png diff --git a/documentation/articles/img/table1.png b/documentation/articles/img/table1.png Binary files differnew file mode 100644 index 0000000000..d319f883d5 --- /dev/null +++ b/documentation/articles/img/table1.png diff --git a/documentation/articles/img/table2.png b/documentation/articles/img/table2.png Binary files differnew file mode 100644 index 0000000000..590f8dadca --- /dev/null +++ b/documentation/articles/img/table2.png diff --git a/documentation/articles/img/theme-editor.png b/documentation/articles/img/theme-editor.png Binary files differnew file mode 100644 index 0000000000..6785823d4e --- /dev/null +++ b/documentation/articles/img/theme-editor.png diff --git a/documentation/articles/img/tooltip.png b/documentation/articles/img/tooltip.png Binary files differnew file mode 100644 index 0000000000..1c4b1357ec --- /dev/null +++ b/documentation/articles/img/tooltip.png diff --git a/documentation/articles/img/vaadingflot.zip b/documentation/articles/img/vaadingflot.zip Binary files differnew file mode 100644 index 0000000000..d4dd3b2f4d --- /dev/null +++ b/documentation/articles/img/vaadingflot.zip diff --git a/documentation/articles/img/viewmode-disabled.png b/documentation/articles/img/viewmode-disabled.png Binary files differnew file mode 100644 index 0000000000..5c81d9698d --- /dev/null +++ b/documentation/articles/img/viewmode-disabled.png diff --git a/documentation/articles/img/viewmode-readonly.png b/documentation/articles/img/viewmode-readonly.png Binary files differnew file mode 100644 index 0000000000..785ecaf73d --- /dev/null +++ b/documentation/articles/img/viewmode-readonly.png |