This question is regarding my approach to the following implementation requirement. Can you suggest a better alternative that doesn't suffer from the issues I'll point out in my implementation?

##REQUIREMENT:
Create an input field (View) that updates itself automatically whenever a specific property on the Model changes.

* The input field should be generic (ie: it should work for any Model, and any appropriately typed property)
* The Parent View that holds the Field should not be responsible for updating the field when the Model changes
* The programming language is ActionScript, which is a strongly typed EMCAScript variant. I'm not sure whether this is relevant, since this question doesn't deal with code directly.

##MY SOLUTION:

Model implements an Interface that defines the following methods:

* `registerInput()`: the Field registers itself with the model and indicates which properties it wants to monitor
* `unregisterInput()`: for cleanup /gc purposes
* `updateInputField()`: the Field can update the model when the user interacts with it

Input Field implements an Interface that defines the following method:

 * `updateField()`: the Model can call this on any registered Field, when a monitored property changes

The Input Field is instantiated by the Parent View (that has a reference to the Model), by providing it with the Model instance (typed to the Interface) and the NAME (String) of the property on the Model we're want to monitor.


## ISSUES:

This solution works well, but there are two issues, both dealing with the fact that the Model's property is passed _as a string_, which means the IDE can't use autosuggest or type checking.

1. The Parent View passes the Model property **as a string**. So if there's a typo, we'll only find out about it at runtime, and we have to copy-and-paste it from the Model's function definition.

2. The Model updates the Field by passing the property **as a string**, with similar drawbacks.

3. The value of the property can't be typed for the IDE.

##Code:

###ILiveModel

    public interface ILiveModel 
    {
    	function registerLiveListener(liveListener:ILiveListener, fields:Array):Array;
    	function unregisterLiveListener(liveListener:ILiveListener):void ;
    	function updateLiveField(fieldName:String, newValue:*):void;
    	function getLiveFieldValue(fieldName:String):*;
    }

###ILiveListener

    public interface ILiveListener 
    {
    	function updateLiveField(field:String, value:*):void;
    }

###ILiveModel implementation

    public class KioskModel extends EventDispatcher implements ILiveModel {
    	private var _termsAndConditionsAgree:Boolean = false;
    	protected var _liveListeners:Dictionary = new Dictionary(true);
    	protected var _monitoredFields:Object = { };
    
    	public function get termsAndConditionsAgree():Boolean {
    		return _termsAndConditionsAgree;
    	}
    
    	public function set termsAndConditionsAgree(agree:Boolean):void {
    		_termsAndConditionsAgree = agree;
    		
    		updateRegisteredLiveListeners("termsAndConditionsAgree");
    	}
    
    	public function registerLiveListener(liveListener:ILiveListener, fields:Array):Array {
    		var monitoredFieldListeners:Array;
    		var fieldValues:Array = [];
    		
    		trace("<o> Registering ", liveListener, " to watch ", fields);
    		
    		// Register the input in the Dictionary, so we can unregister its fields when asked to
    		_liveListeners[liveListener] = fields;
    		
    		// For each field we've been asked to monitor, add the input, unless it's already watching that field
    		for each(var field:String in fields) {
    			if (! field in this) {
    				throw new ArgumentError("Cannot register Listener Field '" + field + "' on model " + this + ": property or method does not exist");
    			}
    			monitoredFieldListeners = _monitoredFields[field];
    			
    			if (monitoredFieldListeners && monitoredFieldListeners.length) {
    				if (monitoredFieldListeners.indexOf(liveListener == -1)) {
    					monitoredFieldListeners.push(liveListener);
    					_monitoredFields[field] = monitoredFieldListeners;
    				}
    			} else {
    				_monitoredFields[field] = [liveListener];
    			}
    			
    			fieldValues.push(getLiveFieldValue(field));
    		}
    		
    		// Return an array of all the values we're watching, for convenience.
    		return fieldValues;
    	}
    	
    	public function unregisterLiveListener(liveListener:ILiveListener):void {
    		var monitoredFields:Array = _liveListeners[liveListener];
    		var monitoredFieldListeners:Array;
    		var fieldIndex:int;
    		
    		trace("<-> Unregistering ", liveListener);
    		
    		if (monitoredFields) {
    			for each (var field:String in monitoredFields) {
    				monitoredFieldListeners = _monitoredFields[field];
    				
    				if (monitoredFieldListeners && monitoredFieldListeners.length) {
    					fieldIndex = monitoredFieldListeners.indexOf(liveListener);
    					
    					if (fieldIndex != -1) {
    						monitoredFieldListeners.splice(fieldIndex, 1);
    						
    						if (monitoredFieldListeners.length){
    							_monitoredFields[field] = monitoredFieldListeners;
    						} else {
    							delete _monitoredFields[field];
    						}
    					}
    				}
    			}
    		}
    		
    		delete _liveListeners[liveListener];
    	}
    	
    	public function updateLiveField(fieldName:String, newValue:*):void {
    		if (fieldName in this) {
    			this[fieldName] = newValue;
    		} else {
    			throw new ArgumentError("Cannot update Listener Field '" + fieldName + "' on model " + this + ": property or method is not valid.");
    		}
    	}
    	
    	public function getLiveFieldValue(fieldName:String):* {
    		if (fieldName in this) {
    			return (this[fieldName]);
    		} else {
    			throw new ArgumentError("Listener Field '" + fieldName + "' in " + this + " is not a valid property or method");
    		}
    	}
    	
    	protected function updateRegisteredLiveListeners(fieldName:String) {			
    		var liveListeners:Array = _monitoredFields[fieldName];
    		
    		if (liveListeners) {
    			for each (var liveListener:ILiveListener in liveListeners) {
    				liveListener.updateLiveField(fieldName, this[fieldName]);
    			}
    		}
    	}
    }

