ImageViewer Add-On

While am at it, I might as well pitch another add-on today: the ImageViewer!

I began implementing ImageViewer as an attempt to create “flash-like” animated web image viewer without actually using flash. This was done mostly as an experiment to see if this would be more or less demanding than Flash on older hardware. Disappointingly, there was no performance benefit in animating with GWT over Flash :(.

Main features are:

  • Animated display of a ‘flow’ of images
  • Easy to use from keyboard (left, right, space)
  • Only loads the visible images (+ one extra on each side) to the browser

Usage should be quite easy: there are loads of JavaDoc to explain it to you. Images are given to the component as a List of Vaadin Resources. Please ask for help if there are any problems.

As a general tip you should probably keep the size of the images at a maximum of 300kB
since the visible images are loaded to the browser and resized using the browser’s
resize functionality.

Known issues with current version:

  • Safari 5: Works, but with animation glitches
  • Chrome: Works, but there is a minor random animation glitch when expanding an image
  • IE6-9: Works, but animations are noticeably slower than with other browsers
    [url=http://vaadin.com/directory#addon/imageviewer]

ImageViewer Add-On can be found here (download includes source code).
[/url]
[url=http://tepi.virtuallypreinstalled.com/imageviewer?restartApplication]

A simple demo application is running here. Feel free to try out the various settings.
[/url]

Once again, all bug reports and feature requests are welcome! If you have the time to report them, I’ll try to find the time to fix them.

-Teppo

Hi Teppo.

I took your add-on into use over the weekend. Works great and quite easy to use. Some minor things that came up while using it:

  • The component throws a NPE if there are no listeners attached to it. ImageViewer.changeVariables(Object, Map) should do a nullcheck on listeners before doing “for (ImageSelectionListener l : listeners)” on line 111.
  • setImages should accept List<? extends Resource> or something similar. Now you can’t give to it for example a List directly
  • The zooming feature is nice but in the app that I used the add-on in, I’d guess that some user might accidentally trigger it. For this reason I would like to have a way to disable the zooming.
  • In zoomed state and navigating with keyboard, the next image always appears from the left. Everything looks good when you press the left key, but when you press the right key, the old pictures flies to the left and the new one appears also from the left (=should come from right)
  • In Chrome, when moving to an image that takes the whole height to show, for example portrait images, the animation is jumpy. The last frame moves the picture to the left about 10px and then instantly moves it back. This doesn’t happen for images that doesn’t take the full height, for example images in landscape orientation.
  • A standard constructor that has JavaDocs would be nice, saying that you should provide the images with setImages(List) That would be enough to get something on the screen for this. A constructor that accepts the resources directly would also be nice.

That’s all for now. :slight_smile: Great add-on!

Hi, and thanks for the nice set of feedback!

I released ImageViewer 0.4.1 which addresses some of the issues you reported:

These should be fixed in the 0.4.1 version.

Agreed, I will add the option in 0.4.2.

I could not reproduce this with either latest Chrome or Firefox. Please come by and show me some day :).

Yes, this is a known issue which I’ve so far been unable to solve :frowning: Something in the size calculations goes very wrong, but only in webkit browsers. Any help or debugging regarding this is appreciated greatly ;).

Thanks, please enjoy it :slight_smile:

Hi, I am Dhananjay using this plugin but getting the errors when using any resource like file, theme and class resource. Using the file or class resource it is showing small square with cross in the browser. i hava tried it by many methods by simply embedding, uploading but it is always giving the error.
Please suggest if anyone have the solution for this problem.

I think you have to add Client,UI,application files of Image viewer(SVN code) to your application .

Image viewer add on demo URL which is given above is not working

please fix this issue

Hi:

Is there any example code?..there is no source code with the download…

regards,

Hugo

When I use the mouse wheel very quickly, the images are displayed one after the other but i can not click on the centered image because the wheel event is turned on !

A new version is envisaged ?

Is it possible to link two imageviewers ?

Thanks for reply.

while adding the images i am getting error will u give the sample code to enter that images…pls

Some ugly sample test component I did a while ago. You have to add the dependency for org.htmlparser to your pom or ivy.xml for it to work.
see http://mvnrepository.com/artifact/org.htmlparser/htmlparser/1.6
It reads the images from a web url and displays them in imageviewer:


package com.example.slideshow;

import java.util.ArrayList;
import java.util.List;

import org.htmlparser.Parser;
import org.htmlparser.Tag;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.htmlparser.util.SimpleNodeIterator;
import org.vaadin.tepi.imageviewer.ImageViewer;

import com.vaadin.annotations.AutoGenerated;
import com.vaadin.terminal.ExternalResource;
import com.vaadin.ui.AbsoluteLayout;
import com.vaadin.ui.Button;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.TextField;

public class imgslide extends CustomComponent implements Button.ClickListener{

	@AutoGenerated
	private AbsoluteLayout mainLayout;
	@AutoGenerated
	private Button button_1;
	@AutoGenerated
	private TextField textField_1;
	@AutoGenerated
	private ImageViewer imageViewer_1;

	private ExternalResource img;
	private List<ExternalResource> images = new ArrayList<ExternalResource>();

	private Parser parser;
	private NodeList list;
	private String imgurl;
	
	
	
	public imgslide() {
		buildMainLayout();
		setCompositionRoot(mainLayout);
		
		button_1.addListener(this);
		fillimages();
  
		   
		}
		
		
	

	public void buttonClick (Button.ClickEvent event) {
		
		fillimages();
	}
	
	public void fillimages(){
		images = new ArrayList<ExternalResource>();

		imgurl = textField_1.toString();
		
		if (imgurl.isEmpty()){
			imgurl="http://bild.de";
			textField_1.setValue(imgurl);
		}
		
		
		try {
			
			parser = new Parser(imgurl);
		
		} catch (ParserException e) {

			e.printStackTrace();
			return;
		}
		
		try {
			list = parser.parse(new TagNameFilter("IMG"));
		} catch (ParserException e) {

			e.printStackTrace();
			return;
		}

		for ( SimpleNodeIterator iterator = list.elements(); iterator.hasMoreNodes(); ) {

		    Tag tag = (Tag) iterator.nextNode();
		    img = new ExternalResource(tag.getAttribute("src"));
		    
		    images.add(img);
		
		}
		imageViewer_1.setImages(images);
		imageViewer_1.setCaption(imgurl);
		imageViewer_1.requestRepaint();
		imageViewer_1.focus();
	}
	
	@AutoGenerated
	private AbsoluteLayout buildMainLayout() {
		// common part: create layout
		mainLayout = new AbsoluteLayout();
		mainLayout.setImmediate(false);
		mainLayout.setWidth("100%");
		mainLayout.setHeight("100%");
		mainLayout.setMargin(false);
		
		// top-level component properties
		setWidth("100.0%");
		setHeight("100.0%");
		
		// imageViewer_1
		imageViewer_1 = new ImageViewer();
		imageViewer_1.setImmediate(false);
		imageViewer_1.setWidth("100.0%");
		imageViewer_1.setHeight("420px");
		mainLayout.addComponent(imageViewer_1,
				"top:240.0px;right:18.0px;left:60.0px;");
		
		// textField_1
		textField_1 = new TextField();
		textField_1.setImmediate(false);
		textField_1.setWidth("260px");
		textField_1.setHeight("-1px");
		textField_1.setSecret(false);
		mainLayout.addComponent(textField_1, "top:76.0px;left:60.0px;");
		
		// button_1
		button_1 = new Button();
		button_1.setCaption("load images from url");
		button_1.setImmediate(false);
		button_1.setWidth("-1px");
		button_1.setHeight("-1px");
		mainLayout.addComponent(button_1, "top:140.0px;left:60.0px;");
		
		return mainLayout;
	}

}

Hi,

Please try the demo site again. At least it’s working for me right now. Do note that the demo application is not running on a “production-level” server so there may be some downtime every now and then. Unfortunately there isn’t anything I can do about it.

-Tepi

Please help me…
i tried a lot applet integration with vaadin…but failed…plz give me one simple program with explanation to add applet viewer to vaadin application…its urgent

Hi,

here’s the whole ImageViewer demo application code (it just has this one class):

package org.vaadin.tepi.imageviewer.demo;

import java.util.ArrayList;
import java.util.List;

import org.vaadin.tepi.imageviewer.ImageViewer;
import org.vaadin.tepi.imageviewer.ImageViewer.ImageSelectedEvent;

import com.vaadin.Application;
import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.terminal.Resource;
import com.vaadin.terminal.ThemeResource;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Layout;
import com.vaadin.ui.Slider;
import com.vaadin.ui.Slider.ValueOutOfBoundsException;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.themes.Reindeer;

@SuppressWarnings("serial")
public class ImageViewerDemoApplication extends Application {

    private ImageViewer imageViewer;
    private VerticalLayout mainLayout;
    private TextField selectedImage = new TextField();

    @Override
    public void init() {
        Window mainWindow = new Window("ImageViewer Demo Application");
        mainWindow.setSizeFull();

        mainLayout = (VerticalLayout) mainWindow.getContent();
        mainLayout.addStyleName(Reindeer.LAYOUT_BLACK);
        mainLayout.setSizeFull();
        mainLayout.setMargin(true);
        mainLayout.setSpacing(true);

        Label info = new Label(
                "<b>ImageViewer Demo Application</b>&nbsp;&nbsp;&nbsp;"
                        + "<i>Try the arrow keys, space/enter and home/end."
                        + " You can also click on the pictures or use the "
                        + "mouse wheel.&nbsp;&nbsp;", Label.CONTENT_XHTML);
        Button style = new Button("Click here to toggle style.");
        style.setStyleName(Reindeer.BUTTON_LINK);
        style.addListener(new Button.ClickListener() {
            public void buttonClick(ClickEvent event) {
                if (mainLayout.getStyleName().contains("black")) {
                    mainLayout.removeStyleName(Reindeer.LAYOUT_BLACK);
                } else {
                    mainLayout.addStyleName(Reindeer.LAYOUT_BLACK);
                }
            }
        });
        imageViewer = new ImageViewer();
        imageViewer.setSizeFull();
        imageViewer.setImages(createImageList());
        imageViewer.setAnimationEnabled(false);
        imageViewer.setSideImageRelativeWidth(0.7f);

        imageViewer.addListener(new ImageViewer.ImageSelectionListener() {
            public void imageSelected(ImageSelectedEvent e) {
                if (e.getSelectedImageIndex() >= 0) {
                    selectedImage.setValue(e.getSelectedImageIndex());
                } else {
                    selectedImage.setValue("-");
                }
            }
        });
        HorizontalLayout hl = new HorizontalLayout();
        hl.setSizeUndefined();
        hl.setMargin(false);
        hl.setSpacing(true);
        hl.addComponent(info);
        hl.addComponent(style);
        mainLayout.addComponent(hl);
        mainLayout.addComponent(imageViewer);
        mainLayout.setExpandRatio(imageViewer, 1);

        Layout ctrls = createControls();
        mainLayout.addComponent(ctrls);
        mainLayout.setComponentAlignment(ctrls, Alignment.BOTTOM_CENTER);

        Label images = new Label(
                "Sample Photos: Bruno Monginoux / www.Landscape-Photo.net (cc-by-nc-nd)");
        images.setSizeUndefined();
        images.setStyleName("licence");
        mainLayout.addComponent(images);
        mainLayout.setComponentAlignment(images, Alignment.BOTTOM_RIGHT);

        setMainWindow(mainWindow);
        imageViewer.setCenterImageIndex(0);
        imageViewer.focus();
        setTheme("imageviewertheme");
    }

    private Layout createControls() {
        HorizontalLayout hl = new HorizontalLayout();
        hl.setSizeUndefined();
        hl.setMargin(false);
        hl.setSpacing(true);

        CheckBox c = new CheckBox("HiLite");
        c.setImmediate(true);
        c.addListener(new Property.ValueChangeListener() {
            public void valueChange(ValueChangeEvent event) {
                boolean checked = (Boolean) event.getProperty().getValue();
                imageViewer.setHiLiteEnabled(checked);
                imageViewer.focus();
            }
        });
        c.setValue(true);
        hl.addComponent(c);
        hl.setComponentAlignment(c, Alignment.BOTTOM_CENTER);

        c = new CheckBox("Animate");
        c.setImmediate(true);
        c.addListener(new Property.ValueChangeListener() {
            public void valueChange(ValueChangeEvent event) {
                boolean checked = (Boolean) event.getProperty().getValue();
                imageViewer.setAnimationEnabled(checked);
                imageViewer.focus();
            }
        });
        c.setValue(true);
        hl.addComponent(c);
        hl.setComponentAlignment(c, Alignment.BOTTOM_CENTER);

        Slider s = new Slider("Animation duration (ms)");
        s.setMin(200);
        s.setMax(2000);
        s.setImmediate(true);
        s.setWidth("120px");
        s.addListener(new Property.ValueChangeListener() {
            public void valueChange(ValueChangeEvent event) {
                try {
                    int duration = (int) Math.round((Double) event
                            .getProperty().getValue());
                    imageViewer.setAnimationDuration(duration);
                    imageViewer.focus();
                } catch (Exception ignored) {
                }
            }
        });
        try {
            s.setValue(350);
        } catch (ValueOutOfBoundsException e) {
        }
        hl.addComponent(s);
        hl.setComponentAlignment(s, Alignment.BOTTOM_CENTER);

        s = new Slider("Center image width");
        s.setMin(0.1);
        s.setMax(1);
        s.setResolution(2);
        s.setImmediate(true);
        s.setWidth("120px");
        s.addListener(new Property.ValueChangeListener() {
            public void valueChange(ValueChangeEvent event) {
                try {
                    double d = (Double) event.getProperty().getValue();
                    imageViewer.setCenterImageRelativeWidth((float) d);
                    imageViewer.focus();
                } catch (Exception ignored) {
                }
            }
        });
        try {
            s.setValue(0.55);
        } catch (ValueOutOfBoundsException e) {
        }
        hl.addComponent(s);
        hl.setComponentAlignment(s, Alignment.BOTTOM_CENTER);

        s = new Slider("Side image count");
        s.setMin(1);
        s.setMax(5);
        s.setImmediate(true);
        s.setWidth("120px");
        s.addListener(new Property.ValueChangeListener() {
            public void valueChange(ValueChangeEvent event) {
                try {
                    int sideImageCount = (int) Math.round((Double) event
                            .getProperty().getValue());
                    imageViewer.setSideImageCount(sideImageCount);
                    imageViewer.focus();
                } catch (Exception ignored) {
                }
            }
        });
        try {
            s.setValue(2);
        } catch (ValueOutOfBoundsException e) {
        }
        hl.addComponent(s);
        hl.setComponentAlignment(s, Alignment.BOTTOM_CENTER);

        s = new Slider("Side image width");
        s.setMin(0.5);
        s.setMax(0.8);
        s.setResolution(2);
        s.setImmediate(true);
        s.setWidth("120px");
        s.addListener(new Property.ValueChangeListener() {
            public void valueChange(ValueChangeEvent event) {
                try {
                    double d = (Double) event.getProperty().getValue();
                    imageViewer.setSideImageRelativeWidth((float) d);
                    imageViewer.focus();
                } catch (Exception ignored) {
                }
            }
        });
        try {
            s.setValue(0.65);
        } catch (ValueOutOfBoundsException e) {
        }
        hl.addComponent(s);
        hl.setComponentAlignment(s, Alignment.BOTTOM_CENTER);

        s = new Slider("Horizontal padding");
        s.setMin(0);
        s.setMax(10);
        s.setImmediate(true);
        s.setWidth("120px");
        s.addListener(new Property.ValueChangeListener() {
            public void valueChange(ValueChangeEvent event) {
                try {
                    double d = (Double) event.getProperty().getValue();
                    imageViewer.setImageHorizontalPadding((int) Math.round(d));
                    imageViewer.focus();
                } catch (Exception ignored) {
                }
            }
        });
        try {
            s.setValue(1);
        } catch (ValueOutOfBoundsException e) {
        }
        hl.addComponent(s);
        hl.setComponentAlignment(s, Alignment.BOTTOM_CENTER);

        s = new Slider("Vertical padding");
        s.setMin(0);
        s.setMax(10);
        s.setImmediate(true);
        s.setWidth("120px");
        s.addListener(new Property.ValueChangeListener() {
            public void valueChange(ValueChangeEvent event) {
                try {
                    double d = (Double) event.getProperty().getValue();
                    imageViewer.setImageVerticalPadding((int) Math.round(d));
                    imageViewer.focus();
                } catch (Exception ignored) {
                }
            }
        });
        try {
            s.setValue(5);
        } catch (ValueOutOfBoundsException e) {
        }
        hl.addComponent(s);
        hl.setComponentAlignment(s, Alignment.BOTTOM_CENTER);

        selectedImage.setWidth("50px");
        selectedImage.setImmediate(true);
        hl.addComponent(selectedImage);
        hl.setComponentAlignment(selectedImage, Alignment.BOTTOM_CENTER);

        return hl;
    }

    /**
     * Creates a list of Resources to be shown in the ImageViewer.
     * 
     * @return List of Resource instances
     */
    private List<Resource> createImageList() {
        List<Resource> img = new ArrayList<Resource>();
        for (int i = 1; i < 10; i++) {
            img.add(new ThemeResource("images/" + i + ".jpg"));
        }
        return img;
    }
}

In addition, you will of course need the images to show. In the demo app they are within the app’s theme, in other words in the folder WebContent/VAADIN/themes/imageviewertheme/images/ (I will not post those images here, use your own images).

You will of course need to get the add-on package itself and add the two jars from it to your project in the WEB-INF/lib folder (where the vaadin jar is located) and then compile the widgetset.

If you still have some issues, please post a more specific question.

-tepi

Hi thanks for your reply…

        But i need applet integration with vaadin application.. iam trying to add my own applet to vaadin application..but getting ClassFormatException..where i need to place my applet class file and jar containin my applet class ??
          Plz help me.......

Hi,

okay then I misread a bit. I think you will get more answers if you create a new thread since this one does not deal with applet integration issues :slight_smile: Anyway, there’s this
add-on
in the Directory, but I don’t know anything more about it. Please create a new thread to ask about the applet integration in detail.

-tepi

Thanks for your suggestion…

Hi,

thank you for this good add-on.
I started to develop a little app with Vaadin 7 and I recognized that the version 0.4.1.v7 is not compatible with Vaadin 7 beta 11.
So I started to do the necessary changes myself. I have done the “quick and dirty” hack as described
here
. I just want to know what your plans are for Vaadin 7. Do you plan to rewrite the code to adept to the Vaadin 7 changes?

Thanks
Regards Christian

Hi,

yes, unfortunately the v7 version of this add-on is no more compatible with the latest betas :(. I am planning to port it to final Vaadin 7 but currently I can’t say when I have the time.

If you have the time and willingness to do that, your input is more than welcome. I can publish it in the directory also and add credits for the porting.

-tepi

Hi,

I can give you the code with the quick and dirty hack for now. This works with Vaadin beta 11 (at least on my machine :wink: ) . The Vaadin 7 API should be stable since beta 11, so perhaps now is a good time to start porting the addon to v7. I will see what I can do the next rainy evenings :wink:

I created a maven module out of your code, is it ok for you if I send you the complete module, or are you only interested in the few files which have changed.

Regards
Christian

Hi,

There’s now a Vaadin 7 beta 11 (and hopefully V7 final) -compatible version of the ImageViewer available in the
directory
. The version number for this is 0.5.1-v7. The code has been modified to use V7 shared state and RPC properly, so no need for any legacy stuff. Functionality-wise there has not been any changes to the add-on.

The demo application is also updated to run the V7 version.

Note! There is some kind of layout bug in Vaadin 7 beta 11 which prevents the height from being measured correctly on the first render. This makes ImageViewer’s internal size calculations go wrong and results into nothing being displayed. The bug is already fixed so please use a nightly version of Vaadin 7 with this add-on.

As always, please try it out and report any issues you may find here. Thanks!

-tepi