Elastic Path Commerce Development

Elastic Path UI Framework

Elastic Path UI Framework

The Elastic Path UI Framework is the basis for the user interface of the Commerce Manager.

UI development with the SWT and JFace libraries used by the Commerce Manager's underlying RCP/RAP framework demands thorough knowledge of the widgets and composites in Eclipse. This has led to the creation of the Elastic Path UI Framework having in mind that it will be an evolving product extended whenever there's need for that. The balance between building something huge and unmaintainable and having something that will ease the development is really important. For that reason the UI framework should be used for wrapping the most used SWT widgets. For all the particular cases that are not common to the EP RCP the native SWT API should be employed.

The UI framework wraps the SWT widgets and composites. While using it there should always be a way to get the native SWT widget or composite lying beneath the wrapping object. This does not restrict the developer on using only the UI framework once starting a composite with it and makes the development a little bit more flexible.

Description of the development model of the Elastic Path UI Framework

Eclipse has two main component types - composites and controls. The composite acts as a container for controls. Each composite is also a control which allows to nest a composite inside a composite. The SWT UI forms a tree-like structure where the leafs are the various controls visible to the user and branches are the composites holding them and organizing their location and composition.

For building an organized UI a layout has to be utilized. There are various layout managers in Eclipse. The most prominent and powerful are the GridLayout and TableWrapLayout. They both are very similar in design. They possess the ability to lay out the components according to a predefined grid. The components are added to the grid from left to right filling the rows from top to bottom. Another task of the layouts is to define how exactly the component should be placed inside the cell. This includes setting the component to stick to the left, right, top or bottom of the cell, stretch and employ the whole available space of the cell or take a number of rows and/or columns in the grid.

For more information on the layouts, please visit the following page:

http://www.eclipse.org/articles/Article-Understanding-Layouts/Understanding-Layouts.htm

Having in mind this simple structure of the composites, controls and layouts the easiest way to abstract that was to create a class wrapping the creation of composites and controls altogether. This was achieved by the IEpLayoutComposite interface. This is the basic interface of the framework. It represents a composite (pane) and has the functionality of adding UI widgets to it.

IEpLayoutComposite.JPG

All methods starting with 'add...' create new component and add it to the composite. The two main methods responsible for nesting composites one into another are

IEpLayoutComposite.addTableWrapLayoutComposite(int numColumns, boolean equalWidthColumns, IEpLayoutData data);

IEpLayoutComposite.addGridLayoutComposite(int numColumns, boolean equalWidthColumns, IEpLayoutData data);

All the add methods have a parameter for the IEpLayoutData. This interface represents the layout data for the layouts. It abstracts the two different layout data - GridLayoutData and TableWrapLayoutData.

IEpLayoutData.JPG

The layout data can be created from the IEpLayoutComposite using the createLayoutData(...) methods. The following picture shows how particular components (marked with blue line) will be placed inside their sells having set the values of the layout data. The six parameters of IEpLayoutData set on each component are as follows:

  • horizontalAlignment - how control will be positioned horizontally within a cell
  • verticalAlignment - how control will be positioned vertically within a cell
  • grabExcessHorizontalSpace - whether cell will be made wide enough to fit the remaining horizontal space
  • grabExcessVerticalSpace - whether cell will be made high enough to fit the remaining vertical space
  • horizontalSpan - the number of column cells that the control will take up
  • verticalSpan - the number of row cells that the control will take up

If the layout data parameter is set to null when adding new components with the 'add...' methods, then the default layout data is set for filling the cell both horizontally and vertically but not grabbing vertical/horizontal space.

When creating a new composite the CompositeFactory class should be used. It creates an instance of the IEpLayoutComposite with one of the two supported layouts - GridLayout or TableWrapLayout. This is not visible to the developer as far as the IEPLayoutData is used.

The recommendation is that the GridLayout should be basically used for views where a stretch/shrink behavior is expected whilst TableWrapLayout is for editors where the components are integrated in the Eclipse Forms framework. These are the methods of the CompositeFactory class:

CompositeFactory.JPG

The parameters that have to be specified are the parent composite, the columns' count for the created layout grid and if these columns have to be with an equal width.

Example

The following example is taken from the above mentioned introduction to SWT layouts. It has been transformed using the EP UI Framework. You can easily compare the sources of the two classes:

UI Framework SWT
final IEpLayoutComposite mainComposite = CompositeFactory
        .createGridLayoutComposite(shell, 3, false);

final IEpLayoutData horizontalFill = mainComposite.createLayoutData(
        IEpLayoutData.FILL, IEpLayoutData.FILL);
