 }
 
 import flash.display.DisplayObject;
+import flash.utils.Dictionary;
 
 import mx.binding.utils.BindingUtils;
-
+import mx.containers.VBox;
+import mx.core.IUIComponent;
+import mx.utils.ObjectUtil;
+
+import weave.api.core.ILinkableHashMap;
+import weave.api.core.ILinkableObject;
 import weave.api.core.ILinkableVariable;
+import weave.api.detectLinkableObjectChange;
 import weave.api.getCallbackCollection;
+import weave.api.ui.ISelectableAttributes;
+import weave.compiler.StandardLib;
 import weave.core.ClassUtils;
+import weave.core.SessionManager;
 import weave.core.UIUtils;
+import weave.editors.DynamicEditor;
+import weave.primitives.WeaveTreeItem;
 import weave.utils.EventUtils;
+import weave.utils.VectorUtils;
+
+internal class ComponentUpdater
+{
+	public function ComponentUpdater(target:ILinkableObject, label:String)
+	{
+		var sm:SessionManager = WeaveAPI.SessionManager as SessionManager;
+		tree = sm.getSessionStateTree(target, label);
+		sm.addTreeCallback(target, update, true);
+	}
+	
+	// create a vbox to contain the child editors
+	public const vbox:VBox = new VBox();
+	private const cachedComponents:Dictionary = new Dictionary(true); // ILinkableObject -> component
+	private var tree:WeaveTreeItem;
+	
+	private function update():void
+	{
+		// stop if nothing changed
+		if (!detectLinkableObjectChange(update, tree.source))
+			return;
+		
+		vbox.label = lang(tree.label);
+		vbox.setStyle('verticalGap', 4);
+		UIUtils.pad(vbox, 4, 100, 100);
+		
+		var subtrees:Array = tree.children || [];
+		
+		if (!(tree.source is ILinkableHashMap))
+		{
+			// sort by class, then by name
+			StandardLib.sort(subtrees, function(st1:WeaveTreeItem, st2:WeaveTreeItem):int {
+				var c1:Class = Object(st1.source).constructor;
+				var c2:Class = Object(st2.source).constructor;
+				var l1:String = st1.label;
+				var l2:String = st2.label;
+				return ObjectUtil.stringCompare(String(c1), String(c2))
+					|| ObjectUtil.stringCompare(l1, l2);
+			});
+		}
+		
+		// editors for enumerated selectable attributes should appear first
+		var sa:ISelectableAttributes = tree.source as ISelectableAttributes;
+		if (sa)
+		{
+			// this code adds duplicate items to the subtrees array
+			var lookup:Dictionary = VectorUtils.createLookup(subtrees, 'source');
+			var names:Array = sa.getSelectableAttributeNames();
+			subtrees = sa.getSelectableAttributes()
+				.map(function(attr:*, i:*, a:*):WeaveTreeItem{
+					return subtrees[lookup[attr]]
+						|| (WeaveAPI.SessionManager as SessionManager).getSessionStateTree(attr, names[i]);
+				})
+				.concat(subtrees);
+		}
+		
+		var ignore:Dictionary = new Dictionary(true);
+		var i:int;
+		var component:IUIComponent;
+		var orderedComponents:Array = [];
+		for (i = 0; i < subtrees.length; i++)
+		{
+			var subtree:WeaveTreeItem = subtrees[i] as WeaveTreeItem;
+			
+			// avoid handling an item twice
+			if (ignore[subtree])
+				continue;
+			ignore[subtree] = true;
+			
+			var childTarget:ILinkableObject = subtree.source;
+			
+			// create component if necessary
+			component = cachedComponents[childTarget];
+			if (!component)
+				cachedComponents[childTarget] = component = DynamicEditor.createComponent(childTarget, subtree.label, true);
+			
+			// add to vbox if necessary
+			if (component.parent != vbox)
+				UIUtils.spark_addChild(vbox, component as DisplayObject);
+			
+			// remember order
+			orderedComponents.push(component);
+		}
+		
+		// set child order
+		for (i = 0; i < orderedComponents.length; i++)
+			UIUtils.spark_setChildIndex(vbox, orderedComponents[i], i);
+		
+		// remove extra children
+		while (UIUtils.spark_numChildren(vbox) > orderedComponents.length)
+			UIUtils.spark_removeChildAt(vbox, orderedComponents.length);
+	}
+}
 
 internal class JsonSynchronizer
 {

	import weave.ui.Indent;
	import weave.ui.IndentTextInput;
	import weave.ui.SessionStateEditor;
	import weave.utils.VectorUtils;
	
	public class DynamicEditor
	{
		/**
		 * Creates an editor component for an ILinkableObject.
		 * @param obj Either an ILinkableObject or a session state tree generated by SessionManager.getSessionStateTree().
		 * @param label A label for the ILinkableObject.
		 * @param indentChildren If true and the object does not have a simple editor, an Indent component will be created with
		 *                       the specified label in front of a vertical list of child editors. If false, the vertical list
		 *                       will not be indented.
		 */
		public static function createComponent(object:Object, label:String, indentChildren:Boolean):IUIComponent
		{
			var sm:SessionManager = WeaveAPI.SessionManager as SessionManager;
			
			var tree:WeaveTreeItem = object as WeaveTreeItem;
			var target:ILinkableObject = object as ILinkableObject;
			if (target)
				tree = sm.getSessionStateTree(target, label);
			else
				target = tree.source as ILinkableObject;
			
			var indent:Indent;
			
			var lb:LinkableBoolean = target as LinkableBoolean;

	import weave.api.data.IAttributeColumn;
	import weave.api.linkBindableProperty;
	import weave.api.ui.ISelectableAttributes;
	import weave.core.LinkableBoolean;
	import weave.core.LinkableNumber;
	import weave.core.SessionManager;
