001 package edu.nrao.sss.webapp.faces.component; 002 003 import javax.faces.context.FacesContext; 004 import javax.faces.context.ResponseWriter; 005 import javax.faces.el.ValueBinding; 006 007 import java.io.IOException; 008 009 import java.util.Map; 010 011 /** 012 * This component extends the FloatingDialogUIComponent to provide both a 013 * serverside and clientside check for unsaved data when the exit button is 014 * hit. This component relies on 1 java script method being available in the 015 * rendered page: 016 * 017 * <p> 018 * <code>getClientSideChangesExist()</code>: returns true if changes have occured 019 * that have not yet been submitted. This method is <em>not</em> rendered by 020 * this component and must be provided by the application! 021 * </p> 022 * <p> 023 * TODO: Need to rewrite this and the FloatingDialogUIComponent to break up the 024 * rendering of the link/button into a separate method that's more easily 025 * overridden. 026 * </p> 027 * <p> 028 * NOTE: There is one outstanding problem with this safety mechanism, and that 029 * is, if a user changes a field, doesn't submit the form, but instead reloads 030 * the page for some reason, the form will still hold the new values they typed 031 * in (thanks to their trusty web browser) but neither the server, nor the java 032 * script will know that there have been changes. In this case, the user may 033 * loose data. It's rare enough that I don't think it's necessary to try and 034 * fix it. At least not yet. 035 * </p> 036 */ 037 public class ExitDialogUIComponent extends FloatingDialogUIComponent 038 { 039 private static final String javaScriptBegin = "var elementStyle = document.getElementById('"; 040 private static final String javaScriptEnd = "').style;" + 041 "elementStyle.display='block'; return false;"; 042 protected static final String RENDERED_SCRIPT_KEY = 043 "edu.nrao.sss.webapp.faces.component.ExitDialogUIComponent.JavaScriptRendered"; 044 045 private Boolean submittedChanges = null; 046 047 /** 048 * Override the encodeBegin method to make it output a button instead of a 049 * link and to write out fancier java script to handle the clientside 050 * changes flag. 051 */ 052 public void encodeBegin(FacesContext context) 053 throws IOException 054 { 055 if (!isRendered()) 056 return; 057 058 String id = getClientId(context); 059 encodeJavascriptMethods(context, id); 060 061 String linkStyleClass = (String)getAttributes().get("linkStyleClass"); 062 String dialogStyleClass = (String)getAttributes().get("dialogStyleClass"); 063 String styleClass = (String)getAttributes().get("styleClass"); 064 065 String dialogId = id + ":popup"; 066 067 ResponseWriter writer = context.getResponseWriter(); 068 writer.write('\n'); 069 writer.write('\n'); 070 071 //Start a wrapper div with position: relative so the popup can be 072 //positioned relative to it. 073 writer.startElement("div", this); 074 writer.writeAttribute("id", id, null); 075 076 if (styleClass != null) 077 writer.writeAttribute("class", styleClass, null); 078 079 writer.write('\n'); 080 081 //This is our button section. 082 writer.startElement("input", this); 083 writer.writeAttribute("value", "Exit", null); 084 writer.writeAttribute("type", "submit", null); 085 writer.writeAttribute("id", "LOGOUT", null); 086 writer.writeAttribute("name", "LOGOUT", null); 087 088 String title = getTitle(); 089 if (title != null) 090 writer.writeAttribute("title", title, null); 091 092 if (linkStyleClass != null) 093 writer.writeAttribute("class", linkStyleClass, null); 094 095 String onclick = javaScriptBegin + dialogId + javaScriptEnd; 096 097 //If we don't have server side changes, we have to check if we client side 098 //changes in the javascript. 099 if (!getHasSubmittedChanges()) 100 { 101 onclick = "if (getClientSideChangesExist()) { " + 102 onclick + "} else {return true;}"; 103 } 104 105 writer.writeAttribute("onclick", onclick, null); 106 107 writer.endElement("input"); 108 writer.write('\n'); 109 110 //This is our popupDialog 111 writer.startElement("div", this); 112 writer.writeAttribute("id", dialogId, null); 113 writer.writeAttribute("style", "display:none;", null); 114 115 if (dialogStyleClass != null) 116 writer.writeAttribute("class", dialogStyleClass, null); 117 118 writer.write('\n'); 119 120 //render our children as the contents of our dialog in the encodeChildren 121 //method. 122 } 123 124 /** 125 * Overridden to output the necessary javascript for this component. If the 126 * javascript has already be rendered for this response, nothing is done. 127 */ 128 @SuppressWarnings("unchecked") 129 public void encodeJavascriptMethods(FacesContext context, String id) 130 throws IOException 131 { 132 Map requestMap = context.getExternalContext().getRequestMap(); 133 Boolean scriptRendered = (Boolean)requestMap.get(RENDERED_SCRIPT_KEY); 134 135 if (scriptRendered != Boolean.TRUE) 136 { 137 requestMap.put(RENDERED_SCRIPT_KEY, Boolean.TRUE); 138 ResponseWriter writer = context.getResponseWriter(); 139 140 writer.startElement("script", this); 141 writer.writeAttribute("type", "text/javascript", null); 142 143 StringBuilder sb = new StringBuilder(); 144 sb.append("function getClientSideChangesExist() {\n"); 145 sb.append("\tform = document.getElementById('" + id + "');\n"); 146 sb.append("\tif (!form.clientSideChangesExist) {\n"); 147 sb.append("\t\tform.clientSideChangesExist = false;\n"); 148 sb.append("\t}\n"); 149 sb.append("\treturn form.clientSideChangesExist;\n"); 150 sb.append("}\n"); 151 sb.append("\n"); 152 sb.append("function setClientSideChangesExist(changed) {\n"); 153 sb.append("\tform = document.getElementById('" + id + "');\n"); 154 sb.append("\tform.clientSideChangesExist = changed;\n"); 155 sb.append("}\n"); 156 157 sb.append("var myrules = {\n"); 158 sb.append("'input' : function(el) {\n"); 159 sb.append("\tel.oldOnChange = el.onchange;\n"); 160 sb.append("\tel.onchange = function(e) {\n"); 161 sb.append("\tif (this.type != 'submit' && this.type != 'button') {\n"); 162 sb.append("\tsetClientSideChangesExist(true);\n"); 163 sb.append("\tif (this.oldOnChange) {\n"); 164 sb.append("\tthis.oldOnChange(e);\n"); 165 sb.append("\t}\n"); 166 sb.append("\t}\n"); 167 sb.append("\t};\n"); 168 sb.append("},\n"); 169 sb.append("'select' : function(el) {\n"); 170 sb.append("\tel.oldOnChange = el.onchange;\n"); 171 sb.append("\tel.onchange = function(e) {\n"); 172 sb.append("\tsetClientSideChangesExist(true);\n"); 173 sb.append("\tif (this.oldOnChange) {\n"); 174 sb.append("\tthis.oldOnChange(e);\n"); 175 sb.append("\t}\n"); 176 sb.append("\t};\n"); 177 sb.append("},\n"); 178 sb.append("'textarea' : function(el) {\n"); 179 sb.append("\tel.oldOnKeyPress = el.onkeypress;\n"); 180 sb.append("\tel.onkeypress = function(e) {\n"); 181 sb.append("\tsetClientSideChangesExist(true);\n"); 182 sb.append("\tif (this.oldOnKeyPress) {\n"); 183 sb.append("\tthis.oldOnKeyPress(e);\n"); 184 sb.append("\t}\n"); 185 sb.append("\t};\n"); 186 sb.append("\t}\n"); 187 sb.append("}\n"); 188 sb.append("Behaviour.register(myrules);\n"); 189 190 191 writer.write(sb.toString()); 192 writer.endElement("script"); 193 } 194 } 195 196 public Boolean getHasSubmittedChanges() 197 { 198 if (this.submittedChanges != null) 199 return this.submittedChanges; 200 201 ValueBinding vb = getValueBinding("submittedChanges"); 202 if (vb != null) 203 { 204 Object val = vb.getValue(getFacesContext()); 205 206 if (val instanceof Boolean && val != null) 207 return (Boolean)val; 208 } 209 210 //else 211 return Boolean.FALSE; 212 } 213 214 public void setHasSubmittedChanges(Boolean e) 215 { 216 this.submittedChanges = e; 217 } 218 219 public Object saveState(FacesContext context) 220 { 221 Object[] state = new Object[2]; 222 state[0] = super.saveState(context); 223 state[1] = this.submittedChanges; 224 225 return state; 226 } 227 228 public void restoreState(FacesContext context, Object state) 229 { 230 Object[] myState = (Object[])state; 231 super.restoreState(context, myState[0]); 232 setHasSubmittedChanges((Boolean)myState[1]); 233 } 234 }