This repository has been archived by the owner on Jul 5, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 24
/
IterationCssBehavior.java
267 lines (250 loc) · 9.31 KB
/
IterationCssBehavior.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
/**
* Copyright 2013 55 Minutes (http://www.55minutes.com)
*
* Licensed 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 fiftyfive.wicket.css;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.list.Loop;
import org.apache.wicket.markup.html.list.LoopItem;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.RepeatingView;
/**
* Emits {@code odd}, {@code even}, {@code first} and {@code last} CSS classes (or any
* combination thereof) for repeating components. Supports {@link ListView}, {@link Loop} and
* subclasses of {@link RepeatingView}
* (e.g. {@link org.apache.wicket.markup.repeater.data.DataView DataView}).
* <p>
* To use, attach this behavior to the item being populated. Upon constructing the behavior
* you can specify exactly which classes you would like the behavior to manage. In this example,
* we ask for {@code first} and {@code last} to be emitted to each item in a list:
*
* <pre class="example">
* Example.html
*
* <ul>
* <li wicket:id="items"></li>
* </ul></pre>
*
* <pre class="example">
* Example.java
*
* add(new ListView("items", listOfThreeThings) {
* @Override
* protected void populateItem(ListItem it)
* {
* it.add(new IterationCssBehavior("first", "last"));
* }
* });</pre>
*
* <pre class="example">
* Output
*
* <ul>
* <li class="first"></li>
* <li></li>
* <li class="last"></li>
* </ul></pre>
*
* @since 2.0.4
*/
public class IterationCssBehavior extends CssClassModifier
{
/**
* Valid CSS classes that this behavior can be configured to emit during the rendering
* of repeating components.
*/
public enum CssClass
{
/** The "odd" css class, emitted on odd iterations. The first item is considered odd. */
ODD,
/** The "even" css class, emitted on even iterations. The first item is considered odd. */
EVEN,
/** The "first" css class, emitted on the first iteration only. */
FIRST,
/** The "last" css class, emitted on the last iteration only. */
LAST,
/**
* The "iteration" css class. It is emitted on every iteration, with a suffix added
* representing the iteration number. The numbering starts at 1. In other words:
* {@code iteration1}, {@code iteration2}, {@code iteration3}, etc.
*/
ITERATION
}
private final List<CssClass> classes = new ArrayList<CssClass>();
/**
* Construct a behavior that will output the specified css classes. The behavior must
* be added to a repeating item like an {@link Item} or {@link ListItem}.
* This is a convenience constructor that, while not typesafe, is much more concise.
* <pre class="example">
* // These are equivalent:
* new IterationCssBehavior("odd", "even");
* new IterationCssBehavior(IterationCssBehavior.CssClass.ODD, IterationCssBehavior.CssClass.EVEN);</pre>
*
* @param classes One or more of the 5 classes declared in the {@link CssClass} enum.
*
* @throws IllegalArgumentException if one or more of the specified strings does not
* exactly match a {@link CssClass} value (case-insensitive)
*/
public IterationCssBehavior(String... classes)
{
for(String cls : classes)
{
this.classes.add(CssClass.valueOf(cls.toUpperCase()));
}
}
/**
* Construct a behavior that will output the specified css classes. The behavior must
* be added to a repeating item like an {@link Item} or {@link ListItem}.
*
* @param classes One or more of the 5 classes declared in the {@link CssClass} enum.
*/
public IterationCssBehavior(CssClass... classes)
{
this.classes.addAll(Arrays.asList(classes));
}
/**
* For each of the {@link CssClass} values provided in the constructor, inspect the
* component to which this behavior is bound and determine if the css class is applicable.
* If so, add that class to the Set of classes that will be emitted in the markup.
* For example, if the class is "odd", determine if the component is odd-numbered within
* its repeating view; if so, add "odd" to the classes that will be emitted.
*/
@Override
protected void modifyClasses(Component component, Set<String> values)
{
final int iteration = getIndex(component) + 1;
final int size = getSize(component);
for(CssClass cls : this.classes)
{
switch(cls)
{
case ODD:
if(iteration % 2 == 1)
{
values.remove("even");
values.add("odd");
}
break;
case EVEN:
if(iteration % 2 == 0)
{
values.remove("odd");
values.add("even");
}
break;
case FIRST:
if(1 == iteration)
{
if(size > 1)
{
values.remove("last");
}
values.add("first");
}
break;
case LAST:
if(size == iteration)
{
if(size > 1)
{
values.remove("first");
}
values.add("last");
}
break;
case ITERATION:
values.add("iteration" + iteration);
break;
}
}
}
/**
* Assume that the component is a {@link ListItem},
* {@link LoopItem}, or {@link Item}
* and get its index. Note that the index begins from zero.
*
* @throws UnsupportedOperationException if the component is not one of the three supported
* types
*/
protected int getIndex(Component component)
{
if(component instanceof ListItem)
{
return ((ListItem) component).getIndex();
}
if(component instanceof LoopItem)
{
return ((LoopItem) component).getIndex();
}
if(component instanceof Item)
{
return ((Item) component).getIndex();
}
throw new UnsupportedOperationException(String.format(
"Don't know how to find the index of component %s (%s). " +
"Only list.ListItem, list.LoopItem and repeater.Item are supported. " +
"Perhaps you attached IterationCssBehavior to the wrong component?",
component.getPath(),
component.getClass()));
}
/**
* Assume that the component has an immediate parent of {@link ListView}, {@link Loop}, or
* {@link RepeatingView} and use what we know about those implementations to infer the
* size of the list that is being iterated. Note that in the case of pagination, this is the
* size of the visible items (i.e the current page), not the total size that includes other
* pages.
*
* @throws UnsupportedOperationException if the parent component is not one of the three
* supported types
*/
protected int getSize(Component component)
{
MarkupContainer parent = component.getParent();
if(parent instanceof ListView)
{
return ((ListView) parent).getViewSize();
}
if(parent instanceof Loop)
{
return ((Loop) parent).getIterations();
}
if(parent instanceof RepeatingView)
{
// TODO: more efficent way?
int size = 0;
Iterator iter = parent.iterator();
while(iter.hasNext())
{
iter.next();
size ++;
}
return size;
}
throw new IllegalStateException(String.format(
"Don't know how to find the size of the repeater that contains component " +
"%s (%s). " +
"Only list.ListItem, list.LoopItem and repeater.Item are supported. " +
"Perhaps you attached IterationCssBehavior to the wrong component?",
component.getPath(),
component.getClass()));
}
}