Handling Flex States & TitleWindow

mbarjawi's Avatar

mbarjawi

05 Jul, 2012 05:41 AM

Hi,

I have a popup window, when a button is clicked on it, its state changes, resulting in adding a new button to it. This button doesn't respond to clicking because when onRegister was called at the beginning, this button was null (because it is not added to stage yet, not until the state changes later on) and I couldn't add event handler to it. When states change, the onRegister doesn't get called again. So I am not sure how to handle this. Maybe I should listen for state change event, and then add the event listener over there (if the new button was already created by that time)... but not sure if this is the right way to handle this.

To make things more clear, I am creating a dialog (pop up window) that has a list of items. The user can select one of these items, and click the "details" button. Doing so, the state changes, the list disappears, a details form appears. The "details" button disappear, and a "back" button appears.

The onRegister has structure like the following:

override public function onRegister():void
{
   // listen to view events
   if( view.detailsButton )
       eventMap.mapListener( view.detailsButton, MouseEvent.CLICK, detailsButton_clickHandler, MouseEvent );

   if( view.backButton )
       eventMap.mapListener( view.backButton, MouseEvent.CLICK, backButton_clickHandler, MouseEvent );
}

The onRegister function gets called only one time (when the window is popped up), at that time the state is "normal", the "back" button is not there yet, so the event listener is not added.

Sorry if this was asked elsewhere, but I have searched the forum for answers and I couldn't find my answer.

One last question, is there a good way to separate the component from the popup window. i.e., to fix the above problem, I thought about moving the whole code inside the pop up window into a normal component, then just use that component inside the popup window. However the problem with this method is that I am utilizing the "controlBar" of the TitleWindow, and I couldn't figure out a good way to have the buttons in the component appear in the controlBar of the TitleWindow.

I am feeling a bit sleepy, so I am sorry if I confused you. Please let me know if you need me to clarify anything that doesn't make sense.

