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&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}