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    }