package er.coolcomponents;
import com.webobjects.appserver.WOActionResults;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WOElement;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WOResponse;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import er.ajax.AjaxConstantOption;
import er.ajax.AjaxDynamicElement;
import er.ajax.AjaxOption;
import er.ajax.AjaxOptions;
import er.ajax.AjaxUtils;
import er.extensions.appserver.ERXWOContext;
/**
* WebObjects wrapper for LivePipe Rating component, MIT license.
* <p>
* CCRating is a fully customizable CSS based ratings widget. By default it acts as an input. Alternatively, it can notify the
* server when the rating is changed, update the bound value, and call an action method.
* <p>
* It uses four (customizable) CSS class names to determine each link's state:
* <ul>
* <li>rating_off</li>
* <li>rating_half</li>
* <li>rating_on</li>
* <li>rating_selected</li>
* </ul>
*
* <h3>Example Usages</h3>
*
* <pre>
* Rating: CCRating {
* value = rating;
* }
* </pre>
* <pre>
* Rating: CCRating {
* value = rating;
* actAsInput = false;
* }
* </pre>
* <pre>
* Rating: CCRating {
* value = rating;
* actAsInput = false;
* action = rated;
* }
* </pre>
* <pre>
* Rating: CCRating {
* value = rating;
* min = 1;
* max = 10;
* multiple = true;
* }
* </pre>
*
* @binding value the value to show in the ratings widget and the value set when the user selects a different rating
* @binding actAsInput optional, default is <code>true</code>, if false updates the value binding when clicked and optionally calls action method
* @binding action optional, action method to fire when rating changed. Ignored if actAsInput is <code>true</code> or unbound
* @binding min optional, the value sent to the server when the lowest rating is selected, indirectly controls the number of rating points displayed
* @binding max optional, the value sent to the server when the highest rating is selected, indirectly controls the number of rating points displayed
* @binding multiple optional, <code>true</code> if the user can change a previous rating
* @binding id optional, HTML ID for the div and Control.Rating widget
* @binding capture optional, stops the click event on each rating from propagating
* @binding style optional CSS style for container element
* @binding class optional CSS class for container element in addition to the standard rating_container class
* @binding classNames optional, dictionary of state names and CSS class names with state names of: off, half, on, selected
* @binding rated optional, <code>true</code> if this has already been rated
* @binding reverse optional, <code>true</code> if the links should be shown in reverse order
* @binding updateOptions highly optional, Ajax Options for the request
* @binding formValueName optional, the name of the form value that will contain the value
* @binding elementName optional, defaults to div, the name of the HTML element to use to hold the rating UI
* @binding afterChange, optional, script to run client side after a change e.g. afterChange = "alert(v)";. Receives one
* parameter, v, the new value selected
*
* @see <a href="http://livepipe.net/control/rating">Control.Rating</a>
*
* @author chill (WebObjects wrapper only, not LivePipe Rating)
*/
public class CCRating extends AjaxDynamicElement {
public CCRating(String name, NSDictionary associations, WOElement children){
super(name, associations, children);
}
@Override
protected void addRequiredWebResources(WOResponse response, WOContext context) {
// Common resources
addScriptResourceInHead(context, response, "Ajax", "prototype.js");
addScriptResourceInHead(context, response, "Ajax", "effects.js");
addScriptResourceInHead(context, response, "Ajax", "controls.js");
// Library specific resources
addScriptResourceInHead(context, response, "ERCoolComponents", "Rating/livepipe.js");
// Component specific resources
addScriptResourceInHead(context, response, "ERCoolComponents", "Rating/rating.js");
addStylesheetResourceInHead(context, response, "ERCoolComponents", "Rating/rating.css");
}
/**
* Build div, optional input, and JavaScript into response.
*
* @see er.ajax.AjaxDynamicElement#appendToResponse(com.webobjects.appserver.WOResponse, com.webobjects.appserver.WOContext)
*/
@Override
public void appendToResponse(WOResponse response, WOContext context) {
// We don't contain anything, but we need to call super so it calls addRequiredWebResources(WOResponse, WOContext)
super.appendToResponse(response, context);
// Build container element like <div id="e_1_0_0_1_3_7" class="rating_container"></div>
String id = id(context);
String elementName = (String) valueForBinding("elementName", "div", context.component());
response.appendContentString("<");
response.appendContentString(elementName);
response.appendContentString(" ");
appendTagAttributeToResponse(response, "id", id);
String className = "rating_container";
if (hasBinding("class")) {
className += " " + stringValueForBinding("class", context.component());
}
appendTagAttributeToResponse(response, "class", className);
if (hasBinding("style")) {
appendTagAttributeToResponse(response, "style", stringValueForBinding("style", context.component()));
}
response.appendContentString("></");
response.appendContentString(elementName);
response.appendContentString(">");
// Build optional input like <input id="e_1_0_0_1_3_7_input" name="e_1_0_0_1_3_7_value" value="5" type="hidden"/>
if (actAsInput(context)) {
response.appendContentString("<input ");
appendTagAttributeToResponse(response, "id", id + "_input");
appendTagAttributeToResponse(response, "name", formValueName(context));
appendTagAttributeToResponse(response, "value", valueForBinding("value", context.component()));
appendTagAttributeToResponse(response, "type", "hidden");
response.appendContentString("/>");
}
// Build script like
// <script type="text/javascript">
// var e_1_0_0_1_3_7 = new Control.Rating('e_1_0_0_1_3_7',
// {multiple:true, value:5, min:2, max:8, rated:false, input:'e_1_0_0_1_3_7_input', updateParameterName:'e_1_0_0_1_3_7_value'});
// </script>
response.appendContentString("<script type=\"text/javascript\">");
response.appendContentString("var ");
response.appendContentString(id);
response.appendContentString(" = new Control.Rating('");
response.appendContentString(id);
response.appendContentString("', ");
AjaxOptions.appendToResponse(createOptions(context), response, context);
response.appendContentString("); </script>");
}
/**
* Produce dictionary for options object for Control.Rating.
*
* @param context WOContext providing component to resolve bindings in
* @return binding values converted into Ajax options
*/
protected NSMutableDictionary createOptions(WOContext context) {
NSMutableArray ajaxOptionsArray = new NSMutableArray();
// Standard options from Control.Rating
ajaxOptionsArray.addObject(new AjaxOption("min", AjaxOption.NUMBER));
ajaxOptionsArray.addObject(new AjaxOption("max", AjaxOption.NUMBER));
ajaxOptionsArray.addObject(new AjaxOption("value", AjaxOption.NUMBER));
ajaxOptionsArray.addObject(new AjaxOption("capture", AjaxOption.BOOLEAN));
ajaxOptionsArray.addObject(new AjaxOption("classNames", AjaxOption.DICTIONARY));
ajaxOptionsArray.addObject(new AjaxOption("multiple", AjaxOption.BOOLEAN));
ajaxOptionsArray.addObject(new AjaxOption("rated", AjaxOption.BOOLEAN));
ajaxOptionsArray.addObject(new AjaxOption("reverse", AjaxOption.BOOLEAN));
ajaxOptionsArray.addObject(new AjaxOption("afterChange", AjaxOption.FUNCTION_1));
// updateParameterName is renamed to formValueName to be more WO like
ajaxOptionsArray.addObject(new AjaxConstantOption("updateParameterName", "formValueName", formValueName(context), AjaxOption.STRING));
// These parameters are mutually exclusive at present, but dataUpdateUrl could be used with an input if there is a reason for it.
// I can't think of one right now
if ( ! actAsInput(context)) {
ajaxOptionsArray.addObject(new AjaxConstantOption("dataUpdateUrl", AjaxUtils.ajaxComponentActionUrl(context), AjaxOption.STRING));
ajaxOptionsArray.addObject(new AjaxOption("updateOptions", AjaxOption.DICTIONARY));
}
else {
ajaxOptionsArray.addObject(new AjaxConstantOption("input", id(context) + "_input", AjaxOption.STRING));
}
return AjaxOption.createAjaxOptionsDictionary(ajaxOptionsArray, context.component(), associations());
}
/**
* Handles server action if this is not being use as an input. Sets the value binding and calls the optional action method.
*
* @see er.ajax.AjaxDynamicElement#handleRequest(com.webobjects.appserver.WORequest, com.webobjects.appserver.WOContext)
* @see #takeValuesFromRequest(WORequest, WOContext)
*
* @return null, this component returns nothing to the client
*/
@Override
public WOActionResults handleRequest(WORequest request, WOContext context) {
setValueFromFormValue(request, context);
// Nothing gets returned to the client from the CCRating action so we discard any result from firing the action binding
if (hasBinding("action")) {
valueForBinding("action", context.component());
}
return null;
}
/**
* Sets value binding if this is being used as an input.
*
* @see er.ajax.AjaxDynamicElement#takeValuesFromRequest(com.webobjects.appserver.WORequest, com.webobjects.appserver.WOContext)
* @see #handleRequest(WORequest, WOContext)
*/
@Override
public void takeValuesFromRequest(WORequest request, WOContext context) {
if (actAsInput(context)) {
setValueFromFormValue(request, context);
}
super.takeValuesFromRequest(request, context);
}
/**
* Sets the value binding based on the form value.
*
* @see #takeValuesFromRequest(WORequest, WOContext)
* @see #handleRequest(WORequest, WOContext)
*
* @param request the WORequest to get the form values from
* @param context WOContext used to determine component used in
*/
protected void setValueFromFormValue(WORequest request, WOContext context) {
Object ratingValue = request.formValueForKey(formValueName(context));
if (ratingValue instanceof String) {
ratingValue = Integer.valueOf((String)ratingValue);
}
setValueForBinding(ratingValue, "value", context.component());
}
/**
* @param context WOContext used to determine component used in
* @return optional value for formValueName, or calculated value if unbound
*/
protected String formValueName(WOContext context) {
return (String)valueForBinding("formValueName", id(context) + "_value", context.component());
}
/**
* @param context WOContext used to determine component used in
* @return optional value for id, or calculated value if unbound
*/
@Override
public String id(WOContext context) {
return (String) valueForBinding("id", ERXWOContext.safeIdentifierName(context, false), context.component());
}
/**
* @param context WOContext used to determine component used in
* @return optional value for actAsInput, or <code>true</code> if unbound
*/
protected boolean actAsInput(WOContext context) {
return booleanValueForBinding("actAsInput", true, context.component());
}
}