1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.tobago.internal.renderkit.renderer;
21
22 import org.apache.myfaces.tobago.component.Attributes;
23 import org.apache.myfaces.tobago.component.Facets;
24 import org.apache.myfaces.tobago.component.LabelLayout;
25 import org.apache.myfaces.tobago.component.RendererTypes;
26 import org.apache.myfaces.tobago.component.Tags;
27 import org.apache.myfaces.tobago.context.Markup;
28 import org.apache.myfaces.tobago.event.PageActionEvent;
29 import org.apache.myfaces.tobago.event.SheetAction;
30 import org.apache.myfaces.tobago.event.SortActionEvent;
31 import org.apache.myfaces.tobago.internal.component.AbstractUIColumn;
32 import org.apache.myfaces.tobago.internal.component.AbstractUIColumnBase;
33 import org.apache.myfaces.tobago.internal.component.AbstractUIColumnNode;
34 import org.apache.myfaces.tobago.internal.component.AbstractUIColumnSelector;
35 import org.apache.myfaces.tobago.internal.component.AbstractUIData;
36 import org.apache.myfaces.tobago.internal.component.AbstractUILink;
37 import org.apache.myfaces.tobago.internal.component.AbstractUIOut;
38 import org.apache.myfaces.tobago.internal.component.AbstractUIReload;
39 import org.apache.myfaces.tobago.internal.component.AbstractUIRow;
40 import org.apache.myfaces.tobago.internal.component.AbstractUISheet;
41 import org.apache.myfaces.tobago.internal.component.AbstractUIStyle;
42 import org.apache.myfaces.tobago.internal.layout.Cell;
43 import org.apache.myfaces.tobago.internal.layout.Grid;
44 import org.apache.myfaces.tobago.internal.layout.OriginCell;
45 import org.apache.myfaces.tobago.internal.renderkit.CommandMap;
46 import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
47 import org.apache.myfaces.tobago.internal.util.JsonUtils;
48 import org.apache.myfaces.tobago.internal.util.RenderUtils;
49 import org.apache.myfaces.tobago.layout.ShowPosition;
50 import org.apache.myfaces.tobago.layout.TextAlign;
51 import org.apache.myfaces.tobago.layout.VerticalAlign;
52 import org.apache.myfaces.tobago.model.ExpandedState;
53 import org.apache.myfaces.tobago.model.Selectable;
54 import org.apache.myfaces.tobago.model.SheetState;
55 import org.apache.myfaces.tobago.model.TreePath;
56 import org.apache.myfaces.tobago.renderkit.RendererBase;
57 import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
58 import org.apache.myfaces.tobago.renderkit.css.CssItem;
59 import org.apache.myfaces.tobago.renderkit.css.Icons;
60 import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
61 import org.apache.myfaces.tobago.renderkit.html.CustomAttributes;
62 import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
63 import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
64 import org.apache.myfaces.tobago.renderkit.html.HtmlButtonTypes;
65 import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
66 import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
67 import org.apache.myfaces.tobago.util.AjaxUtils;
68 import org.apache.myfaces.tobago.util.ComponentUtils;
69 import org.apache.myfaces.tobago.util.ResourceUtils;
70 import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
71 import org.slf4j.Logger;
72 import org.slf4j.LoggerFactory;
73
74 import javax.el.ValueExpression;
75 import javax.faces.application.Application;
76 import javax.faces.component.NamingContainer;
77 import javax.faces.component.UIColumn;
78 import javax.faces.component.UIComponent;
79 import javax.faces.component.UIData;
80 import javax.faces.component.UINamingContainer;
81 import javax.faces.component.behavior.AjaxBehavior;
82 import javax.faces.component.behavior.ClientBehavior;
83 import javax.faces.component.behavior.ClientBehaviorHolder;
84 import javax.faces.context.FacesContext;
85 import java.io.IOException;
86 import java.lang.invoke.MethodHandles;
87 import java.text.MessageFormat;
88 import java.util.ArrayList;
89 import java.util.Collections;
90 import java.util.List;
91 import java.util.Locale;
92 import java.util.Map;
93
94 public class SheetRenderer<T extends AbstractUISheet> extends RendererBase<T> {
95
96 private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
97
98 private static final String SUFFIX_WIDTHS = ComponentUtils.SUB_SEPARATOR + "widths";
99 private static final String SUFFIX_COLUMN_RENDERED = ComponentUtils.SUB_SEPARATOR + "rendered";
100 private static final String SUFFIX_SCROLL_POSITION = ComponentUtils.SUB_SEPARATOR + "scrollPosition";
101 private static final String SUFFIX_SELECTED = ComponentUtils.SUB_SEPARATOR + "selected";
102 private static final String SUFFIX_LAZY = NamingContainer.SEPARATOR_CHAR + "pageActionlazy";
103 private static final String SUFFIX_PAGE_ACTION = "pageAction";
104
105 @Override
106 public void decodeInternal(final FacesContext facesContext, final T component) {
107
108 final List<AbstractUIColumnBase> columns = component.getAllColumns();
109 final String clientId = component.getClientId(facesContext);
110
111 String key = clientId + SUFFIX_WIDTHS;
112 final Map<String, String> requestParameterMap = facesContext.getExternalContext().getRequestParameterMap();
113 final SheetState state = component.getState();
114 if (requestParameterMap.containsKey(key)) {
115 final String widths = requestParameterMap.get(key);
116 ensureColumnWidthsSize(state.getColumnWidths(), columns, JsonUtils.decodeIntegerArray(widths));
117 }
118
119 key = clientId + SUFFIX_SELECTED;
120 if (requestParameterMap.containsKey(key)) {
121 final String selected = requestParameterMap.get(key);
122 if (LOG.isDebugEnabled()) {
123 LOG.debug("selected = " + selected);
124 }
125 List<Integer> selectedRows;
126 try {
127 selectedRows = JsonUtils.decodeIntegerArray(selected);
128 } catch (final NumberFormatException e) {
129 LOG.warn(selected, e);
130 selectedRows = Collections.emptyList();
131 }
132
133 ComponentUtils.setAttribute(component, Attributes.selectedListString, selectedRows);
134 }
135
136 final String value
137 = facesContext.getExternalContext().getRequestParameterMap().get(clientId + SUFFIX_SCROLL_POSITION);
138 if (value != null) {
139 state.getScrollPosition().update(value);
140 }
141 RenderUtils.decodedStateOfTreeData(facesContext, component);
142
143 decodeSheetAction(facesContext, component);
144 decodeColumnAction(facesContext, columns);
145
146
147
148
149
150 }
151
152 private void decodeColumnAction(final FacesContext facesContext, final List<AbstractUIColumnBase> columns) {
153 for (final AbstractUIColumnBase column : columns) {
154 final boolean sortable = ComponentUtils.getBooleanAttribute(column, Attributes.sortable);
155 if (sortable) {
156 final String sourceId = facesContext.getExternalContext().getRequestParameterMap().get("javax.faces.source");
157 final String columnId = column.getClientId(facesContext);
158 final String sorterId = columnId + "_" + AbstractUISheet.SORTER_ID;
159
160 if (sorterId.equals(sourceId)) {
161 final UIData data = (UIData) column.getParent();
162 data.queueEvent(new SortActionEvent(data, column));
163 }
164 }
165 }
166 }
167
168
169 private void decodeSheetAction(final FacesContext facesContext, final AbstractUISheet component) {
170 final String sourceId = facesContext.getExternalContext().getRequestParameterMap().get("javax.faces.source");
171
172 final String clientId = component.getClientId(facesContext);
173 if (LOG.isDebugEnabled()) {
174 LOG.debug("sourceId = '{}'", sourceId);
175 LOG.debug("clientId = '{}'", clientId);
176 }
177
178 final String sheetClientIdWithAction
179 = clientId + UINamingContainer.getSeparatorChar(facesContext) + SUFFIX_PAGE_ACTION;
180 if (sourceId != null && sourceId.startsWith(sheetClientIdWithAction)) {
181 String actionString = sourceId.substring(sheetClientIdWithAction.length());
182 int index = actionString.indexOf('-');
183 SheetAction action;
184 if (index != -1) {
185 action = SheetAction.valueOf(actionString.substring(0, index));
186 } else {
187 action = SheetAction.valueOf(actionString);
188 }
189 PageActionEvent event = null;
190 switch (action) {
191 case first:
192 case prev:
193 case next:
194 case last:
195 event = new PageActionEvent(component, action);
196 break;
197 case toPage:
198 case toRow:
199 case lazy:
200 event = new PageActionEvent(component, action);
201 final int target;
202 final String value;
203 if (index == -1) {
204 final Map<String, String> map = facesContext.getExternalContext().getRequestParameterMap();
205 value = map.get(sourceId);
206 } else {
207 value = actionString.substring(index + 1);
208 }
209 try {
210 target = Integer.parseInt(value);
211 } catch (final NumberFormatException e) {
212 LOG.error("Can't parse integer value for action " + action.name() + ": " + value);
213 break;
214 }
215 event.setValue(target);
216 break;
217 default:
218 }
219 if (event != null) {
220 component.queueEvent(event);
221 }
222 }
223 }
224
225 @Override
226 public void encodeBeginInternal(final FacesContext facesContext, final T component) throws IOException {
227
228 final String sheetId = component.getClientId(facesContext);
229 final Markup markup = component.getMarkup();
230 final TobagoResponseWriter writer = getResponseWriter(facesContext);
231 final AbstractUIReload reload = ComponentUtils.getReloadFacet(component);
232
233 UIComponent header = component.getHeader();
234 if (header == null) {
235 header = ComponentUtils.createComponent(facesContext, Tags.panel.componentType(), null, "_header");
236 header.setTransient(true);
237 final List<AbstractUIColumnBase> columns = component.getAllColumns();
238 int i = 0;
239 for (final AbstractUIColumnBase column : columns) {
240 if (!(column instanceof AbstractUIRow)) {
241 final AbstractUIOut out = (AbstractUIOut) ComponentUtils.createComponent(
242 facesContext, Tags.out.componentType(), RendererTypes.Out, "_col" + i);
243
244 out.setTransient(true);
245 ValueExpression valueExpression = column.getValueExpression(Attributes.label.getName());
246 if (valueExpression != null) {
247 out.setValueExpression(Attributes.value.getName(), valueExpression);
248 } else {
249 out.setValue(ComponentUtils.getAttribute(column, Attributes.label));
250 }
251 valueExpression = column.getValueExpression(Attributes.rendered.getName());
252 if (valueExpression != null) {
253 out.setValueExpression(Attributes.rendered.getName(), valueExpression);
254 } else {
255 out.setRendered(ComponentUtils.getBooleanAttribute(column, Attributes.rendered));
256 }
257 out.setLabelLayout(LabelLayout.skip);
258 header.getChildren().add(out);
259 }
260 i++;
261 }
262 component.setHeader(header);
263 }
264 component.init(facesContext);
265
266
267 writer.startElement(HtmlElements.TOBAGO_SHEET);
268 writer.writeIdAttribute(sheetId);
269 HtmlRendererUtils.writeDataAttributes(facesContext, writer, component);
270 writer.writeClassAttribute(
271 component.getCustomClass(),
272 TobagoClass.SHEET.createMarkup(markup),
273 markup != null && markup.contains(Markup.SPREAD) ? TobagoClass.SPREAD : null);
274 if (reload != null && reload.isRendered()) {
275 writer.writeAttribute(DataAttributes.RELOAD, reload.getFrequency());
276 }
277 writer.writeAttribute(DataAttributes.SELECTION_MODE, component.getSelectable().name(), false);
278 writer.writeAttribute(DataAttributes.FIRST, Integer.toString(component.getFirst()), false);
279 writer.writeAttribute(CustomAttributes.ROWS, component.getRows());
280 writer.writeAttribute(CustomAttributes.ROW_COUNT, Integer.toString(component.getRowCount()), false);
281 writer.writeAttribute(CustomAttributes.LAZY, component.isLazy());
282 writer.writeAttribute(CustomAttributes.LAZY_UPDATE, component.isLazy() && AjaxUtils.isAjaxRequest(facesContext));
283
284 final boolean autoLayout = component.isAutoLayout();
285 if (!autoLayout) {
286 writer.writeAttribute(DataAttributes.LAYOUT, JsonUtils.encode(component.getColumnLayout(), "columns"), true);
287 }
288
289 encodeBehavior(writer, facesContext, component);
290 }
291
292 @Override
293 public void encodeChildrenInternal(final FacesContext facesContext, final T component) throws IOException {
294 for (final UIComponent child : component.getChildren()) {
295 if (child instanceof AbstractUIStyle) {
296 child.encodeAll(facesContext);
297 }
298 }
299 }
300
301 @Override
302 public void encodeEndInternal(final FacesContext facesContext, final T component) throws IOException {
303
304 final TobagoResponseWriter writer = getResponseWriter(facesContext);
305
306 final String sheetId = component.getClientId(facesContext);
307 final Selectable selectable = component.getSelectable();
308 final Application application = facesContext.getApplication();
309 final SheetState state = component.getSheetState(facesContext);
310 final List<Integer> columnWidths = component.getState().getColumnWidths();
311 final boolean definedColumnWidths = component.getState().isDefinedColumnWidths();
312 final List<Integer> selectedRows = getSelectedRows(component, state);
313 final List<AbstractUIColumnBase> columns = component.getAllColumns();
314 final boolean autoLayout = component.isAutoLayout();
315
316 ensureColumnWidthsSize(columnWidths, columns, Collections.emptyList());
317
318 if (!autoLayout) {
319 encodeHiddenInput(writer,
320 JsonUtils.encode(definedColumnWidths ? columnWidths : Collections.emptyList()),
321 sheetId + SUFFIX_WIDTHS);
322
323 final ArrayList<Boolean> encodedRendered = new ArrayList<>();
324 for (final AbstractUIColumnBase column : columns) {
325 if (!(column instanceof AbstractUIRow)) {
326 encodedRendered.add(column.isRendered());
327 }
328 }
329
330 encodeHiddenInput(writer,
331 JsonUtils.encode(encodedRendered.toArray(new Boolean[0])),
332 sheetId + SUFFIX_COLUMN_RENDERED);
333 }
334
335 encodeHiddenInput(writer,
336 component.getState().getScrollPosition().encode(),
337 component.getClientId(facesContext) + SUFFIX_SCROLL_POSITION);
338
339 if (selectable != Selectable.none) {
340 encodeHiddenInput(writer,
341 JsonUtils.encode(selectedRows),
342 sheetId + SUFFIX_SELECTED);
343 }
344
345 if (component.isLazy()) {
346 encodeHiddenInput(writer, null, sheetId + SUFFIX_LAZY);
347 }
348
349 final List<Integer> expandedValue = component.isTreeModel() ? new ArrayList<>() : null;
350
351 encodeTableBody(
352 facesContext, component, writer, sheetId, selectable, columnWidths, selectedRows, columns, autoLayout,
353 expandedValue);
354
355 if (component.isPagingVisible()) {
356 writer.startElement(HtmlElements.FOOTER);
357 writer.writeClassAttribute(TobagoClass.SHEET__FOOTER);
358
359
360 final Markup showRowRange = markupForLeftCenterRight(component.getShowRowRange());
361 if (showRowRange != Markup.NULL) {
362 final AbstractUILink command
363 = ensurePagingCommand(facesContext, component, Facets.pagerRow.name(), SheetAction.toRow.name(), false);
364 final String pagerCommandId = command.getClientId(facesContext);
365
366 writer.startElement(HtmlElements.UL);
367 writer.writeClassAttribute(TobagoClass.SHEET__PAGING, TobagoClass.SHEET__PAGING.createMarkup(showRowRange),
368 BootstrapClass.PAGINATION);
369 writer.startElement(HtmlElements.LI);
370 writer.writeClassAttribute(BootstrapClass.PAGE_ITEM);
371 writer.writeAttribute(HtmlAttributes.TITLE,
372 ResourceUtils.getString(facesContext, "sheet.setRow"), true);
373 writer.startElement(HtmlElements.SPAN);
374 writer.writeClassAttribute(TobagoClass.SHEET__PAGING_TEXT, BootstrapClass.PAGE_LINK);
375 if (component.getRowCount() != 0) {
376 final Locale locale = facesContext.getViewRoot().getLocale();
377 final int first = component.getFirst() + 1;
378 final int last1 = component.hasRowCount()
379 ? component.getLastRowIndexOfCurrentPage()
380 : -1;
381 final boolean unknown = !component.hasRowCount();
382 final String key;
383 if (unknown) {
384 key = first == last1 ? "sheet.rowX" : "sheet.rowXtoY";
385 } else {
386 key = first == last1 ? "sheet.rowXofZ" : "sheet.rowXtoYofZ";
387 }
388 final String inputMarker = "{#}";
389 final Object[] args = {inputMarker, last1 == -1 ? "?" : last1, unknown ? "" : component.getRowCount()};
390 final MessageFormat detail = new MessageFormat(ResourceUtils.getString(facesContext, key), locale);
391 final String formatted = detail.format(args);
392 final int pos = formatted.indexOf(inputMarker);
393 if (pos >= 0) {
394 writer.writeText(formatted.substring(0, pos));
395 writer.startElement(HtmlElements.SPAN);
396 writer.writeClassAttribute(TobagoClass.SHEET__PAGING_OUTPUT);
397 writer.writeText(Integer.toString(first));
398 writer.endElement(HtmlElements.SPAN);
399 writer.startElement(HtmlElements.INPUT);
400 writer.writeIdAttribute(pagerCommandId);
401 writer.writeNameAttribute(pagerCommandId);
402 writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.TEXT);
403 writer.writeClassAttribute(TobagoClass.SHEET__PAGING_INPUT);
404 writer.writeAttribute(HtmlAttributes.VALUE, first);
405 if (!unknown) {
406 writer.writeAttribute(HtmlAttributes.MAXLENGTH, Integer.toString(component.getRowCount()).length());
407 }
408 writer.endElement(HtmlElements.INPUT);
409 writer.writeText(formatted.substring(pos + inputMarker.length()));
410 } else {
411 writer.writeText(formatted);
412 }
413 }
414 ComponentUtils.removeFacet(component, Facets.pagerRow);
415 writer.endElement(HtmlElements.SPAN);
416 writer.endElement(HtmlElements.LI);
417 writer.endElement(HtmlElements.UL);
418 }
419
420
421 final Markup showDirectLinks = markupForLeftCenterRight(component.getShowDirectLinks());
422 if (showDirectLinks != Markup.NULL) {
423 writer.startElement(HtmlElements.UL);
424 writer.writeClassAttribute(TobagoClass.SHEET__PAGING, TobagoClass.SHEET__PAGING.createMarkup(showDirectLinks),
425 BootstrapClass.PAGINATION);
426 if (component.isShowDirectLinksArrows()) {
427 final boolean disabled = component.isAtBeginning();
428 encodeLink(
429 facesContext, component, application, disabled, SheetAction.first, null, Icons.STEP_BACKWARD, null);
430 encodeLink(facesContext, component, application, disabled, SheetAction.prev, null, Icons.BACKWARD, null);
431 }
432 encodeDirectPagingLinks(facesContext, application, component);
433 if (component.isShowDirectLinksArrows()) {
434 final boolean disabled = component.isAtEnd();
435 encodeLink(facesContext, component, application, disabled, SheetAction.next, null, Icons.FORWARD, null);
436 encodeLink(facesContext, component, application, disabled || !component.hasRowCount(), SheetAction.last, null,
437 Icons.STEP_FORWARD, null);
438 }
439 writer.endElement(HtmlElements.UL);
440 }
441
442
443 final Markup showPageRange = markupForLeftCenterRight(component.getShowPageRange());
444 if (showPageRange != Markup.NULL) {
445 final AbstractUILink command
446 = ensurePagingCommand(facesContext, component, Facets.pagerPage.name(), SheetAction.toPage.name(), false);
447 final String pagerCommandId = command.getClientId(facesContext);
448
449 writer.startElement(HtmlElements.UL);
450 writer.writeClassAttribute(TobagoClass.SHEET__PAGING, TobagoClass.SHEET__PAGING.createMarkup(showPageRange),
451 BootstrapClass.PAGINATION);
452 if (component.isShowPageRangeArrows()) {
453 final boolean disabled = component.isAtBeginning();
454 encodeLink(
455 facesContext, component, application, disabled, SheetAction.first, null, Icons.STEP_BACKWARD, null);
456 encodeLink(facesContext, component, application, disabled, SheetAction.prev, null, Icons.BACKWARD, null);
457 }
458 writer.startElement(HtmlElements.LI);
459 writer.writeClassAttribute(BootstrapClass.PAGE_ITEM);
460 writer.startElement(HtmlElements.SPAN);
461 writer.writeClassAttribute(TobagoClass.SHEET__PAGING_TEXT, BootstrapClass.PAGE_LINK);
462 writer.writeAttribute(HtmlAttributes.TITLE,
463 ResourceUtils.getString(facesContext, "sheet.setPage"), true);
464 if (component.getRowCount() != 0) {
465 final Locale locale = facesContext.getViewRoot().getLocale();
466 final int first = component.getCurrentPage() + 1;
467 final boolean unknown = !component.hasRowCount();
468 final int pages = unknown ? -1 : component.getPages();
469 final String key;
470 if (unknown) {
471 key = first == pages ? "sheet.pageX" : "sheet.pageXtoY";
472 } else {
473 key = first == pages ? "sheet.pageXofZ" : "sheet.pageXtoYofZ";
474 }
475 final String inputMarker = "{#}";
476 final Object[] args = {inputMarker, pages == -1 ? "?" : pages};
477 final MessageFormat detail = new MessageFormat(ResourceUtils.getString(facesContext, key), locale);
478 final String formatted = detail.format(args);
479 final int pos = formatted.indexOf(inputMarker);
480 if (pos >= 0) {
481 writer.writeText(formatted.substring(0, pos));
482 writer.startElement(HtmlElements.SPAN);
483 writer.writeClassAttribute(TobagoClass.SHEET__PAGING_OUTPUT);
484 writer.writeText(Integer.toString(first));
485 writer.endElement(HtmlElements.SPAN);
486 writer.startElement(HtmlElements.INPUT);
487 writer.writeIdAttribute(pagerCommandId);
488 writer.writeNameAttribute(pagerCommandId);
489 writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.TEXT);
490 writer.writeClassAttribute(TobagoClass.SHEET__PAGING_INPUT);
491 writer.writeAttribute(HtmlAttributes.VALUE, first);
492 if (!unknown) {
493 writer.writeAttribute(HtmlAttributes.MAXLENGTH, Integer.toString(pages).length());
494 }
495 writer.endElement(HtmlElements.INPUT);
496 writer.writeText(formatted.substring(pos + inputMarker.length()));
497 } else {
498 writer.writeText(formatted);
499 }
500 }
501 ComponentUtils.removeFacet(component, Facets.pagerPage);
502 writer.endElement(HtmlElements.SPAN);
503 writer.endElement(HtmlElements.LI);
504 if (component.isShowPageRangeArrows()) {
505 final boolean disabled = component.isAtEnd();
506 encodeLink(facesContext, component, application, disabled, SheetAction.next, null, Icons.FORWARD, null);
507 encodeLink(facesContext, component, application, disabled || !component.hasRowCount(), SheetAction.last, null,
508 Icons.STEP_FORWARD, null);
509 }
510 writer.endElement(HtmlElements.UL);
511 }
512
513 writer.endElement(HtmlElements.FOOTER);
514 }
515
516 if (component.isTreeModel()) {
517 writer.startElement(HtmlElements.INPUT);
518 writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN);
519 final String expandedId = sheetId + ComponentUtils.SUB_SEPARATOR + AbstractUIData.SUFFIX_EXPANDED;
520 writer.writeNameAttribute(expandedId);
521 writer.writeIdAttribute(expandedId);
522 writer.writeClassAttribute(TobagoClass.SHEET__EXPANDED);
523 writer.writeAttribute(HtmlAttributes.VALUE, JsonUtils.encode(expandedValue), false);
524 writer.endElement(HtmlElements.INPUT);
525 }
526
527 writer.endElement(HtmlElements.TOBAGO_SHEET);
528 UIComponent header = component.getHeader();
529 if (header.isTransient()) {
530 component.getFacets().remove("header");
531 }
532 }
533
534 private void encodeTableBody(
535 final FacesContext facesContext, final AbstractUISheet sheet, final TobagoResponseWriter writer,
536 final String sheetId,
537 final Selectable selectable, final List<Integer> columnWidths, final List<Integer> selectedRows,
538 final List<AbstractUIColumnBase> columns, final boolean autoLayout, final List<Integer> expandedValue)
539 throws IOException {
540
541 final boolean showHeader = sheet.isShowHeader();
542 final Markup sheetMarkup = sheet.getMarkup() != null ? sheet.getMarkup() : Markup.NULL;
543 final ExpandedState expandedState = sheet.isTreeModel() ? sheet.getExpandedState() : null;
544
545 if (showHeader && !autoLayout) {
546
547
548 writer.startElement(HtmlElements.HEADER);
549 writer.writeClassAttribute(TobagoClass.SHEET__HEADER);
550 writer.startElement(HtmlElements.TABLE);
551 writer.writeAttribute(HtmlAttributes.CELLSPACING, "0", false);
552 writer.writeAttribute(HtmlAttributes.CELLPADDING, "0", false);
553 writer.writeAttribute(HtmlAttributes.SUMMARY, "", false);
554 writer.writeClassAttribute(
555 BootstrapClass.TABLE,
556 TobagoClass.SHEET__HEADER_TABLE,
557 sheetMarkup.contains(Markup.DARK) ? BootstrapClass.TABLE_DARK : null,
558 sheetMarkup.contains(Markup.BORDERED) ? BootstrapClass.TABLE_BORDERED : null,
559 sheetMarkup.contains(Markup.SMALL) ? BootstrapClass.TABLE_SM : null,
560 TobagoClass.TABLE_LAYOUT__FIXED);
561
562 writeColgroup(writer, columnWidths, columns, true);
563
564 writer.startElement(HtmlElements.TBODY);
565 encodeHeaderRows(facesContext, sheet, writer, columns);
566 writer.endElement(HtmlElements.TBODY);
567 writer.endElement(HtmlElements.TABLE);
568 writer.endElement(HtmlElements.HEADER);
569 }
570
571 writer.startElement(HtmlElements.DIV);
572 writer.writeIdAttribute(sheetId + ComponentUtils.SUB_SEPARATOR + "data_div");
573 writer.writeClassAttribute(TobagoClass.SHEET__BODY);
574
575 writer.startElement(HtmlElements.TABLE);
576 writer.writeAttribute(HtmlAttributes.CELLSPACING, "0", false);
577 writer.writeAttribute(HtmlAttributes.CELLPADDING, "0", false);
578 writer.writeAttribute(HtmlAttributes.SUMMARY, "", false);
579 writer.writeClassAttribute(
580 BootstrapClass.TABLE,
581 TobagoClass.SHEET__BODY_TABLE,
582 sheetMarkup.contains(Markup.DARK) ? BootstrapClass.TABLE_DARK : null,
583 sheetMarkup.contains(Markup.STRIPED) ? BootstrapClass.TABLE_STRIPED : null,
584 sheetMarkup.contains(Markup.BORDERED) ? BootstrapClass.TABLE_BORDERED : null,
585 sheetMarkup.contains(Markup.HOVER) ? BootstrapClass.TABLE_HOVER : null,
586 sheetMarkup.contains(Markup.SMALL) ? BootstrapClass.TABLE_SM : null,
587 !autoLayout ? TobagoClass.TABLE_LAYOUT__FIXED : null);
588
589 if (showHeader && autoLayout) {
590 writer.startElement(HtmlElements.THEAD);
591 encodeHeaderRows(facesContext, sheet, writer, columns);
592 writer.endElement(HtmlElements.THEAD);
593 }
594
595 if (!autoLayout) {
596 writeColgroup(writer, columnWidths, columns, false);
597 }
598
599
600
601 if (LOG.isDebugEnabled()) {
602 LOG.debug("first = " + sheet.getFirst() + " rows = " + sheet.getRows());
603 }
604
605 writer.startElement(HtmlElements.TBODY);
606
607 final String var = sheet.getVar();
608
609 boolean emptySheet = true;
610
611 final int last = sheet.isRowsUnlimited() ? Integer.MAX_VALUE : sheet.getFirst() + sheet.getRows();
612
613 for (int rowIndex = sheet.getFirst(); rowIndex < last; rowIndex++) {
614 sheet.setRowIndex(rowIndex);
615 if (!sheet.isRowAvailable()) {
616 break;
617 }
618
619 final Object rowRendered = sheet.getAttributes().get("rowRendered");
620 if (rowRendered instanceof Boolean && !((Boolean) rowRendered)) {
621 continue;
622 }
623
624 emptySheet = false;
625
626 if (LOG.isDebugEnabled()) {
627 LOG.debug("var " + var);
628 LOG.debug("list " + sheet.getValue());
629 }
630
631 if (sheet.isTreeModel()) {
632 final TreePath path = sheet.getPath();
633 if (sheet.isFolder() && expandedState.isExpanded(path)) {
634 expandedValue.add(rowIndex);
635 }
636 }
637
638 writer.startElement(HtmlElements.TR);
639 writer.writeAttribute(CustomAttributes.ROW_INDEX, rowIndex);
640 final boolean selected = selectedRows.contains(rowIndex);
641 final String[] rowMarkups = (String[]) sheet.getAttributes().get("rowMarkup");
642 Markup rowMarkup = Markup.NULL;
643 if (selected) {
644 rowMarkup = rowMarkup.add(Markup.SELECTED);
645 }
646 if (rowMarkups != null) {
647 rowMarkup = rowMarkup.add(Markup.valueOf(rowMarkups));
648 }
649 final String parentId = sheet.getRowParentClientId();
650 if (parentId != null) {
651
652
653
654 writer.writeAttribute(DataAttributes.TREE_PARENT, parentId, false);
655 }
656
657 AbstractUIRow row = null;
658 for (final UIColumn column : columns) {
659 if (column.isRendered()) {
660 if (column instanceof AbstractUIRow) {
661 row = (AbstractUIRow) column;
662
663 }
664 }
665 }
666
667 writer.writeIdAttribute(row != null ? row.getClientId(facesContext): sheet.getRowClientId());
668 writer.writeClassAttribute(
669 TobagoClass.SHEET__ROW,
670 TobagoClass.SHEET__ROW.createMarkup(rowMarkup),
671 selected ? BootstrapClass.TABLE_INFO : null,
672 row != null ? row.getCustomClass() : null,
673 sheet.isRowVisible() ? null : BootstrapClass.D_NONE);
674
675 for (final AbstractUIColumnBase column : columns) {
676 if (column.isRendered()) {
677 if (column instanceof AbstractUIColumn || column instanceof AbstractUIColumnSelector
678 || column instanceof AbstractUIColumnNode) {
679 writer.startElement(HtmlElements.TD);
680 Markup markup = column.getMarkup();
681 if (markup == null) {
682 markup = Markup.NULL;
683 }
684 if (column instanceof AbstractUIColumn) {
685 final AbstractUIColumn normalColumn = (AbstractUIColumn) column;
686 markup = markup.add(getMarkupForAlign(normalColumn));
687 markup = markup.add(getMarkupForVerticalAlign(normalColumn));
688 }
689 writer.writeClassAttribute(
690 TobagoClass.SHEET__CELL,
691 TobagoClass.SHEET__CELL.createMarkup(markup),
692 column.getCustomClass());
693
694 if (column instanceof AbstractUIColumnSelector) {
695 final AbstractUIColumnSelector selector = (AbstractUIColumnSelector) column;
696 writer.startElement(HtmlElements.INPUT);
697 writer.writeNameAttribute(sheetId + "_data_row_selector_" + rowIndex);
698 if (selectable.isSingle()) {
699 writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.RADIO);
700 } else {
701 writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.CHECKBOX);
702 }
703 writer.writeAttribute(HtmlAttributes.CHECKED, selected);
704 writer.writeAttribute(HtmlAttributes.DISABLED, selector.isDisabled());
705 writer.writeClassAttribute(
706 BootstrapClass.FORM_CHECK_INLINE,
707 TobagoClass.SHEET__COLUMN_SELECTOR);
708 writer.endElement(HtmlElements.INPUT);
709 } else {
710 column.encodeAll(facesContext);
711 }
712
713
714
715
716
717
718 writer.endElement(HtmlElements.TD);
719 }
720 }
721 }
722
723 writer.startElement(HtmlElements.TD);
724 writer.writeClassAttribute(TobagoClass.SHEET__CELL, TobagoClass.SHEET__CELL.createMarkup(Markup.FILLER));
725 writer.startElement(HtmlElements.DIV);
726 writer.endElement(HtmlElements.DIV);
727 encodeBehavior(writer, facesContext, row);
728 writer.endElement(HtmlElements.TD);
729
730 writer.endElement(HtmlElements.TR);
731 }
732
733 sheet.setRowIndex(-1);
734
735 if (emptySheet && showHeader) {
736 writer.startElement(HtmlElements.TR);
737 int countColumns = 0;
738 for (final UIColumn column : columns) {
739 if (!(column instanceof AbstractUIRow)) {
740 countColumns++;
741 }
742 }
743 writer.startElement(HtmlElements.TD);
744 writer.writeAttribute(HtmlAttributes.COLSPAN, countColumns);
745 writer.startElement(HtmlElements.DIV);
746 writer.writeClassAttribute(BootstrapClass.TEXT_CENTER);
747 writer.writeText(ResourceUtils.getString(facesContext, "sheet.empty"));
748 writer.endElement(HtmlElements.DIV);
749 writer.endElement(HtmlElements.TD);
750 if (!autoLayout) {
751 writer.startElement(HtmlElements.TD);
752 writer.writeClassAttribute(TobagoClass.SHEET__CELL, TobagoClass.SHEET__CELL.createMarkup(Markup.FILLER));
753
754 writer.startElement(HtmlElements.DIV);
755 writer.endElement(HtmlElements.DIV);
756 writer.endElement(HtmlElements.TD);
757 }
758 writer.endElement(HtmlElements.TR);
759 }
760
761 writer.endElement(HtmlElements.TBODY);
762
763 writer.endElement(HtmlElements.TABLE);
764 writer.endElement(HtmlElements.DIV);
765
766
767 }
768
769 private void encodeHiddenInput(final TobagoResponseWriter writer, final String value, final String idWithSuffix)
770 throws IOException {
771 writer.startElement(HtmlElements.INPUT);
772 writer.writeIdAttribute(idWithSuffix);
773 writer.writeNameAttribute(idWithSuffix);
774 writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN);
775 writer.writeAttribute(HtmlAttributes.VALUE, value, false);
776 writer.endElement(HtmlElements.INPUT);
777 }
778
779 private void ensureColumnWidthsSize(
780 final List<Integer> columnWidths, final List<AbstractUIColumnBase> columns, final List<Integer> samples) {
781
782
783 int i = 0;
784 int j = 0;
785 for (final AbstractUIColumnBase column : columns) {
786 if (!(column instanceof AbstractUIRow)) {
787 final Integer newValue;
788 if (j < samples.size()) {
789 newValue = samples.get(j);
790 j++;
791 } else {
792 newValue = null;
793 }
794 if (columnWidths.size() > i) {
795 if (newValue != null) {
796 columnWidths.set(i, newValue);
797 }
798 } else {
799 columnWidths.add(newValue != null ? newValue : -1);
800 }
801 i++;
802 }
803 }
804 }
805
806 private Markup getMarkupForAlign(final UIColumn column) {
807 final String textAlign = ComponentUtils.getStringAttribute(column, Attributes.align);
808 if (textAlign != null) {
809 switch (TextAlign.valueOf(textAlign)) {
810 case right:
811 return Markup.RIGHT;
812 case center:
813 return Markup.CENTER;
814 case justify:
815 return Markup.JUSTIFY;
816 default:
817
818 }
819 }
820 return null;
821 }
822
823 private Markup getMarkupForVerticalAlign(final AbstractUIColumn column) {
824 final VerticalAlign verticalAlign = column.getVerticalAlign();
825 if (verticalAlign != null) {
826 switch (verticalAlign) {
827 case bottom:
828 return Markup.BOTTOM;
829 case middle:
830 return Markup.MIDDLE;
831 default:
832
833 }
834 }
835 return null;
836 }
837
838 private void encodeHeaderRows(
839 final FacesContext facesContext, final AbstractUISheet sheet, final TobagoResponseWriter writer,
840 final List<AbstractUIColumnBase> columns)
841 throws IOException {
842
843 final Selectable selectable = sheet.getSelectable();
844 final Grid grid = sheet.getHeaderGrid();
845 final boolean autoLayout = sheet.isAutoLayout();
846 final boolean multiHeader = grid.getRowCount() > 1;
847 int offset = 0;
848
849 for (int i = 0; i < grid.getRowCount(); i++) {
850 writer.startElement(HtmlElements.TR);
851 final AbstractUIRow row = ComponentUtils.findChild(sheet, AbstractUIRow.class);
852 if (row != null) {
853 writer.writeClassAttribute(row.getCustomClass());
854 }
855 for (int j = 0; j < columns.size(); j++) {
856 final AbstractUIColumnBase column = columns.get(j);
857 if (!column.isRendered() || column instanceof AbstractUIRow) {
858 offset++;
859 } else {
860 final Cell cell = grid.getCell(j - offset, i);
861 if (cell instanceof OriginCell) {
862 writer.startElement(HtmlElements.TH);
863 if (cell.getColumnSpan() > 1) {
864 writer.writeAttribute(HtmlAttributes.COLSPAN, cell.getColumnSpan());
865 }
866 if (cell.getRowSpan() > 1) {
867 writer.writeAttribute(HtmlAttributes.ROWSPAN, cell.getRowSpan());
868 }
869
870 final UIComponent cellComponent = cell.getComponent();
871
872 final Markup align;
873 final String alignString = ComponentUtils.getStringAttribute(column, Attributes.align);
874 if (multiHeader && cell.getColumnSpan() > 1) {
875 align = Markup.CENTER;
876 } else if (alignString != null) {
877 switch (TextAlign.valueOf(alignString)) {
878 case right:
879 align = Markup.RIGHT;
880 break;
881 case center:
882 align = Markup.CENTER;
883 break;
884 case justify:
885 align = Markup.JUSTIFY;
886 break;
887 default:
888 align = null;
889 }
890 } else {
891 align = null;
892 }
893 writer.writeClassAttribute(
894 TobagoClass.SHEET__HEADER_CELL,
895 TobagoClass.SHEET__CELL.createMarkup(align),
896 column.getCustomClass());
897 writer.startElement(HtmlElements.SPAN);
898 Markup markup = Markup.NULL;
899 String tip = ComponentUtils.getStringAttribute(column, Attributes.tip);
900
901 CommandMap behaviorCommands = null;
902 if (cell.getColumnSpan() == 1 && cellComponent instanceof AbstractUIOut) {
903 final boolean sortable = ComponentUtils.getBooleanAttribute(column, Attributes.sortable);
904 if (sortable) {
905 AbstractUILink sortCommand = (AbstractUILink) ComponentUtils.getFacet(column, Facets.sorter);
906 if (sortCommand == null) {
907
908 column.getClientId(facesContext);
909 final String sorterId = column.getId() + "_" + AbstractUISheet.SORTER_ID;
910 sortCommand = (AbstractUILink) ComponentUtils.createComponent(
911 facesContext, Tags.link.componentType(), RendererTypes.Link, sorterId);
912 sortCommand.setTransient(true);
913 final AjaxBehavior reloadBehavior = createReloadBehavior(sheet);
914 sortCommand.addClientBehavior("click", reloadBehavior);
915 ComponentUtils.setFacet(column, Facets.sorter, sortCommand);
916 }
917 writer.writeIdAttribute(sortCommand.getClientId(facesContext));
918 behaviorCommands = getBehaviorCommands(facesContext, sortCommand);
919 ComponentUtils.removeFacet(column, Facets.sorter);
920 if (tip == null) {
921 tip = "";
922 } else {
923 tip += " - ";
924 }
925 tip += ResourceUtils.getString(facesContext, "sheet.sorting");
926
927 markup = markup.add(Markup.SORTABLE);
928
929 final SheetState sheetState = sheet.getSheetState(facesContext);
930 if (column.getId().equals(sheetState.getSortedColumnId())) {
931 final String sortTitle;
932 if (sheetState.isAscending()) {
933 sortTitle = ResourceUtils.getString(facesContext, "sheet.ascending");
934 markup = markup.add(Markup.ASCENDING);
935 } else {
936 sortTitle = ResourceUtils.getString(facesContext, "sheet.descending");
937 markup = markup.add(Markup.DESCENDING);
938 }
939 tip += " - " + sortTitle;
940 }
941 }
942 }
943
944 writer.writeClassAttribute(TobagoClass.SHEET__HEADER, TobagoClass.SHEET__HEADER.createMarkup(markup));
945 writer.writeAttribute(HtmlAttributes.TITLE, tip, true);
946
947 encodeBehavior(writer, behaviorCommands);
948
949 if (column instanceof AbstractUIColumnSelector && selectable.isMulti()) {
950 writer.startElement(HtmlElements.INPUT);
951 writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.CHECKBOX);
952
953 writer.writeClassAttribute(TobagoClass.SHEET__COLUMN_SELECTOR);
954 writer.writeAttribute(
955 HtmlAttributes.TITLE,
956 ResourceUtils.getString(facesContext, "sheet.selectAll"),
957 true);
958 writer.endElement(HtmlElements.INPUT);
959 } else {
960 cellComponent.encodeAll(facesContext);
961 }
962
963 writer.endElement(HtmlElements.SPAN);
964 if (!autoLayout) {
965 if (column.isResizable()) {
966 encodeResizing(writer, sheet, j - offset + cell.getColumnSpan() - 1);
967 }
968 }
969
970 writer.endElement(HtmlElements.TH);
971 }
972 }
973 }
974 if (!autoLayout) {
975
976 encodeHeaderFiller(writer, sheet);
977 encodeHeaderFiller(writer, sheet);
978 }
979
980 writer.endElement(HtmlElements.TR);
981 }
982 }
983
984 private void encodeHeaderFiller(final TobagoResponseWriter writer, final AbstractUISheet sheet) throws IOException {
985 writer.startElement(HtmlElements.TH);
986 writer.writeClassAttribute(
987 TobagoClass.SHEET__HEADER_CELL,
988 TobagoClass.SHEET__HEADER_CELL.createMarkup(Markup.FILLER));
989 writer.startElement(HtmlElements.SPAN);
990 writer.writeClassAttribute(TobagoClass.SHEET__HEADER);
991 writer.endElement(HtmlElements.SPAN);
992 writer.endElement(HtmlElements.TH);
993 }
994
995 private void writeColgroup(
996 final TobagoResponseWriter writer, final List<Integer> columnWidths,
997 final List<AbstractUIColumnBase> columns, final boolean isHeader) throws IOException {
998 writer.startElement(HtmlElements.COLGROUP);
999
1000 int i = 0;
1001 for (final AbstractUIColumnBase column : columns) {
1002 if (!(column instanceof AbstractUIRow)) {
1003 if (column.isRendered()) {
1004 final Integer width = columnWidths.get(i);
1005 writeCol(writer, width >= 0 ? width : null);
1006 }
1007 i++;
1008 }
1009 }
1010 writeCol(writer, null);
1011 if (isHeader) {
1012 writeCol(writer, null);
1013 }
1014
1015 writer.endElement(HtmlElements.COLGROUP);
1016 }
1017
1018 private void writeCol(final TobagoResponseWriter writer, final Integer columnWidth) throws IOException {
1019 writer.startElement(HtmlElements.COL);
1020 writer.writeAttribute(HtmlAttributes.WIDTH, columnWidth);
1021 writer.endElement(HtmlElements.COL);
1022 }
1023
1024 private Markup markupForLeftCenterRight(final ShowPosition position) {
1025 switch (position) {
1026 case left:
1027 return Markup.LEFT;
1028 case center:
1029 return Markup.CENTER;
1030 case right:
1031 return Markup.RIGHT;
1032 default:
1033 return Markup.NULL;
1034 }
1035 }
1036
1037 @Override
1038 public boolean getRendersChildren() {
1039 return true;
1040 }
1041
1042 private List<Integer> getSelectedRows(final AbstractUISheet data, final SheetState state) {
1043 List<Integer> selected = (List<Integer>) ComponentUtils.getAttribute(data, Attributes.selectedListString);
1044 if (selected == null && state != null) {
1045 selected = state.getSelectedRows();
1046 }
1047 if (selected == null) {
1048 selected = Collections.emptyList();
1049 }
1050 return selected;
1051 }
1052
1053 private void encodeLink(
1054 final FacesContext facesContext, final AbstractUISheet data, final Application application,
1055 final boolean disabled, final SheetAction action, final Integer target, final Icons icon, final CssItem liClass)
1056 throws IOException {
1057
1058 final String facet = action == SheetAction.toPage || action == SheetAction.toRow
1059 ? action.name() + "-" + target
1060 : action.name();
1061 final AbstractUILink command = ensurePagingCommand(facesContext, data, facet, facet, disabled);
1062 if (target != null) {
1063 ComponentUtils.setAttribute(command, Attributes.pagingTarget, target);
1064 }
1065
1066 final Locale locale = facesContext.getViewRoot().getLocale();
1067 final String message = ResourceUtils.getString(facesContext, action.getBundleKey());
1068 final String tip = new MessageFormat(message, locale).format(new Integer[]{target});
1069
1070 final TobagoResponseWriter writer = getResponseWriter(facesContext);
1071 writer.startElement(HtmlElements.LI);
1072 writer.writeClassAttribute(liClass, disabled ? BootstrapClass.DISABLED : null, BootstrapClass.PAGE_ITEM);
1073 writer.startElement(HtmlElements.BUTTON);
1074 writer.writeAttribute(HtmlAttributes.TYPE, HtmlButtonTypes.BUTTON);
1075 writer.writeClassAttribute(BootstrapClass.PAGE_LINK);
1076 writer.writeIdAttribute(command.getClientId(facesContext));
1077 writer.writeAttribute(HtmlAttributes.TITLE, tip, true);
1078 writer.writeAttribute(HtmlAttributes.DISABLED, disabled);
1079 if (icon != null) {
1080 writer.startElement(HtmlElements.I);
1081 writer.writeClassAttribute(Icons.FA, icon);
1082 writer.endElement(HtmlElements.I);
1083 } else {
1084 writer.writeText(String.valueOf(target));
1085 }
1086 if (!disabled) {
1087 encodeBehavior(writer, facesContext, command);
1088 }
1089 data.getFacets().remove(facet);
1090 writer.endElement(HtmlElements.BUTTON);
1091 writer.endElement(HtmlElements.LI);
1092 }
1093
1094
1095
1096
1097
1098 private void encodeResizing(final TobagoResponseWriter writer, final AbstractUISheet sheet, final int columnIndex)
1099 throws IOException {
1100 writer.startElement(HtmlElements.SPAN);
1101 writer.writeClassAttribute(TobagoClass.SHEET__HEADER_RESIZE);
1102 writer.writeAttribute(DataAttributes.COLUMN_INDEX, Integer.toString(columnIndex), false);
1103 writer.write(" ");
1104 writer.endElement(HtmlElements.SPAN);
1105 }
1106
1107 private void encodeDirectPagingLinks(
1108 final FacesContext facesContext, final Application application, final AbstractUISheet sheet)
1109 throws IOException {
1110
1111 int linkCount = ComponentUtils.getIntAttribute(sheet, Attributes.directLinkCount);
1112 linkCount--;
1113 final ArrayList<Integer> prevs = new ArrayList<>(linkCount);
1114 int page = sheet.getCurrentPage() + 1;
1115 for (int i = 0; i < linkCount && page > 1; i++) {
1116 page--;
1117 if (page > 0) {
1118 prevs.add(0, page);
1119 }
1120 }
1121
1122 final ArrayList<Integer> nexts = new ArrayList<>(linkCount);
1123 page = sheet.getCurrentPage() + 1;
1124 final int pages = sheet.hasRowCount() || sheet.isRowsUnlimited() ? sheet.getPages() : Integer.MAX_VALUE;
1125 for (int i = 0; i < linkCount && page < pages; i++) {
1126 page++;
1127 if (page > 1) {
1128 nexts.add(page);
1129 }
1130 }
1131
1132 if (prevs.size() > (linkCount / 2)
1133 && nexts.size() > (linkCount - (linkCount / 2))) {
1134 while (prevs.size() > (linkCount / 2)) {
1135 prevs.remove(0);
1136 }
1137 while (nexts.size() > (linkCount - (linkCount / 2))) {
1138 nexts.remove(nexts.size() - 1);
1139 }
1140 } else if (prevs.size() <= (linkCount / 2)) {
1141 while (prevs.size() + nexts.size() > linkCount) {
1142 nexts.remove(nexts.size() - 1);
1143 }
1144 } else {
1145 while (prevs.size() + nexts.size() > linkCount) {
1146 prevs.remove(0);
1147 }
1148 }
1149
1150 int skip = prevs.size() > 0 ? prevs.get(0) : 1;
1151 if (!sheet.isShowDirectLinksArrows() && skip > 1) {
1152 skip -= linkCount - (linkCount / 2);
1153 skip--;
1154 if (skip < 1) {
1155 skip = 1;
1156 }
1157 encodeLink(facesContext, sheet, application, false, SheetAction.toPage, skip, Icons.ELLIPSIS_H, null);
1158 }
1159 for (final Integer prev : prevs) {
1160 encodeLink(facesContext, sheet, application, false, SheetAction.toPage, prev, null, null);
1161 }
1162 encodeLink(facesContext, sheet, application, false, SheetAction.toPage,
1163 sheet.getCurrentPage() + 1, null, BootstrapClass.ACTIVE);
1164
1165 for (final Integer next : nexts) {
1166 encodeLink(facesContext, sheet, application, false, SheetAction.toPage, next, null, null);
1167 }
1168
1169 skip = nexts.size() > 0 ? nexts.get(nexts.size() - 1) : pages;
1170 if (!sheet.isShowDirectLinksArrows() && skip < pages) {
1171 skip += linkCount / 2;
1172 skip++;
1173 if (skip > pages) {
1174 skip = pages;
1175 }
1176 encodeLink(facesContext, sheet, application, false, SheetAction.toPage, skip, Icons.ELLIPSIS_H, null);
1177 }
1178 }
1179
1180 private AbstractUILink ensurePagingCommand(
1181 final FacesContext facesContext, final AbstractUISheet sheet, final String facet, final String id,
1182 final boolean disabled) {
1183
1184 final Map<String, UIComponent> facets = sheet.getFacets();
1185 AbstractUILink command = (AbstractUILink) facets.get(facet);
1186 if (command == null) {
1187 command = (AbstractUILink) ComponentUtils.createComponent(facesContext, Tags.link.componentType(),
1188 RendererTypes.Link, SUFFIX_PAGE_ACTION + id);
1189 command.setRendered(true);
1190 command.setDisabled(disabled);
1191 command.setTransient(true);
1192 facets.put(facet, command);
1193
1194
1195 final AjaxBehavior behavior = createReloadBehavior(sheet);
1196 command.addClientBehavior("click", behavior);
1197 }
1198 return command;
1199 }
1200
1201 private AjaxBehavior createReloadBehavior(final AbstractUISheet sheet) {
1202 final AjaxBehavior reloadBehavior = findReloadBehavior(sheet);
1203 final ArrayList<String> renderIds = new ArrayList<>();
1204 renderIds.add(sheet.getId());
1205 if (reloadBehavior != null) {
1206 renderIds.addAll(reloadBehavior.getRender());
1207 }
1208 final ArrayList<String> executeIds = new ArrayList<>();
1209 executeIds.add(sheet.getId());
1210 if (reloadBehavior != null) {
1211 executeIds.addAll(reloadBehavior.getExecute());
1212 }
1213 final AjaxBehavior behavior = new AjaxBehavior();
1214 behavior.setExecute(executeIds);
1215 behavior.setRender(renderIds);
1216 behavior.setTransient(true);
1217 return behavior;
1218 }
1219
1220 private AjaxBehavior findReloadBehavior(final ClientBehaviorHolder holder) {
1221 final List<ClientBehavior> reload = holder.getClientBehaviors().get("reload");
1222 if (reload != null && !reload.isEmpty() && reload.get(0) instanceof AjaxBehavior) {
1223 return (AjaxBehavior) reload.get(0);
1224 } else {
1225 return null;
1226 }
1227 }
1228 }