package 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. import com.google.gwt.event.logical.shared.*; import com.google.web.bindery.event.shared.*; import com.google.gwt.regexp.shared.*; import votorola.g.hold.*; import votorola.g.lang.*; import votorola.g.web.gwt.event.*; /** A URL fragment variable for controlling the navigable state of the application. The * {@linkplain #SWITCH_SPEC_PATTERN format} is similar to that of a query parameter. * Switch names are documented and reserved on a per application basis. See {@linkplain * votorola.s.gwt.scene.Scenes Scenes} for an example. */ public class Switch implements Hold, ValueChangeHandler { /** Constructs a Switch and initializes it. Call {@linkplain #release release}() if * you are finished with it prior to releasing the provided history stack. * * @see #name() * @see #history() */ public Switch( String _name, HistoryX _history ) { this( _name, _history, /*toInit*/true ); } /** Constructs a Switch. Call {@linkplain #release release}() if you are finished * with it prior to releasing the provided history stack. * * @see #name() * @see #history() * @param toInit whether to initialize the newly constructed instance. * Subclasses may use this to defer initialization so they can use their * overridden versions of the init methods. */ protected Switch( String _name, HistoryX _history, final boolean toInit ) { name = _name; history = _history; spool.add( new Hold() { final HandlerRegistration hR = history.addHandler( Switch.this ); public void release() { hR.removeHandler(); } }); if( toInit ) syncFromHistory(); // init state } // ------------------------------------------------------------------------------------ /** Registers a handler to receive change events fired by this switch. These are * similar to the change events of the history stack, only more specific. Both types * of event are fired only after all switches have been updated with their new * values. * * @see HistoryX#addHandler(ValueChangeHandler) */ public final HandlerRegistration addHandler( final ValueChangeHandler handler ) { return history.bus().addHandlerToSource( ValueChangeEvent.getType(), /*source*/Switch.this, handler ); } /** Removes this switch from the URL and returns its value. * * @see HistoryX#clearSwitchValue(String) */ public final String clear() { return history.clearSwitchValue( name ); } // We'll eventually need a reference mechanism for holding onto the switch and // automatically clearing it when all references are dropped. That way multiple, // independent components may share the switch as a common state variable. They // may depend on it to retain its value in the URL for as long as one component // needs it, and no longer. /** Returns the value of this switch, or null if it has no value. * * @see HistoryX#getSwitchValue(String) */ public final String get() { return history.getSwitchValue( name ); } /** The history stack. */ public final HistoryX history() { return history; } private HistoryX history; // would be final except an apparent GWT compiler bug results in the error // "blank final field history may not have been initialized" /** The unique name of this switch. */ public final String name() { return name; } private final String name; /** Sets the value of this switch and returns the old value. Setting an empty value * has the same effect as clearing the switch. * * @see HistoryX#setSwitchValue(String,String) */ public final String set( final String value ) { return history.setSwitchValue( name, value ); } /** The pattern of a switch specification. The form is nearly identical to that of * an HTTP query parameter specification: NAME=VALUE. A match * sets the name and value to groups 1 and 2. * *

Like in the value of a query parameter, actual spaces ' ' in the switch value * are encoded as plus signs '+' in the formal URL. Unlike in a query parameter, * actual plus signs are encoded as escaped spaces (%20), not plus signs * (%2B). This is the only difference between the two.

*/ public static final RegExp SWITCH_SPEC_PATTERN = RegExp.compile( "^(.+)=(.+)$" ); /** The pattern of a switch specification separator. As in HTTP query parameters, * multiple specifications are separated by ampersand characters (&). */ public static final RegExp SWITCH_SPEC_SEPARATOR_PATTERN = RegExp.compile( "\\&" ); // - H o l d -------------------------------------------------------------------------- public final void release() { spool.unwind(); } // - V a l u e - C h a n g e - H a n d l e r ------------------------------------------ /** Handles changes to the overall fragment. */ public final void onValueChange( final ValueChangeEvent e ) { if( !syncFromHistory() ) return; // no actual change history.bus().fireEventFromSource( new ValueChange(lastValue), Switch.this ); // lastValue is actually the new value, as changed } //// P r i v a t e /////////////////////////////////////////////////////////////////////// protected String lastValue; protected Spool spool = new Spool1(); /** Ensures that lastValue is correctly set and returns true if it was changed. * {@linkplain #onValueChange(ValueChangeEvent) onValueChange}(e) calls this method * in order to determine whether to fire a switch change event. */ private @Warning("init call") final boolean syncFromHistory() { final String value = history.getSwitchValue( name ); if( ObjectX.nullEquals( lastValue, value )) return false; // no actual change lastValue = value; return true; } }