Open Source & Free  

It's Full Of Stars & Terse Commands

It's Full Of Stars & Terse Commands

Header Image

A very common UI pattern is the 5 star ranking system. Up until recently we always had the same answer
when developers asked us how to implement it: “Use toggle buttons
(CheckBox)”.

This is still not a bad answer but we think there is a “better” simpler way to do this thru the
Slider which was
effectively designed with this usage in mind.

The best way to do that is to just create two images with all 5 stars full and with all 5 stars empty and assign
this to the Slider/SliderFull UIID’s. Keep in mind that you need to apply both to the selected and unselected
states of the UIID’s.

You can change the UIID of slider itself e.g. to something like “Stars” at which point the UIID’s will be
Stars & StarsFull.

This will allow your users to click/drag to select the number of stars. The code below uses the star material icon
to generate something like this on the fly without any resources.

We enclose the slider in a FlowLayout to prevent it from growing. This is because I chose the stars to be tiled
instead of aligned (like we could if we used an image). So if the component will grow it won’t have the right feel.
Enclosing the component in a FlowLayout is an old trick to prevent components from growing beyond their preferred
size.
private void initStarRankStyle(Style s, Image star) {
    s.setBackgroundType(Style.BACKGROUND_IMAGE_TILE_BOTH);
    s.setBorder(Border.createEmpty());
    s.setBgImage(star);
    s.setBgTransparency(0);
}

private Slider createStarRankSlider() {
    Slider starRank = new Slider();
    starRank.setEditable(true);
    starRank.setMinValue(0);
    starRank.setMaxValue(10);
    Font fnt = Font.createTrueTypeFont("native:MainLight", "native:MainLight").
            derive(Display.getInstance().convertToPixels(5, true), Font.STYLE_PLAIN);
    Style s = new Style(0xffff33, 0, fnt, (byte)0);
    Image fullStar = FontImage.createMaterial(FontImage.MATERIAL_STAR, s).toImage();
    s.setOpacity(100);
    s.setFgColor(0);
    Image emptyStar = FontImage.createMaterial(FontImage.MATERIAL_STAR, s).toImage();
    initStarRankStyle(starRank.getSliderEmptySelectedStyle(), emptyStar);
    initStarRankStyle(starRank.getSliderEmptyUnselectedStyle(), emptyStar);
    initStarRankStyle(starRank.getSliderFullSelectedStyle(), fullStar);
    initStarRankStyle(starRank.getSliderFullUnselectedStyle(), fullStar);
    starRank.setPreferredSize(new Dimension(fullStar.getWidth() * 5, fullStar.getHeight()));
    return starRank;
}
private void showStarPickingForm() {
    Form hi = new Form("Star Slider", new BoxLayout(BoxLayout.Y_AXIS));
    hi.add(FlowLayout.encloseCenter(createStarRankSlider()));
    hi.show();
}

In this code you will notice we allow selecting a value between 0 & 10 where 10 is really 5 stars. This allows us
to pick values like 4.5 stars and just divide the actual value. However, most ranking systems don’t allow a value
below 1 star. To solve this you can just use a Label to represent the first star and use a the Slider for the remaining
4 stars. In which case the values would be between 0 – 8.

Terse commands

I love lambdas. I wasn’t a fan before they were introduced but they grew on me and made me a convert.

One of the annoyances I had with Codename One was with using Command syntax which forced me to fallback
to pre-lambda code for practically everything as Command is a class and not a single method interface. This
bothered me enough to do something about it so now we have ActionListener versions of many Command API’s.

These all redirect to the
Command.create(String,Image,ActionListener)
method which effectively creates a Command with the given details for the given action listener. So instead
of writing code like this:

form.getToolbar().addToSideMenu(new Command("My Command") {
     public void actionPerformed(ActionEvent ev) {
         myCodeHere();
     }
});

I can write this:

form.getToolbar().addToSideMenu(Command.create("My Command", null, (ev) -> {
    myCodeHere();
}));

And to make things even simpler I created helper methods that do that implicitly in Toolbar and Form:

form.getToolbar().addToSideMenu("My Command", null, (ev) -> {
    myCodeHere();
});

Notice that the version of this method that accepts a the action listener also returns the created Command instance
which might be useful if you want to do something with the command later on (e.g. remove it). So this should work:

Command cmd = form.getToolbar().addToSideMenu("My Command", null, (ev) -> {
    myCodeHere();
});

9 Comments

Leave a Reply