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.freemarker;
018
019import java.util.*;
020
021import org.apache.juneau.console.*;
022
023/**
024 * A rail section of checkboxes over the distinct values of one dictionary column of a data source.
025 *
026 * <p>
027 * Publishes {@code {schemaVersion:1, viewId, columns:{<dim>: [selected...]}, options:{deselected:[...]}}}, omitting
028 * {@code columns} and {@code options} when every value is selected.  The {@code deselected} list names the values the
029 * user has unticked (capped at {@link #max(int) max}), so a reload can list them again and tick them back.  The array
030 * value is a data-source extension: datatables do not apply it.
031 * </p>
032 *
033 * <p>
034 * Unticking every value publishes an empty list ({@code columns:{<dim>: []}}).  The data sources map an empty list to
035 * no restriction on the dimension, so "nothing ticked" shows every row, the same as "everything ticked".  The section
036 * shows a "Select all" button while a filter is in force.
037 * </p>
038 *
039 * <h5 class='section'>Example:</h5>
040 * <p class='bjava'>
041 *    ChoiceSection.<jsm>fromDim</jsm>(<js>"orders"</js>, <js>"reason"</js>).label(<js>"Reason"</js>).max(50)
042 *       .publishes(Topics.<jsm>filter</jsm>(<js>"reasons"</js>));
043 * </p>
044 *
045 * @since 10.0.0
046 */
047@SuppressWarnings({
048   "java:S1192" // Repeated literals are contract wire keys; named constants would obscure the contract shape.
049})
050public final class ChoiceSection extends Section {
051
052   private final String source;
053   private final String dim;
054   private int max = 50;
055
056   private ChoiceSection(String source, String dim) {
057      this.source = source;
058      this.dim = dim;
059   }
060
061   /**
062    * Creates a choice section.
063    *
064    * @param source The data source id (its topic is {@code data:<source>}).
065    * @param dim The dictionary column whose values are listed.
066    * @return A new section.
067    */
068   public static ChoiceSection fromDim(String source, String dim) {
069      return new ChoiceSection(source, dim);
070   }
071
072   /**
073    * Caps the values shown before "Show all".
074    *
075    * @param value The cap, at least 1.  Defaults to 50.
076    * @return This object.
077    */
078   public ChoiceSection max(int value) {
079      max = value;
080      return this;
081   }
082
083   @Override /* Section */
084   public ChoiceSection publishes(String topic) {
085      super.publishes(topic);
086      return this;
087   }
088
089   @Override /* Section */
090   public ChoiceSection label(String label) {
091      super.label(label);
092      return this;
093   }
094
095   @Override String kind() { return "choice"; }
096
097   /** @return The source id. */
098   String source() {
099      return source;
100   }
101
102   @Override void check(String dash) {
103      super.check(dash);
104      if (! Topics.isKey(source))
105         throw e77(dash, "choice section '" + id() + "' source '" + source + "' is not a valid topic key");
106      if (! Topics.isKey(dim))
107         throw e77(dash, "choice section '" + id() + "' dim '" + dim + "' is not a valid topic key");
108      if (dim.equals("__proto__") || dim.equals("constructor") || dim.equals("prototype"))
109         throw e77(dash, "choice section '" + id() + "' dim '" + dim + "' is a reserved name");
110      if (max < 1)
111         throw e77(dash, "choice section '" + id() + "' max must be at least 1");
112   }
113
114   @Override void body(Map<String,Object> m) {
115      m.put("source", source);
116      m.put("dim", dim);
117      m.put("max", max);
118   }
119}