2 Replies Latest reply on Mar 21, 2014 5:03 PM by Flex harUI

    Relationship between a Spark DropDownList's dataProvider and selectedItem

    rgg1880

      I'm working on an existing project and have come across a somewhat complicated issue regarding a Spark DropDownList's (actually, a custom component inheriting from it) displayed item not changing when its dataProvider is updated.

       

      Here's the code for the custom TitleWindow component containing the custom DropDownList:

      <?xml version="1.0" encoding="utf-8"?>
      <components:RFTitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009" 
                                xmlns:s="library://ns.adobe.com/flex/spark" 
                                xmlns:mx="library://ns.adobe.com/flex/mx"
                                xmlns:components="components.*"
                                xmlns:rfform="rfform.*"
                                width="550" height="450"
                                title="{currentState} Site Equipment"
                                cancelButton="{btnCancel}"
                                defaultButton="{btnSave}"
                                initialFocus="{rftName}"
                                creationComplete="init()" close="cancelEdit()">
        
        <fx:Script>
          <![CDATA[
            import events.SiteEquipmentEvent;
            import events.SiteEquipmentTypeEvent;
            
            import mx.collections.ArrayList;
            import mx.controls.Menu;
            import mx.core.FlexGlobals;
            import mx.core.UIComponent;
            import mx.events.MenuEvent;
            import mx.managers.PopUpManager;
            import mx.validators.ValidationResult;
            import mx.validators.Validator;
            
            import spark.events.IndexChangeEvent;
            
            private const TYPES:Array = ["A", "B", "C", "D"];
            
            [Bindable] [Embed(source="../assets/images/circle_red_x.gif")] private var icoRedX:Class;
            [Bindable] private var siteEquipment:SiteEquipment;
            
            private var validators:Array;
            private var typePopUp:Menu;
            
            public static function open(item:SiteEquipment):SiteEquipmentEdit
            {
              var s:SiteEquipmentEdit = new SiteEquipmentEdit();
              
              s.siteEquipment = item;
              
              PopUpManager.addPopUp(s, DisplayObject(FlexGlobals.topLevelApplication), true);
              
              PopUpManager.centerPopUp(s);
              
              return s;
            }
            
            private function init():void
            {
              initTypePopUp();
              
              if (siteEquipment.id == SiteEquipment.NEW_ID)
                currentState = "New";
              else
              {
                currentState = "Edit";
                validateAll();
                
                if (!rftName.enabled)
                  rfcmbType.setFocus();
              }
              
              siteEquipment.addEventListener(SiteEquipmentEvent.SITE_EQUIPMENT_SAVE, saveComplete);
            }
            
            override public function dispose(event:Event = null):void
            {
              super.dispose();
              
              siteEquipment.removeEventListener(SiteEquipmentEvent.SITE_EQUIPMENT_SAVE, saveComplete);
              typePopUp.removeEventListener("itemClick", typePopUp_click);
              
              siteEquipment.dispose();
              
              icoRedX = null;
              siteEquipment = null;
              validators = null;
              typePopUp = null;
              
              removeChildren();
            }
            
            private function cancelEdit():void
            {
              siteEquipment.cancelEdit();
            }
            
            private function saveForm():void
            {
              var newValue:SiteEquipment = new SiteEquipment();
              
              newValue.id = siteEquipment.id;
              newValue.locid = siteEquipment.locid;
              newValue.name = rftName.trimmedText;
              
              siteEquipment.save(newValue);
            }
            
            private function saveComplete(event:Event):void
            {
              dispose();
            }
            
            private function typePopUp_click(event:MenuEvent):void
            {
              switch(event.item)
              {
                case "Edit Selected Type":
                  SiteEquipmentType(rfcmbType.selectedItem).showEditForm();
                  break;
                case "Create A New Type":
                  new SiteEquipmentType().showEditForm(type_created, null, type_removeListeners);
                  break;
              }
            }
            
            private function type_created(event:SiteEquipmentTypeEvent = null):void
            {
              type_removeListeners(event);
              rfcmbType.selectedItem = event.siteEquipmentType;
            }
            
            private function type_removeListeners(event:Event):void
            {
              var s:SiteEquipmentType = SiteEquipmentType(event.target);
              s.removeEventListener(SiteEquipmentTypeEvent.SITE_EQUIPMENT_TYPE_CREATE, type_created);
              s.removeEventListener(SiteEquipmentType.SITE_EQUIPMENT_TYPE_CANCEL, type_removeListeners);
            }
            
            private function initTypePopUp():void
            {
              typePopUp = Menu.createMenu(puType, ['Edit Selected Type', 'Create A New Type']);
              typePopUp.addEventListener("itemClick", typePopUp_click);
            }
            
            private function popupTypeOptions():void
            {
              var p:Point = puType.localToGlobal(new Point(0, puType.height + 5));
              typePopUp.show(p.x, p.y);
            }
            
            private function validateAll():void
            {
              var isValid:Boolean;
              
              for each (var validator:Validator in validators)
              {
                isValid = true;
                
                for each (var result:ValidationResult in validator.validate().results)
                {
                  if (result.isError)
                  {
                    isValid = false;
                    break;
                  }
                }
                
                UIComponent(validator.source).styleName = "valid_" + isValid.toString();
              }
            }
            
            private function rfcmbType_changeHandler(event:IndexChangeEvent):void
            {
              if (TYPES.indexOf(SiteEquipmentType(RFDropDownList(event.target).selectedItem).name) > -1)
              {
                rftName.text = "Blah";
                rftName.enabled = false;
              }
              else
                rftName.enabled = true;
            }
          ]]>
        </fx:Script>
        
        
        <fx:Declarations>
          <!-- Place non-visual elements (e.g., services, value objects) here -->
          <s:DateTimeFormatter id="mdy"
                               dateTimePattern="MM/dd/yyyy"/>
          
      …
          
        </fx:Declarations>
        
        <components:states>
          <s:State name="Edit"/>
          <s:State name="New"/>
        </components:states>
        
        
        <rfform:RFForm width="100%" height="100%"
                       saveButton="{btnSave}"
                       validators="{new ArrayList([valQuantity, valHeight])}">
          
      …
          
          <rfform:RFFormItem label="Type"
                             width="100%">
            <s:HGroup width="100%">
              <rfform:RFDropDownList id="rfcmbType"
                                     width="100%"
                                     originalValue="{siteEquipment.model.eqType}"
                                     dataProvider="{SiteEquipmentType.all}"
                                     change="rfcmbType_changeHandler(event)"/>
              <rfform:RFButton id="puType"
                               icon="@Embed(source='/Shared/src/assets/images/settings.png')"
                               width="30"
                               click="popupTypeOptions()"/>
            </s:HGroup>
          </rfform:RFFormItem>
          
      …
          
        </rfform:RFForm>
        
      </components:RFTitleWindow>

       

      I've omitted a little bit of code, so please ask if you need anything that I didn't include.

       

      In regards to the RFDropDownList, originalValue is saved so that when a new selection is made, the new selection can be compared to the original value.  Setting originalValue sets the RFDropDownList's selectedItem to originalValue.

       

      When this window is first opened, a new instance of SiteEquipment is created.  Unless the equipment is new, the SiteEquipment instance sets the SiteEquipmentType to whatever has already been saved, using this line:

       

      eqType = SiteEquipmentType.selectFromObject(item.SITE_EQUIPMENT_TYPE);

       

      And here's the code from the SiteEquipmentType class:

       

      package components
      {
        import common.UpdateStatus;
        import events.SiteEquipmentTypeEvent;
        import scripts.MyUtility;
        
        import flash.events.Event;
        import flash.events.EventDispatcher;
        import flash.utils.setTimeout;
        
        import mx.collections.ArrayCollection;
        import mx.collections.Sort;
        import mx.collections.SortField;
        import mx.controls.Alert;
        import mx.rpc.events.ResultEvent;
        import mx.utils.ObjectProxy;
        
        [Event(name="cacheChanged", type="flash.events.Event")]
        [Event(name="SITE_EQUIPMENT_TYPE_CREATE", type="events.SiteEquipmentTypeEvent")]
        [Event(name="SITE_EQUIPMENT_TYPE_EDIT", type="events.SiteEquipmentTypeEvent")]
        [Event(name="SITE_EQUIPMENT_TYPE_CANCEL", type="flash.events.Event")]
        
        public class SiteEquipmentType extends ObjectProxy
        {
          public static const NEW_ID:int = -1;
          public static const SITE_EQUIPMENT_TYPE_CANCEL:String = "siteEquipmentTypeCancel";
          
          [Bindable]
          public var id:int = NEW_ID;
          
          [DefaultProperty]
          [Bindable]
          public var name:String = "";
          
          private static var dict:Object = new Object();
          private static var getAllServiceCalled:Boolean = false;
          private static var eventDispatcher:EventDispatcher = new EventDispatcher();
          
          private var updateStatus:UpdateStatus = UpdateStatus.COMPLETE;
          
          
          public function SiteEquipmentType(item:Object = null, keepInCache:Boolean = true)
          {
            if (item == null)
              return;
            else if (item is int)
            {
              id = int(item);
              refreshFromDb();
            }
            else
              updateFromResult(item);
            
            if (keepInCache)
              addToCache(this);
          }
          
          private static var _all:ArrayCollection = new ArrayCollection();
          
          public static function get all():ArrayCollection
          {
            if (!getAllServiceCalled)
            {
              getAllServiceCalled = true;
              MyUtility.httpPost("/page.aspx", {request:"SITE_EQUIPMENT_TYPE"}, getAll_result);
              var s:Sort = new Sort();
              s.fields = [new SortField("name", true)];
              _all.sort = s;
            }
            
            return _all;
          }
          
          public static function addEventListener(type:String, listener:Function):void
          {
            eventDispatcher.addEventListener(type, listener);
          }
          
          public static function removeEventListener(type:String, listener:Function):void
          {
            eventDispatcher.removeEventListener(type, listener);
          }
          
          public static function dispatchEvent(event:Event):Boolean
          {
            return eventDispatcher.dispatchEvent(event);
          }
          
          private static function addToCache(item:SiteEquipmentType):void
          {
            removeFromCache(item.id);
            
            dict[item.id] = item;
            _all.addItem(item);
            
            dispatchEvent(new Event("cacheChanged"));
          }
          
          private static function removeFromCache(id:int):void
          {
            var item:SiteEquipmentType = dict[id];
            
            if (item)
            {
              delete dict[id];
              _all.removeItemAt(_all.getItemIndex(item));
            }
          }
          
          public static function selectById(id:int):SiteEquipmentType
          {
            if (dict[id])
              return dict[id];
            
            return new SiteEquipmentType(id);
          }
          
          public static function selectFromObject(obj:Object):SiteEquipmentType
          {
            var selected:SiteEquipmentType;
            
            var objId:int = obj.SITE_EQUIPMENT_TYPE_ID;
            
            if (dict[objId])
            {
              selected = dict[objId];
              selected.updateFromResult(obj);
            }
            else
              selected = new SiteEquipmentType(obj);
            
            return selected;
          }
          
          private function updateFromResult(item:Object):void
          {
            if (item)
            {
              id = item.SITE_EQUIPMENT_TYPE_ID;
              name = item.SITE_EQUIPMENT_TYPE_NAME;
            }
          }
          
          private function toRequestObject():Object
          {
            var o:Object = new Object();
            
            o.SITE_EQUIPMENT_TYPE_ID = id;
            o.SITE_EQUIPMENT_TYPE_NAME = name;
            
            return o;
          }
          
          public function copy():SiteEquipmentType
          {
            return new SiteEquipmentType(this.toRequestObject());
          }
          
          public function save(value:SiteEquipmentType):void
          {
            if (updateStatus == UpdateStatus.PENDING)
            {
              Alert.show("Site Equipment Type is pending an update.", "Pending Update");
              return;
            }
            else if (updateStatus == UpdateStatus.ERROR)
            {
              Alert.show("There is a problem with this Site Equipment Type, and it cannot be saved. Please close the window.", "Save Failure");
              return;
            }
            
            var request:Object;
            
            if (id == NEW_ID)
            {
              if (value)
                request = value.toRequestObject();
              else
                request = toRequestObject();
              
              request._COMMAND = "INSERT";
            }
            else
            {
              if (value)
              {
                request = MyUtility.objectDiff(this.toRequestObject(), value.toRequestObject());
      
                if (!request)
                {
                  dispatchEvent(new SiteEquipmentTypeEvent(SiteEquipmentTypeEvent.SITE_EQUIPMENT_TYPE_CREATE, this));
                  return;
                }
              }
              else
                request = toRequestObject();
              
              request._COMMAND = "UPDATE";
              request._SITE_EQUIPMENT_TYPE_ID = id;
            }
            
            delete request.SITE_EQUIPMENT_TYPE_ID;
            
            updateStatus = UpdateStatus.PENDING;
            MyUtility.httpPost("/updatetable.aspx?_SCHEMA=dbo&_TABLENAME=SITE_EQUIPMENT_TYPE&_RETURNRECORD=1", request, httpSave_result);
          }
          
          public function toString():String
          {
            return name;
          }
          
          public function refreshFromDb():void
          {
            if (updateStatus != UpdateStatus.PENDING)
            {
              updateStatus = UpdateStatus.PENDING;
              
              MyUtility.httpPost("/page.aspx", {id:id, request:"Site_Equipment_Type"}, httpSelect_result);
            }
          }
          
          public function showEditForm(onCreate:Function = null, onEdit:Function = null, onCancel:Function = null):void
          {
            if (id == 0)
            {
              Alert.show("Edits are not allowed on this Site Equipment Type.", "Not Allowed");
              return;
            }
            
            if (onCreate != null)
              addEventListener(SiteEquipmentTypeEvent.SITE_EQUIPMENT_TYPE_CREATE, onCreate);
            
            if (onEdit != null)
              addEventListener(SiteEquipmentTypeEvent.SITE_EQUIPMENT_TYPE_EDIT, onEdit);
            
            if (onCancel != null)
              addEventListener(SITE_EQUIPMENT_TYPE_CANCEL, onCancel);
            
            if (updateStatus == UpdateStatus.PENDING)
              setTimeout(showEditForm, 500);
            else if (updateStatus == UpdateStatus.COMPLETE)
              SiteEquipmentTypeEdit.open(this);
          }
          
          public function cancelEdit():void
          {
            dispatchEvent(new Event(SITE_EQUIPMENT_TYPE_CANCEL));
          }
          
          private static function getAll_result(event:ResultEvent):void
          {
            if (event.result.ROOT)
            {
              if (event.result.ROOT.ERROR)
                Alert.show(event.result.ROOT.ERROR, "Error");
              else
              {
                for each (var o:Object in MyUtility.ToArrayCollection(event.result.ROOT.SITE_EQUIPMENT_TYPE))
                {
                  var i:SiteEquipmentType = dict[o.SITE_EQUIPMENT_TYPE_ID];
                  
                  // If the Site Equipment Type is already in the cache, update it based on the XML returned
                  if (i)
                    i.updateFromResult(o);
                    // Otherwise, create a new one from the XML returned
                  else
                    i = new SiteEquipmentType(o);
                }
                _all.refresh();
              }
            }
          }
          
          private function httpSelect_result(event:ResultEvent):void
          {
            if (event.result.ROOT)
            {
              if (event.result.ROOT.ERROR)
              {
                Alert.show(event.result.ROOT.ERROR, "Error");
                updateStatus = UpdateStatus.ERROR;
              }
              else
              {
                updateFromResult(event.result.ROOT.SITE_EQUIPMENT_TYPE);
                updateStatus = UpdateStatus.COMPLETE;
              }
            }
            else
              updateStatus = UpdateStatus.ERROR;
          }
          
          private function httpSave_result(event:ResultEvent):void
          {
            if (event.result.ROOT.ERROR)
            {
              Alert.show(event.result.ROOT.ERROR, "Error");
              updateStatus = UpdateStatus.ERROR;
            }
            else
            {
              try
              {
                updateStatus = UpdateStatus.COMPLETE;
                
                if (id == NEW_ID)
                {
                  id = event.result.ROOT.RESULTS.IDENTITY;
                  addToCache(this);
                  updateFromResult(event.result.ROOT["DBO.SITE_EQUIPMENT_TYPE"]);
                  dispatchEvent(new SiteEquipmentTypeEvent(SiteEquipmentTypeEvent.SITE_EQUIPMENT_TYPE_CREATE, this));
                }
                else
                {
                  updateFromResult(event.result.ROOT["DBO.SITE_EQUIPMENT_TYPE"]);
                  dispatchEvent(new SiteEquipmentTypeEvent(SiteEquipmentTypeEvent.SITE_EQUIPMENT_TYPE_EDIT, this));
                  SiteEquipmentType.dispatchEvent(new SiteEquipmentTypeEvent(SiteEquipmentTypeEvent.SITE_EQUIPMENT_TYPE_EDIT, this));
                }
              }
              catch(ex:Error)
              {
                Alert.show(ex.message, "Error");
                updateStatus = UpdateStatus.ERROR;
              }
            }
          }
        }
      }

       

      So the "_all" ArrayCollection serves as a cache of all of the different site equipment types.  When the edit window is first opened and a new instance of SiteEquipment is created, the SiteEquipmentType of the current SiteEquipment is added to _all and becomes the only SiteEquipmentType within _all.

       

      At this point, the DropDownList has the correct item selected.  The problem occurs when the result comes in from getting all of the SiteEquipmentTypes.  The contents of _all are flushed, and each SiteEquipmentType is added in.  Once this happens, the DropDownList will have nothing for selectedItem.

       

      I know this is quite complex, but any help would be greatly appreciated.

        • 1. Re: Relationship between a Spark DropDownList's dataProvider and selectedItem
          rgg1880 Level 1

          Ok, I have this figured out now.  First, though, I'll point out that this worked fine when using MX components and the Halo theme; the problem only appeared after switching over to Spark components and the Spark theme.  Anyway, since this class functions as a singleton (as best I understand them), I added a boolean variable to the SiteEquipmentType class representing whether or not the results from the HTTPService call had come back and been inserted into _all.  So in the TitleWindow's init method, I only populated the DropDownList's originalValue (which in turn sets the selectedItem) if that variable is true.  The first time an instance of the TitleWindow component is opened, though, this variable will initially be false.  So back in the SiteEquipmentType class, I have it dispatch an event right after setting the variable to true, and the TitleWindow componet will add a listener for this event if the variable is originally false.  If anyone needs further explanation, please ask.

          • 2. Re: Relationship between a Spark DropDownList's dataProvider and selectedItem
            Flex harUI Adobe Employee

            Not sure I understood, but there isn't much thinking going on the the component.  If you make drastic changes to the data provider, the selectedItem will be come null and selectedIndex == -1 because the component saw that what was selected disappeared or moved in a way that was un-trackable.  It doesn't go looking for that old selectedItem.  You'll have to set selectedIndex/selectedItem again after certain kinds of changes to the data provider.