final IEpLayoutData horizontalFill2Cells = mainComposite
        .createLayoutData(IEpLayoutData.FILL, IEpLayoutData.FILL, true,
                false, 2, 1);
final IEpLayoutData horizontalFillAndStretchHorizontally = mainComposite
        .createLayoutData(IEpLayoutData.FILL, IEpLayoutData.FILL, true,
                false);

mainComposite.addLabel("Dog's Name:", null);

final Text dogName = mainComposite.addTextField(false,
        horizontalFill2Cells);

mainComposite.addLabel("Breed:", null);

final Combo dogBreed = mainComposite.addComboBox(true,
        horizontalFillAndStretchHorizontally);
dogBreed.setItems(new String[] { "Collie", "Pitbull", "Poodle",
        "Scottie" });

mainComposite.addLabel("Categories", mainComposite.createLayoutData(
        IEpLayoutData.CENTER, IEpLayoutData.FILL));

mainComposite.addLabel("Photo:", null);
final Canvas dogPhoto = new Canvas(mainComposite.getSwtComposite(),
        SWT.BORDER);

final IEpLayoutData canvasLayoutData = mainComposite.createLayoutData(
        IEpLayoutData.FILL, IEpLayoutData.FILL, true, true, 1, 3);

dogPhoto.setLayoutData(canvasLayoutData.getSwtLayoutData());
dogPhoto.addPaintListener(new PaintListener() {
    public void paintControl(final PaintEvent event) {
        if (UIFrameworkSample.dogImage != null) {
            event.gc.drawImage(UIFrameworkSample.dogImage, 0, 0);
        }
    }
});

// TODO add addList to the UI framework
final List categories = new List(mainComposite.getSwtComposite(),
        SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
categories.setItems(new String[] { "Best of Breed", "Prettiest Female",
        "Handsomest Male", "Best Dressed", "Fluffiest Ears",
        "Most Colors", "Best Performer", "Loudest Bark",
        "Best Behaved", "Prettiest Eyes", "Most Hair", "Longest Tail",
        "Cutest Trick" });

final IEpLayoutData horizontalAndVerticalFill4Cells = mainComposite
        .createLayoutData(IEpLayoutData.FILL, IEpLayoutData.FILL,
                false, false, 1, 4);
categories.setLayoutData(horizontalAndVerticalFill4Cells
        .getSwtLayoutData());

final Button browse = mainComposite.addPushButton("Browse...",
        horizontalFill);

browse.addSelectionListener(new SelectionAdapter() {
    @Override
    public void widgetSelected(final SelectionEvent event) {
        final String fileName = new FileDialog(shell).open();
        if (fileName != null) {
            dogImage = new Image(display, fileName);
        }
    }
});

final Button delete = mainComposite.addPushButton("Delete",
        mainComposite.createLayoutData(IEpLayoutData.FILL,
                IEpLayoutData.BEGINNING));
delete.addSelectionListener(new SelectionAdapter() {
    @Override
    public void widgetSelected(final SelectionEvent event) {
        if (UIFrameworkSample.dogImage != null) {
            UIFrameworkSample.dogImage.dispose();
            UIFrameworkSample.dogImage = null;
            dogPhoto.redraw();
        }
    }
});

final IEpLayoutComposite ownerInfo = mainComposite.addGroup(
        "Owner Info", 2, false, horizontalFill2Cells);

ownerInfo.addLabel("Name:", null);
final Text ownerName = ownerInfo.addTextField(false,
        horizontalFillAndStretchHorizontally);

ownerInfo.addLabel("Phone:", null);
final Text ownerPhone = ownerInfo.addTextField(false,
        horizontalFillAndStretchHorizontally);

final IEpLayoutData horizontalEnd3Cells = mainComposite
        .createLayoutData(IEpLayoutData.END, IEpLayoutData.FILL, false,
                false, 3, 1);

final Button enter = mainComposite.addPushButton("Enter",
        horizontalEnd3Cells);
enter.addSelectionListener(new SelectionAdapter() {
    @Override
    public void widgetSelected(final SelectionEvent event) {
        System.out.println("\nDog Name: " + dogName.getText());
        System.out.println("Dog Breed: " + dogBreed.getText());
        System.out.println("Owner Name: " + ownerName.getText());
        System.out.println("Owner Phone: " + ownerPhone.getText());
        System.out.println("Categories:");
        final String cats[] = categories.getSelection();
        for (int i = 0; i < cats.length; i++) {
            System.out.println("\t" + cats[i]);
        }
    }
});
GridLayout gridLayout;
new Label(shell, SWT.NULL).setText("Dog's Name:");
dogName = new Text(shell, SWT.SINGLE | SWT.BORDER);

GridData gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
gridData.horizontalSpan = 2;
dogName.setLayoutData(gridData);

new Label(shell, SWT.NULL).setText("Breed:");

dogBreed = new Combo(shell, SWT.NULL);
dogBreed.setItems(new String[] { "Collie", "Pitbull", "Poodle",
        "Scottie" });
dogBreed.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));