Thanks,

  1. Support Staff 2 Posted by Ondina D.F. on 05 Jul, 2012 10:15 AM

    Ondina D.F.'s Avatar

    Hi!

    A long-winded way of solving this would be:

    In a UsersView I’d create 2 states: “list” and ”details”

    then I would add

    A UserListView and a button (id=”goToDetails) , both having includeIn="list".

    A UserDetailsView and a button (id=”goToList) , both having includeIn="details".

    I’d let UsersView change states in the handlers of the click event dispatched by goToDetails and goToList.

    I’d mediate UserListView through UserListMediator and UserDetailsView through UserDetailsMediator.

    UserListView would dispatch a custom event in the handler of IndexChangeEvent dispatched by the list.
    UserListMediator would add an event listener for UsersEvent.USERS_LIST_SELECTED dispatched by the view:
    addViewListener(UsersEvent.USERS_LIST_SELECTED, dispatch, UsersEvent);
    and immediately redispatch it triggering a command which would set the selected user’s data on a UserModel. (or call a service to retrieve data from an external resource)

    When UsersView changes states to “details”, the UserDetailsView gets added to the stage and its UserDetailsMediator gets created.
    UserDetailsMediator in its onRegister() adds a listener:
    addContextListener(UsersEvent.USERS_SHOW_DETAILS, onShowDetails, UsersEvent);
    and dispatches an event to get the users data from the model:
    dispatch(new UsersEvent(UsersEvent.USERS_GET_DETAILS));

    which will trigger a command, which will read the UserModel’s data and dispatch an UsersEvent.USERS_SHOW_DETAILS with user’s data as a payload.

    UserDetailsMediator in its onShowDetails:
    view.showDetails(event.userData);

    Done.

    There are other solutions to get data for a view whose instantiation is deferred:
    (Copied and pasted from another thread ;) http://knowledge.robotlegs.org/discussions/robotlegs-2/36-mediators... ):

    • Stray’s RelaxedEventMap (https://github.com/Stray/robotlegs-utilities-RelaxedEventMap) – probably the best solution

    • ask for data within your onRegister() method: dispatch an event triggering a command, that calls the Model (after registering an event listener for the event dispatched by the Model in Mediator’s onRegister())

    • the disputed ‘Inject your Model into your Mediator’ – with probably more nays than ayes . I’m abstaining ;)

    In any case I’d mediate UsersListView and UserDetailView, and let UsersView handle the states changes. This way you could re-use UsersListView and UserDetailView elsewhere, if need be.
    Please tell me which solution works best for you.
    Cheers,
    Ondina

  2. Support Staff 3 Posted by Ondina D.F. on 05 Jul, 2012 10:25 AM

    Ondina D.F.'s Avatar

    To add buttons to the control bar you only have to do this:
    <s:controlBarContent>

      <s:Button id="someButton"/>
    

    </s:controlBarContent>

  3. 4 Posted by mbarjawi on 05 Jul, 2012 05:34 PM

    mbarjawi's Avatar

    Ondina, thank you so much for your thoughts and efforts. However, I am afraid you didn't answer my question, and I am assuming because I wasn't so clear in asking it. Your efforts didn't go in vain though... as you taught me about not injecting my model into the mediator (which I am currently doing), I'll start doing that after I fully understand the problem and its solutions.

    In my question above, I almost have the same design as what you described (almost)... here is my problem using your example to make things simpler:

    • UsersView is a TitleWindow popup.
    • I want the two buttons goToDetails and goToList to appear on the UsersView control bar (using the controlBarContent as you mention).
    • So I moved out those two buttons from inside the two views UserListView and UserDetailsView and placed them inside the controlBarContent in the UsersView.

    So things look something like this now:

    <UsersView> <!-- UsersView Extends TitleWindow -->
        <states>
            <State name="list"/>
            <State name="details"/>
        </states>
    
        <UserListView includeIn="list"/>
        <UserDetailsView includeIn="details"/>
    
        <controlBarContent>
            <Button id="goToDetails" includeIn="list" click="currentState='details'"/>
            <Button id="goToList" includeIn="details"  click="currentState='list'"/>
        </controlBarContent>
    </UsersView>
    
    • Now, when the popup is first created and added to stage, it is in the list state. So when the onRegister() function (in the UsersViewMediator class) is called, the goToList button is not created yet. Which means I couldn't not set event listener on the goToList button.
    • When the state changes into details, the goToList button is created, but the onRegister() is not called anymore, and I couldn't set the listener on the goToList button.
    • And this is exactly my problem:

      1) I feel that the place of these buttons is better inside the UserListView and UserDetailsView but I cannot keep them there as I wouldn't be able to put them in the controlBarContent.

      2) I couldn't set the listener for the goToList button as it is not created yet when the onRegister() is called.

    I hope things are more clear now. Thanks for your help.

  4. 5 Posted by mbarjawi on 06 Jul, 2012 02:57 AM

    mbarjawi's Avatar

    I think I found out one way of solving my problem. However, not sure if it is the best way out there.

    What I did is I created mediators for the two buttons themselves, and then inside of each mediator, listened for the MouseEvent.CLICK event, and dispatched an injected signal. The same signal I am listening for inside the UsersView, which should solve the problem.

    However, for some reason my mediators are not being attached/called... even though my code runs the:

    mediatorMap.mapView( goToList, goToListMediator );
    mediatorMap.mapView( goToDetails, goToDetailsMediator );
    

    But still when the buttons are added to stage, the onRegister() inside these two mediators are not being called. I guess I am missing something over there. I'll take a second look and come back with the results.

    However, is the method mentioned in this post the right way to solve my problem?

  5. 6 Posted by mbarjawi on 06 Jul, 2012 03:36 AM

    mbarjawi's Avatar

    Just figured it out... it seems that not only the Popup window that needs to have its mediator created/removed manually, but also any component inside the popup window needs to have its mediator manually created/removed. I did that for the two buttons and bingo... it works.

    However the question still stands, is this the best way to solve the problem at hand?

    Thanks

  6. Support Staff 7 Posted by Ondina D.F. on 06 Jul, 2012 08:02 AM

    Ondina D.F.'s Avatar

    I’ll be back with answers in the course of the day. It’s 10 AM here:)

  7. Support Staff 8 Posted by Ondina D.F. on 06 Jul, 2012 12:04 PM

    Ondina D.F.'s Avatar

    In this post I’ll answer these:
    >1) I feel that the place of these buttons is better inside the UserListView and UserDetailsView but I cannot keep them there as I wouldn't be able to put them in the controlBarContent.

    >2) I couldn't set the listener for the goToList button as it is not created yet when the onRegister() is called.

    In this scenario UsersView is not a TitleWindow. I’ll talk about popups in a following post.
    If you want UsersMediator to react to state changes in the view (UsersView), you add an event listener like this in its onregister():

    eventMap.mapListener(view, UsersEvent.USERS_STATE_CHANGED, onStateChanged, UsersEvent);

    In UsersView you either dispatch that custom event in the handler of each button or you add an event listener for stateChangeComplete and
    in stateChangeCompleteHandler you inform the mediator about the changes.
    (Let me know if you don’t know how to create a custom event)

    //sorry for the formatting!

    <pre>
    <code>
    public function changeCurrentState(state:String):void
    {
    currentState=state;
    }

    protected function stateChangeCompleteHandler(event:FlexEvent):void
    {
    dispatchEvent(new UsersEvent(UsersEvent.USERS_STATE_CHANGED, currentState));      
    }

    protected function onGoToList(event:MouseEvent):void
    {
    dispatchEvent(new UsersEvent(UsersEvent.USERS_STATE_CHANGED, "list"));                                          
    }

    protected function onGoToDetails(event:MouseEvent):void
    {
    dispatchEvent(new UsersEvent(UsersEvent.USERS_STATE_CHANGED, "details"));                                    
    }

    protected function goToDetails_clickHandler(event:MouseEvent):void
    {
    currentState="details";
    //dispatchEvent(new UsersEvent(UsersEvent.USERS_STATE_CHANGED, currentState));                                        
    }

    protected function goToList_clickHandler(event:MouseEvent):void
    {
    currentState="list";
    //dispatchEvent(new UsersEvent(UsersEvent.USERS_STATE_CHANGED, currentState));
    }
    <s:states>
    <s:State name="list"
    <s:State name="details"
    </s:states>
    <s:controlBarContent>
    <s:Button id="goToDetails" label="show details" includeIn="list" click="goToDetails_clickHandler(event)"
    <s:Button id="goToList" label="show list" includeIn="details" click="goToList_clickHandler(event)"
    </s:controlBarContent>        
    <components:UsersListView id="usersList" includeIn="list" x="10" y="10" itemDestructionPolicy="auto"     
    <components:UserDetailsView id="usersDetails" includeIn="details" x="10" y="10" itemDestructionPolicy="auto"           
    </code>
    </pre>

    If UsersView wouldn’t be a TitleWindow, the mediators (UsersMediator, UsersListMediator, UserDetailsMediator) would be created automatically in response to their views being added to the display list.

    Why am I using a custom event?
    The mediator shouldn’t care about the details of the view. If I changed my mind and wanted the view to change the currentState in response to another user’s interaction (selectedItem in a list or dataGrid, or a mouse over etc) I would have to add event listeners in the mediator for all these occurrences. Or, in your scenario, if I’d wanted to add another state to the stack, say “images”, and then another state “sounds”,  I’d have to add new buttons and of course new event listeners in my mediator.

    As you saw, you couldn’t add an event listener in your mediator to a component that wasn’t on stage at the time onRegister() has run. With a custom event you wouldn’t have this problem.

    With a custom event things are more flexible, reusable and decoupled, and, also, the event listeners in the mediator wouldn’t create a strong reference to the view and/or its subcomponents (important for gc).

    Why is not good to have goToDetails button in the UserDetails view and goToList button in your UsersListView?
    Because UsersListView and UsersDetailsView shouldn’t care bout their parent’s state and if you want to re-use them elsewhere, where state changes aren’t required, what are you going to do? Comment out the buttons?
    And if you had more than 2 states in the parent view, would you add a goToImages and a goToSounds buttons in your UserListView to satisfy the new requirements? UsersListView’s single responsibility is to present a list of users and to inform its mediator about selected items.

    The navigation between views is UsersView’s ( or any other parent view of UsersList and UsersDetails) responsibility.

    To be continued..

  8. Support Staff 9 Posted by Ondina D.F. on 06 Jul, 2012 01:38 PM

    Ondina D.F.'s Avatar

    Just figured it out... it seems that not only the Popup window that needs to have its mediator created/removed manually, but also any component inside the popup window needs to have its mediator manually created/removed. I did that for the two buttons and bingo... it works.

    You’re right, you have to create/remove popup’s and its children mediators manually (in robotlegs 1).
    You can let UsersView create the mediators for UsersListView and UserDetailsView in its onStateChanged method, called after the view dispatches the UsersEvent.USERS_STATE_CHANGED from the example in the previous post, or trigger a command that would do just that.

    To be able to add a mediator for UsersListView, which is included in the “list” state, the first one when UsersView gets created, you’ll probably have to introduce a new “start” state as the initial state and in your UsersMediator onRegister() change the UsersView state to “list”, in case you want to react to state changes.

    In Robotlegs 2 the handling of popups is much easier than in Robotlegs 1, just for your information ;)

    You asked:

    However, is the method mentioned in this post the right way to solve my problem?

    and then

    However the question still stands, is this the best way to solve the problem at hand?

    I don’t know anymore what method or way are you referring to, there where several aspects to your use case: popups, states, where to put the buttons, how to add event listeners…

    One question from me:
    Do you need to use a popup in this way, meaning should the list view and the details view be contained in the popup? Or you rather meant to open a popup with details for an item selected from a list?

  9. 10 Posted by mbarjawi on 15 Jul, 2012 04:13 AM

    mbarjawi's Avatar

    Again thanks a lot for your efforts and replies. Sorry for taking so much long time to get back to you on this subject.

    • Answering your question:

      Do you need to use a popup in this way, meaning should the list view and the details view be contained in the popup? Or you rather meant to open a popup with details for an item selected from a list?

      • Well, my software has area where a study plan can be created for students. Now, in that area, I want the user to be able to click an "Add" button to add another item to the study plan. Once the "add" button is clicked, a dialog pops up showing the list of items from which the user can choose. This list, doesn't contain details of each items. So in the dialog itself, I added a "details" button, when clicked, the current selected study plan item will be viewed in details by switching the state of the dialog into the "details" view. I thought this is better than opening another dialog on top of the first dialog.
    • In regards to my problem and its solution

      • My solution was a combination based on your post (post 8) and on my post (post 5). You inspired me a lot in post 8... so thanks a lot.
      • What I did was to separate all view specific events/actions what so ever... from the rest of the application (which is connected through the mediator).
      • For example, in the mediator, I think we should not listen for a MouseEvent.CLICK event coming from one of the buttons... because this will make the mediator too much tightly coupled with the view.
      • Instead, in the mediator we should listen for application specific events coming from the view. For example, the view itself listens for its CLICK event, then dispatches a "IMPORTANT_APP_EVENT" that the mediator is listening for.
      • I found it event simple and easier to create signals on the view itself. So in the mediator, it would look something like: view.importantAppSignal.add( myHandler );
      • This helped me solve my problem in the following way: 1- In the dialog, I listened events coming from the buttons. Based on the examples we discussed above, the view would look something like that:

      <!-- UsersView Extends TitleWindow -->

        <states>
            <State name="list"/>
            <State name="details"/>
        </states>
      
        <script>
            public var importantAppSignal:Signal;
      
            protected function goToDetailsHandler():void
            {
                currentState='details';
            }
      
            protected function goToDetailsHandler():void
            {
                currentState='list';
                importantAppSignal.dispatch();
            }
        </script>
      
        <UserListView includeIn="list"/>
        <UserDetailsView includeIn="details"/>
      
        <controlBarContent>
            <Button id="goToDetails" includeIn="list" click="goToDetailsHandler()"/>
            <Button id="goToList" includeIn="details"  click="goToListHandler()"/>
        </controlBarContent>
      

      2- This way, even if the button is added later on in the life of the dialog, the handler will still be called. The outside world doesn't need to know this much specific details about the view. This is why all this happens inside the view. The mediator only cares about the signal that got dispatched in these handlers.

    I hope this helps someone out there.
    Thanks Ondina a lot for your support, I will wait couple of days before closing this thread, in case anybody had some comments to add.

    Mutasem

  10. Support Staff 11 Posted by Ondina D.F. on 16 Jul, 2012 08:23 AM

    Ondina D.F.'s Avatar

    Glad to have been of help:)
    Nice sum-up of our discussion!

  11. mbarjawi closed this discussion on 24 Jul, 2012 12:57 AM.

Comments are currently closed for this discussion. You can start a new one.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac