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