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.*;
025
026/**
027 * A named cell-renderer reference in the {@code VIEW_META} wire contract.
028 *
029 * <p>
030 * Serializes to the canonical object form <c>{"id":"tag","meta":{"field":"status"}}</c>; the optional
031 * {@link #meta} and {@link #popover} members are omitted from the wire when unset.  The client
032 * {@code juneau-renders.js} registry looks up {@link #id} and invokes the matching renderer, passing the
033 * {@link #meta} map through as per-column context (and painting {@link #popover}, when present, from row data
034 * already on the client).  Typed factories exist for the renderers that take structured metadata:
035 * {@link #progress(int)} and {@link #pill(String)}.  Timestamp renderers ({@code ts-zulu}, {@code datetime})
036 * honor {@code meta.popup}: {@code off} disables the local/California hover popup; {@code ts-zulu} defaults
037 * the popup on, {@code datetime} defaults it off.
038 *
039 * <p>
040 * A compact string sugar is supported via {@link #parse(String)}: everything after the <b>first</b> colon becomes
041 * <c>meta.field</c> &mdash; e.g. <c>"tag:status"</c> &rarr; <c>{id:"tag", meta:{field:"status"}}</c> and
042 * <c>"date"</c> &rarr; <c>{id:"date"}</c>.
043 *
044 * <h5 class='section'>See Also:</h5>
045 * <ul>
046 *    <li class='jc'>{@link ViewTable}
047 * </ul>
048 *
049 * @since 10.0.0
050 */
051@BeanType(properties="id,meta,popover")
052@SuppressWarnings({
053   "java:S1845" // Fluent-builder setters intentionally mirror field names (Juneau DSL convention).
054})
055public class Render {
056
057   /** The renderer id (registry key in {@code juneau-renders.js}). */
058   public String id;
059
060   /** Optional per-column renderer metadata.  Omitted from the wire when absent. */
061   public Map<String,Object> meta;
062
063   /**
064    * Creates a renderer reference with the specified id and no metadata.
065    *
066    * @param id The renderer id.  Must not be <jk>null</jk>.
067    * @return A new {@link Render}.
068    */
069   public static Render of(String id) {
070      var r = new Render();
071      r.id = id;
072      return r;
073   }
074
075   /**
076    * Parses a render-id string into a {@link Render}, applying the <c>"id:field"</c> string sugar.
077    *
078    * <p>
079    * Everything after the <b>first</b> colon becomes <c>meta.field</c>; a bare id (no colon) yields a
080    * metadata-free renderer.
081    *
082    * @param s The render-id string.  Must not be <jk>null</jk> or blank.
083    * @return A new {@link Render}.
084    */
085   public static Render parse(String s) {
086      if (ib(s))
087         throw iaex("Render id must not be null or blank.");
088      var i = s.indexOf(':');
089      if (i < 0)
090         return of(s);
091      return of(s.substring(0, i)).meta("field", s.substring(i + 1));
092   }
093
094   /**
095    * Adds a metadata key/value pair to this renderer reference.
096    *
097    * @param key The metadata key.  Must not be <jk>null</jk>.
098    * @param value The metadata value.  Serialized as given, so a {@link Number} or {@link Boolean} stays a JSON
099    *    number or boolean (for example {@code meta("places", 2)}).
100    * @return This object.
101    */
102   public Render meta(String key, Object value) {
103      if (meta == null)
104         meta = m();
105      meta.put(key, value);
106      return this;
107   }
108
109   /**
110    * Optional per-cell popover, omitted from the wire when unset.
111    *
112    * <p>
113    * The runtime appends a sibling trigger next to the rendered cell and paints the popover from row data
114    * already on the client.  Never assigned {@code innerHTML}.
115    */
116   public CellPopover popover;
117
118   /**
119    * Sets the optional per-cell popover.
120    *
121    * @param value The popover definition.  Can be <jk>null</jk> to unset.
122    * @return This object.
123    */
124   public Render popover(CellPopover value) {
125      popover = value;
126      return this;
127   }
128
129   /**
130    * Creates a {@code progress} renderer with the given maximum (denominator).
131    *
132    * <p>
133    * Equivalent to {@code Render.of("progress").meta("max", String.valueOf(max))}.  Chain
134    * {@link #meta(String,Object)} for {@code label} / {@code field}.
135    *
136    * @param max The ratio denominator.  Serialized as the string {@code meta.max}.
137    * @return A new {@link Render}.
138    */
139   public static Render progress(int max) {
140      return of("progress").meta("max", String.valueOf(max));
141   }
142
143   /**
144    * Creates a {@code progress} renderer with maximum and optional warn/exceeds thresholds.
145    *
146    * <p>
147    * {@code warn} and {@code exceeds} are omitted from {@code meta} when <jk>null</jk>.  A boxed {@code 0} is a
148    * real threshold ({@code "0"}), not omitted.
149    *
150    * @param max The ratio denominator.
151    * @param warn Warn threshold in the same units as the cell value.  Can be <jk>null</jk>.
152    * @param exceeds Exceeds threshold in the same units as the cell value.  Can be <jk>null</jk>.
153    * @return A new {@link Render}.
154    */
155   public static Render progress(int max, Integer warn, Integer exceeds) {
156      var r = progress(max);
157      if (warn != null)
158         r.meta("warn", String.valueOf(warn));
159      if (exceeds != null)
160         r.meta("exceeds", String.valueOf(exceeds));
161      return r;
162   }
163
164   /**
165    * Creates a {@code pill} renderer with the leading dot on and the tone inherited from the chip theme.
166    *
167    * <p>
168    * Equivalent to {@code Render.of("pill")}.  The dot color follows the {@code .tag.<field>.<value>} theme
169    * ({@code currentColor}) until an explicit {@code tone} is chosen.  Chain {@link #meta(String,Object)} for
170    * {@code field}, {@code dot} ({@code on}|{@code off}) and {@code action} (a {@code rowActions} id).
171    *
172    * <p>
173    * {@code action} is optional and only meaningful on a table column: a pill named on a field-grid fill
174    * sink is unconditionally display-only and rejects an {@code action} outright.
175    *
176    * @return A new {@link Render}.
177    */
178   public static Render pill() {
179      return of("pill");
180   }
181
182   /**
183    * Creates a {@code pill} renderer with an explicit dot tone.
184    *
185    * <p>
186    * {@code tone} is one of the five status tones
187    * {@code info}|{@code success}|{@code warning}|{@code error}|{@code neutral} (see
188    * {@link StatusTone}).  A <jk>null</jk> or blank {@code tone} is omitted
189    * from {@code meta} (identical to {@link #pill()}); {@code neutral} is emitted verbatim and, like an absent tone,
190    * leaves the dot inheriting {@code currentColor}.  The tone is not validated server-side: the client
191    * (<c>NS._render.pillTones</c>) ignores an off-palette token, so the dot gets no tone class.  Prefer
192    * {@link #pill(StatusTone)} to stay on the palette.
193    *
194    * <p>
195    * Not to be confused with {@link #progress(int, Integer, Integer)}'s {@code warn}/{@code exceeds} meta, which are
196    * numeric thresholds rather than tone names and share no vocabulary with this palette.
197    *
198    * @param tone The explicit dot tone.  Can be <jk>null</jk> or blank to inherit.
199    * @return A new {@link Render}.
200    */
201   public static Render pill(String tone) {
202      var r = pill();
203      if (inb(tone))
204         r.meta("tone", tone);
205      return r;
206   }
207
208   /**
209    * Creates a {@code pill} renderer with an explicit dot tone taken from the {@link StatusTone} palette.
210    *
211    * <p>
212    * Equivalent to <c>pill(tone == <jk>null</jk> ? <jk>null</jk> : tone.wire())</c>; a <jk>null</jk> tone is
213    * omitted from {@code meta}, identical to {@link #pill()}.
214    *
215    * <h5 class='section'>Example:</h5>
216    * <p class='bjava'>
217    *    <jc>// Renders as {id:"pill",meta:{tone:"warning"}}.</jc>
218    *    Render <jv>r</jv> = Render.<jsm>pill</jsm>(StatusTone.<jsf>WARNING</jsf>);
219    * </p>
220    *
221    * @param tone The explicit dot tone.  Can be <jk>null</jk> to inherit.
222    * @return A new {@link Render}.
223    */
224   public static Render pill(StatusTone tone) {
225      return pill(tone == null ? null : tone.wire());
226   }
227}