001    /*
002     * Apache License
003     * Version 2.0, January 2004
004     * http://www.apache.org/licenses/
005     *
006     * Copyright 2008-2010 by chenillekit.org
007     *
008     * Licensed under the Apache License, Version 2.0 (the "License");
009     * you may not use this file except in compliance with the License.
010     * You may obtain a copy of the License at
011     *
012     * http://www.apache.org/licenses/LICENSE-2.0
013     */
014    
015    package org.chenillekit.tapestry.core.components;
016    
017    import org.apache.tapestry5.Asset;
018    import org.apache.tapestry5.ComponentResources;
019    import org.apache.tapestry5.MarkupWriter;
020    import org.apache.tapestry5.ValueEncoder;
021    import org.apache.tapestry5.annotations.Component;
022    import org.apache.tapestry5.annotations.Environmental;
023    import org.apache.tapestry5.annotations.Import;
024    import org.apache.tapestry5.annotations.Parameter;
025    import org.apache.tapestry5.annotations.Path;
026    import org.apache.tapestry5.annotations.Property;
027    import org.apache.tapestry5.corelib.base.AbstractField;
028    import org.apache.tapestry5.corelib.components.Label;
029    import org.apache.tapestry5.corelib.components.Loop;
030    import org.apache.tapestry5.corelib.components.Radio;
031    import org.apache.tapestry5.corelib.components.RadioGroup;
032    import org.apache.tapestry5.ioc.annotations.Inject;
033    import org.apache.tapestry5.ioc.services.PropertyAccess;
034    import org.apache.tapestry5.json.JSONObject;
035    import org.apache.tapestry5.services.Environment;
036    import org.apache.tapestry5.services.javascript.JavaScriptSupport;
037    import org.chenillekit.tapestry.core.internal.GenericValueEncoder;
038    
039    import java.util.List;
040    
041    
042    /**
043     * This component provides the ability to associate a RadioGroup and its
044     * subordinate Radio fields with a set of values displayed as a rating scale.
045     * This is typified by the "star field" where grayed stars represent the choices
046     * and highlighted stars represent the chosen value and all values up to the
047     * chosen value from left to right.
048     * <p/>
049     * This is in fact that default visual appearance. However, the images can be
050     * overridden via parameters and the entire component can, of course, be styled
051     * via CSS.
052     * <p/>
053     * As an added benefit, since the underlying representation is simply a
054     * RadioGroup with Radio fields it should degrade well when JS and/or CSS is
055     * disabled. This should keep the component rather accessible.
056     * <p/>
057     * By default the first value display image will be hidden as this typically
058     * will indicate no value.
059     *
060     * @version $Id: RatingField.java 674 2010-07-29 12:47:25Z homburgs $
061     */
062    @Import(library = {"../Chenillekit.js", "Rating.js"}, stylesheet = {"Rating.css"})
063    public class RatingField<T> extends AbstractField
064    {
065            /**
066             * The value to read or update.
067             */
068            @Parameter(required = true)
069            @Property
070            private T value;
071    
072            /**
073             * the rateable value list.
074             */
075            @Parameter(required = true)
076            @Property
077            private List<T> source;
078    
079            /**
080             * Encoder used to translate between server-side objects
081             * and client-side strings.
082             */
083            @Parameter
084            private ValueEncoder encoder;
085    
086            /**
087             * the optional Selected-Image
088             */
089            @Parameter(required = false)
090            private Asset selectedImage;
091    
092            /**
093             * the optional UnSelected-Image
094             */
095            @Parameter(required = false)
096            private Asset unselectedImage;
097    
098            @Inject
099            @Path("rating_default_selected.gif")
100            private Asset defaultSelectedImage;
101    
102            @Inject
103            @Path("rating_default_unselected.gif")
104            private Asset defaultUnselectedImage;
105    
106            @Inject
107            private Environment environment;
108    
109            @Environmental
110            private JavaScriptSupport javascriptSupport;
111    
112            @Inject
113            private ComponentResources componentResources;
114    
115            @Inject
116            private PropertyAccess propertyAccess;
117    
118            @Component(parameters = {"value=prop:value", "encoder=encoder"})
119            private RadioGroup radioGroup;
120    
121            @Component(parameters = {"source=prop:source", "value=loopValue"})
122            private Loop loop;
123    
124            @Property
125            private T loopValue;
126    
127            @Component(parameters = {"value=loopValue", "label=prop:radioLabel"})
128            private Radio radio;
129    
130            @Component(parameters = {"for=radio"})
131            private Label label;
132    
133            /**
134             * Returns the image representing an unselected value.
135             *
136             * @return
137             */
138            public Asset getUnselectedImage()
139            {
140                    return (unselectedImage == null) ? defaultUnselectedImage : unselectedImage;
141            }
142    
143            /**
144             * Returns the image representing a selected value.
145             *
146             * @return
147             */
148            public Asset getSelectedImage()
149            {
150                    return selectedImage == null ? defaultSelectedImage : selectedImage;
151            }
152    
153            /**
154             * Returns an appropriate ValueEncoder implementation based on the value
155             * type.
156             *
157             * @return
158             */
159            @SuppressWarnings("unchecked")
160            public ValueEncoder getEncoder()
161            {
162                    if (encoder == null)
163                            encoder = new GenericValueEncoder(source);
164    
165                    return encoder;
166            }
167    
168            /**
169             * Returns a reasonable label for the radio value. If the value is primitive
170             * it will be returned as is. Otherwise the toString() method will be called
171             * on the value object.
172             *
173             * @return
174             */
175            public String getRadioLabel()
176            {
177                    return loopValue.toString();
178            }
179    
180            /**
181             * Method implemented by subclasses to actually do the work of processing the submission of the form. The element's
182             * elementName property will already have been set. This method is only invoked if the field is <strong>not {@link
183             * #isDisabled() disabled}</strong>.
184             *
185             * @param elementName the name of the element (used to find the correct parameter in the request)
186             */
187            protected void processSubmission(String elementName)
188            {
189            }
190    
191            public void afterRender(MarkupWriter writer)
192            {
193                    JSONObject options = new JSONObject();
194    
195                    options.put("disabled", isDisabled());
196    
197                    //
198                    // Let subclasses do more.
199                    //
200                    configure(options);
201    
202                    javascriptSupport.addScript("new Ck.RatingField('%s', '%s', '%s', %s);",
203                                                                            getClientId(),
204                                                                            getSelectedImage().toClientURL(),
205                                                                            getUnselectedImage().toClientURL(),
206                                                                            options);
207            }
208    
209            /**
210             * Invoked to allow subclasses to further configure the parameters passed to this component's javascript
211             * options. Subclasses may override this method to configure additional features of the Window.
212             * <p/>
213             * This implementation does nothing.
214             *
215             * @param options windows option object
216             */
217            protected void configure(JSONObject options)
218            {
219            }
220    }