###ILiveListener implementation

    public class InputRadioButtonView extends AssetView implements ILiveListener {
    	private const LABEL_OFF = 'off';
    	private const LABEL_ON = 'on';
    	
    	protected var _model:ILiveModel;
    	protected var _modelProperty:String;
    	protected var _value:Boolean;
    	
    	public function InputRadioButtonView(assetClip:*, model:ILiveModel, modelProperty:String, disabled:Boolean=false, localizableTextFieldName:String="label") {
    		super(assetClip, disabled, localizableTextFieldName);
    		
    		_model = model;
    		_modelProperty = modelProperty;
    	}
    	
    	public function updateLiveField(field:String, value:*):void {
    		updateClip();
    	}
    	
    	
    	/**
    	 * Be sure the clip accurately reflects the current value, every time we switch to its screen.
    	 */
    	override public function setupBeforeLocalize():void {
    		updateClip();
    		
    		super.setupBeforeLocalize();
    	}
    	
    	override public function activate() {
    		var values:Array;
    		
    		if (activatable) {
    			clip.addEventListener(MouseEvent.CLICK, onClick, false, 0, true);
    			values = _model.registerLiveListener(this, [_modelProperty]);
    			
    			DebugUtils.print_r(values);
    		}
    		
    		super.activate();
    	}
    	
    	override public function deactivate() {
    		if (deactivatable) {
    			clip.removeEventListener(MouseEvent.CLICK, onClick);
    			_model.unregisterLiveListener(this);
    		}
    		
    		super.deactivate();
    	}
    	
    	/**
    	 * Attempts to update the clip, and lets you know if it was successful or not.
    	 * @return
    	 */
    	protected function updateClip():Boolean {
    		// Stash the value for convenience
    		_value = _model.getLiveFieldValue(_modelProperty);
    		
    		if (clip){
    			if (_value) {
    				movieClip.gotoAndStop(LABEL_ON);
    			} else {
    				movieClip.gotoAndStop(LABEL_OFF);
    			}
    			
    			return true;
    		}
    		
    		return false;
    	}
    	
    	protected function onClick(event:MouseEvent):void {
    		_model.updateLiveField(_modelProperty, ! _value);
    	}
    }

###Parent View implementation


    public class HowThisWorksBox extends ContentAssetView {
    	private var _kioskModel:KioskModel;
    	
    	public function HowThisWorksBox(clip:*, disabled:Boolean = false) {
    		super(clip, ["heading", "body"], disabled);
    	}
    	
    	override protected function defineFlexItems(parentItem:FlexItem = null):FlexItem {
    		_kioskModel = KioskModel.getInstance();
    		
    		return super.defineFlexItems(parentItem)
    			.chainAsset(new ButtonAssetView("terms_button", ["label"], onTermsButtonClicked))
    			.chainAsset(new InputRadioButtonView("agree_button", _kioskModel, "termsAndConditionsAgree"));
    	}
    	
    	
    	private function onTermsButtonClicked():void {
    		screen.router.setScreen("main__terms");
    	}
    }


Leverages the [Kestrel][1] mobile development framework - which defines the superclasses of this View.


  [1]: https://github.com/TomAuger/kestrel