Flex List Within a List?

502 Views Asked by At

I am trying to display a small set of hierarchical data and have found the AdvancedDataGrid so terrible in how it handles layout I am going to try and approach it another way using Spark Lists.

I am getting from a MySQL database an ArrayCollection of Lessons. Each Lesson has a parent Topic (I have included a TopicName field within each Lesson for ease) and I want to display the Lessons after grouping them by their respective Topics.

I could create a hierarchical data structure, possibly by using a GroupingCollection2, and wondered if I could display a Spark List of Topics and within the topicItemRenderer I create display a Spark List of Lessons for the given Topic?

Any thoughts very welcome.

Chris

2

There are 2 best solutions below

2
On

You could do it that way and it would work, But its not a good idea if you have a lot data. You would have to worry about item renderers. I would probably use a tree and create custom item renderers if need be. May be some code might help.

0
On

To create a grouped list of data I replaced my AdvancedDataGrid with a Spark List within a Spark List. It now has a layout which is predictable and sizes properly. In my case I know that I only have to display approximately 20 items in my grouped List so I don't really have any performance issues.

This is how I did it:

General - I created a new mxml component dedicated to the list of Lessons. I organise my code following a Model, View, Presenter pattern so I created LessonList_View (mxml) and LessonList_Presenter (ActionScript class). I have a Model class (Singleton) which the presenter classes instantiate to put/get data. The Model raises events when properties change informing the presenters, who have eventListeners, of the change so that they can update their local properties of which the views are bound. The Model calls PHP methods in a Amfphp Service to acquire data from the MySQL database.

Prepare Data - I have a relational MySQL database containing a table of Lessons and a table of Topics. Each Lesson has to have one parent Topic. A Topic will have many Lessons. I am using Amfphp to get a subset of Lessons data. Each Lesson row from the database is mapped to a Flex class giving me an ArrayCollection of strongly typed value objects of Type VoLesson. To make life simpler I included the topicName field in my VoLesson ActionScript class, only the topicId is available within the MySQL table, and included this in my SELECT statement when getting the data. I also sort the data by Topic and then by Lesson here so its ready for the next step.

Next I need to create an Array Collection containing ArrayCollections of Lessons of the same Topic. This way, I figured, I can have a parent Spark List displaying Topics and within the ItemRenderer for each Topic List Item I can have a List of Lessons.

Once my LessonList_Presenter has got an ArrayCollection of VoLessons I iterate through it. A new, temporary, ArrayCollection of Lessons (_topicLessons) is populated with Lessons until the topicName changes whereupon I add the current _topicLessons ArrayCollection of VoLessons into a parent ArrayCollection (courseTopicLessons).

The function is as follows:

    private function updateCourseTopicLessons():void {

    // Reset courseTopicLessons.
    this.courseTopicLessons = new ArrayCollection();

    // Create a variable to hold the last topicName.
    var _topicName:String = "";

    // Create an ArrayCollection to hold all of the Lessons for a single Topic.
    var _topicLessons:ArrayCollection = new ArrayCollection();

    // Iterate through the courseLessons.
    for each(var _lesson:VoLesson in this.courseLessons)
    {
        // check to see if this lesson has a different topicName.
        if (_lesson.topicName != _topicName) {

            //trace("Different Topic: " + _lesson.topicName);

            // Add the previous _topicLessons into the courseTopicLessons ArrayCollection.
            if (_topicLessons.length > 0) {
                //trace("Adding _topicLessons " + _topicLessons.length + " to courseTopicLessons");
                this.courseTopicLessons.addItemAt(_topicLessons, 0)
            }

            // This is a new Topic. Reset _topicLessons.
            //trace("Reset _topicLessons");
            _topicLessons = new ArrayCollection();

            // Update _topicName.
            _topicName = _lesson.topicName;
        }

        // Add the Lesson to _topicLessons.
        //trace("Add Lesson: " + _lesson.lessonTitle + " to _topicLessons")
        _topicLessons.addItemAt(_lesson, 0);

    }

    // Add the previous _topicLessons into the courseTopicLessons ArrayCollection.
    if (_topicLessons.length > 0) {
        //trace("Adding final _topicLessons " + _topicLessons.length + " to courseTopicLessons")
        this.courseTopicLessons.addItemAt(_topicLessons, 0)
    }

    //trace(this.courseTopicLessons)

}

I used .addItemAt() to keep the sort order correct.

Views and ItemRenderers - In my LessonList_View I created the List and set it as follows:

