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