Very damn cool.
Tracy
Okay so on the same theme as why isn't the datagrid like Excel, I have created a (very cool) Filter header. 1) The filter looks like any other header (thanks to the alpha ability on the dropdown_mc), except when you do a mouse roll over 2) What you are filtering on is actually part of the header text (a smoke and mirrors thing thanks to how the combo box gets it's label) in italics and non-bold thanks to TextFormatting. 3) You can have multiple header filters for different columns, the natural conjunction with multiple header filters is AND. 4) Due ...
Okay so on the same theme as why isn't the datagrid like Excel, I have created
a (very cool) Filter header.
1) The filter looks like any other header (thanks to the alpha ability on the
dropdown_mc), except when you do a mouse roll over
2) What you are filtering on is actually part of the header text (a smoke and
mirrors thing thanks to how the combo box gets it's label) in italics and
non-bold thanks to TextFormatting.
3) You can have multiple header filters for different columns, the natural
conjunction with multiple header filters is AND.
4) Due to the CON in the next section you can add items to the dataProvider
and it will automatically populate the new piece of data into the drop down.
5) All function is the default, meaning essentially you don't wish to filter
on that column.
6) before adding items you can do a
HeaderFilter.showItem(grid._name,itemToAdd) and it will verify that no column
has issue with displaying the item so that you can add it if you wish.
CONS
1) you must maintain the same dataProvider.
2) sorting is no good. you might want to re-sort after a filter is done (but
providing events and what not are still something I need to provide, probable a
modelChanged event utilizing the eventName 'filter'
There is still a lot to do for this renderer to make it more usable, but I
figure I've gotten everyone to a dropping off point it shouldn't be too hard.
As I progress with this I'll post more to this thread
// THE FOLLOWING IS AN EXAMPLE USING THE HEADER FILTER
<mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml" xmlns="../*"
initialize="doInit()" >
<mx:Script>
<![CDATA[
function doInit()
{
if(grid.dataProvider == undefined)
grid.dataProvider = new Array();
var v=0;
var object = new Object();
object.optionName = "1";
object.description="ABC";
var object2 = new Object();
object2.optionName = "1";
object2.description="X,YZ";
grid.dataProvider.addItem(object);
grid.dataProvider.addItem(object2);
object = new Object();
object.optionName="2";
object.description="ABC";
grid.dataProvider.addItem(object);
}
]]>
</mx:Script>
<mx:Panel title="Header Filter" width="100%" height="100%">
<mx:DataGrid id="grid" width="100%" height="100%"
sortableColumns="false" hGridLines="false" vGridLines="false" >
<mx:columns>
<mx:Array>
<mx:DataGridColumn width="300" headerText="Option"
columnName="optionName" headerRenderer="{renderer.HeaderFilter}" />
<mx:DataGridColumn width="300" headerText="Option Description"
columnName="description" headerRenderer="{renderer.HeaderFilter}"/>
</mx:Array>
</mx:columns>
</mx:DataGrid>
</mx:Panel>
</mx:Application>
// THE FOLLOWING SHOULD GO IN renderer/HeaderFilter.mxml file
<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox xmlns:mx="http://www.macromedia.com/2003/mxml" verticalGap="0"
horizontalGap="0" marginLeft="0" marginRight="0"
cornerRadius="0" change="handleChange(event)"
mouseOver="if(!_showingDropdown)doLater(this,'doSh ow'); mouseOut=false;"
mouseOut="mouseOut=true; if(!_showingDropdown)doLater(this,'doHide');"
initialize="handleInitialize()" >
<mx:Script>
<![CDATA[
private static var filteredInfo:Object;
static function removeModelChangedListeners(name)
{
var temp = filteredInfo[name]['columns'];
if(temp == undefined || temp.length <1) return;
for(var i =0;i<temp.length;i++)
{
temp[i].removeModelChangedListener();
}
}
static function addModelChangedListeners(name)
{
var temp = filteredInfo[name]['columns'];
if(temp == undefined || temp.length <1) return;
for(var i =0;i<temp.length;i++)
{
temp[i].addModelChangedListener();
}
}
static function showItem(name,item):Boolean
{
var temp = filteredInfo[name]['columns'];
if(temp == undefined || temp.length <1) return true;
var ret:Boolean = true;
for(var i =0;ret && i<temp.length;i++)
{
if(temp[i] == undefined ) continue;
ret = ret && temp[i].isShown(item);
}
return ret;
}
function handleInitialize()
{
doHide();
if(filteredInfo == undefined)
filteredInfo = {};
if(filteredInfo[listOwner._name] == undefined)
{
filteredInfo[listOwner._name] = {};
filteredInfo[listOwner._name]['fdata'] = new Array();
filteredInfo[listOwner._name]['columns'] = new Array();
}
fData = filteredInfo[listOwner._name]['fdata'];
filteredInfo[listOwner._name]['columns'].push(this);
dataProvider = new Array();
listener = new Object();
listener.modelChanged = mx.utils.Delegate.create(this,handleModelChanged);
addModelChangedListener();
if(listOwner.length>0)
{
doLater(this,'handleModelChanged',[{target:listOwner,eventName:'updateAll'}]);
}
doLater(this,'dissolve',[75,0]);
this.setStyle('themeColor',_root.getStyle("themeCo lor"));
}
private var listOwner : MovieClip;//reference set by Flex upon instantiation
(owner component)
private var hO : Object; //reference set by Flex upon instantiation (owner
component)
private var column : Object; //set by Flex upon instantiation by owner
component
private var headerText:String;
private var fData:Array; // filtered data.
private var listener:Object;
var mouseOut:Boolean = true;
function doShow()
{
this.downArrow_mc.alpha=65;
this.border_mc.alpha = 65;
this.text_mc.border_mc.alpha = 65;
}
function doHide()
{
this.downArrow_mc.alpha=0;
this.border_mc.alpha = 0;
this.text_mc.border_mc.alpha = 0;
}
function setValue(val:String)
{
headerText = val;
}
function close():Void
{
if(mouseOut)
doLater(this,'doHide');
super.close();
}
function handleModelChanged(event:Object):Void
{
var sel = selectedItem;
if(dataProvider.getItemAt(0) == "All" && (event.eventName == 'addItems'
||event.eventName == 'updateAll') )
{
dataProvider.removeItemAt(0);
}
if(event.eventName == 'addItems')
{
for(var i = event.firstItem ;i<=event.lastItem;i++)
{
dataProvider.addItem(''+event.target.getItemAt(i)[column.columnName]);
}
dataProvider.sort(1);
}
else if(event.eventName == 'updateAll')
{
removeAll();
var arr = new Array();
var input = listOwner.dataProvider;
for(var i =0;i<input.length;i++)
{
arr.push(''+input[i][column.columnName]);
}
arr.sort(1);
dataProvider = arr;
}
else
{
return;
}
var found = null;
for(var i =length-1;i>-1;i--)
{
if(getItemAt(i-1) == getItemAt(i))
{
removeItemAt(i);
if(found != null)
found--;
}else if(found == null && sel == getItemAt(i) )
{
found =i;
}
}
addItemAt(0,"All");
if(found != null)
{
selectedIndex =found+1;
}
else
{
selectedIndex = 0;
}
}
function isShown(item):Boolean
{
if(this.selectedIndex <1 || item == undefined ) return true;
return ((''+item[this.column.columnName] )== this.selectedItem);
}
function setSelectedIndex(v:Number) : Void
{
super.setSelectedIndex(v);
updateLabel();
}
function updateLabel()
{
var tf = new TextFormat();
tf.italic=true;
tf.bold = false;
tf.color = 0x666666;
text_mc.label.setTextFormat(headerText.length,text _mc.label.length,tf);
}
function draw() : Void
{
super.draw();
updateLabel();
}
function removeModelChangedListener()
{
listOwner.dataProvider.removeEventListener("modelC hanged",listener);
}
function addModelChangedListener()
{
listOwner.dataProvider.addEventListener("modelChan ged",listener);
}
function handleChange()
{
// first ignore updates
removeModelChangedListeners(listOwner._name);
if(selectedIndex ==0)
{
var temp:Array = new Array();
//put all back.
for(var i =fData.length-1;i>-1;i--)
{
if(fData[i] == undefined) continue;
if(showItem(listOwner._name,fData[i]))
{
temp.push(fData[i]);
fData[i] = undefined;
}
}
listOwner.dataProvider.addItemsAt(0,temp);
}
else
{
//search for others and add to fdata.
for(var i=listOwner.length;i>=0;i--)
{
if(listOwner.getItemAt(i)== undefined) continue;
if(!showItem(listOwner._name,listOwner.getItemAt(i )))
{
fData.push(listOwner.removeItemAt(i));
}
}
var temp:Array = new Array();
for(var i =fData.length-1;i>-1;i--)
{
if( fData[i] != undefined
&& showItem(listOwner._name,fData[i]))
{
temp.push(fData[i]);
fData[i] = undefined;
}
}
listOwner.dataProvider.addItemsAt(0,temp);
}
// put listener back
addModelChangedListeners(listOwner._name);
}
function get selectedLabel() : String
{
if(selectedIndex == undefined || selectedIndex <1) return headerText;
return headerText +" = "+ super.selectedLabel;
}
]]>
</mx:Script>
</mx:ComboBox>
Very damn cool.
Tracy
Thanks Tracy for all the kudos you've given me today, I think I've used up my
cool implementations for the year though.
I have an expanding datagrid component, but It's a little more difficult to
use, you have to have your dataprovider items implement an interface, it's
fairly easy to implement 3 set and 3 get methods. I'm thinking about posting
it as well, but it's a more volatile renderer, it especially doesn't like
sorting of any type and you have to recode if you want to use the filter as
well.
Nice thing about it is I use the values of the Tree skins to skin my
expansions, so themes and colors that effect the tree effect it, but I'm still
a little skiddish about posting it, i think it needs a lot of testing I'll
probably wait until I have it in production.
update.
1) uses the filterModel eventName on the modelChanged event when the filter is
changed, thus giving the end user the opportunity to update a sorting function,
also includes an attribute filteredData that holds the array of filtered out
data.
2) removes all filtered data when an updateAll eventName on the modelChanged
event occurs
3) add static function getFiteredData(name) that returns filteredData array.
4) the only time that the drop down has all contents removed and re-populated
is when an updateAll eventName on the modelChanged event occurs. When an
addItems eventName on the modelChanged event occurs then the recently added
items are combed through and their data added to the drop down list.
5) Added filterType to filterModel eventName on the modelChanged event that
will be
a)startedUpdatingDropDown -- indicates that we are no longer registered
interest in updates to the data provider modehChanged event
b)finishedUpdatingDropDown-- indicates that we have registered
interest in updates to the data provider modehChanged event
c)update -- indicates that a filter has occurred and what has been
filtered from the dataprovider.
Use 5a and 5b if you regularly update your datagrid, this will allow you to
realize that between 5a and 5b that no updates to the drop down will occur if
you make changes to the dataprovider.
<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox xmlns:mx="http://www.macromedia.com/2003/mxml" verticalGap="0"
horizontalGap="0" marginLeft="0" marginRight="0"
cornerRadius="0" change="handleChange(event)"
mouseOver="if(!_showingDropdown)doLater(this,'doSh ow'); mouseOut=false;"
mouseOut="mouseOut=true; if(!_showingDropdown)doLater(this,'doHide');"
initialize="handleInitialize()" >
<mx:Script>
<![CDATA[
private static var filteredInfo:Object;
static function removeModelChangedListeners(name)
{
var temp = filteredInfo[name]["columns"];
if(temp == undefined || temp.length <1) return;
for(var i =0;i<temp.length;i++)
{
temp[i].removeModelChangedListener();
}
}
static function addModelChangedListeners(name)
{
var temp = filteredInfo[name]["columns"];
if(temp == undefined || temp.length <1) return;
for(var i =0;i<temp.length;i++)
{
temp[i].addModelChangedListener();
}
}
static function getFiteredData(name):Array
{
return filteredInfo[name]["fdata"];
}
static function showItem(name,item):Boolean
{
var temp = filteredInfo[name]["columns"];
if(temp == undefined || temp.length <1) return true;
var ret:Boolean = true;
for(var i =0;ret && i<temp.length;i++)
{
if(temp[i] == undefined ) continue;
ret = ret && temp[i].isShown(item);
}
return ret;
}
function handleInitialize()
{
doHide();
if(filteredInfo == undefined)
filteredInfo = {};
if(filteredInfo[listOwner._name] == undefined)
{
filteredInfo[listOwner._name] = {};
filteredInfo[listOwner._name]["fdata"] = new Array();
filteredInfo[listOwner._name]["columns"] = new Array();
}
fData = filteredInfo[listOwner._name]["fdata"];
filteredInfo[listOwner._name]["columns"].push(this);
dataProvider = new Array();
listener = new Object();
listener.modelChanged = mx.utils.Delegate.create(this,handleModelChanged);
addModelChangedListener();
if(listOwner.length>0)
{
doLater(this,"handleModelChanged",[{target:listOwner,eventName:"updateAll"}]);
}
this.setStyle("themeColor",_root.getStyle("themeCo lor"));
}
private var listOwner : MovieClip;//reference set by Flex upon instantiation
(owner component)
private var hO : Object; //reference set by Flex upon instantiation (owner
component)
private var column : Object; //set by Flex upon instantiation by owner
component
private var headerText:String;
private var fData:Array; // filtered data.
private var listener:Object;
var mouseOut:Boolean = true;
function doShow()
{
this.downArrow_mc.alpha=65;
this.border_mc.alpha = 65;
this.text_mc.border_mc.alpha = 65;
}
function doHide()
{
this.downArrow_mc.alpha=0;
this.border_mc.alpha = 0;
this.text_mc.border_mc.alpha = 0;
}
function setValue(val:String)
{
headerText = val;
}
function close():Void
{
if(mouseOut)
doLater(this,"doHide");
super.close();
}
function handleModelChanged(event:Object):Void
{
var sel = selectedItem;
if(dataProvider.getItemAt(0) == "All" && (event.eventName == "addItems"
||event.eventName == "updateAll") )
{
dataProvider.removeItemAt(0);
}
if(event.eventName == "addItems")
{
for(var i = event.firstItem ;i<=event.lastItem;i++)
{
dataProvider.addItem(""+event.target.getItemAt(i)[column.columnName]);
}
dataProvider.sort(1);
}
else if(event.eventName == "updateAll")
{
removeAll();
fData.length=0;
var arr = new Array();
var input = listOwner.dataProvider;
for(var i =0;i<input.length;i++)
{
arr.push(""+input[i][column.columnName]);
}
arr.sort(1);
dataProvider = arr;
}
else
{
return;
}
var found = null;
for(var i =length-1;i>-1;i--)
{
if(getItemAt(i-1) == getItemAt(i))
{
removeItemAt(i);
if(found != null)
found--;
}else if(found == null && sel == getItemAt(i) )
{
found =i;
}
}
addItemAt(0,"All");
if(found != null)
{
selectedIndex =found+1;
}
else
{
selectedIndex = 0;
}
}
function isShown(item):Boolean
{
if(this.selectedIndex <1 || item == undefined ) return true;
return ((""+item[this.column.columnName] )== this.selectedItem);
}
function setSelectedIndex(v:Number) : Void
{
super.setSelectedIndex(v);
updateLabel();
}
function updateLabel()
{
var tf = new TextFormat();
tf.italic=true;
tf.bold = false;
tf.color = 0x666666;
text_mc.label.setTextFormat(headerText.length,text _mc.label.length,tf);
}
function draw() : Void
{
super.draw();
updateLabel();
}
function removeModelChangedListener()
{
listOwner.dataProvider.removeEventListener("modelC hanged",listener);
}
function addModelChangedListener()
{
listOwner.dataProvider.addEventListener("modelChan ged",listener);
}
function handleChange()
{
// first ignore updates
listOwner.dataProvider.dispatchEvent({type:"modelC hanged",
eventName:"filterModel",filterType:'startedUpdatin gDropDown'});
removeModelChangedListeners(listOwner._name);
if(selectedIndex ==0)
{
var temp:Array = new Array();
//put all back.
for(var i =fData.length-1;i>-1;i--)
{
if(fData[i] == undefined) continue;
if(showItem(listOwner._name,fData[i]))
{
temp.push(fData[i]);
fData[i] = undefined;
}
}
listOwner.dataProvider.addItemsAt(0,temp);
}
else
{
//search for others and add to fdata.
for(var i=listOwner.length;i>=0;i--)
{
if(listOwner.getItemAt(i)== undefined) continue;
if(!showItem(listOwner._name,listOwner.getItemAt(i )))
{
fData.push(listOwner.removeItemAt(i));
}
}
var temp:Array = new Array();
for(var i =fData.length-1;i>-1;i--)
{
if( fData[i] != undefined
&& showItem(listOwner._name,fData[i]))
{
temp.push(fData[i]);
fData[i] = undefined;
}
}
listOwner.dataProvider.addItemsAt(0,temp);
}
// notify people about the change
listOwner.dataProvider.dispatchEvent({type:"modelC hanged",
eventName:"filterModel",filterType:"update", filteredData:fData});
// put listener back
addModelChangedListeners(listOwner._name);
listOwner.dataProvider.dispatchEvent({type:"modelC hanged",
eventName:"filterModel",filterType:'finishedUpdati ngDropDown'});
listOwner.dataProvider.dispatchEvent({type:"modelC hanged",
eventName:"filterModel",filteredData:fData});
}
function get selectedLabel() : String
{
if(selectedIndex == undefined || selectedIndex <1) return headerText;
return headerText +" = "+ super.selectedLabel;
}
]]>
</mx:Script>
</mx:ComboBox>
Bookmarks