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