Thursday, May 3, 2012

KirSQLite - Flex AIR Database Manager: Part 2

In this tutorial we'll work a little more on the layout - the data grid of the table view screen, specifically.

Firstly, we'll turn the contents of the first NavigatorContent object into a VGroup, which contains an HBox (something I added just now) and an AdvancedDataGrid that we already have.

The HBox is a container which will contain some tools for editing the data grid. We'll add a new column to the datagrid today, which will be a column of CheckBox objects. This way the user will be able to select one or multiple items to perform an action on all of them, for example, delete them all.

For this feature specifically the HBox will contain a CheckBox which will allow the user to select or deselect all of the entries in the table. Set its label to "Select all" and set its change event listener to a function called selectAllChange(), and pass "event" in the parameter. Next to the checkbox, add a button (which does nothing right now) with a label "Delete selected".

<mx:HBox width="100%" height="30" paddingLeft="8" paddingTop="6">
<mx:CheckBox label="Select all" change="selectAllChange(event);" />
<s:Button label="Delete selected" />
</mx:HBox>

In the AdvancedDataGrid itself, we'll do quite a few changes too. First, set its id to tableGrid. Set its editable property to true.

In the columns array, add a new AdvancedDataGridColumn object with blank headerText and width of 30. Set its sortable, draggable, editable and resizable properties to false. Set this particular column's itemRenderer to an fx:Component container, which contains an mx:Box container, which contains a CheckBox object. Set this CheckBox's selected property bound to data.sel.

Set the idnum column's editable property to false. Those are all the changes we made to the data grid:

<mx:AdvancedDataGrid id="tableGrid" width="100%" height="100%" dataProvider="{testData}" editable="true">
<mx:columns>
<mx:AdvancedDataGridColumn headerText=" " width="30" sortable="false" draggable="false" resizable="false" editable="false">
<mx:itemRenderer>
<fx:Component>
<mx:Box width="30" horizontalAlign="center">
<mx:CheckBox selected="@{data.sel}" />
</mx:Box>
</fx:Component>
</mx:itemRenderer>
</mx:AdvancedDataGridColumn>
<mx:AdvancedDataGridColumn dataField="idnum" headerText="ID" editable="false" />
<mx:AdvancedDataGridColumn dataField="firstname" headerText="First name" />
<mx:AdvancedDataGridColumn dataField="lastname" headerText="Last name" />
<mx:AdvancedDataGridColumn dataField="age" headerText="Age"/>
</mx:columns>
</mx:AdvancedDataGrid>

The "sel" property of the "data" object is a boolean variable that is set in the testData array collection. Let's add that to all the items in the array and set the values to false. Let's also increase the number of objects in the array for testing purposes:

<mx:ArrayCollection id="testData">
<fx:Object idnum="1" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="2" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="3" sel="false" firstname="Bob" lastname="Smith" age="33" />
<fx:Object idnum="4" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="5" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="6" sel="false" firstname="Bob" lastname="Smith" age="33" />
<fx:Object idnum="7" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="8" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="9" sel="false" firstname="Bob" lastname="Smith" age="33" />
<fx:Object idnum="10" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="11" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="12" sel="false" firstname="Bob" lastname="Smith" age="33" />
<fx:Object idnum="13" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="14" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="15" sel="false" firstname="Bob" lastname="Smith" age="33" />
<fx:Object idnum="16" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="17" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="18" sel="false" firstname="Bob" lastname="Smith" age="33" />
</mx:ArrayCollection>

Now create fx:Script tags to create a function called selectAllChange(). This function checks if the "Select all" checkbox is selected or not. If it is selected, then loop through all items in testData and set their "sel" properties to true. Otherwise, loop through them and set the values to false.

<fx:Script>
<![CDATA[
import flash.events.Event;
import mx.controls.Alert;

private function selectAllChange(evt:Event):void {
var i:int;
if (evt.currentTarget.selected) {
for (i = 0; i < testData.length; i++) {
testData[i].sel = true;
}
} else
if (!evt.currentTarget.selected) {
for (i = 0; i < testData.length; i++) {
testData[i].sel = false;
}
}
tableGrid.invalidateDisplayList();
tableGrid.invalidateList();
}
]]>
</fx:Script>

What's worth noticing is that all List-like components in Flex (such as List, DataGrid and TileList) don't always create a separate item renderer for each item in the data provider. For example, if we have 500 items in the List, it doesn't mean that the List will have 500 item renderers that all display values for their own assigned items. In these situations, the component usually has a scrollbar (or even two) and not all of the items are visible. For example, only 10 items might be visible out of 500 at a time. The List component just displays 10 and changes their values as the user scrolls through the content.

This boosts up the performance a lot, but can also cause some problems. Like in our situation - if we have a lot of items in the datagrid, say, 100, we obviously won't be able to view all of them at the same time, maybe around 15 at a time - the data grid will only display these 15 visible items.

