View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.devacfr.maven.skins.reflow.model;
20  
21  import javax.annotation.Nonnull;
22  import javax.annotation.Nullable;
23  
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.List;
27  
28  import com.google.common.base.Strings;
29  import com.google.common.collect.Lists;
30  import org.apache.commons.lang3.builder.ToStringBuilder;
31  import org.codehaus.plexus.util.xml.Xpp3Dom;
32  import org.devacfr.maven.skins.reflow.ISkinConfig;
33  import org.devacfr.maven.skins.reflow.SkinConfigTool;
34  import org.devacfr.maven.skins.reflow.Xpp3Utils;
35  
36  import static java.util.Objects.requireNonNull;
37  
38  /**
39   * Represents the navside menu component used in document page.
40   * <p>
41   * A sample configuration would be like that:
42   * </p>
43   *
44   * <pre>
45   * {@code
46   * <custom>
47   *   <reflowSkin>
48   *     <pages>
49   *       <document type="doc">
50   *          <menu name="Documentation" selectFirstOnExpand="true">
51   *              <item name="Get Started" href="get-started.html" />
52   *              <item name="Layouts">
53   *                  <item name="Overview" href="layouts.html" />
54   *                  <item name="Body" href="body.html" />
55   *              </item>
56   *              <item name="Migration" href="migration.html" />
57   *          </menu>
58   *       </document>
59   *     </pages>
60   *   </reflowSkin>
61   * </custom>
62   * }∂
63   * </pre>
64   *
65   * @author Christophe Friederich
66   * @since 2.0
67   */
68  public class NavSideMenu extends BsComponent {
69  
70      /** */
71      private static final String COMPONENT = "navside-menu";
72  
73      /** */
74      private static final String MENU_COMPONENT = "menu";
75  
76      /** */
77      private String name;
78  
79      /** */
80      private List<SideNavMenuItem> items;
81  
82      /** */
83      private boolean selectFirstOnExpand = false;
84  
85      /**
86       * Find all {@link SideNavMenuItem sidenav menu items} declared in all document pages.
87       *
88       * @param config
89       *            a config (can <b>not</b> be {@code null}).
90       * @return Returns a list of all all {@link SideNavMenuItem sidenav menu items} declared in all document pages
91       *         (returns list can <b>not</b> be {@code null}).
92       */
93      @Nonnull
94      public static List<SideNavMenuItem> findAllSideNavMenuItems(@Nonnull final ISkinConfig config) {
95          requireNonNull(config);
96          final Xpp3Dom pagesNode = Xpp3Utils.getFirstChild(config.getGlobalProperties(), "pages", config.getNamespace());
97          if (pagesNode == null) {
98              return Collections.emptyList();
99          }
100         final Xpp3Dom[] pages = pagesNode.getChildren();
101         final List<SideNavMenuItem> includePages = new ArrayList<>();
102         for (final Xpp3Dom page : pages) {
103             final String type = page.getAttribute("type");
104             if ("doc".equals(type)) {
105                 // This allows preventing accidental reuse of child page in other module of
106                 // project
107                 final String projectId = page.getAttribute("project");
108                 if (!Strings.isNullOrEmpty(projectId) && !projectId.equals(config.getProjectId())) {
109                     continue;
110                 }
111                 final Xpp3Dom menu = page.getChild(MENU_COMPONENT);
112                 if (menu == null) {
113                     continue;
114                 }
115                 final String pageName = page.getName();
116                 // create a flatten list containing all menuItem.
117                 addMenuItemRecursively(includePages, config, menu, pageName, true);
118             }
119         }
120         return includePages;
121     }
122 
123     /**
124      * Default constructor.
125      *
126      * @param config
127      *            a config (can <b>not</b> be {@code null}).
128      */
129     public NavSideMenu(@Nonnull final ISkinConfig config) {
130         super("navside");
131         requireNonNull(config);
132         final Xpp3Dom pageNode = config.getPageProperties();
133         final Xpp3Dom menu = pageNode.getChild(MENU_COMPONENT);
134         final List<SideNavMenuItem> items = Lists.newArrayList();
135         if (menu != null) {
136             final String pageName = pageNode.getName();
137             addMenuItemRecursively(items, config, menu, pageName, false);
138 
139             this.withName(menu.getAttribute("name"))
140                     .withItems(items)
141                     .withSelectFirstOnSelect(
142                         config.getAttributeValue(MENU_COMPONENT, "selectFirstOnExpand", Boolean.class, true));
143             this.setTheme(config.getAttributeValue(COMPONENT, "theme", String.class, "light"));
144             this.setBackground(config.getAttributeValue(COMPONENT, "background", String.class, "light"));
145             this.setCssClass(config.getAttributeValue(COMPONENT, "cssClass", String.class, null));
146         }
147         this.addCssOptions("m-sidenav-enabled");
148         if (isSelectFirstOnExpand()) {
149             this.addCssOptions("m-sidenav-select-first-on-select");
150         }
151     }
152 
153     /**
154      * Gets the name of menu displayed on top of navside menu.
155      *
156      * @return Returns a {@link String} representing the name of menu.
157      */
158     @Nullable public String getName() {
159         return name;
160     }
161 
162     /**
163      * Sets the name of menu displayed on top of navside menu.
164      *
165      * @param name
166      *            the name to use.
167      * @return Returns the fluent instance.
168      */
169     protected NavSideMenu withName(final String name) {
170         this.name = name;
171         return this;
172     }
173 
174     /**
175      * Gets the indicating whether menu contains a least one menu item.
176      *
177      * @return Returns {@code true} whether menu contains a least one menu item, otherwise returns {@code false}.
178      */
179     public boolean isHasItems() {
180         return items != null && !items.isEmpty();
181     }
182 
183     /**
184      * @return Returns the {@link List} of {@link SideNavMenuItem} containing in {@code <menu> element}.
185      */
186     @Nonnull
187     public List<SideNavMenuItem> getItems() {
188         return items;
189     }
190 
191     /**
192      * Sets the {@link List} of {@link SideNavMenuItem}.
193      *
194      * @param items
195      *            list of items to use.
196      * @return Returns the fluent instance.
197      */
198     protected NavSideMenu withItems(final List<SideNavMenuItem> items) {
199         this.items = items;
200         return this;
201     }
202 
203     /**
204      * Gets the indicating whether the first sub menu item should be selected when a dropdown menu item is selected and
205      * should expand.
206      *
207      * @return Returns {@code true} whether the first sub menu item should be selected when a dropdown menu item is
208      *         selected and expanded, otherwise returns {@code false}.
209      */
210     public boolean isSelectFirstOnExpand() {
211         return selectFirstOnExpand;
212     }
213 
214     /**
215      * Sets the indicating whether the first sub menu item should be selected when a dropdown menu item is selected and
216      * should expand.
217      *
218      * @param selectFirstOnExpand
219      *            a value to use.
220      * @return Returns the fluent instance.
221      */
222     protected NavSideMenu withSelectFirstOnSelect(final boolean selectFirstOnExpand) {
223         this.selectFirstOnExpand = selectFirstOnExpand;
224         return this;
225     }
226 
227     /**
228      * {@inheritDoc}
229      */
230     @Override
231     public String toString() {
232         return ToStringBuilder.reflectionToString(this);
233     }
234 
235     /**
236      * @param menuItems
237      * @param parentNode
238      * @param pageName
239      * @param flatten
240      */
241     private static void addMenuItemRecursively(@Nonnull final List<SideNavMenuItem> menuItems,
242         @Nonnull final ISkinConfig config,
243         @Nonnull final Xpp3Dom parentNode,
244         @Nonnull final String pageName,
245         final boolean flatten) {
246         for (final Xpp3Dom item : Xpp3Utils.getChildrenNodes(parentNode, "item")) {
247             final String href = item.getAttribute("href");
248             final SideNavMenuItem menuItem = new SideNavMenuItem().withName(item.getAttribute("name"))
249                     .withParent(pageName)
250                     .withHref(config.relativeLink(href))
251                     .withSlugName(SkinConfigTool.slugFilename(href))
252                     .withIcon(item.getAttribute("icon"));
253             menuItems.add(menuItem);
254             if (flatten) {
255                 addMenuItemRecursively(menuItems, config, item, pageName, true);
256             } else {
257                 final List<SideNavMenuItem> list = new ArrayList<>();
258                 menuItem.withItems(list);
259                 addMenuItemRecursively(list, config, item, pageName, false);
260             }
261         }
262     }
263 }