wicket.extensions.rating.RatingPanel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of wicket-extensions Show documentation
Show all versions of wicket-extensions Show documentation
Wicket Extensions is a rich component library for the Wicket framework.
package wicket.extensions.rating;
import wicket.Component;
import wicket.ResourceReference;
import wicket.ajax.AjaxRequestTarget;
import wicket.ajax.markup.html.AjaxFallbackLink;
import wicket.behavior.HeaderContributor;
import wicket.behavior.SimpleAttributeModifier;
import wicket.markup.html.PackageResourceReference;
import wicket.markup.html.WebMarkupContainer;
import wicket.markup.html.basic.Label;
import wicket.markup.html.list.Loop;
import wicket.markup.html.panel.Panel;
import wicket.model.IModel;
import wicket.model.Model;
import wicket.model.StringResourceModel;
/**
* Rating component that generates a number of stars where a user can click on
* to rate something. Subclasses should implement
* {@link #onRated(int, AjaxRequestTarget)} to provide the calculation of the
* rating, and {@link #onIsStarActive(int)} to indicate whether to render an
* active star or an inactive star.
*
* Active stars are the stars that show the rating, inactive stars are the left
* overs. E.G. a rating of 3.4 on a scale of 5 stars will render 3 active stars,
* and 2 inactive stars (provided that the {@link #onIsStarActive(int)} returns
* true
for each of the first three stars).
*
* Use this component in the following way:
*
*
* add(new RatingPanel("rating", new PropertyModel(rating, "rating"), 5)
* {
* protected boolean onIsStarActive(int star)
* {
* return rating.isActive(star);
* }
*
* protected void onRated(int rating, AjaxRequestTarget target)
* {
* rating1.addRating(rating);
* }
* });
*
*
* The user of this component is responsible for creating a model that supplies
* a Double (or Float) value for the rating message, however the rating panel
* doesn't necessarily have to contain a float or number rating value.
*
* Though not obligatory, you could also supply a value for the number of votes
* cast, which allows the component to render a more complete message in the
* rating label.
*
*
Customizing the rating value and label
* To customize the rating value, one should override the
* {@link #newRatingLabel(String, IModel, IModel)} method and create another
* label instead, based on the provided models. If you do so, and use another
* system of rating than returning a Float or Double, then you should also
* customize the rating resource bundle to reflect your message. The default
* resource bundle assumes a numeric value for the rating.
*
* Resource bundle
* This component uses two types of messages: rating.simple and rating.complete.
* The first message is used when no model is given for the number of cast
* votes. The complete message shows the text 'Rating xx.yy from zz votes'.
*
*
* rating.simple=Rated {0,number,#.#}
* rating.complete=Rated {0,number,#.#} from {1,number,#} votes
*
*
* Customizing the star images
* To customize the images shown, override the {@link #getActiveStarUrl(int)}
* and {@link #getInactiveStarUrl(int)} methods. Using the iteration parameter
* it is possible to use a different image for each star, creating a fade effect
* or something similar.
*
* @author Martijn Dashorst
*/
public abstract class RatingPanel extends Panel
{
/**
* Renders the stars and the links necessary for rating.
*/
private final class RatingStarBar extends Loop
{
/** For serialization. */
private static final long serialVersionUID = 1L;
private RatingStarBar(String id, IModel model)
{
super(id, model);
}
protected void populateItem(LoopItem item)
{
// Use an AjaxFallbackLink for rating to make voting work even
// without Ajax.
AjaxFallbackLink link = new AjaxFallbackLink("link")
{
private static final long serialVersionUID = 1L;
public void onClick(AjaxRequestTarget target)
{
LoopItem item = (LoopItem)getParent();
// adjust the rating, and provide the target to the subclass
// of our rating component, so other components can also get
// updated in case of an AJAX event.
onRated(item.getIteration() + 1, target);
// if we process an AJAX event, update this panel
if (target != null)
{
target.addComponent(RatingPanel.this.get("rater"));
}
}
public boolean isEnabled()
{
return !((Boolean)hasVoted.getObject(RatingPanel.this)).booleanValue();
}
};
int iteration = item.getIteration();
// add the star image, which is either active (highlighted) or
// inactive (no star)
link.add(new WebMarkupContainer("star").add(new SimpleAttributeModifier("src",
(onIsStarActive(iteration)
? getActiveStarUrl(iteration)
: getInactiveStarUrl(iteration)))));
item.add(link);
}
}
/** For serialization. */
private static final long serialVersionUID = 1L;
/**
* Star image for no selected star
*/
public static final ResourceReference STAR0 = new PackageResourceReference(RatingPanel.class,
"star0.gif");
/**
* Star image for selected star
*/
public static final ResourceReference STAR1 = new PackageResourceReference(RatingPanel.class,
"star1.gif");
/**
* The number of stars that need to be shown, should result in an Integer
* object.
*/
private IModel nrOfStars = new Model(new Integer(5));
/**
* The number of votes that have been cast, should result in an Integer
* object.
*/
private IModel nrOfVotes;
/**
* The flag on whether the current user has voted already.
*/
private IModel hasVoted;
/**
* Handle to the rating label to set the visibility.
*/
private Component ratingLabel;
/**
* Constructs a rating component with 5 stars, using a compound property
* model as its model to retrieve the rating.
*
* @param id
* the component id.
*/
public RatingPanel(String id)
{
this(id, null, 5, true);
}
/**
* Constructs a rating component with 5 stars, using the rating for
* retrieving the rating.
*
* @param id
* the component id
* @param rating
* the model to get the rating
*/
public RatingPanel(String id, IModel rating)
{
this(id, rating, new Model(new Integer(5)), null, new Model(Boolean.FALSE), true);
}
/**
* Constructs a rating component with nrOfStars stars, using a compound
* property model as its model to retrieve the rating.
*
* @param id
* the component id
* @param nrOfStars
* the number of stars to display
*/
public RatingPanel(String id, int nrOfStars)
{
this(id, null, 5, true);
}
/**
* Constructs a rating component with nrOfStars stars, using the rating for
* retrieving the rating.
*
* @param id
* the component id
* @param rating
* the model to get the rating
* @param nrOfStars
* the number of stars to display
* @param addDefaultCssStyle
* should this component render its own default CSS style?
*/
public RatingPanel(String id, IModel rating, int nrOfStars, boolean addDefaultCssStyle)
{
this(id, rating, new Model(new Integer(nrOfStars)), null, new Model(Boolean.FALSE),
addDefaultCssStyle);
}
/**
* Constructs a rating panel with nrOfStars stars, where the rating model is
* used to retrieve the rating, the nrOfVotes model to retrieve the number
* of casted votes. This panel doens't keep track of whether the user has
* already voted.
*
* @param id
* the component id
* @param rating
* the model to get the rating
* @param nrOfStars
* the number of stars to display
* @param nrOfVotes
* the number of cast votes
* @param addDefaultCssStyle
* should this component render its own default CSS style?
*/
public RatingPanel(String id, IModel rating, int nrOfStars, IModel nrOfVotes,
boolean addDefaultCssStyle)
{
this(id, rating, new Model(new Integer(nrOfStars)), nrOfVotes, new Model(Boolean.FALSE),
addDefaultCssStyle);
}
/**
* Constructs a rating panel with nrOfStars stars, where the rating model is
* used to retrieve the rating, the nrOfVotes model used to retrieve the
* number of votes cast and the hasVoted model to retrieve whether the user
* already had cast a vote.
*
* @param id
* the component id.
* @param rating
* the (calculated) rating, i.e. 3.4
* @param nrOfStars
* the number of stars to display
* @param nrOfVotes
* the number of cast votes
* @param hasVoted
* has the user already voted?
* @param addDefaultCssStyle
* should this component render its own default CSS style?
*/
public RatingPanel(String id, IModel rating, IModel nrOfStars, IModel nrOfVotes,
IModel hasVoted, boolean addDefaultCssStyle)
{
super(id, rating);
this.nrOfStars = nrOfStars;
this.nrOfVotes = nrOfVotes;
this.hasVoted = hasVoted;
WebMarkupContainer rater = new WebMarkupContainer("rater");
rater.add(newRatingStarBar("element", nrOfStars));
// add the text label for the message 'Rating 4.5 out of 25 votes'
rater.add(ratingLabel = newRatingLabel("rating", rating, nrOfVotes));
// set auto generation of the markup id on, such that ajax calls work.
rater.setOutputMarkupId(true);
add(rater);
// don't render the outer tags in the target document, just the div that
// is inside the panel.
setRenderBodyOnly(true);
if (addDefaultCssStyle)
{
addDefaultCssStyle();
}
}
/**
* Will let the rating panel contribute a CSS include to the page's header.
* It will add RatingPanel.css from this package. This method is typically
* called by the class that creates the rating panel.
*/
public final void addDefaultCssStyle()
{
add(HeaderContributor.forCss(RatingPanel.class, "RatingPanel.css"));
}
/**
* Creates a new bar filled with stars to click on.
*
* @param id
* the bar id
* @param nrOfStars
* the number of stars to generate
* @return the bar with rating stars
*/
protected Component newRatingStarBar(String id, IModel nrOfStars)
{
return new RatingStarBar(id, nrOfStars);
}
/**
* Creates a new rating label, showing a message like 'Rated 5.4 from 53
* votes'.
*
* @param id
* the id of the label
* @param rating
* the model containing the rating
* @param nrOfVotes
* the model containing the number of votes (may be null)
* @return the label component showing the message.
*/
protected Component newRatingLabel(String id, IModel rating, IModel nrOfVotes)
{
IModel model = null;
if (nrOfVotes == null)
{
Object[] parameters = new Object[] { rating };
model = new StringResourceModel("rating.simple", this, null, parameters);
}
else
{
Object[] parameters = new Object[] { rating, nrOfVotes };
model = new StringResourceModel("rating.complete", this, null, parameters);
}
return new Label(id, model);
}
/**
* Returns the url pointing to the image of active stars, is used to set the
* URL for the image of an active star. Override this method to provide your
* own images.
*
* @param iteration
* the sequence number of the star
* @return the url pointing to the image for active stars.
*/
protected String getActiveStarUrl(int iteration)
{
return getRequestCycle().urlFor(STAR1).toString();
}
/**
* Returns the url pointing to the image of inactive stars, is used to set
* the URL for the image of an inactive star. Override this method to
* provide your own images.
*
* @param iteration
* the sequence number of the star
* @return the url pointing to the image for inactive stars.
*/
protected String getInactiveStarUrl(int iteration)
{
return getRequestCycle().urlFor(STAR0).toString();
}
/**
* Sets the visibility of the rating label.
*
* @param visible
* true when the label should be visible
* @return this for chaining.
*/
public RatingPanel setRatingLabelVisible(boolean visible)
{
ratingLabel.setVisible(visible);
return this;
}
/**
* Returns true
when the star identified by its sequence
* number should be shown as active.
*
* @param star
* the sequence number of the star (ranging from 0 to nrOfStars)
* @return true
when the star should be rendered as active
*/
protected abstract boolean onIsStarActive(int star);
/**
* Notification of a click on a rating star. Add your own components to the
* request target when you want to have them updated in the Ajax request.
* NB the target may be null when the click isn't handled
* using AJAX, but using a fallback scenario.
*
* @param rating
* the number of the star that is clicked, ranging from 1 to
* nrOfStars
* @param target
* the request target, null if the request is a regular, non-AJAX
* request.
*/
protected abstract void onRated(int rating, AjaxRequestTarget target);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy