Navigation Menu

Skip to content

Commit

Permalink
Add support for a sortProperty parameter
Browse files Browse the repository at this point in the history
Use the sort property when sorting on the client
  • Loading branch information
hlship committed Jul 12, 2012
1 parent c2f7c48 commit e1ae6df
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 28 deletions.
@@ -1,4 +1,4 @@
// Copyright 2011 Howard M. Lewis Ship // Copyright 2011, 2012 Howard M. Lewis Ship
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -29,10 +29,8 @@
import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONArray; import org.apache.tapestry5.json.JSONArray;
import org.apache.tapestry5.json.JSONObject; import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.BeanModelSource; import org.apache.tapestry5.plastic.PlasticUtils;
import org.apache.tapestry5.services.ComponentDefaultProvider; import org.apache.tapestry5.services.*;
import org.apache.tapestry5.services.FormSupport;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.javascript.JavaScriptSupport; import org.apache.tapestry5.services.javascript.JavaScriptSupport;


import java.util.ArrayList; import java.util.ArrayList;
Expand All @@ -44,17 +42,19 @@
* Both a simplification of the Tapestry {@link Palette} component, and an extension in that it supports * Both a simplification of the Tapestry {@link Palette} component, and an extension in that it supports
* adding new values on the fly, using a form located inside a Modalbox modal dialog. Specifically * adding new values on the fly, using a form located inside a Modalbox modal dialog. Specifically
* limited to editing a <em>Set</em> of values: element order is immaterial, and the UI keeps * limited to editing a <em>Set</em> of values: element order is immaterial, and the UI keeps
* the values sorted in alphabetical order by {@linkplain MultipleSelectModel#toLabel(Object) label}. * the values sorted in alphabetical order by {@linkplain MultipleSelectModel#toLabel(Object) label},
* <p> * or via an alternate property (defined by the sortProperty parameter).
* <p/>
* The UI includes an "Add" button to add a new value of the type appropriate to the set. This sets up a modal dialog on * The UI includes an "Add" button to add a new value of the type appropriate to the set. This sets up a modal dialog on
* the client side, and a uses a server-side {@link BeanEditor} to render the form. Informal blocks bound do this * the client side, and a uses a server-side {@link BeanEditor} to render the form. Informal blocks bound do this
* component will, in turn, be provided to the BeanEditor component for use as property overrides. * component will, in turn, be provided to the BeanEditor component for use as property overrides.
* <p> * <p/>
* TODO: Rename this to SetEditor and delete the current SetEditor. * TODO: Rename this to SetEditor and delete the current SetEditor.
*/ */
@Import(stack = "tapx-core") @Import(stack = "tapx-core")
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@SupportsInformalParameters @SupportsInformalParameters
@Events("configureNewValue: allows the container to configure a new value prior to it being saved")
public class MultipleSelect implements Field public class MultipleSelect implements Field
{ {
/** /**
Expand Down Expand Up @@ -100,6 +100,14 @@ public class MultipleSelect implements Field
@Parameter(defaultPrefix = BindingConstants.LITERAL, value = "message:tapx-multiple-select-available-column-label") @Parameter(defaultPrefix = BindingConstants.LITERAL, value = "message:tapx-multiple-select-available-column-label")
private String availableColumnLabel; private String availableColumnLabel;


/**
* Property used when sorting the options. If null (the default), the sorting is based on the label.
* The property should be a String property, or a numeric value. Numbers are converted to zero-padded strings.
* Really only meant for positive numbers. Floats or decimals will cause a runtime exception.
*/
@Parameter(defaultPrefix = BindingConstants.LITERAL)
private String sortProperty;

/** /**
* Alternate label used to represent a "single" instance of the value; this is used as part of * Alternate label used to represent a "single" instance of the value; this is used as part of
* button labels, and in the title of the modal dialog. * button labels, and in the title of the modal dialog.
Expand Down Expand Up @@ -146,6 +154,10 @@ public class MultipleSelect implements Field
@Inject @Inject
private BeanModelSource beanModelSource; private BeanModelSource beanModelSource;


@Inject

private PropertyConduitSource propertyConduitSource;

Object defaultBeanModel() Object defaultBeanModel()
{ {
return new AbstractBinding() return new AbstractBinding()
Expand All @@ -158,40 +170,79 @@ public boolean isInvariant()


public Object get() public Object get()
{ {
if (newValue == null) Object instance =
return null; newValue != null ? newValue : model.createEmptyInstance();


return beanModelSource.createEditModel(newValue.getClass(), resources.getContainerMessages()); return beanModelSource.createEditModel(instance.getClass(), resources.getContainerMessages());
} }
}; };
} }


private static class Pair implements Comparable<Pair> private static class Option implements Comparable<Option>
{ {
final String label; final String label;


final String clientValue; final String clientValue;


public Pair(String label, String clientValue) final String sortKey;

public Option(String label, String clientValue, String sortKey)
{ {
this.label = label; this.label = label;
this.clientValue = clientValue; this.clientValue = clientValue;
this.sortKey = sortKey;
} }


public int compareTo(Pair o) public int compareTo(Option o)
{ {
return this.label.compareTo(o.label); return this.sortKey.compareTo(o.sortKey);
} }
} }


private final Mapper<Object, Pair> toPair = new Mapper<Object, Pair>() private final Mapper<Object, Option> toPair = new Mapper<Object, Option>()
{ {
public Pair map(Object value) public Option map(Object value)
{ {
return new Pair(model.toLabel(value), model.toClient(value));
String sortValue = toSortKey(value);

return new Option(model.toLabel(value), model.toClient(value), sortValue);
} }
}; };


private String toSortKey(Object value)
{

if (sortProperty == null)
{
return model.toLabel(value);
}


PropertyConduit conduit = propertyConduitSource.create(value.getClass(), sortProperty);

Class propertyType = conduit.getPropertyType();
Object rawKey = conduit.get(value);

if (propertyType == String.class)
{
return rawKey.toString();
}

if (Number.class.isInstance(rawKey))
{
// 19 digits is enough to encode a long value. That's overkill. In the future, we can use a StrategyRegistry
// to adapt this to the type appropriately.

return String.format("%019d", rawKey);
}

throw new IllegalArgumentException(String.format("Sort property '%s' of %s is type %s which can not be converted to a sort key.",
sortProperty,
PlasticUtils.toTypeName(value.getClass()),
PlasticUtils.toTypeName(propertyType)));
}

public static class ProcessSubmission implements ComponentAction<MultipleSelect> public static class ProcessSubmission implements ComponentAction<MultipleSelect>
{ {
private static final long serialVersionUID = -7656903896600563715L; private static final long serialVersionUID = -7656903896600563715L;
Expand Down Expand Up @@ -234,13 +285,17 @@ public String getLabel()
return label; return label;
} }


/** Always returns false. */ /**
* Always returns false.
*/
public boolean isDisabled() public boolean isDisabled()
{ {
return false; return false;
} }


/** Always returns false. */ /**
* Always returns false.
*/
public boolean isRequired() public boolean isRequired()
{ {
return false; return false;
Expand Down Expand Up @@ -274,9 +329,9 @@ void setupRender()


Flow<Object> valuesFlow = F.flow(model.getAvailableValues()); Flow<Object> valuesFlow = F.flow(model.getAvailableValues());


for (Pair pair : valuesFlow.map(toPair).sort()) for (Option option : valuesFlow.map(toPair).sort())
{ {
spec.append("model", new JSONArray(pair.clientValue, pair.label)); spec.append("model", new JSONArray(option.clientValue, option.label, option.sortKey));
} }


jss.addInitializerCall("tapxMultipleSelect", spec); jss.addInitializerCall("tapxMultipleSelect", spec);
Expand Down Expand Up @@ -339,20 +394,26 @@ Object onNewValue(String clientId)
return editor; return editor;
} }


/** Event handler triggered when the modal dialog is submitted. */ /**
* Event handler triggered when the modal dialog is submitted.
*/
void onPrepareForSubmitFromNewValue(String clientId) void onPrepareForSubmitFromNewValue(String clientId)
{ {
this.clientId = clientId; this.clientId = clientId;
} }


/** Event handler when preparing to render or submit the new value form; creates a new empty instance. */ /**
* Event handler when preparing to render or submit the new value form; creates a new empty instance.
*/
void onPrepareFromNewValue() void onPrepareFromNewValue()
{ {
newValue = model.createEmptyInstance(); newValue = model.createEmptyInstance();
} }


Object onSuccessFromNewValue() Object onSuccessFromNewValue()
{ {
resources.triggerEvent("configureNewValue", new Object[]{newValue}, null);

// Save the new value to the database (or whatever it takes to assign a propery id to it). // Save the new value to the database (or whatever it takes to assign a propery id to it).


model.persistNewInstance(newValue); model.persistNewInstance(newValue);
Expand All @@ -365,8 +426,10 @@ Object onSuccessFromNewValue()


void onWriteSuccessJavaScript() void onWriteSuccessJavaScript()
{ {
JSONObject spec = new JSONObject("clientId", clientId, "clientValue", model.toClient(newValue), "label", JSONObject spec = new JSONObject("clientId", clientId,
model.toLabel(newValue)); "clientValue", model.toClient(newValue),
"label", model.toLabel(newValue),
"sort", toSortKey(newValue));


jss.addInitializerCall("tapxMultipleSelectNewValue", spec); jss.addInitializerCall("tapxMultipleSelectNewValue", spec);
} }
Expand Down
Expand Up @@ -31,8 +31,9 @@ T5.extendInitializers(function () {


function moveOption(option, to) { function moveOption(option, to) {


var optionSort = option.readAttribute("data-sort");
var before = $A(to.options).detect(function (targetOption) { var before = $A(to.options).detect(function (targetOption) {
return targetOption.innerHTML > option.innerHTML; return targetOption.readAttribute("data-sort") > optionSort;
}); });


if (Prototype.IE) { if (Prototype.IE) {
Expand Down Expand Up @@ -127,10 +128,15 @@ T5.extendInitializers(function () {
(spec.model || []).each(function (row) { (spec.model || []).each(function (row) {


var valueId = row[0]; var valueId = row[0];
var label = row[1];
var sort = row[2];

var selected = (spec.values || []).include(valueId); var selected = (spec.values || []).include(valueId);
var selectElement = selected ? selectedSelect : availableSelect; var selectElement = selected ? selectedSelect : availableSelect;


var option = new Element("option").update(row[1].escapeHTML()); var option = new Element("option").update(label.escapeHTML());

option.writeAttribute("data-sort", sort);


option.txClientValue = valueId; option.txClientValue = valueId;


Expand Down Expand Up @@ -193,6 +199,8 @@ T5.extendInitializers(function () {


var option = new Element("option").update(spec.label.escapeHTML()); var option = new Element("option").update(spec.label.escapeHTML());


option.writeAttribute("data-sort", spec.sort);

option.txClientValue = spec.clientValue; option.txClientValue = spec.clientValue;


$(spec.clientId).fire("tapx:multiselect:newvalue", option); $(spec.clientId).fire("tapx:multiselect:newvalue", option);
Expand Down

0 comments on commit e1ae6df

Please sign in to comment.