001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.rest.widget;
014
015import static org.apache.juneau.common.internal.IOUtils.*;
016import static org.apache.juneau.common.internal.StringUtils.*;
017import static org.apache.juneau.common.internal.ThrowableUtils.*;
018
019import java.io.*;
020
021import org.apache.juneau.html.*;
022import org.apache.juneau.internal.*;
023import org.apache.juneau.rest.*;
024import org.apache.juneau.serializer.*;
025
026/**
027 * A subclass of widgets for rendering menu items with drop-down windows.
028 *
029 * <h5 class='section'>See Also:</h5><ul>
030 *    <li class='link'><a class="doclink" href="../../../../../index.html#jrs.HtmlPredefinedWidgets">Predefined Widgets</a>
031 *    <li class='link'><a class="doclink" href="../../../../../index.html#jrs.HtmlWidgets">Widgets</a>
032 * </ul>
033 */
034public abstract class MenuItemWidget extends Widget {
035
036   /**
037    * Returns the Javascript needed for the show and hide actions of the menu item.
038    */
039   @Override /* Widget */
040   public String getScript(RestRequest req, RestResponse res) {
041      return loadScript(req, "scripts/MenuItemWidget.js");
042   }
043
044   /**
045    * Optional Javascript to execute immediately before a menu item is shown.
046    *
047    * <p>
048    * For example, the following shows how the method could be used to make an AJAX call back to the REST
049    * interface to populate a SELECT element in the contents of the popup dialog:
050    *
051    * <p class='bjava'>
052    *    <ja>@Override</ja>
053    *    <jk>public</jk> String getBeforeShowScript(RestRequest <jv>req</jv>) {
054    *       <jk>return</jk> <js>""</js>
055    *          + <js>"\n   var xhr = new XMLHttpRequest();"</js>
056    *          + <js>"\n   xhr.open('GET', '/petstore/pet?s=status=AVAILABLE&amp;v=id,name', true);"</js>
057    *          + <js>"\n   xhr.setRequestHeader('Accept', 'application/json');"</js>
058    *          + <js>"\n   xhr.onload = function() {"</js>
059    *          + <js>"\n       var pets = JSON.parse(xhr.responseText);"</js>
060    *          + <js>"\n      var select = document.getElementById('addPet_names');"</js>
061    *          + <js>"\n      select.innerHTML = '';"</js>
062    *          + <js>"\n      for (var i in pets) {"</js>
063    *          + <js>"\n         var pet = pets[i];"</js>
064    *          + <js>"\n         var opt = document.createElement('option');"</js>
065    *          + <js>"\n         opt.value = pet.id;"</js>
066    *          + <js>"\n         opt.innerHTML = pet.name;"</js>
067    *          + <js>"\n         select.appendChild(opt);"</js>
068    *          + <js>"\n      }"</js>
069    *          + <js>"\n   }"</js>
070    *          + <js>"\n   xhr.send();"</js>
071    *       ;
072    *    }
073    * </p>
074    *
075    * <p>
076    * Note that it's often easier (and cleaner) to use the {@link #loadScript(RestRequest,String)} method and read the Javascript from
077    * your classpath:
078    *
079    * <p class='bjava'>
080    *    <ja>@Override</ja>
081    *    <jk>public</jk> String getBeforeShowScript(RestRequest <jv>req</jv>) <jk>throws</jk> Exception {
082    *       <jk>return</jk> loadScript(<js>"AddOrderMenuItem_beforeShow.js"</js>);
083    *    }
084    * </p>
085    *
086    * @param req The HTTP request object.
087    * @param res The HTTP response object.
088    * @return Javascript code to execute, or <jk>null</jk> if there isn't any.
089    */
090   public String getBeforeShowScript(RestRequest req, RestResponse res) {
091      return null;
092   }
093
094   /**
095    * Optional Javascript to execute immediately after a menu item is shown.
096    *
097    * <p>
098    * Same as {@link #getBeforeShowScript(RestRequest,RestResponse)} except this Javascript gets executed after the popup dialog has become visible.
099    *
100    * @param req The HTTP request object.
101    * @param res The HTTP response object.
102    * @return Javascript code to execute, or <jk>null</jk> if there isn't any.
103    */
104   public String getAfterShowScript(RestRequest req, RestResponse res) {
105      return null;
106   }
107
108   /**
109    * Defines a <js>"menu-item"</js> class that needs to be used on the outer element of the HTML returned by the
110    * {@link #getHtml(RestRequest,RestResponse)} method.
111    */
112   @Override /* Widget */
113   public String getStyle(RestRequest req, RestResponse res) {
114      return loadStyle(req, "styles/MenuItemWidget.css");
115   }
116
117   @Override /* Widget */
118   public String getHtml(RestRequest req, RestResponse res) {
119      StringBuilder sb = new StringBuilder();
120
121      // Need a unique number to define unique function names.
122      Integer id = null;
123
124      String pre = nullIfEmpty(getBeforeShowScript(req, res)), post = nullIfEmpty(getAfterShowScript(req, res));
125
126      sb.append("\n<div class='menu-item'>");
127      if (pre != null || post != null) {
128         id = getId(req);
129
130         sb.append("\n\t<script>");
131         if (pre != null) {
132            sb.append("\n\t\tfunction onPreShow" + id + "() {");
133            sb.append("\n").append(pre);
134            sb.append("\n\t\t}");
135         }
136         if (post != null) {
137            sb.append("\n\t\tfunction onPostShow" + id + "() {");
138            sb.append("\n").append(pre);
139            sb.append("\n\t\t}");
140         }
141         sb.append("\n\t</script>");
142      }
143      String onclick = (pre == null ? "" : "onPreShow"+id+"();") + "menuClick(this);" + (post == null ? "" : "onPostShow"+id+"();");
144      sb.append(""
145         + "\n\t<a onclick='"+onclick+"'>"+getLabel(req, res)+"</a>"
146         + "\n<div class='popup-content'>"
147      );
148      Object o = getContent(req, res);
149      if (o instanceof Reader) {
150         try (Reader r = (Reader)o; Writer w = new StringBuilderWriter(sb)) {
151            pipe(r, w);
152         } catch (IOException e) {
153            throw asRuntimeException(e);
154         }
155      } else if (o instanceof CharSequence) {
156         sb.append((CharSequence)o);
157      } else {
158         WriterSerializerSession session = HtmlSerializer.DEFAULT
159            .createSession()
160            .properties(req.getAttributes().asMap())
161            .debug(req.isDebug() ? true : null)
162            .uriContext(req.getUriContext())
163            .useWhitespace(req.isPlainText() ? true : null)
164            .resolver(req.getVarResolverSession())
165            .build();
166         session.indent = 2;
167         try {
168            session.serialize(o, sb);
169         } catch (Exception e) {
170            throw asRuntimeException(e);
171         }
172      }
173      sb.append(""
174         + "\n\t</div>"
175         + "\n</div>"
176      );
177      return sb.toString();
178   }
179
180   private Integer getId(RestRequest req) {
181      Integer id = req.getAttribute("LastMenuItemId").as(Integer.class).orElse(0) + 1;
182      req.setAttribute("LastMenuItemId", id);
183      return id;
184   }
185
186   /**
187    * The label for the menu item as it's rendered in the menu bar.
188    *
189    * @param req The HTTP request object.
190    * @param res The HTTP response object.
191    * @return The menu item label.
192    */
193   public abstract String getLabel(RestRequest req, RestResponse res);
194
195   /**
196    * The content of the popup.
197    *
198    * @param req The HTTP request object.
199    * @param res The HTTP response object.
200    * @return
201    *    The content of the popup.
202    *    <br>Can be any of the following types:
203    *    <ul>
204    *       <li>{@link Reader} - Serialized directly to the output.
205    *       <li>{@link CharSequence} - Serialized directly to the output.
206    *       <li>Other - Serialized as HTML using {@link HtmlSerializer#DEFAULT}.
207    *          <br>Note that this includes any of the {@link org.apache.juneau.dto.html5} beans.
208    *    </ul>
209    */
210   public abstract Object getContent(RestRequest req, RestResponse res);
211}