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.internal.StringUtils.*;
016
017import java.io.*;
018
019import org.apache.juneau.html.*;
020import org.apache.juneau.internal.*;
021import org.apache.juneau.rest.*;
022import org.apache.juneau.serializer.*;
023
024/**
025 * A subclass of widgets for rendering menu items with drop-down windows.
026 *
027 * <h5 class='section'>See Also:</h5>
028 * <ul>
029 *    <li class='link'>{@doc juneau-rest-server.HtmlDocAnnotation.PredefinedWidgets}
030 * </ul>
031 */
032public abstract class MenuItemWidget extends Widget {
033
034   /**
035    * Returns the Javascript needed for the show and hide actions of the menu item.
036    */
037   @Override /* Widget */
038   public String getScript(RestRequest req) throws Exception {
039      return loadScript("MenuItemWidget.js");
040   }
041
042   /**
043    * Optional Javascript to execute immediately before a menu item is shown.
044    *
045    * <p>
046    * For example, the following shows how the method could be used to make an AJAX call back to the REST
047    * interface to populate a SELECT element in the contents of the popup dialog:
048    *
049    * <p class='bcode w800'>
050    *    <ja>@Override</ja>
051    *    <jk>public</jk> String getBeforeShowScript(RestRequest req) {
052    *       <jk>return</jk> <js>""</js>
053    *          + <js>"\n   var xhr = new XMLHttpRequest();"</js>
054    *          + <js>"\n   xhr.open('GET', '/petstore/pet?s=status=AVAILABLE&v=id,name', true);"</js>
055    *          + <js>"\n   xhr.setRequestHeader('Accept', 'application/json');"</js>
056    *          + <js>"\n   xhr.onload = function() {"</js>
057    *          + <js>"\n       var pets = JSON.parse(xhr.responseText);"</js>
058    *          + <js>"\n      var select = document.getElementById('addPet_names');"</js>
059    *          + <js>"\n      select.innerHTML = '';"</js>
060    *          + <js>"\n      for (var i in pets) {"</js>
061    *          + <js>"\n         var pet = pets[i];"</js>
062    *          + <js>"\n         var opt = document.createElement('option');"</js>
063    *          + <js>"\n         opt.value = pet.id;"</js>
064    *          + <js>"\n         opt.innerHTML = pet.name;"</js>
065    *          + <js>"\n         select.appendChild(opt);"</js>
066    *          + <js>"\n      }"</js>
067    *          + <js>"\n   }"</js>
068    *          + <js>"\n   xhr.send();"</js>
069    *       ;
070    *    }
071    * </p>
072    *
073    * <p>
074    * Note that it's often easier (and cleaner) to use the {@link #loadScript(String)} method and read the Javascript from
075    * your classpath:
076    *
077    * <p class='bcode w800'>
078    *    <ja>@Override</ja>
079    *    <jk>public</jk> String getBeforeShowScript(RestRequest req) <jk>throws</jk> Exception {
080    *       <jk>return</jk> loadScript(<js>"AddOrderMenuItem_beforeShow.js"</js>);
081    *    }
082    * </p>
083    *
084    * @param req The current request.
085    * @return Javascript code to execute, or <jk>null</jk> if there isn't any.
086    * @throws Exception
087    */
088   public String getBeforeShowScript(RestRequest req) throws Exception {
089      return null;
090   }
091
092   /**
093    * Optional Javascript to execute immediately after a menu item is shown.
094    *
095    * <p>
096    * Same as {@link #getBeforeShowScript(RestRequest)} except this Javascript gets executed after the popup dialog has become visible.
097    *
098    * @param req The current request.
099    * @return Javascript code to execute, or <jk>null</jk> if there isn't any.
100    * @throws Exception
101    */
102   public String getAfterShowScript(RestRequest req) throws Exception {
103      return null;
104   }
105
106   /**
107    * Defines a <js>"menu-item"</js> class that needs to be used on the outer element of the HTML returned by the
108    * {@link #getHtml(RestRequest)} method.
109    */
110   @Override /* Widget */
111   public String getStyle(RestRequest req) throws Exception {
112      return loadStyle("MenuItemWidget.css");
113   }
114
115   @Override /* Widget */
116   public String getHtml(RestRequest req) throws Exception {
117      StringBuilder sb = new StringBuilder();
118
119      // Need a unique number to define unique function names.
120      Integer id = null;
121
122      String pre = nullIfEmpty(getBeforeShowScript(req)), post = nullIfEmpty(getAfterShowScript(req));
123
124      sb.append("\n<div class='menu-item'>");
125      if (pre != null || post != null) {
126         id = getId(req);
127
128         sb.append("\n\t<script>");
129         if (pre != null) {
130            sb.append("\n\t\tfunction onPreShow" + id + "() {");
131            sb.append("\n").append(pre);
132            sb.append("\n\t\t}");
133         }
134         if (post != null) {
135            sb.append("\n\t\tfunction onPostShow" + id + "() {");
136            sb.append("\n").append(pre);
137            sb.append("\n\t\t}");
138         }
139         sb.append("\n\t</script>");
140      }
141      String onclick = (pre == null ? "" : "onPreShow"+id+"();") + "menuClick(this);" + (post == null ? "" : "onPostShow"+id+"();");
142      sb.append(""
143         + "\n\t<a onclick='"+onclick+"'>"+getLabel(req)+"</a>"
144         + "\n<div class='popup-content'>"
145      );
146      Object o = getContent(req);
147      if (o instanceof Reader) {
148         try (Reader r = (Reader)o; Writer w = new StringBuilderWriter(sb)) {
149            IOUtils.pipe(r, w);
150         }
151      } else if (o instanceof CharSequence) {
152         sb.append((CharSequence)o);
153      } else {
154         SerializerSessionArgs args = new SerializerSessionArgs(req.getProperties(), null, req.getLocale(), null, null, null, req.isDebug() ? true : null, req.getUriContext(), req.isPlainText() ? true : null);
155         WriterSerializerSession session = HtmlSerializer.DEFAULT.createSession(args);
156         session.indent = 2;
157         session.serialize(o, sb);
158      }
159      sb.append(""
160         + "\n\t</div>"
161         + "\n</div>"
162      );
163      return sb.toString();
164   }
165
166   private Integer getId(RestRequest req) {
167      Integer id = (Integer)req.getAttribute("LastMenuItemId");
168      if (id == null)
169         id = 1;
170      else
171         id = id + 1;
172      req.setAttribute("LastMenuItemId", id);
173      return id;
174   }
175
176   /**
177    * The label for the menu item as it's rendered in the menu bar.
178    *
179    * @param req The HTTP request object.
180    * @return The menu item label.
181    * @throws Exception
182    */
183   public abstract String getLabel(RestRequest req) throws Exception;
184
185   /**
186    * The content of the popup.
187    *
188    * @param req The HTTP request object.
189    * @return
190    *    The content of the popup.
191    *    <br>Can be any of the following types:
192    *    <ul>
193    *       <li>{@link Reader} - Serialized directly to the output.
194    *       <li>{@link CharSequence} - Serialized directly to the output.
195    *       <li>Other - Serialized as HTML using {@link HtmlSerializer#DEFAULT}.
196    *          <br>Note that this includes any of the {@link org.apache.juneau.dto.html5} beans.
197    *    </ul>
198    * @throws Exception
199    */
200   public abstract Object getContent(RestRequest req) throws Exception;
201}