<!-- Lessons List -->
    <s:List
            id="lessonList"
            dataProvider="{presenter.courseTopicLessons}"
            itemRenderer="views.LessonListTopicItemRenderer_View"
            borderVisible="false"
            borderColor="0xff69b4"
            preventSelection="true"
            contentBackgroundAlpha="0">

        <s:layout>
            <s:VerticalLayout
                    useVirtualLayout="false"
                    requestedMinRowCount="1"
                    gap="8"
                    paddingTop="8" paddingBottom="8"/>
        </s:layout>

    </s:List>

I used the borders when checking everything to see the extents of the Lists.

My data provider is an ArrayCollection of ArrayCollections. I want to display List of Topics and within each Topic List Item I want to display a List of Lessons. To display the Topics I know that each ArrayCollection within the parent ArrayCollection will have at least 1 VoLesson (I hope you're following this!). I can display the topicName value from this item. Here is my code for the Lesson List's ItemRenderer:

<s:ItemRenderer
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:presenters="presenters.*"
    width="100%" height="100%"
    autoDrawBackground="false">

<fx:Declarations>
    <!-- Place non-visual elements (e.g., services, value objects) here -->
    <presenters:LessonListTopicItemRenderer_Presenter id="presenter"/>
</fx:Declarations>

<fx:Script>
<![CDATA[
    import models.Globals;
    import vo.VoLesson;

    override public function set data( value:Object ) : void {
        super.data = value;

        // Check to see if the data property is null.
        if (value== null)
            return;

        // If the data property is not null.
        var _lesson:VoLesson = VoLesson(value[0]);
        topicLabel.text = _lesson.topicName;

    }
    ]]>
</fx:Script>

<s:VGroup gap="8" width="100%">

    <!-- Divider line between Topics -->
    <s:Line id="topicDividerLine" width="100%">
        <s:stroke>
            <s:SolidColorStroke color="{presenter.selectedActivityColour_Mid}" weight="1" />
        </s:stroke>
    </s:Line>

    <!-- Topic Label -->
    <s:Label
            id="topicLabel"
            styleName="topicStyle"
            color="{presenter.selectedActivityColour}"
            maxWidth="{presenter.lessonsListTopicColumnWidth}" />

    <s:HGroup paddingLeft="{Globals.LESSONS_LIST_TOPIC_COLUMN_WIDTH}">
        <s:List
                id="lessonList"
                dataProvider="{data}"
                borderColor="0xadff2f"
                itemRenderer="views.LessonListLessonItemRenderer_View"
                borderVisible="false"
                preventSelection="true">

            <s:layout>
                <s:VerticalLayout
                        useVirtualLayout="false"
                        requestedMinRowCount="1"
                        gap="16"
                        paddingTop="8" paddingBottom="8"/>
            </s:layout>

        </s:List>
    </s:HGroup>

</s:VGroup>

The key thing to remember is that the ItemRenderer will be passed only the data for an individual item in the List, in this case an ArrayCollection of VoLesson objects. Within the element I get the topicName for the first item in the ArrayCollection of VoLessons passed in as 'data' and set my Label's text property.

Below the Topic Label I have my List of Lessons which has the same data provider, an ArrayCollection of VoLesson objects for the same Topic. The ItemRenderer for this List is as follows:

<s:ItemRenderer
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:views="views.*"
    xmlns:presenters="presenters.*"
    height="100%"
    autoDrawBackground="false">

<fx:Declarations>
    <!-- Place non-visual elements (e.g., services, value objects) here -->
    <presenters:LessonListLessonItemRenderer_Presenter id="presenter"/>
</fx:Declarations>

<fx:Script>
<![CDATA[
    import vo.VoLesson;

    override public function set data( value:Object ) : void {
        super.data = value;

        // Check to see if the data property is null.
        if (value== null)
            return;

        // If the data property is not null.
        var _lesson:VoLesson = VoLesson(value);
        lessonLabel.text = _lesson.lessonTitle;
    }
    ]]>
</fx:Script>

<s:HGroup gap="8" verticalAlign="middle">

    <views:IconLesson_View />

    <s:Label
            id="lessonLabel"
            styleName="lessonStyle"
            color="{presenter.textDarkGrey}"/>

</s:HGroup>

Remember that the 'data' object for this ItemRenderer, and there will be one for each item in the List of Lessons, will be a single VoLesson object. In the element I get the lessonTitle property from the VoLesson and set the lessonLabel Label's text property.

Final List

The List appears as follows: enter image description here

I have spent many days trying to coerce an AdvancedDataGrid to size itself and layout the content properly, it was dreadful. Yesterday I decided to start again and this works so much better. For a simple grouped list I would recommend a similar approach.

Regards

Chris