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 — 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}