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