What's the problem then? It's that if we use custom modifications, such as CheckBoxes as item renderers of the columns, they also keep their properties like "selected" even if the item renderer is switched to display another item, which might have a different value.

To prevent this, we must bind the select value to data.sel using the {} braces. Because our check boxes are also editable (the user can tick and untick them), we two-way-bind them using @{data.sel}.

Full code so far:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                       xmlns:s="library://ns.adobe.com/flex/spark"
                       xmlns:mx="library://ns.adobe.com/flex/mx" showStatusBar="false">
   
<s:menu>
<mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key"/>
</s:menu>

<fx:Declarations>
<fx:XML id="windowMenu">
<root>
<menuitem label="Database">
<menuitem label="New" key="n" controlKey="true" />
<menuitem label="Open" key="o" controlKey="true" />
<menuitem label="Save" key="s" controlKey="true" />
<menuitem label="Save As.." key="s" controlKey="true" shiftKey="true" />
</menuitem>
<menuitem label="Table">
<menuitem label="New" key="t" controlKey="true" />
<menuitem label="Edit" key="e" controlKey="true" />
<menuitem label="Delete"/>
</menuitem>
</root>
</fx:XML>
<fx:XMLList id="testTree">
<root>
<treeitem label="Database1">
<treeitem label="Table1"/>
<treeitem label="Table2"/>
<treeitem label="Table3"/>
</treeitem>
</root>
</fx:XMLList>
<mx:ArrayCollection id="testData">
<fx:Object idnum="1" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="2" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="3" sel="false" firstname="Bob" lastname="Smith" age="33" />
<fx:Object idnum="4" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="5" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="6" sel="false" firstname="Bob" lastname="Smith" age="33" />
<fx:Object idnum="7" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="8" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="9" sel="false" firstname="Bob" lastname="Smith" age="33" />
<fx:Object idnum="10" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="11" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="12" sel="false" firstname="Bob" lastname="Smith" age="33" />
<fx:Object idnum="13" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="14" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="15" sel="false" firstname="Bob" lastname="Smith" age="33" />
<fx:Object idnum="16" sel="false" firstname="John" lastname="Jackson" age="32" />
<fx:Object idnum="17" sel="false" firstname="Ted" lastname="Jacobson" age="25" />
<fx:Object idnum="18" sel="false" firstname="Bob" lastname="Smith" age="33" />
</mx:ArrayCollection>
</fx:Declarations>

<fx:Script>
<![CDATA[
import flash.events.Event;
import mx.controls.Alert;

private function selectAllChange(evt:Event):void {
var i:int;
if (evt.currentTarget.selected) {
for (i = 0; i < testData.length; i++) {
testData[i].sel = true;
}
} else
if (!evt.currentTarget.selected) {
for (i = 0; i < testData.length; i++) {
testData[i].sel = false;
}
}
tableGrid.invalidateDisplayList();
tableGrid.invalidateList();
}
]]>
</fx:Script>

<s:HGroup gap="0" width="100%" height="100%">
<s:VGroup width="200" height="100%" gap="0">
<mx:Tree width="100%" height="100%" dataProvider="{testTree}" showRoot="false" labelField="@label"/>
</s:VGroup>
<s:VGroup width="100%" height="100%" gap="0">
<mx:TabNavigator width="100%" height="100%" paddingTop="0">
<s:NavigatorContent label="Table contents">
<s:VGroup width="100%" height="100%" gap="0">
<mx:HBox width="100%" height="30" paddingLeft="8" paddingTop="6">
<mx:CheckBox label="Select all" change="selectAllChange(event);" />
<s:Button label="Delete selected" />
</mx:HBox>
<mx:AdvancedDataGrid id="tableGrid" width="100%" height="100%" dataProvider="{testData}" editable="true">
<mx:columns>
<mx:AdvancedDataGridColumn headerText=" " width="30" sortable="false" draggable="false" resizable="false" editable="false">
<mx:itemRenderer>
<fx:Component>
<mx:Box width="30" horizontalAlign="center">
<mx:CheckBox selected="@{data.sel}" />
</mx:Box>
</fx:Component>
</mx:itemRenderer>
</mx:AdvancedDataGridColumn>
<mx:AdvancedDataGridColumn dataField="idnum" headerText="ID" editable="false" />
<mx:AdvancedDataGridColumn dataField="firstname" headerText="First name" />
<mx:AdvancedDataGridColumn dataField="lastname" headerText="Last name" />
<mx:AdvancedDataGridColumn dataField="age" headerText="Age"/>
</mx:columns>
</mx:AdvancedDataGrid>
</s:VGroup>
</s:NavigatorContent>
<s:NavigatorContent label="Edit columns">

</s:NavigatorContent>
<s:NavigatorContent label="Query">

</s:NavigatorContent>
</mx:TabNavigator>
</s:VGroup>
</s:HGroup>

</s:WindowedApplication>

Thanks for reading!

No comments:

Post a Comment