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} &mdash; 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 &mdash; 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 &mdash; 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" &mdash; 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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash;
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 &mdash; 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 &mdash; 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 &mdash; 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}