001package votorola.g.web.gwt; // Copyright 2010-2012, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. 002 003import com.google.gwt.event.logical.shared.*; 004import com.google.web.bindery.event.shared.*; 005import com.google.gwt.regexp.shared.*; 006import votorola.g.hold.*; 007import votorola.g.lang.*; 008import votorola.g.web.gwt.event.*; 009 010 011/** A URL fragment variable for controlling the navigable state of the application. The 012 * {@linkplain #SWITCH_SPEC_PATTERN format} is similar to that of a query parameter. 013 * Switch names are documented and reserved on a per application basis. See {@linkplain 014 * votorola.s.gwt.scene.Scenes Scenes} for an example. 015 */ 016public class Switch implements Hold, ValueChangeHandler<String> 017{ 018 019 020 /** Constructs a Switch and initializes it. Call {@linkplain #release release}() if 021 * you are finished with it prior to releasing the provided history stack. 022 * 023 * @see #name() 024 * @see #history() 025 */ 026 public Switch( String _name, HistoryX _history ) { this( _name, _history, /*toInit*/true ); } 027 028 029 030 /** Constructs a Switch. Call {@linkplain #release release}() if you are finished 031 * with it prior to releasing the provided history stack. 032 * 033 * @see #name() 034 * @see #history() 035 * @param toInit whether to initialize the newly constructed instance. 036 * Subclasses may use this to defer initialization so they can use their 037 * overridden versions of the init methods. 038 */ 039 protected Switch( String _name, HistoryX _history, final boolean toInit ) 040 { 041 name = _name; 042 history = _history; 043 044 spool.add( new Hold() 045 { 046 final HandlerRegistration hR = history.addHandler( Switch.this ); 047 public void release() { hR.removeHandler(); } 048 }); 049 if( toInit ) syncFromHistory(); // init state 050 } 051 052 053 054 // ------------------------------------------------------------------------------------ 055 056 057 /** Registers a handler to receive change events fired by this switch. These are 058 * similar to the change events of the history stack, only more specific. Both types 059 * of event are fired only after all switches have been updated with their new 060 * values. 061 * 062 * @see HistoryX#addHandler(ValueChangeHandler) 063 */ 064 public final HandlerRegistration addHandler( final ValueChangeHandler<String> handler ) 065 { 066 return history.bus().addHandlerToSource( ValueChangeEvent.getType(), /*source*/Switch.this, 067 handler ); 068 } 069 070 071 072 /** Removes this switch from the URL and returns its value. 073 * 074 * @see HistoryX#clearSwitchValue(String) 075 */ 076 public final String clear() { return history.clearSwitchValue( name ); } 077 078 // We'll eventually need a reference mechanism for holding onto the switch and 079 // automatically clearing it when all references are dropped. That way multiple, 080 // independent components may share the switch as a common state variable. They 081 // may depend on it to retain its value in the URL for as long as one component 082 // needs it, and no longer. 083 084 085 086 /** Returns the value of this switch, or null if it has no value. 087 * 088 * @see HistoryX#getSwitchValue(String) 089 */ 090 public final String get() { return history.getSwitchValue( name ); } 091 092 093 094 /** The history stack. 095 */ 096 public final HistoryX history() { return history; } 097 098 099 private HistoryX history; 100 // would be final except an apparent GWT compiler bug results in the error 101 // "blank final field history may not have been initialized" 102 103 104 105 /** The unique name of this switch. 106 */ 107 public final String name() { return name; } 108 109 110 private final String name; 111 112 113 114 /** Sets the value of this switch and returns the old value. Setting an empty value 115 * has the same effect as clearing the switch. 116 * 117 * @see HistoryX#setSwitchValue(String,String) 118 */ 119 public final String set( final String value ) { return history.setSwitchValue( name, value ); } 120 121 122 123 /** The pattern of a switch specification. The form is nearly identical to that of 124 * an HTTP query parameter specification: <var>NAME</var>=<var>VALUE</var>. A match 125 * sets the name and value to groups 1 and 2. 126 * 127 * <p>Like in the value of a query parameter, actual spaces ' ' in the switch value 128 * are encoded as plus signs '+' in the formal URL. Unlike in a query parameter, 129 * actual <em>plus signs</em> are encoded as escaped spaces (%20), not plus signs 130 * (%2B). This is the only difference between the two.</p> 131 */ 132 public static final RegExp SWITCH_SPEC_PATTERN = RegExp.compile( "^(.+)=(.+)$" ); 133 134 135 136 /** The pattern of a switch specification separator. As in HTTP query parameters, 137 * multiple specifications are separated by ampersand characters (&). 138 */ 139 public static final RegExp SWITCH_SPEC_SEPARATOR_PATTERN = RegExp.compile( "\\&" ); 140 141 142 143 // - H o l d -------------------------------------------------------------------------- 144 145 146 public final void release() { spool.unwind(); } 147 148 149 150 // - V a l u e - C h a n g e - H a n d l e r ------------------------------------------ 151 152 153 /** Handles changes to the overall fragment. 154 */ 155 public final void onValueChange( final ValueChangeEvent<String> e ) 156 { 157 if( !syncFromHistory() ) return; // no actual change 158 159 history.bus().fireEventFromSource( new ValueChange<String>(lastValue), Switch.this ); 160 // lastValue is actually the new value, as changed 161 } 162 163 164 165//// P r i v a t e /////////////////////////////////////////////////////////////////////// 166 167 168 protected String lastValue; 169 170 171 172 protected Spool spool = new Spool1(); 173 174 175 176 /** Ensures that lastValue is correctly set and returns true if it was changed. 177 * {@linkplain #onValueChange(ValueChangeEvent) onValueChange}(e) calls this method 178 * in order to determine whether to fire a switch change event. 179 */ 180 private @Warning("init call") final boolean syncFromHistory() 181 { 182 final String value = history.getSwitchValue( name ); 183 if( ObjectX.nullEquals( lastValue, value )) return false; // no actual change 184 185 lastValue = value; 186 return true; 187 } 188 189 190 191}