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.*;
024
025/**
026 * Author-declared per-cell popover attached to a {@link Render}.
027 *
028 * <p>
029 * Omitted from the {@code VIEW_META} wire when <jk>null</jk>.  The client opens it on click/Enter/Space and
030 * fills it from the DataTables row already on the client &mdash; no fetch, no {@code innerHTML}.
031 *
032 * @since 10.0.0
033 */
034@BeanType(properties="title,fields")
035@SuppressWarnings({
036   "java:S1845" // Fluent-builder setters intentionally mirror field names (Juneau DSL convention).
037})
038public class CellPopover {
039
040   /** Optional dialog title.  Also used as the trigger's accessible name when non-blank. */
041   public String title;
042
043   /** Ordered fields.  At least one is required. */
044   public List<PopoverField> fields;
045
046   /**
047    * Creates a popover with the specified fields.
048    *
049    * @param fields The fields, in display order.
050    * @return A new {@link CellPopover}.
051    */
052   public static CellPopover of(PopoverField...fields) {
053      var p = new CellPopover();
054      p.fields = l(fields);
055      return p;
056   }
057
058   /**
059    * Sets the optional dialog title.
060    *
061    * @param value The title.  Can be <jk>null</jk>.
062    * @return This object.
063    */
064   public CellPopover title(String value) {
065      title = value;
066      return this;
067   }
068
069   /**
070    * Sets the ordered fields.
071    *
072    * @param value The fields, in display order.
073    * @return This object.
074    */
075   public CellPopover fields(PopoverField...value) {
076      fields = l(value);
077      return this;
078   }
079
080   /**
081    * Fail-closed bean validation.
082    *
083    * @throws IllegalArgumentException If this popover is not well-formed.
084    */
085   public void validate() {
086      if (ie(fields))
087         throw iaex("CellPopover must declare at least one field.");
088      var keys = new HashSet<String>();
089      for (var f : fields)
090         validateField(keys, f);
091   }
092
093   /**
094    * Validates one field slot: non-null, a non-blank {@code data} key unique within this popover, and (when a
095    * renderer is declared) a non-blank render id present in the popover render allowlist.
096    *
097    * @param keys The field-data keys seen so far in this popover; mutated to add this field's key.
098    * @param f The field to validate.
099    * @throws IllegalArgumentException If this field is not well-formed.
100    */
101   private static void validateField(Set<String> keys, PopoverField f) {
102      if (f == null)
103         throw iaex("CellPopover field must not be null.");
104      if (f.data == null || f.data.isBlank())
105         throw iaex("PopoverField data must not be null or blank.");
106      if (!keys.add(f.data))
107         throw iaex("CellPopover duplicate field data key '%s'.", f.data);
108      if (f.render != null) {
109         if (f.render.id == null || f.render.id.isBlank())
110            throw iaex("PopoverField render id must not be null or blank.");
111         SinkRenderAllowlist.assertPopoverAllowed(f.render.id);
112      }
113   }
114}