001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.console.views; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.*; 022 023/** 024 * Builds the standard table ribbon. 025 * 026 * <p> 027 * {@code [search] [create] [filters] [export: copy/csv/link + extras] [view: collapse / columns / reset] [refresh]} 028 * 029 * <p> 030 * Each slot has a canonical group, so the ribbon sorts the same way everywhere: 031 * 032 * <table class='styled'> 033 * <caption>Ribbon slots</caption> 034 * <tr><th>Slot</th><th>Group</th><th>Item ids</th></tr> 035 * <tr><td>search</td><td>{@code search}</td><td>{@code search}</td></tr> 036 * <tr><td>create</td><td>{@code entity}</td><td>your {@link Standard#create(RibbonItem...)} items</td></tr> 037 * <tr><td>filters</td><td>{@code filter}</td><td>your {@link Standard#filter(RibbonItem...)} items</td></tr> 038 * <tr><td>export</td><td>{@code export}</td><td>{@code export} (buttons {@code copy}, {@code csv} plus extras), {@code link}</td></tr> 039 * <tr><td>view</td><td>{@code viewing}</td><td>{@code collapse}, {@code columns}, {@code reset}</td></tr> 040 * <tr><td>refresh</td><td>{@code page}</td><td>{@code refresh}</td></tr> 041 * </table> 042 * 043 * <p> 044 * Every item carries an explicit id, so the output describes itself. The ids {@code collapse} and {@code reset} 045 * are explicit overrides of the stock defaults {@code collapseAll} and {@code resetDefaults}. {@code build()} 046 * replaces the group of every item you pass to {@code create} or {@code filter} with the slot's group. 047 * 048 * <p> 049 * The {@code copy} and {@code csv} buttons need the DataTables Buttons extension, so a page using the preset must 050 * list the {@code datatables-buttons} toolkit pack. Without it the export cluster is omitted at runtime and the link 051 * button still renders. The collapse button is hidden at runtime on a table with no row detail, and the columns and 052 * reset buttons on a table without {@code columnConfig}. 053 * 054 * <h2 class='section'>Example:</h2> 055 * <p class='bjava'> 056 * TableSpec.<jsm>create</jsm>(<js>"pets"</js>) 057 * .ribbon(Ribbons.<jsm>standard</jsm>() 058 * .create(RibbonItem.<jsm>dialog</jsm>(<js>"add-pet"</js>).title(<js>"Add pet"</js>)) 059 * .export(<js>"excel"</js>, <js>"pdf"</js>) 060 * .build()); 061 * </p> 062 */ 063@SuppressWarnings({ 064 "java:S1192" // Ribbon item ids read more clearly inline than as constants. 065}) 066public final class Ribbons { 067 068 private static final List<String> VALID_IDS = List.of( 069 "search", "export", "copy", "csv", "excel", "pdf", "link", "collapse", "columns", "reset", "refresh"); 070 071 private Ribbons() {} 072 073 /** 074 * Starts the standard preset. 075 * 076 * @return A new builder. 077 */ 078 public static Standard standard() { 079 return new Standard(); 080 } 081 082 /** Builder for the standard table ribbon. */ 083 public static final class Standard { 084 085 private final List<RibbonItem> create = new ArrayList<>(); 086 private final List<RibbonItem> filter = new ArrayList<>(); 087 private final List<String> extras = new ArrayList<>(); 088 private final Set<String> without = new LinkedHashSet<>(); 089 private String refreshId = "refresh"; 090 091 private Standard() {} 092 093 /** 094 * Adds items to the create slot (group {@code entity}); repeatable. 095 * 096 * <p> 097 * Ids of items added here are not checked for duplicates by the builder; build-time id validation applies 098 * to the stock items only, so keep the ids you pass unique among themselves and against the stock ids. 099 * 100 * @param items The items. 101 * @return This object. 102 */ 103 public Standard create(RibbonItem...items) { 104 create.addAll(List.of(items)); 105 return this; 106 } 107 108 /** 109 * Adds items to the filter slot (group {@code filter}); repeatable. 110 * 111 * @param items The items. 112 * @return This object. 113 */ 114 public Standard filter(RibbonItem...items) { 115 filter.addAll(List.of(items)); 116 return this; 117 } 118 119 /** 120 * Adds optional export buttons (for example {@code "excel"}, {@code "pdf"}) after copy and csv. 121 * 122 * <p> 123 * An optional button is omitted at runtime when its library is missing. 124 * 125 * @param buttons The buttons, each {@code excel} or {@code pdf}. 126 * @return This object. 127 */ 128 public Standard export(String...buttons) { 129 extras.addAll(List.of(buttons)); 130 return this; 131 } 132 133 /** 134 * Drops a preset item or export button by id. 135 * 136 * <p> 137 * Valid ids are {@code search}, {@code export}, {@code copy}, {@code csv}, {@code excel}, {@code pdf}, 138 * {@code link}, {@code collapse}, {@code columns}, {@code reset}, {@code refresh}, and the id given to 139 * {@link #refreshId(String)}. An unknown id is rejected by {@link #build()}. 140 * 141 * @param id The id. 142 * @return This object. 143 */ 144 public Standard without(String id) { 145 without.add(id); 146 return this; 147 } 148 149 /** 150 * Renames the refresh item (default {@code "refresh"}). 151 * 152 * @param id The id. Must not be <jk>null</jk> or blank. 153 * @return This object. 154 */ 155 public Standard refreshId(String id) { 156 if (ib(id)) 157 throw new IllegalArgumentException("Ribbons refresh id must not be null or blank."); 158 refreshId = id; 159 return this; 160 } 161 162 /** 163 * Builds the items in slot order, each with its canonical group. 164 * 165 * @return An immutable list, for {@link TableSpec#ribbon(List)}. 166 * @throws IllegalArgumentException If {@link #without(String)} named an id that is not a preset id. 167 */ 168 @SuppressWarnings({ 169 "java:S3776" // Branching is inherent to build(), which assembles the ribbon from many optional items. 170 }) 171 public List<RibbonItem> build() { 172 for (var w : without) 173 if (! VALID_IDS.contains(w) && ! w.equals(refreshId)) 174 throw new IllegalArgumentException("Unknown ribbon preset id '" + w + "'. Valid ids: " + VALID_IDS + " and the refresh id '" + refreshId + "'."); 175 var out = new ArrayList<RibbonItem>(); 176 if (! without.contains("search")) 177 out.add(RibbonItem.search().id("search").group("search")); 178 create.forEach(i -> out.add(i.group("entity"))); 179 filter.forEach(i -> out.add(i.group("filter"))); 180 if (! without.contains("export")) { 181 var buttons = new ArrayList<String>(); 182 if (! without.contains("copy")) 183 buttons.add("copy"); 184 if (! without.contains("csv")) 185 buttons.add("csv"); 186 var optional = extras.stream().filter(x -> ! without.contains(x)).toList(); 187 if (! buttons.isEmpty() || ! optional.isEmpty()) { 188 var e = RibbonItem.export(buttons.toArray(String[]::new)).id("export").group("export"); 189 if (! optional.isEmpty()) 190 e.optional(optional.toArray(String[]::new)); 191 out.add(e); 192 } 193 } 194 if (! without.contains("link")) 195 out.add(RibbonItem.linkButton().id("link").group("export")); 196 if (! without.contains("collapse")) 197 out.add(RibbonItem.collapseAll().id("collapse").group("viewing")); 198 if (! without.contains("columns")) 199 out.add(RibbonItem.columnSettings().id("columns").group("viewing")); 200 if (! without.contains("reset")) 201 out.add(RibbonItem.resetDefaults().id("reset").group("viewing")); 202 if (! without.contains(refreshId)) 203 out.add(RibbonItem.refresh().id(refreshId).group("page")); 204 return List.copyOf(out); 205 } 206 } 207}