001package gwt.material.design.addins.client.rating; 002 003/* 004 * #%L 005 * GwtMaterial 006 * %% 007 * Copyright (C) 2016 GwtMaterialDesign 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import com.google.gwt.core.client.GWT; 024import com.google.gwt.event.dom.client.*; 025import com.google.gwt.event.logical.shared.ValueChangeEvent; 026import com.google.gwt.event.logical.shared.ValueChangeHandler; 027import com.google.gwt.event.shared.HandlerRegistration; 028import com.google.gwt.user.client.DOM; 029import com.google.gwt.user.client.ui.HasValue; 030import gwt.material.design.client.base.MaterialWidget; 031import gwt.material.design.client.constants.IconType; 032import gwt.material.design.client.ui.MaterialIcon; 033import gwt.material.design.client.ui.MaterialToast; 034 035import java.util.LinkedList; 036import java.util.List; 037 038/** 039 * <p> 040 * MaterialRating is the component used by the 5-star rating system, for 041 * example, allowing users to easily express their opinion about a product, 042 * review, video and so on. 043 * </p> 044 * <p> 045 * By default, it uses the {@link IconType#STAR} to represent the selected 046 * rating, but other icons can be set using the 047 * {@link #setSelectedRatingIcon(IconType)} method. 048 * </p> 049 * 050 * <h3>XML Namespace Declaration</h3> 051 * 052 * <pre> 053 * {@code 054 * xmlns:ma='urn:import:gwt.material.design.addins.client' 055 * } 056 * </pre> 057 * 058 * <h3>UiBinder Usage:</h3> 059 * 060 * <pre> 061 * {@code 062 * <ma:rating.MaterialRating ui:field="rating" /> 063 * } 064 * </pre> 065 * 066 * To use different icons, for instance, hearts, you can set: 067 * 068 * <pre> 069 * {@code 070 * <ma:rating.MaterialRating ui:field="rating" selectedRatingIcon="FAVORITE" unselectedRatingIcon="FAVORITE_BORDER" textColor="red" /> 071 * } 072 * </pre> 073 * 074 * You can also set the maximum rating (the default is 5): 075 * 076 * <pre> 077 * {@code 078 * <ma:rating.MaterialRating ui:field="rating" maxRating="7" /> 079 * } 080 * </pre> 081 * 082 * <h3>Example Java Usage:</h3> 083 * 084 * <pre> 085 * {@code 086 * MaterialRating rating = ... //create using new or using UiBinder 087 * rating.addValueChangeHandler(...); // MaterialRating implements HasValue<Integer> 088 * rating.setEditable(false); // disables user interaction 089 * rating.setValue(2); // directly sets the desired rating 090 * int selectedValue = rating.getValue(); // retrieves the selected rating 091 * } 092 * </pre> 093 * 094 * <h3>Custom styling:</h3> 095 * <p> 096 * You use change the MaterialRating style by using the 097 * <code>material-rating</code> CSS class. Selected rating icons have the 098 * <code>material-rating-selected</code> CSS class, and unselected the 099 * <code>material-rating-unselected</code> CSS class. 100 * </p> 101 * 102 * @author gilberto-torrezan 103 * 104 */ 105public class MaterialRating extends MaterialWidget implements HasValue<Integer> { 106 107 private boolean editable = true; 108 private int currentRating = 0; 109 private int maxRating = 5; 110 private IconType selectedRatingIcon = IconType.STAR; 111 private IconType unselectedRatingIcon = IconType.STAR_BORDER; 112 private List<MaterialIcon> iconList = new LinkedList<>(); 113 114 /** 115 * Default constructor. 116 */ 117 public MaterialRating() { 118 super(DOM.createDiv(), "material-rating"); 119 revalidateLayout(); 120 } 121 122 /** 123 * Sets the maximum number of icons to show - which represents the maximum 124 * selectable rating. The default is 5. 125 * 126 * @param maxRating 127 * The maximum selectable rating for this component 128 */ 129 public void setMaxRating(int maxRating) { 130 this.maxRating = maxRating; 131 revalidateLayout(); 132 } 133 134 /** 135 * Returns the maximum selectable rating in this component. The default is 136 * 5. 137 * 138 * @return The maximum rating 139 */ 140 public int getMaxRating() { 141 return maxRating; 142 } 143 144 /** 145 * Sets the {@link IconType} to be used to represent the selected ratings. 146 * The default is {@link IconType#STAR}. 147 * 148 * @param selectedRatingIcon 149 * The icon of the selected ratings 150 */ 151 public void setSelectedRatingIcon(IconType selectedRatingIcon) { 152 this.selectedRatingIcon = selectedRatingIcon; 153 revalidateLayout(); 154 } 155 156 /** 157 * Returns the {@link IconType} used to represent the selected ratings. The 158 * default is {@link IconType#STAR}. 159 * 160 * @return The icon for selected ratings 161 */ 162 public IconType getSelectedRatingIcon() { 163 return selectedRatingIcon; 164 } 165 166 /** 167 * Sets the {@link IconType} to be used to represent the not selected 168 * ratings. The default is {@link IconType#STAR_BORDER}. 169 * 170 * @param unselectedRatingIcon 171 * The icon of the unselected ratings 172 */ 173 public void setUnselectedRatingIcon(IconType unselectedRatingIcon) { 174 this.unselectedRatingIcon = unselectedRatingIcon; 175 revalidateLayout(); 176 } 177 178 /** 179 * Returns the {@link IconType} used to represent the not selected ratings. 180 * The default is {@link IconType#STAR_BORDER}. 181 * 182 * @return The icon for unselected ratings 183 */ 184 public IconType getUnselectedRatingIcon() { 185 return unselectedRatingIcon; 186 } 187 188 @Override 189 public void clear() { 190 iconList.clear(); 191 super.clear(); 192 } 193 194 /** 195 * Method called internally by the component to revalidade the number of 196 * icons when the maximum rating is changed. 197 */ 198 protected void revalidateLayout() { 199 for (MaterialIcon icon : iconList) { 200 icon.removeFromParent(); 201 } 202 iconList.clear(); 203 204 // same mouse-out handler for all icons 205 MouseOutHandler outHandler = new MouseOutHandler() { 206 @Override 207 public void onMouseOut(MouseOutEvent event) { 208 if (!isEnabled() || !isEditable()) { 209 return; 210 } 211 revalidateSelection(currentRating); 212 } 213 }; 214 215 for (int i = 0; i < maxRating; i++) { 216 final int rating = i + 1; 217 MaterialIcon icon = new MaterialIcon(unselectedRatingIcon); 218 icon.addClickHandler(new ClickHandler() { 219 @Override 220 public void onClick(ClickEvent event) { 221 if (!isEnabled() || !isEditable()) { 222 return; 223 } 224 setValue(rating, true); 225 } 226 }); 227 icon.addMouseOverHandler(new MouseOverHandler() { 228 @Override 229 public void onMouseOver(MouseOverEvent event) { 230 if (!isEnabled() || !isEditable()) { 231 return; 232 } 233 revalidateSelection(rating); 234 } 235 }); 236 icon.addMouseOutHandler(outHandler); 237 add(icon); 238 iconList.add(icon); 239 } 240 GWT.log(unselectedRatingIcon.getCssName()); 241 revalidateSelection(currentRating); 242 } 243 244 /** 245 * Method called internally by the component to revalidade selections by the 246 * user, switching the icons accordingly. 247 */ 248 protected void revalidateSelection(int rating) { 249 for (MaterialIcon icon : iconList) { 250 icon.removeStyleName("material-rating-unselected"); 251 icon.removeStyleName("material-rating-selected"); 252 } 253 254 for (int i = 0; i < rating && i < iconList.size(); i++) { 255 MaterialIcon icon = iconList.get(i); 256 icon.setIconType(selectedRatingIcon); 257 icon.addStyleName("material-rating-selected"); 258 } 259 for (int i = rating; i < iconList.size(); i++) { 260 MaterialIcon icon = iconList.get(i); 261 icon.setIconType(unselectedRatingIcon); 262 icon.addStyleName("material-rating-unselected"); 263 } 264 } 265 266 @Override 267 public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Integer> handler) { 268 return addHandler(handler, ValueChangeEvent.getType()); 269 } 270 271 @Override 272 public Integer getValue() { 273 return currentRating; 274 } 275 276 @Override 277 public void setValue(Integer value) { 278 setValue(value, false); 279 } 280 281 @Override 282 public void setValue(Integer value, boolean fireEvents) { 283 currentRating = value; 284 revalidateSelection(currentRating); 285 if (fireEvents) { 286 ValueChangeEvent.fire(this, value); 287 } 288 } 289 290 /** 291 * Sets whether the user can interact with the component or not. 292 * Non-editable MaterialRatings can only show values, not allowing users to 293 * change them. The default is <code>true</code> (editable). 294 * 295 * @param editable 296 * <code>true</code> to allow the user change the state of the 297 * component, <code>false</code> otherwise 298 */ 299 public void setEditable(boolean editable) { 300 this.editable = editable; 301 } 302 303 /** 304 * Returns whether the component is editable by the user. The default is 305 * <code>true</code> (editable). 306 * 307 * @return <code>true</code> if the component is editable by the user, 308 * <code>false</code> otherwise' 309 */ 310 public boolean isEditable() { 311 return editable; 312 } 313}