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 023import org.apache.juneau.commons.bean.*; 024import org.apache.juneau.console.widgets.Op; 025 026/** 027 * A per-row action descriptor in the {@code VIEW_META} wire contract (the transport-agnostic 028 * "row-action intent" model). 029 * 030 * <p> 031 * An FTL {@code <@card type="datatables">} {@code VIEW_META} catalog declares zero or more of these via 032 * {@code rowActions}; the 033 * {@code juneau-views.js} runtime renders each as a row-menu item and, on activation, submits the declared 034 * {@link #endpoint} with the declared {@link #method} as a JSON request carrying the process's CSRF token. 035 * 036 * <h5 class='section'>The frozen wire schema</h5> 037 * <p> 038 * This is the <b>complete</b> set of wire fields; it is pinned in full now so that later waves (a future declarative 039 * modal/form + typed result) can rely on it verbatim without forcing another fail-loud 040 * contract bump. Only {@link #id} is required; every other field is optional and is omitted from the serialized 041 * form when unset (null-valued properties are dropped). 042 * <table class='styled'> 043 * <tr><th>Field</th><th>Type / tokens</th><th>Meaning</th></tr> 044 * <tr><td>{@code id}</td><td>string</td><td>Stable action id (menu-item key; also the submit's logical name).</td></tr> 045 * <tr><td>{@code label}</td><td>string</td><td>The menu-item text.</td></tr> 046 * <tr><td>{@code icon}</td><td>string</td><td>Optional glyph name (resolved by the shared icon registry).</td></tr> 047 * <tr><td>{@code endpoint}</td><td>string</td><td>The URL the action submits to; may carry a {@code {property}} 048 * token substituted from the current row - a value that cannot be resolved refuses the submission (see 049 * {@link #endpoint(String) endpoint}).</td></tr> 050 * <tr><td>{@code method}</td><td>{@code POST}|{@code PUT}|{@code PATCH}|{@code DELETE}</td> 051 * <td>The non-safe HTTP method (see {@link Method}).</td></tr> 052 * <tr><td>{@code confirm}</td><td>string</td><td>Optional confirmation prompt shown before the submit.</td></tr> 053 * <tr><td>{@code form}</td><td>string</td><td>Optional form-source URL supplying the action's input fields; 054 * may carry a {@code {property}} token substituted from the current row before the confirmation GET 055 * (see {@link #form(String) form}). Blank/absent {@code form} is the confirm-only local path.</td></tr> 056 * <tr><td>{@code present}</td><td>{@code page}|{@code dialog}|{@code inline}</td> 057 * <td>How the action's form/confirmation is presented (see {@link Present}); {@code inline} keeps it out of the row menu.</td></tr> 058 * <tr><td>{@code onSuccess}</td><td>{@code redraw}|{@code mergeRow}|{@code navigate}</td> 059 * <td>What the runtime does with a successful result (see {@link OnSuccess}).</td></tr> 060 * <tr><td>{@code enabledWhen}</td><td>{@code [{field,op,value?,reason}, ...]}</td> 061 * <td>Row-state disable-with-reason rules (see {@link #enabledWhen(String,Op,Object,String)}).</td></tr> 062 * <tr><td>{@code visibleWhen}</td><td>{@code [{field,op,value?}, ...]}</td> 063 * <td>Row-state visibility rules (see {@link #visibleWhen(VisibilityRule...)}).</td></tr> 064 * <tr><td>{@code tone}</td><td>{@code default}|{@code danger}</td> 065 * <td>Styling of the confirmation (see {@link Tone}); {@code danger} focuses Cancel first.</td></tr> 066 * <tr><td>{@code confirmLabel}</td><td>string</td> 067 * <td>Text of the confirm button; defaults to the action's {@code label}.</td></tr> 068 * <tr><td>{@code confirmRenderer}</td><td>string</td> 069 * <td>Name of a confirm renderer registered with the client runtime; absent means the built-in modal. 070 * Like {@code confirm}, it is not consulted when an inline cell edit commits (that never prompts).</td></tr> 071 * </table> 072 * 073 * <p> 074 * {@code enabledWhen} was added after this schema was first frozen; like {@code ModalDef#barSlot}, it is 075 * additive-only (<jk>null</jk> when unset, omitted from the serialized form) and its presence does not bump 076 * {@code VIEW_META} — a {@link RowAction} that leaves it unset serializes 077 * unchanged, and old {@code juneau-views.js} ignores the unknown key. 078 * 079 * <h5 class='section'>Why {@link #method} cannot be a safe method</h5> 080 * <p> 081 * {@link Method} enumerates only {@code POST}/{@code PUT}/{@code PATCH}/{@code DELETE}, so a safe (CSRF-exempt) 082 * method cannot be declared at all — a build-time refusal by construction. This mirrors the server boundary: 083 * {@link org.apache.juneau.rest.server.MethodSafety} fails any {@code @Mutating} operation bound to a safe method at 084 * startup, and {@link org.apache.juneau.rest.server.filter.LoopbackBoundary} applies its Origin/CSRF/JSON checks 085 * only to non-safe methods. A mutating action bound to {@code GET} would be a CSRF-able write; the type system 086 * forbids expressing one. 087 * 088 * <h5 class='section'>Per-row {@code {property}} substitution in {@link #endpoint} and {@link #form}</h5> 089 * <p> 090 * {@link #endpoint(String) endpoint} and {@link #form(String) form} may each carry a {@code {property}} token 091 * (e.g. {@code {id}}, as in the example below) that the client-side runtime substitutes from the row — the 092 * SAME mechanism, same token grammar, and same per-value escaping as the {@code linked} cell renderer's 093 * {@code href} interpolation. Unlike a rendered {@code href}, these URLs are <i>fetched</i> (form: confirmation GET) or 094 * <i>submitted</i> (endpoint: mutating write), so an unresolved substitution <b>refuses</b> instead of issuing a 095 * malformed request; see each setter's javadoc for the full contract. A value with no token is unaffected and 096 * is used as declared. A blank/absent {@code form} is the confirm-only local path (no 097 * GET), not a refusal. 098 * 099 * <h5 class='section'>Example:</h5> 100 * <p class='bjava'> 101 * RowAction <jv>delete</jv> = RowAction.<jsm>create</jsm>(<js>"delete"</js>) 102 * .label(<js>"Delete"</js>) 103 * .endpoint(<js>"servlet:/incidents/{id}"</js>) 104 * .method(RowAction.Method.<jsf>DELETE</jsf>) 105 * .confirm(<js>"Delete incident {id}?"</js>) 106 * .confirmLabel(<js>"Delete forever"</js>) 107 * .tone(RowAction.Tone.<jsf>DANGER</jsf>) 108 * .visibleWhen(VisibilityRule.<jsm>when</jsm>(<js>"locked"</js>).ne(<jk>true</jk>)); 109 * </p> 110 * 111 * <h5 class='section'>See Also:</h5> 112 * <ul> 113 * <li class='jc'>{@link Render} 114 * </ul> 115 * 116 * @since 10.0.0 117 */ 118@BeanType(properties="id,label,icon,endpoint,method,confirm,confirmTitle,bulkMode,form,present,onSuccess,enabledWhen,visibleWhen,tone,confirmLabel,confirmRenderer") 119@SuppressWarnings({ 120 "java:S1845" // Fluent-builder setters intentionally mirror field names (Juneau DSL convention). 121}) 122public class RowAction { 123 124 /** 125 * The non-safe HTTP method a row action submits with. 126 * 127 * <p> 128 * Deliberately limited to the four non-safe methods. A safe method (GET/HEAD/OPTIONS/TRACE) skips the 129 * Origin/CSRF/JSON checks at {@link org.apache.juneau.rest.server.filter.LoopbackBoundary}, so a mutating 130 * action bound to one would be CSRF-able; this enum makes that unexpressible. Each constant's wire token is 131 * its uppercase name, which is exactly what the client sends as the request method. 132 */ 133 public enum Method { 134 135 /** HTTP {@code POST}. */ 136 POST, 137 138 /** HTTP {@code PUT}. */ 139 PUT, 140 141 /** HTTP {@code PATCH}. */ 142 PATCH, 143 144 /** HTTP {@code DELETE}. */ 145 DELETE; 146 147 /** 148 * Returns the wire token for this method (its uppercase name). 149 * 150 * @return The wire token (e.g. {@code "POST"}). 151 */ 152 public String wire() { 153 return name(); 154 } 155 } 156 157 /** 158 * The visual tone of a row action's confirmation. 159 * 160 * <p> 161 * Each constant carries the lowercase wire token emitted for the {@code tone} field. {@link #DANGER} styles the 162 * confirmation as destructive and focuses its Cancel button first. 163 */ 164 public enum Tone { 165 166 /** The ordinary confirmation styling. */ 167 DEFAULT("default"), 168 169 /** A destructive confirmation: danger styling, Cancel focused first. */ 170 DANGER("danger"); 171 172 private final String wire; 173 174 Tone(String wire) { 175 this.wire = wire; 176 } 177 178 /** 179 * Returns the lowercase wire token for this tone. 180 * 181 * @return The wire token (e.g. {@code "danger"}). 182 */ 183 public String wire() { 184 return wire; 185 } 186 } 187 188 /** 189 * How a row action's form/confirmation is presented. 190 * 191 * <p> 192 * Each constant carries the lowercase wire token emitted for the {@code present} field. {@link #INLINE} also 193 * keeps the action out of the row menu. 194 */ 195 public enum Present { 196 197 /** Navigate to / render the action's form as a full page. */ 198 PAGE("page"), 199 200 /** Render the action's form/confirmation in a modal dialog. */ 201 DIALOG("dialog"), 202 203 /** 204 * The action is fired from its cell pill or edit cell, never from the row menu. 205 * 206 * <p> 207 * The row-action menu omits it; every other activation surface still runs it. 208 */ 209 INLINE("inline"); 210 211 private final String wire; 212 213 Present(String wire) { 214 this.wire = wire; 215 } 216 217 /** 218 * Returns the lowercase wire token for this presentation. 219 * 220 * @return The wire token (e.g. {@code "dialog"}). 221 */ 222 public String wire() { 223 return wire; 224 } 225 } 226 227 /** 228 * What the runtime does with a successful action result. 229 * 230 * <p> 231 * Each constant carries the wire token emitted for the {@code onSuccess} field. The behavior behind each 232 * token is implemented by a later wave; this type freezes the vocabulary. 233 */ 234 public enum OnSuccess { 235 236 /** Redraw (reload) the whole table. */ 237 REDRAW("redraw"), 238 239 /** Merge the returned row into the table in place. */ 240 MERGE_ROW("mergeRow"), 241 242 /** Navigate away using the result. */ 243 NAVIGATE("navigate"); 244 245 private final String wire; 246 247 OnSuccess(String wire) { 248 this.wire = wire; 249 } 250 251 /** 252 * Returns the wire token for this success behavior. 253 * 254 * @return The wire token (e.g. {@code "mergeRow"}). 255 */ 256 public String wire() { 257 return wire; 258 } 259 } 260 261 /** 262 * How an action behaves when driven from a bulk-mutate toolbar. 263 * 264 * @since 10.0.0 265 */ 266 public enum BulkMode { 267 268 /** One call per selected row. */ 269 PER_ROW("perRow"), 270 271 /** A single call carrying every selected id. */ 272 AGGREGATE("aggregate"); 273 274 private final String wire; 275 276 BulkMode(String wire) { 277 this.wire = wire; 278 } 279 280 /** 281 * Returns the wire token for this mode. 282 * 283 * @return The wire token ({@code "perRow"} or {@code "aggregate"}). 284 */ 285 public String wire() { 286 return wire; 287 } 288 } 289 290 /** The stable action id (menu-item key; also the submit's logical name). */ 291 public String id; 292 293 /** The menu-item text. */ 294 public String label; 295 296 /** Optional glyph/icon name (resolved by the shared icon registry). */ 297 public String icon; 298 299 /** 300 * The URL this action submits to (see {@link #endpoint(String) endpoint} for the {@code {property}} 301 * substitution grammar). 302 */ 303 public String endpoint; 304 305 /** The non-safe HTTP method wire token (see {@link Method#wire()}). */ 306 public String method; 307 308 /** Optional confirmation prompt shown before the submit, or literal <jk>false</jk> for none (see {@link #noConfirm()}). */ 309 public Object confirm; 310 311 /** Optional confirm-dialog title when this action is driven in bulk. */ 312 public String confirmTitle; 313 314 /** Optional bulk-mode wire token (see {@link BulkMode#wire()}); <jk>null</jk> means {@code perRow}. */ 315 public String bulkMode; 316 317 /** Optional form-source URL supplying the action's input fields. */ 318 public String form; 319 320 /** Optional presentation hint wire token (see {@link Present#wire()}). */ 321 public String present; 322 323 /** Optional success-behavior hint wire token (see {@link OnSuccess#wire()}). */ 324 public String onSuccess; 325 326 /** 327 * The row-state rules that must all match for this action to render enabled. 328 * 329 * <p> 330 * <jk>null</jk> or empty means "never gated" — the action renders exactly as it does today. See 331 * {@link #enabledWhen(String,Op,Object,String)} for the semantics, which mirror 332 * {@code ActionRef.enabledWhen} on the detail-panel {@code ActionBar} (widgets module): presentation only, 333 * disabled never hidden, first-declared-rule-wins, and a field the row does not carry fails closed. 334 */ 335 public List<RowActionEnabledRule> enabledWhen; 336 337 /** 338 * The rules that must all match for this action to render at all. <jk>null</jk> or empty means always visible. 339 * 340 * <p> 341 * Unlike {@link #enabledWhen}, which disables but never hides, a row whose data does not satisfy every rule 342 * here has this action hidden entirely (re-evaluated on every draw). Prefer {@code enabledWhen}, which tells 343 * the user why an action is unavailable, and hide only when the action is irrelevant to the row. Still 344 * presentation only — the endpoint must authorize independently. The evaluation map is the row, plus the 345 * page's {@code facts} under a {@code facts.} prefix, so a rule can test either {@code "status"} or 346 * {@code "facts.viewer.roles"}. 347 */ 348 public List<VisibilityRule> visibleWhen; 349 350 /** Optional confirmation tone wire token (see {@link Tone#wire()}). */ 351 public String tone; 352 353 /** Optional text of the confirm button; <jk>null</jk> means the action's {@link #label}. */ 354 public String confirmLabel; 355 356 /** Optional name of a confirm renderer registered with the client runtime; <jk>null</jk> means the built-in modal. */ 357 public String confirmRenderer; 358 359 /** 360 * Starts a new {@link RowAction} with the specified stable action id. 361 * 362 * @param id The stable action id. Must not be <jk>null</jk> or blank. 363 * @return A new mutable {@link RowAction} to chain builder calls on. 364 * @throws IllegalArgumentException If {@code id} is <jk>null</jk> or blank. 365 */ 366 public static RowAction create(String id) { 367 if (ib(id)) 368 throw iaex("RowAction id must not be null or blank."); 369 var a = new RowAction(); 370 a.id = id; 371 return a; 372 } 373 374 /** 375 * Sets the menu-item text. 376 * 377 * @param value The new value. 378 * @return This object. 379 */ 380 public RowAction label(String value) { 381 label = value; 382 return this; 383 } 384 385 /** 386 * Sets the glyph/icon name. 387 * 388 * @param value The new value. 389 * @return This object. 390 */ 391 public RowAction icon(String value) { 392 icon = value; 393 return this; 394 } 395 396 /** 397 * Sets the URL this action submits to. 398 * 399 * <p> 400 * May contain one or more {@code {property}} tokens (e.g. {@code "servlet:/incidents/{id}/ack"}), substituted 401 * from the CURRENT ROW's own already-fetched data at submit time — the client-side ({@code 402 * juneau-views.js}) runtime, not this Java layer, performs the substitution, by delegating to the identical 403 * {@code interpolateHref} helper the {@code linked} cell renderer uses 404 * (juneau-renders.js): the SAME token grammar (any {@code {property}}, not a hardcoded {@code {id}}) and the 405 * SAME per-value {@code encodeURIComponent} escaping — unchanged. 406 * 407 * <p> 408 * <b>Divergence from a rendered {@code href} is deliberate:</b> because this URL is <i>submitted</i> 409 * rather than merely rendered, the runtime <b>refuses the submission</b> (renders a visible refusal; issues no 410 * request; never a request-time exception) instead of firing a malformed or undeclared write, when: 411 * <ul> 412 * <li>a {@code {property}} token's row value is absent, <jk>null</jk>, or blank; 413 * <li>the resolved URL still carries an unsubstituted {@code {property}} token (e.g. the page shipped 414 * {@code juneau-views.js} without its {@code juneau-renders.js} peer); 415 * <li>the resolved URL contains a {@code ..} path segment (which browser URL resolution would otherwise 416 * normalize into a different, undeclared endpoint); or 417 * <li>no {@code endpoint} is declared at all. 418 * </ul> 419 * {@code Column.href} keeps substituting to an empty string in each of these cases, because a rendered link is 420 * not a write. 421 * 422 * <p> 423 * <b>Backward compatible, narrowed:</b> an {@code endpoint} with no {@code {...}} token is still 424 * issued as declared (three of the four guards above cannot fire on it; the fourth, 425 * the {@code ..} check, only fires on an author-declared {@code ..}). An {@code endpoint} WITH a token now 426 * refuses in cases that previously submitted a malformed URL (e.g. {@code "servlet:/incidents//ack"} for a 427 * row with no {@code id}) rather than issuing it. 428 * 429 * @param value The new value. 430 * @return This object. 431 */ 432 public RowAction endpoint(String value) { 433 endpoint = value; 434 return this; 435 } 436 437 /** 438 * Sets the non-safe HTTP method this action submits with. 439 * 440 * <p> 441 * Only a {@link Method} (POST/PUT/PATCH/DELETE) can be passed, so a safe method can never be declared — 442 * the build-time half of the safe-method refusal. 443 * 444 * @param value The method. Must not be <jk>null</jk>. 445 * @return This object. 446 */ 447 public RowAction method(Method value) { 448 method = value.wire(); 449 return this; 450 } 451 452 /** 453 * Sets the confirmation prompt shown before the submit. 454 * 455 * <p> 456 * The prompt is shown whatever {@link #present(Present) present} says: without {@link Present#DIALOG} the action 457 * opens a confirm-only dialog (or its {@link #confirmRenderer(String) confirmRenderer}) and submits only on 458 * Confirm. Use {@link #noConfirm()} for an action that never prompts. 459 * 460 * <p> 461 * One surface never prompts: committing an inline cell edit (the {@code edit} renderer's pencil) submits the action 462 * directly on Enter or blur even when the action declares a confirm or a 463 * {@link #confirmRenderer(String) confirmRenderer}, because a dialog opening on Enter or blur is awkward. Do not 464 * wire a confirm-guarded action to an inline editor. 465 * 466 * @param value The new value. 467 * @return This object. 468 */ 469 public RowAction confirm(String value) { 470 confirm = value; 471 return this; 472 } 473 474 /** 475 * Sets how this action behaves when driven from a bulk-mutate toolbar: one call per selected row 476 * ({@link BulkMode#PER_ROW}, the default) or a single call carrying every selected id 477 * ({@link BulkMode#AGGREGATE}). 478 * 479 * @param value The new mode. A <jk>null</jk> value clears it (the default, {@link BulkMode#PER_ROW}). 480 * @return This object. 481 */ 482 public RowAction bulkMode(BulkMode value) { 483 bulkMode = value == null ? null : value.wire(); 484 return this; 485 } 486 487 /** 488 * Sets the confirm dialog's title when this action is driven in bulk (e.g. {@code "Abort {count} pending changes"}). 489 * 490 * @param value The title template. May be <jk>null</jk>. 491 * @return This object. 492 */ 493 public RowAction confirmTitle(String value) { 494 confirmTitle = value; 495 return this; 496 } 497 498 /** 499 * Marks this action as never requiring a confirm dialog, in bulk or otherwise — writes a literal 500 * <jk>false</jk> onto the same {@link #confirm} wire key a confirm message would otherwise occupy. 501 * 502 * @return This object. 503 */ 504 public RowAction noConfirm() { 505 confirm = Boolean.FALSE; 506 return this; 507 } 508 509 /** 510 * Sets the form-source URL supplying the action's input fields. 511 * 512 * <p> 513 * May contain one or more {@code {property}} tokens (e.g. {@code "/data/alerts/{id}/ack-form"}), substituted 514 * from the CURRENT ROW's own already-fetched data before the confirmation GET — the client-side 515 * ({@code juneau-views.js}) runtime, not this Java layer, performs the substitution, by delegating to the 516 * identical {@code interpolateHref} helper the {@code linked} cell renderer 517 * uses (juneau-renders.js): the SAME token grammar (any {@code {property}}, not a hardcoded {@code {id}}) and 518 * the SAME per-value {@code encodeURIComponent} escaping. 519 * 520 * <p> 521 * <b>Divergence from a rendered {@code href}:</b> because this URL is <i>fetched</i> (the confirmation GET) rather 522 * than merely rendered, the runtime <b>refuses the open</b> (renders a visible refusal; issues no {@code 523 * fetch}) instead of GETting a malformed or undeclared path, when: 524 * <ul> 525 * <li>a {@code {property}} token's row value is absent, <jk>null</jk>, or blank; 526 * <li>the resolved URL still carries an unsubstituted {@code {property}} token (e.g. the page shipped 527 * {@code juneau-views.js} without its {@code juneau-renders.js} peer); or 528 * <li>the resolved URL contains a {@code ..} path segment (which browser URL resolution would otherwise 529 * normalize into a different, undeclared endpoint). 530 * </ul> 531 * {@code href} keeps substituting to an empty string in each of these cases, because a rendered link is 532 * not a fetch. 533 * 534 * <p> 535 * <b>Divergence from {@link #endpoint(String) endpoint}:</b> a blank/absent {@code form} is still the 536 * confirm-only local path (no GET), not a {@code no-endpoint} refusal. A token-less {@code form} is fetched 537 * as declared. 538 * 539 * @param value The new value. Can be <jk>null</jk> or blank for a confirm-only dialog. 540 * @return This object. 541 */ 542 public RowAction form(String value) { 543 form = value; 544 return this; 545 } 546 547 /** 548 * Sets the presentation hint. 549 * 550 * @param value The presentation. Must not be <jk>null</jk>. 551 * @return This object. 552 */ 553 public RowAction present(Present value) { 554 present = value.wire(); 555 return this; 556 } 557 558 /** 559 * Sets the success-behavior hint. 560 * 561 * @param value The success behavior. Must not be <jk>null</jk>. 562 * @return This object. 563 */ 564 public RowAction onSuccess(OnSuccess value) { 565 onSuccess = value.wire(); 566 return this; 567 } 568 569 /** 570 * Gates this action on the row's own state, comparing one field against one value. 571 * 572 * <p> 573 * Mirrors {@code ActionRef.enabledWhen(String,Op,Object,String)} on the detail-panel {@code ActionBar} 574 * (widgets module). This is presentation only, never the authorization gate: the endpoint behind this 575 * action must still perform its own real check and refuse on its own authority. A row whose field does 576 * not satisfy every declared rule renders this action present but disabled, carrying {@code reason} as its 577 * explanation; disabled, never hidden. Call this more than once to declare more than one rule — all 578 * of them must match, and when several fail, the runtime shows the first-declared failing reason. 579 * 580 * @param field The row field to test. Must not be <jk>null</jk> or blank. 581 * @param op The comparison operator. Must be {@link Op#EQ} or {@link Op#NE}. 582 * @param value The comparison value. Must not be <jk>null</jk>. 583 * @param reason Why the action is unavailable when this rule does not match. Must not be <jk>null</jk> or 584 * blank. 585 * @return This object. 586 */ 587 public RowAction enabledWhen(String field, Op op, Object value, String reason) { 588 return addEnabledRule(RowActionEnabledRule.of(field, op, value, reason)); 589 } 590 591 /** 592 * Gates this action on the presence or absence of a row field. 593 * 594 * <p> 595 * The presence-based counterpart of {@link #enabledWhen(String,Op,Object,String)}, which carries the full 596 * semantics. 597 * 598 * @param field The row field to test. Must not be <jk>null</jk> or blank. 599 * @param op The comparison operator. Must be {@link Op#PRESENT} or {@link Op#ABSENT}. 600 * @param reason Why the action is unavailable when this rule does not match. Must not be <jk>null</jk> or 601 * blank. 602 * @return This object. 603 */ 604 public RowAction enabledWhen(String field, Op op, String reason) { 605 return addEnabledRule(RowActionEnabledRule.of(field, op, reason)); 606 } 607 608 private RowAction addEnabledRule(RowActionEnabledRule rule) { 609 if (enabledWhen == null) 610 enabledWhen = l(); 611 enabledWhen.add(rule); 612 return this; 613 } 614 615 /** 616 * Hides this action, per row, unless every rule matches (see {@link #visibleWhen} for the semantics). 617 * 618 * @param rules The rules; all must match. Replaces any rules set by an earlier call. 619 * @return This object. 620 */ 621 public RowAction visibleWhen(VisibilityRule...rules) { 622 visibleWhen = l(rules); 623 return this; 624 } 625 626 /** 627 * Sets the confirmation tone. 628 * 629 * @param value The tone. Must not be <jk>null</jk>. 630 * @return This object. 631 */ 632 public RowAction tone(Tone value) { 633 tone = value.wire(); 634 return this; 635 } 636 637 /** 638 * Sets the text of the confirm button. 639 * 640 * @param value The label. Can be <jk>null</jk> to fall back to {@link #label}. 641 * @return This object. 642 */ 643 public RowAction confirmLabel(String value) { 644 confirmLabel = value; 645 return this; 646 } 647 648 /** 649 * Sets the name of the confirm renderer to use for this action's confirmation. 650 * 651 * <p> 652 * The name must be registered on the client with {@code JuneauViews.rowActions.registerConfirmRenderer}; an 653 * unregistered name refuses the confirmation. 654 * 655 * <p> 656 * A renderer on its own, with no {@link #confirm(String) confirm} text, is enough to prompt: the action asks the 657 * renderer before it submits even without {@code present(DIALOG)}, and the prompt is titled with the action's name. 658 * 659 * @param value The renderer name. Can be <jk>null</jk> for the built-in modal. 660 * @return This object. 661 */ 662 public RowAction confirmRenderer(String value) { 663 confirmRenderer = value; 664 return this; 665 } 666}