org.apache.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.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.extensions.rating;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.list.Loop;
import org.apache.wicket.markup.html.list.LoopItem;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.request.resource.ResourceReference;
/**
* 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(final String id, final IModel model)
{
super(id, model);
}
@Override
protected void populateItem(final 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;
@Override
public void onClick(final 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.getIndex() + 1, target);
// if we process an AJAX event, update this panel
if (target != null)
{
target.add(RatingPanel.this.get("rater"));
}
}
@Override
public boolean isEnabled()
{
return !hasVoted.getObject();
}
};
int iteration = item.getIndex();
// 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(5);
/**
* The number of votes that have been cast, should result in an Integer object.
*/
private final IModel nrOfVotes;
/**
* The flag on whether the current user has voted already.
*/
private final IModel hasVoted;
/**
* Handle to the rating label to set the visibility.
*/
private Component ratingLabel;
private final boolean addDefaultCssStyle;
/**
* 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(final 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(final String id, final IModel extends Number> rating)
{
this(id, rating, new Model(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(final String id, final int nrOfStars)
{
this(id, null, nrOfStars, 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(final String id, final IModel extends Number> rating, final int nrOfStars,
final boolean addDefaultCssStyle)
{
this(id, rating, new Model(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(final String id, final IModel extends Number> rating, final int nrOfStars,
final IModel nrOfVotes, final boolean addDefaultCssStyle)
{
this(id, rating, new Model(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(final String id, final IModel extends Number> rating,
final IModel nrOfStars, final IModel nrOfVotes,
final IModel hasVoted, final boolean addDefaultCssStyle)
{
super(id, rating);
this.addDefaultCssStyle = addDefaultCssStyle;
this.nrOfStars = wrap(nrOfStars);
this.nrOfVotes = wrap(nrOfVotes);
this.hasVoted = wrap(hasVoted);
WebMarkupContainer rater = new WebMarkupContainer("rater");
rater.add(newRatingStarBar("element", this.nrOfStars));
// add the text label for the message 'Rating 4.5 out of 25 votes'
rater.add(ratingLabel = newRatingLabel("rating", wrap(rating), this.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);
}
@Override
public void renderHead(final IHeaderResponse response)
{
super.renderHead(response);
if (addDefaultCssStyle)
{
response.renderCSSReference(new PackageResourceReference(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(final String id, final 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(final String id, final IModel extends Number> rating,
final IModel nrOfVotes)
{
IModel model;
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(final int iteration)
{
IRequestHandler handler = new ResourceReferenceRequestHandler(STAR1);
return getRequestCycle().urlFor(handler).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(final int iteration)
{
IRequestHandler handler = new ResourceReferenceRequestHandler(STAR0);
return getRequestCycle().urlFor(handler).toString();
}
/**
* Sets the visibility of the rating label.
*
* @param visible
* true when the label should be visible
* @return this for chaining.
*/
public RatingPanel setRatingLabelVisible(final 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);
}