Inject model(s) into specific purpose view

dkarten's Avatar

dkarten

01 Jul, 2015 04:18 PM

I have been struggling with the best Robotlegs solution to this issue. While I know I could accomplish this in a few ways, I keep running myself in circles about which to implement.

I have a very specific view that is used for only one purpose: a user enters a specific type of data and adds a record to the database and subsequently the model. The data the user can enter is a mixture of things from TextInputs to ButtonBars to DropDowns to NumericSteppers. Some of the DropDowns are populated based on settings made elsewhere in the app, and some of the values set on other inputs rely on information kept in the model. Basically, there are a number of dependencies the view needs to function properly. Two obvious solutions are as follows:

1) Request-Provide pattern, as detailed here. The view has a setData method which takes an Object and dishes out properties accordingly. I stopped halfway through implementing this as the amount of data that needs to be pulled from several models started to feel quite cumbersome. Pros: keeps model and view loosely coupled. Cons: More signals/events, the data is so widely varied I'm not sure if it makes sense to type it with a VO.

2) Inject model(s) directly to view. Honestly, this makes more sense, as this view serves a very specific purpose. "Appropriate coupling" vs. "no coupling" as mentioned (here) [http://knowledge.robotlegs.org/discussions/questions/878-injecting-...]. Pros: Fast, simple, less boilerplate. Cons: view is tightly coupled to models.

There will be several different cases of these views, i.e. a view of inputs that deals with adding a specific type of record to the database and model. There is really no way to implement any reusability between the views unless I am completely missing something. Implementing request-provide patterns for all of them feels repetitive. I guess the question really is, is it okay to have tightly coupled models and views when the view is very dependent on several models? What benefits do I reap by keeping the model and view loosely coupled in this case?

  1. 1 Posted by dkarten on 01 Jul, 2015 04:23 PM

    dkarten's Avatar

    Another thought that just crossed my mind, perhaps there could be a model that deals specifically with these AddRecord views? Then when settings are changed elsewhere in the app, this central model could be updated by other models and be fed to the necessary views in a read-only manner?

  2. Support Staff 2 Posted by Ondina D.F. on 02 Jul, 2015 02:31 PM

    Ondina D.F.'s Avatar

    If you don't mind the tight coupling of views and models, then yes, inject the models into your views. The question is, how are you going to update the views with the changed model's data, since the views cannot listen to events dispatched on the shared event dispatcher? Or are you talking about "bindable" model's data like in the other discussion?

    Have you looked at the Presentation Model already?

    Regarding your "central model" :
    I used a similar approach in one of my real projects where I had to deal with a great amount of lists used in different parts of my app. My 'central model' is just holding a list of models, gets an instance of a required model and returns its data to a command.
    I've uploaded a mini-project that I used for experimenting with mediated lists :

    https://github.com/Ondina/robotlegs-bender-mediated-lists

    Each dropdownlist extends a base view and it is mediated by a ListMediator. The base view implements an IListView with 2 getters and setters for a listId and a listDataProvider.
    The only thing this mediator does is requesting a data provider for the list and reacting to changes made to the list's model.

    mediatorMap.map(IListView).toMediator(ListMediator);
    

    Mediator

    [Inject]
    public var view:IListView;
            
    override public function initialize():void
    {
        addContextListener(ListDataProviderEvent.LIST_DP_CHANGED, onListDPChanged, ListDataProviderEvent);
        dispatch(new ListDataProviderEvent(ListDataProviderEvent.LIST_DP_REQUESTED, view.listId));
    }
            
    private function onListDPChanged(event:ListDataProviderEvent):void
    {
        if(event.listId == view.listId)
            view.listDataProvider = event.listDataProvider;
    }
    

    Command

    public class RequestListDataProviderCommand
    {
        [Inject]
        public var event:ListDataProviderEvent;
            
        [Inject]
        public var model:ListDataProviderModel;
            
        [Inject]
        public var dispatcher:IEventDispatcher;
            
        public function execute():void
        {
            dispatcher.dispatchEvent(new ListDataProviderEvent(ListDataProviderEvent.LIST_DP_CHANGED, event.listId, model.getModel(event.listId)));
        }
    }
    

    Model:

    public class ListDataProviderModel
    {
        [Inject]
        public var injector:IInjector;
    
        private var models:Object = {"shapes": ShapesModel, "users": UsersModel, "colors": ColorsModel};
    
        public function ListDataProviderModel()
        {
        }
        
        public function getModel(value:String):ArrayCollection
        {
            var model:* = injector.getInstance(models[value]);
            return model.data;
        }
    }
    

    A disadvantage of this approach is that you need to check the id of the list in the mediator and the model. But, other than that it works well.

  3. 3 Posted by dkarten on 02 Jul, 2015 03:41 PM

    dkarten's Avatar

    The views are short lived (they're actually instances of the windows we worked on a few weeks ago!), so they would get their data from the model when opened. While it's not impossible, it's unlikely that a user would update application settings elsewhere while having this window view open. So my views depend dynamically on the application state, but in a static sort of way, if that makes sense? i.e. they only need to check data from the model when they are created.

    I have read about the presentation model and I can definitely see it applying here. The one thing that jumps out at me is 90% of the view's data and logic is self contained. It needs about 3 lists to display data, and 5 or so settings that determine behavior. Looking at your example I see a few places I could component out the view, like you did with specific dropdowns, as those are the pieces that depend on external data. Would every "presenter" need a mediator and a model? Some I think would only need models, but that is again tight coupling (although kind of the point of presentation model, right?). Finally, would it make sense to compile these presenters into one "regular" Robotlegs view? What about collecting all of the pieces of data into one centralized object?

  4. Support Staff 4 Posted by Ondina D.F. on 02 Jul, 2015 04:37 PM

    Ondina D.F.'s Avatar

    they only need to check data from the model when they are created.

    Oh, alright, if the models are just a sort of configs that the views need when they are added to the stage, then injecting them into views (or their mediators) falls into the category of 'appropriate coupling' :) Especially if you make them read-only and inject them as interfaces as you intended..

    Would every "presenter" need a mediator and a model?

    You either use a Mediator or a PresentationModel, not both.

    Some I think would only need models, but that is again tight coupling (although kind of the point of presentation model, right?).

    Well, with the mediator pattern the view doesn't have any reference to its mediator == no coupling, whereas with the presentation model the view is aware of the model == coupling.

    Finally, would it make sense to compile these presenters into one "regular" Robotlegs view?

    Don't know what you mean.

    What about collecting all of the pieces of data into one centralized object?

    This is also unclear. Do you mean collecting the data from the view or are you referring to the 'central model' from before?

  5. 5 Posted by dkarten on 02 Jul, 2015 06:03 PM

    dkarten's Avatar

    It's pretty hard to communicate directly what I mean through text. I created an image file to exemplify dependencies between components and external data. Let me know if you find it unclear. What I'm thinking is either the entire window gets the read-only interfaced models or the components get broken down and data dependencies get fed to the individual presenters (maybe through data binding?). Could you give an example on how to implement a read-only interface and use it with an already existing model in the application?

    Finally, would it make sense to compile these presenters into one "regular" Robotlegs view?

    What I mean is these components/presenters all live in a Window, could this top level window be treated as a view not a presenter? All of the application state logic would be taken care of in the components.

    What about collecting all of the pieces of data into one centralized object?

    The entire view functions as a "form" of sorts. The data selected and entered has to be collected and sent to the framework at some point, but the window does not need any knowledge of this and needs to reset on submit.

  6. 6 Posted by dkarten on 02 Jul, 2015 06:03 PM

    dkarten's Avatar

    Seems as if the file did not upload.

  7. Support Staff 7 Posted by Ondina D.F. on 03 Jul, 2015 11:37 AM

    Ondina D.F.'s Avatar

    Thanks for the image. It is indeed easier to understand what you mean by looking at it.

    The Window

    What I'm thinking is either the entire window gets the read-only interfaced models or the components get broken down and data dependencies get fed to the individual presenters (maybe through data binding?). What I mean is these components/presenters all live in a Window, could this top level window be treated as a view not a presenter? All of the application state logic would be taken care of in the components.

    Yes, that's right, Winow's only raison d'être should be to show a content in popup manner and to behave nicely when it's closed ( GC&Co ) . It is just a layout container. Being a top level component, like the main WindowedApplication, it can also serve as a contextView in case you want to build a separate Context in this Window ((but it doesn't have to), as I illustrated in my example on github (robotlegs-bender-open-close-window).
    So, the Window doesn't necessarily need a Model.

    To be continued..

  8. Support Staff 8 Posted by Ondina D.F. on 03 Jul, 2015 02:42 PM

    Ondina D.F.'s Avatar

    What I get from your uploaded image is that the Window contains a Navigation (button bars) and several Views contained in something like a ViewStack. I would create a NavigationView mediated by a NavigationMediator and add the NavigationView to the Window. ( please excuse the names of these classes and of those I'll be using further down)

    Now, let's assume that what I see in the picture is one of the Views in that ViewStack. I'll call it ScoreCalculatorView and its mediator ScoreCalculatorMediator.
    Let's say the data that we will send to a database is the following (inspired by your VOs from our previous discussion):
    playerName, playerAge, playerGender, playerPreferedColor, playerScore, playerAvatar belonging to a PlayerVO, and teamName, teamScore belonging to a TeamVO - this is data that 'leaves' the view

    playerName, playerAge and playerGender will be saved in a PlayerPerson db table, playerPreferedColor -> PlayerPreferences, playerScore -> Scores and so on.
    PlayerPreferences has just playerId, colorId and other things ids like prefered food, book, music, Scores has playerId and score, Teams has teamId, teamName, theamScore, playerId etc.

    The ScoreCalculatorView will contain:
    A dropdownlist for playerName, a NumericStepper for playerAge, check boxes for playerGender, a numeric stepper for playerScore, a numeric stepper for teamScore, a dropdown for colors, one for avatars, and a list of teams.

    The dropdowns could get their dataprovider as we already discussed (by mediating each dropdown). This is data that 'enters' the view. ScoreCalculatorView is not interested in the Models of those dropdowns. ScoreCalculatorView's role is to collect data from the different inputs.
    Just for fun, if the player chooses the male check box, the list of colors will filter out womanly colors :P and the playerScore will be set depending on the combination of chosen color, avatar, gender and age. The team score will also change depending on player's score.

    So, we deal with at least 2 Models: Player and Team. Because you like bindings, you can make the VOs bindable, and use 2-way bindable input elements like so:

    TextInput id="playerName" text="@{playerVO.playerName}"
    TeamListView id="teamList" selectedItem="@{teamVO.teamName}"
    ....
    

    On submit button click:

    [Bindable]
    public var vo:CompoundVO = new CompoundVO();
                
    [Bindable]
    public var playerVO:PlayerVO = new PlayerVO();
                
    [Bindable]
    public var teamVO:TeamVO = new TeamVO();
                
    protected function submitButton_clickHandler(event:MouseEvent):void
    {
        vo.playerVO = playerVO;
        vo.teamVO = teamVO;
        dispatchEvent(new CompoundEvent(CompoundEvent.UPDATE_MODEL_REQUESTED, vo));
    }
    

    The CompoundVO

    [Bindable]
    public class CompoundVO
    {
        public var playerVO:UserVO;
        public var teamVO:TeamVO;
            
        public function CompoundVO()
        {
        }
    }
    

    Instead of a compound vo you can of course use an object of your choice to hold the 2 (or more) vos.

    In the command that gets triggered by the event (redispatched by the mediator) you can pass the playerVO to the PlayersModel, the teamVO to the TeamsModel in order to update them and also call a service to send the data to your backend db.
    If you're using a list of players and teams in other views, they will get updated due to their ListMediator from the previous post who would listen to update events from the changed models.

    Of course you can create a model for the ScoreCalculatorView, if you need to keep track of view's state or some view specific data, other than the players and teams. I can't say from just looking at your drawing if there is a need for a separate model or not.

    To conclude: the window holds the stack of views. The views are mediated. The components that serve as a way to compose view's data ( the dropdowns in our example) get their data independently of their parent view, i.e. ScoreCalculatorView. ScoreCalculatorView is managing the relations between its components, i. e. a selectedItem in a dropdown affects another dropdown's filter options or a certain choice is used in an algorithm to calculate a score. Then the final role, to collect data into an appropriate format (in my case as vos) and send it to its mediator via an event to be redispatched. Ah yes, and to clean up after itself when the window gets closed ;)
    If some actions in the ScoreCalculatorView should have an effect on other views, this can happen through events that the mediator can redispatch.

    Another thought that just crossed my mind, perhaps there could be a model that deals specifically with these AddRecord views? Then when settings are changed elsewhere in the app, this central model could be updated by other models and be fed to the necessary views in a read-only manner?

    Maybe you had a Model in mind that would gather the player and team data from the ScoreCalculatorView in my example? So you'd inject it into another view to access the player model and team model that were modified in ScoreCalculatorView? And this other view would maybe modify a game model, also contained in that central model?
    I wouldn't do that. Firstly, a central model that needs to be always updated by other models can be hard to manage, and secondly, such a model would be really large and would contain models of no interest to some views.
    But maybe you had something else in mind.

    Before talking about presentation model, I would suggest to try to use the approach illustrated above, or to tell me why do you think it wouldn't work.
    What are the dependencies that the ScoreCalculatorView would need besides the dataproviders for the dropdowns? You mentioned

    It needs about 3 lists to display data, and 5 or so settings that determine behavior.

    What kind of settings? Something like

    • if user is older than 42, use fontSize=18 pt

    • if user is admin, show delete button

    • if view is CompoundFourView ( after clicking on button 4 from buttonbar) use dropdown filter?

    I would inject a ViewBehaviourModel into the mediator and pass it to the view on initialize. But you can inject it into the view, if you wish.

    A read only model:

    public class UsersModel implements IReadUser, IAddUser
    .... the methods are as in the example on github
    
    // interfaces
    public interface IReadUser
    {
        function get data():ArrayCollection;
    }
    
    public interface IAddUser
    {
        function addItem(value:UserVO):void;    
    }
    
    injector.map(IReadUser).toSingleton(UsersModel);
    injector.map(IAddUser).toSingleton(UsersModel);
    

    and then you inject them like this
    [Inject] public var userModel:IReadUser;

    or like so

    [Inject] public var userModel:IAddUser;

    depending on what you need

  9. 9 Posted by dkarten on 06 Jul, 2015 05:06 PM

    dkarten's Avatar

    What kind of settings? Something like

    if user is older than 42, use fontSize=18 pt

    if user is admin, show delete button

    if view is CompoundFourView ( after clicking on button 4 from buttonbar) use dropdown filter?

    Yes, these are similar to the examples you provided. In the context of the same tournament example, let's say that there are different divisions a team can be in, (in American NCAA basketball there are Divisions I, II, III for example), but this depends on if you wanted the tournament to use divisions or not -- a boolean value. This is a setting in the tournament model, and the divisions list is external data passed to a dropdown from the DivisionsModel. If the tournament doesn't use divisions, there is no need to show the dropdown, or maybe you want to set enabled to false.

    if (tournament.useDivisions) {
        divisions_dd.visible = true;
    }
    \\ Or perhaps more simply
    <components:DivisionsListView visible={tournament.useDivisions} ... />
    

    The divisions list is like in your mediated list view example, easy as pie. What about getting the useDivisions setting? Right now I have the window's mediator request config data which is fine, but I feel like I'm doing all this separation and wiring and not really getting myself any reusability or simplification. Now I just have a very specific window with a set of very specific components. It does not really make sense to have an IConfigVO, as I can't think of other places in the app such configs would be needed, which makes this data request event also very specific and non reusable. This is where I thought the read only model may be applicable, so in the top level window/mediator we would inject an IReadTournament and use it's values for things like useDivision, isCollege, isHighSchool (maybe there are different rulesets for college and high school?) directly instead of creating another intermediary with a config VO. Whenever I start to do something like this I get that "code smell" I guess because it seems to me I am mixing view logic with application data, and the separation of roles is not very clean.

    Finally, another idea has just crossed my mind, does it make any sense to have "static" models? i.e. they hold data that is used commonly across the app, but this data is never (or very rarely) updated by anything else. I'm thinking this because I definitely have a collection of static data that gets displayed in various components several places in the app (ButtonBar, DataGrid, ... etc), so I could hold this list in a model and pass it to specific component implementations, just like in the DropDown example.

  10. Support Staff 10 Posted by Ondina D.F. on 07 Jul, 2015 09:15 AM

    Ondina D.F.'s Avatar

    First an erratum to my previous post.
    If you want to inject IAddUser somewhere and IReadUser somewhere else, this mapping is incorrect:

    injector.map(IReadUser).toSingleton(UsersModel);
    injector.map(IAddUser).toSingleton(UsersModel);
    

    I meant to show you how you can map just one of the interfaces and didn't notice the confusion.

    This is correct:

    var usersModel:UsersModel = injector.getOrCreateNewInstance(UsersModel);
    
    injector.map(IAddUser).toValue(usersModel);
    
    injector.map(IReadUser).toValue(usersModel);
    

    Now, you can inject IAddUser into a class where you, guess what, add users to the list, and IReadUser into the class that just needs the list of users.

  11. Support Staff 11 Posted by Ondina D.F. on 07 Jul, 2015 03:48 PM

    Ondina D.F.'s Avatar

    This is where I thought the read only model may be applicable, so in the top level window/mediator we would inject an IReadTournament and use it's values for things like useDivision, isCollege, isHighSchool (maybe there are different rulesets for college and high school?) directly instead of creating another intermediary with a config VO.

    This sounds good, but it is still not clear to me whether TournamentModel is having all these properties, useDivision, isCollege, isHighSchool, or you would inject several models into the view to get all the needed settings?

    Let's just think about what that window really needs to know.
    I'll use an analogy with a Flex component. When you create a new Button, you set its properties like visibility, enabled, color, label text, etc. The Button doesn't need to know whether the parent component is a Group or a BorderContainer or in which state the parent component is in. You don't add code to your Button which will check if parent.isBig then set label="Big Button" or something like that. The parent of the Button is the one deciding how the Button will look like, and the Button doesn't care about its parent, it just exposes APIs for its properties.

    Likewise, the view containing a divisions list doesn't need to know if tournament.useDivisions is true or false, it just needs to know if the divisions list is to be shown or not (or to be added to the stage or not). Ideally, the view should only expose a divisionsVisibility property and it shouldn't care how or who will set it. Maybe later you will want to make the visibility of the list dependent on other conditions as well, so what will you do? Will you inject yet another model and ignore the tournament model?

    When dealing with settings for a view coming from many models, my preference would be to not make the view directly dependent on all of them. Even if that specific view is not reusable (yet) and even if it is more work, I would create a class like the mentioned SomeViewBehaviour, with all the properties needed by the specific view/window.
    The view would be injected with IGetViewBehaviour. I would create, set its data and open the window in a command. Before opening it, the command would assemble the data for SomeViewBehaviour from several models (IReadTournement, IReadSomeOtherModel...). SomeViewBehaviour would be injected into the command as ISetViewBehaviour:

    viewBehaviour.divisionsVisibility = tournament.useDivisions;
    viewBehaviour.someOtherProperty = someOtherModel.someOtherProperty;

    If you later decide that the view's behaviour should be dictated by other conditions or combinations of models, you just have a single command where you make the changes. The dependencies are reduced: One View <---> One Behaviour Class as opposed to One View <--->Model 1, Model 2, Model 7, Model 11.

    (I'm not talking here about the data providers for the dropdowns which were actually mediated sub-views in my mediated lists example and were getting there data independently of the parent view. )

    You could also have 2 mediators for a view/window, if the view would implement 2 interfaces. One mediator would just take care of the loading of view's settings and the other of collecting view's data and sending it to whomever needs it. This is just a thought, I don't know if it is useful for you or not.

    Finally, another idea has just crossed my mind, does it make any sense to have "static" models? i.e. they hold data that is used commonly across the app, but this data is never (or very rarely) updated by anything else.

    So, now we are back to a global model? ;) I don't know how to say it without repeating myself. Theoretically, if it contains just "static" settings, then there is no harm in injecting it where ever you need it, as a read only interface. Is your TournamentModel holding such settings? If the answer is yes, then you don't need a special or a specific ViewBehaviour class for your views.
    I'm sure you're aware of the associated risks with having such a model 'used commonly across the app', like its tendency to become an "everything drawer" and you'll be carefully designing it. I wouldn't call it a model, though. Models can "do" things, like changing the state of data... This 'holding application's settings' class, however you name it, could implement several read only interfaces: IReadTournament, IReadEducation, IReadSomeRules and so on, so a view needing only tournament settings would be injected with only the IReadTournament and not the IReadTheEntireClass.

    Conclusion: injecting configuration data into views as read-only interfaces is acceptable.
    I hope this helps you at least a little bit.
    There might be other solutions as well, but unlike you, I can't see your entire project and requirements to give you a more practical and concrete answer, and besides, I also don't hold all the answers :)

  12. 12 Posted by dkarten on 07 Jul, 2015 09:14 PM

    dkarten's Avatar

    Ok, this seems great, once I wrote everything out it got very clean. I am just missing how exactly to inject the behavior created in the command as ISetViewBehavior into the view as IGetViewBehavior. Is it just injector.injectInto(view)?

  13. Support Staff 13 Posted by Ondina D.F. on 07 Jul, 2015 09:31 PM

    Ondina D.F.'s Avatar

    Short answer: look for viewProcessorMap

    viewProcessorMap.map(SomeView).toInjection();

    More tomorrow, if need be..

  14. 14 Posted by dkarten on 08 Jul, 2015 01:53 PM

    dkarten's Avatar

    Ok, I think I have everything wired up properly. I was even able to abstract the view behavior to a top level NewRecordWindowBehavior class that holds behavior common to all windows adding a record to the database. So we have the following

    1) NewRecordWindowBehavior, IGetNewRecordWindowBehavior, ISetNewRecordWindowBehavior. The class implements both interfaces, IGet has only getters, ISet has only setters.

    2) NewPlayerWindowBehavior, IGetNewPlayerWindowBehavior, ISetNewPlayerWindowBehavior similar to above.

    3) The injector maps ISetNewPlayerWindowBehavior, IGetNewPlayerWindowBehavior to type NewPlayerWindowBehavior.

    ViewProcessorMap injects behavior correctly into view. All is well.

    My one complaint: Now, if we want to change the behavior of one view, we have 3 separate classes we have to update. Is this the best we can do?

  15. Support Staff 15 Posted by Ondina D.F. on 08 Jul, 2015 02:49 PM

    Ondina D.F.'s Avatar

    My one complaint: Now, if we want to change the behavior of one view, we have 3 separate classes we have to update. Is this the best we can do?

    Which 3 classes?

    I'm sorry, but I don't know what you mean.
    I don't understand the relationship between NewRecordWindowBehavior and NewPlayerWindowBehavior.
    You said that a window extends a NewRecordWindowBehavior.
    Is a new window extending NewRecordWindowBehavior and implementing a NewPlayerWindowBehavior, so it has the functionality common to all windows plus the one specific for a player window?

  16. Support Staff 16 Posted by Ondina D.F. on 08 Jul, 2015 02:50 PM

    Ondina D.F.'s Avatar

    I meant to say :
    Is a new window extending NewRecordWindow which implements the IGetNewRecordWindowBehavior, ISetNewRecordWindowBehavior.

  17. 17 Posted by dkarten on 08 Jul, 2015 03:07 PM

    dkarten's Avatar

    Sorry to come full circle, but I think now I've found the best solution...

    As I was working with more views and planning out more ViewBehaviors, I came to realize that the settings that determine behavior (e.g. useDivisions) are used in many more places, and in views that are not dynamically created as well. The roadblock for me was for dynamically created views -- mediators that do not exist yet won't receive framework events. Enter RelaxedEventMap. I had read about this extension but not fully understood what it was for. It is now clear to me what I want is to essentially "broadcast" a setting made in the model to all interested view components, and mapping relaxed events will broadcast to existing and yet to be created mediators (if I understand correctly).

    To clear up my last post, here is a diagram of inheritances

    NewPlayerWindowBehavior <--extends -- NewRecordWindowBehavior

    IGetNewPlayerWindowBehavior <--extends-- IGetNewRecordWindowBehavior

    ISetNewPlayerWindowBehavior <--extends-- ISetNewRecordWindowBehavior

    NewRecordWindow is pretty much an abstract class, theres not actually an implementation of it, but each window that adds a new record will get behavior that extends the behavior of an abstract NewRecordWindow -- that was the idea at least.

  18. Support Staff 18 Posted by Ondina D.F. on 08 Jul, 2015 03:14 PM

    Ondina D.F.'s Avatar

    So, the problem about how to update the 3 classes is solved and now your question is about the relaxed event map?

  19. 19 Posted by dkarten on 08 Jul, 2015 03:23 PM

    dkarten's Avatar

    I'm going to try to implement the relaxed event map, it seems pretty straightforward so no questions there.

    In the case that I do use a ViewBehavior, I'm asking if

    a) I implemented your suggestion of ISetViewBehavior, IGetViewBehavior, ViewBehavior correctly? (don't worry about the inheritance I mentioned above)

    b) When we add or remove a "behavior/setting" from the ViewBehavior class, we have to change all 3 classes, the two interfaces, and the implementation, right? Is that the best that we can do if we want to use the ViewBehavior approach?

  20. Support Staff 20 Posted by Ondina D.F. on 08 Jul, 2015 03:48 PM

    Ondina D.F.'s Avatar

    To a) yes, it seems so

    To b) if you mean adding or removing methods to/from the ViewBehaviour class, then yes, you have to change both interfaces as well or rather vice versa, ViewBehaviour would have to implement the methods declared in the interface class(es). That's the nature of interfaces. Once you are done with designing your classes and your project will be more 'stable', you probably won't have to change these classes anymore or at least not very often.
    Is that what you meant?

  21. 21 Posted by dkarten on 08 Jul, 2015 05:15 PM

    dkarten's Avatar

    Yes

  22. Ondina D.F. closed this discussion on 29 Jul, 2015 09:32 AM.

  23. dkarten re-opened this discussion on 24 Sep, 2015 03:22 PM

  24. 22 Posted by dkarten on 24 Sep, 2015 03:57 PM

    dkarten's Avatar

    Up until now I have been using your style of mediated lists with no problem. However I encountered a relatively glaring performance issue and was wondering if there was any way to improve it.

    A model contains 3 array collections that need to be passed to a view to populate 3 corresponding datagrids. These data are all related and it makes sense for them to reside in the model as separate collections.

    To view/edit this data, the user opens a popup spark window containing a view of these 3 datagrids. This view has a mediator that listens for LIST_DATA_CHANGED and the appropriate view_id, and dispatches a LIST_DATA_REQUESTED. The problem: it takes, on average, 700ms for the data to complete its trip. The window opens, and the datagrids sit blank for almost a second before appearing with data... which really isn't acceptable.

    I've tried a few workarounds:
    -injecting the ListDataProviderModel directly into the mediator -injecting the DataModel directly into the mediator -using the RelaxedEventMap and dispatching the request event from the window's mediator

    Nothing has really drastically improved the performance. Do you have any suggestions for optimizing this procedure?

  25. Support Staff 23 Posted by Ondina D.F. on 25 Sep, 2015 12:11 PM

    Ondina D.F.'s Avatar

    Hi,

    I think that the behaviour you're describing has rather to do with the way Flex is rendering a DataGrid than with robotlegs.

    There are few known performance issues with a Flex DataGrid. You might want to look into these.

    How fast a datagrid shows its content depends on many factors. For example:

    • on the size of the dataprovider

    • on the number of shown rows

    • on item renderers and maybe the display objects they use

    • on sorting or filtering functions applied to the dataprovider as a collection

    • on layout settings, like dynamic vs. fixed row height or width

    • on effects applied to the datagrid or its subcomponents (transition, animation) , resizing

    I suggest running a few tests.
    Start with a simple application where you just open a simple datagrid in a simple window (simple == no special layout, no effects, no filtering or sorting of the dataprovider collection..).
    Use a mock data provider with a low number of items.
    Start with a low number of visible datagrid rows.
    Increase the size of the collection used as data provider.
    Increase the number of shown rows.
    Add layout constraints as used in your real app.
    Add array/arraycollection filters, sorting, if needed.
    If you're using item renderers, add them too.
    Try the same with having 2 or more datagrids in your window.

    If everything works as expected, add robotlegs and only the classes needed for testing .
    Again, start simple and increase the complexity gradually.
    Also, when using robotlegs, open a window without datagrid(s) and
    check the time elapsed from the opening of the window until receiving a dataprovider from model.
    Then add a datagrid and see the time it needs to render and to dispatch addedToStage and creationComplete and to become visible.

    You can use my example on github (https://github.com/Ondina/robotlegs-bender-mediated-lists) to add your use case.
    If you are sure that the behaviour you're seeing is not due to Flex and you think that robotlegs is the culprit for the strange behaviour of your datagrids, you should attach the modified app containing your tests so I can take a look at it.
    In any case let me know about your findings.

    As I mentioned in one of our discussions, I think that "paginating" large amounts of data is a good idea.

    When delays in loading of large data providers are not avoidable, I use to set the component's visibility to false until it gets its data and to show a 'loading...' icon or text for the time being.
    Same can be achieved with view's states, but they can be tricky sometime..

    I don't know how much you know about Flex components' life cycle. Here are a few links on the topic (that I've already posted elsewhere):
    http://help.adobe.com/en_US/flex/using/WS460ee381960520ad-2811830c1...
    http://www.developmentarc.com/site/wp-content/uploads/pdfs/understa...
    http://www.dlgsoftware.com/primers/Primer_on_Flex3_Component_Lifecy...
    http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c...
    From the above article:
    "creationComplete Dispatched when the component, and all its child components, and all their children, and so on, have been created, laid out, and are visible. Flex dispatches the creationComplete event for a container when those children that are initially required are fully processed and drawn on the screen, including all required children of the children, and so on. Create a listener for the creationCompleteevent, for example, if you must have the children’s dimensions and positions in your event handler. Do not use the creationComplete event for actions that set layout properties, as doing so results in excess processing time."
    I hope this helps.
    Ondina

  26. 24 Posted by dkarten on 26 Sep, 2015 12:13 AM

    dkarten's Avatar

    OK, I've done some further investigating. I think you are right about it being Flex based. Here are some more details and concrete numbers.

    First: All three DataGrids are relatively small and their length is fixed (11, 9, and 12 rows). They are fixed height and fixed width. Each row has 4 columns, 3 of which are ItemRenderers. One is a custom CheckBox ItemRenderer. The other two are inline NumericStepper ItemRenderers. Grid code:

    <s:DataGrid dataProvider="{dakFieldCols}" width="250" height="337" selectionMode="none">
        <s:columns>
            <s:ArrayList>
                <columns:CheckBoxColumn dataField="selected" headerText="" />
                 <s:GridColumn dataField="display" width="80" headerText="Field"/>
                 <s:GridColumn dataField="width" width="55" headerText="Length" rendererIsEditable="true">
                     <s:itemRenderer>
                         <fx:Component>
                             <s:GridItemRenderer>
                                 <s:NumericStepper value="@{data.width}" minimum="1" maximum="99" width="40"/>
                             </s:GridItemRenderer>
                         </fx:Component>
                     </s:itemRenderer>
                 </s:GridColumn>
                 <s:GridColumn dataField="order" width="40" headerText="Order" rendererIsEditable="true">
                      <s:itemRenderer>
                          <fx:Component>
                              <s:GridItemRenderer>
                                  <s:NumericStepper value="@{data.order}" minimum="1" maximum="99" width="40"/>
                              </s:GridItemRenderer>
                          </fx:Component>
                      </s:itemRenderer>
                  </s:GridColumn>
              </s:ArrayList>
          </s:columns>
    </s:DataGrid>
    

    Second: I use a generic OpenPopupWindow Signal to open the window. The Signal has the Window Class as it's argument and is mapped to a command which takes the class from the signal and opens with window after passing it to the viewManager.

    The window is structured as follows:

    -SetupWindow
         |-VGroup
                |-HGroup
                      |-VGroup
                             |-SetupView1
                             |-SetupView2  
                      |-VGroup
                             |-DataGridsView
                                     |-DataGrid1
                                     |-DataGrid2
                                     |-DataGrid3
                             |-DataMonitorView
    

    Here are some numbers:

    -Using the RelaxedEventMap and dispatching the ListDataProviderEvent.LIST_DATA_REQUESTED from the SetupWindowMediators initialize function():

    [trace] OPEN POPUP WINDOW SIGNAL DISPATCHING 9889
    [trace] 9891 DEBUG Context-0-72 [object ViewManagerBasedExistenceWatcher] Adding context existence event listener to container SetupWindow2091
    [trace] POPUP WINDOW OPENED AND VISIBLE 10647
    [trace] dispatching dataprovider event: 10964
    [trace] receiving dataprovider event: 10966
    [trace] done setting dataproviders: 10967
    

    Listening to the Context from the DataGridsViewMediator and dispatching the ListDataProviderEvent.LIST_DATA_REQUESTED from the DataGridsViewMediator's initialize function:

    [trace] OPEN POPUP WINDOW SIGNAL DISPATCHING 26663
    [trace] 26666 DEBUG Context-0-8e [object ViewManagerBasedExistenceWatcher] Adding context existence event listener to container SetupWindow3540
    [trace] POPUP WINDOW OPENED AND VISIBLE 27189
    [trace] dispatching dataprovider event: 27436
    [trace] receiving dataprovider event: 27437
    [trace] done setting dataproviders: 27437
    

    In both of these cases, the datagrids sit blank while after the last line is traced and then appear with the data. My conclusion is that the ItemRenderers are very slow. Without them and just displaying the data, everything happens quite fast.

    If you have any suggestions for improving performance I would be very grateful! But as it seems this is no longer a RobotLegs issue, don't feel obligated. I have scoured the Adobe docs for information on itemRenderer performance but not been able to find very much!

  27. Support Staff 25 Posted by Ondina D.F. on 26 Sep, 2015 03:17 PM

    Ondina D.F.'s Avatar

    All three DataGrids are relatively small and their length is fixed (11, 9, and 12 rows). They are fixed height and fixed width.

    That's good, because a fixed height and width are making the rendering faster. Also try to set the rowHeight to a fixed value.

    All three DataGrids are relatively small and their length is fixed (11, 9, and 12 rows).

    You mean the number of visible rows or the length of their dataproviders?

    My conclusion is that the ItemRenderers are very slow. Without them and just displaying the data, everything happens quite fast.

    Except for the CheckBoxColumn that I can't see, the other columns look ok and I think that the datagrid is quite decent, not too big, not too many renderers and so on. But, maybe having 64+ NumericSteppers in the 3 datagrids is making Flex dizzy?;) You could build your own NumericSteppers inside of a custom renderer, a label for the value, one for the + sign and one for the - sign, and of course the corresponding handlers. Labels are lightweight and get rendered faster than a Numeric stepper.

    I'm not sure, but I think that the 2-way-binding in your item renderers plays a role in the delayed rendering. You may want to investigate this further.
    Anyway, I would use a custom renderer where I could override the set data() or listen for dataChange event, instead of using bindings, if possible. See the linked articles further down.

    Are your datagrids embeded as mxml components inside of your SetupWindow?
    Again, not sure if this helps at all, but try to add them through actionscript from within SetupWindowMediator's initialize(), by calling a SetupWindow's API and see if it's any better.

    I don't feel obliged to answer questions that are not related to robotlegs:) When I can, I answer such questions, also because I'm also interested in topics like flex datagrid, item renderers, etc....
    But I'm afraid I can't help you this time.
    First, because I am no expert in itemRenderers. Second, I'll be on holiday for a while, starting tomorrow, so that I can't experiment myself with a use case like yours.

    Stackoverflow is a good place for asking flex related questions. If you're are lucky, you get an answer from one of the many Flex experts there.

    Articles on item renderers:
    http://www.adobe.com/devnet/flex/articles/itemrenderers_pt1.html
    http://www.adobe.com/devnet/flex/articles/itemrenderers_pt2.html

    http://www.adobe.com/devnet/flex/articles/flex-mobile-performance-c...

    Good luck.
    If you find a solution, please post about it.

    See you.

  28. 26 Posted by dkarten on 22 Oct, 2015 02:00 PM

    dkarten's Avatar

    I hope your vacation went well. I wanted to let you know that creating custom Numeric Steppers like you said greatly improved the speed of opening the window!

  29. Support Staff 27 Posted by Ondina D.F. on 22 Oct, 2015 02:45 PM

    Ondina D.F.'s Avatar

    Yes, the vacation was great but too short, thanks for asking. I could use another one already...

    That's good news about the improved performance of your datagrid! Thank you for letting me know about it. I really appreciate the feedback :)

  30. Ondina D.F. closed this discussion on 28 Oct, 2015 03:48 PM.

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