final Label label = new Label(shell, SWT.NULL);
label.setText("Categories");

label.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
new Label(shell, SWT.NULL).setText("Photo:");

dogPhoto = new Canvas(shell, SWT.BORDER);

gridData = new GridData(GridData.FILL_BOTH);
gridData.widthHint = 80;
gridData.heightHint = 80;
gridData.verticalSpan = 3;

dogPhoto.setLayoutData(gridData);
dogPhoto.addPaintListener(new PaintListener() {
    public void paintControl(final PaintEvent event) {
        if (dogImage != null) {
            event.gc.drawImage(dogImage, 0, 0);
        }
    }
});

categories = new List(shell, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
categories.setItems(new String[] { "Best of Breed", "Prettiest Female",
        "Handsomest Male", "Best Dressed", "Fluffiest Ears",
        "Most Colors", "Best Performer", "Loudest Bark",
        "Best Behaved", "Prettiest Eyes", "Most Hair", "Longest Tail",
        "Cutest Trick" });

gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL
        | GridData.VERTICAL_ALIGN_FILL);
gridData.verticalSpan = 4;
final int listHeight = categories.getItemHeight() * 12;
final Rectangle trim = categories.computeTrim(0, 0, 0, listHeight);
gridData.heightHint = trim.height;

categories.setLayoutData(gridData);

final Button browse = new Button(shell, SWT.PUSH);
browse.setText("Browse...");
gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
gridData.horizontalIndent = 5;
browse.setLayoutData(gridData);
browse.addSelectionListener(new SelectionAdapter() {

    @Override
    public void widgetSelected(final SelectionEvent event) {
        final String fileName = new FileDialog(shell).open();
        if (fileName != null) {
            EclipseSwtSample.dogImage = new Image(display, fileName);
        }
    }
});

final Button delete = new Button(shell, SWT.PUSH);
delete.setText("Delete");
gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL
        | GridData.VERTICAL_ALIGN_BEGINNING);

gridData.horizontalIndent = 5;
delete.setLayoutData(gridData);
delete.addSelectionListener(new SelectionAdapter() {
    @Override
    public void widgetSelected(final SelectionEvent event) {
        if (EclipseSwtSample.dogImage != null) {
            EclipseSwtSample.dogImage.dispose();
            EclipseSwtSample.dogImage = null;
            dogPhoto.redraw();
        }
    }
});

final Group ownerInfo = new Group(shell, SWT.NULL);
ownerInfo.setText("Owner Info");
gridLayout = new GridLayout();
gridLayout.numColumns = 2;

ownerInfo.setLayout(gridLayout);
gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
gridData.horizontalSpan = 2;
ownerInfo.setLayoutData(gridData);

new Label(ownerInfo, SWT.NULL).setText("Name:");

ownerName = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
ownerName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
new Label(ownerInfo, SWT.NULL).setText("Phone:");

ownerPhone = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
ownerPhone.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

final Button enter = new Button(shell, SWT.PUSH);
enter.setText("Enter");

gridData = new GridData(GridData.HORIZONTAL_ALIGN_END);
gridData.horizontalSpan = 3;

enter.setLayoutData(gridData);
enter.addSelectionListener(new SelectionAdapter() {

    @Override
    public void widgetSelected(final SelectionEvent event) {
        System.out.println("\nDog Name: " + dogName.getText());
        System.out.println("Dog Breed: " + dogBreed.getText());
        System.out.println("Owner Name: " + ownerName.getText());
        System.out.println("Owner Phone: " + ownerPhone.getText());
        System.out.println("Categories:");
        final String cats[] = categories.getSelection();
        for (int i = 0; i < cats.length; i++) {
            System.out.println("\t" + cats[i]);
        }
    }
});

|

The result of the two approaches is the same except that the UI Framework by default uses the FormToolkit and therefore takes the more stylish look and feel of the Eclipse Forms.

UI Framework screen shot SWT screen shot

UI Framework Sample UI.JPG

SWT Sample UI.JPG