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