开发者

Datagrid sorting only on click of specific part in headerRenderer

I have a custom FilterColumnHeaderRenderer with an HBox, inside a Label for the Column Header, a Textinput for the filter text and Label to clear the filter. Now I want that the column sorting only takes place on clicking the title.

Currently the I use the sortCompareFunction on HeaderRelease for the whole columnHeader.

How would you solve this requierement?

Full Code, SearchableDataGrid:

package components{
import flash.events.TextEvent;

import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.controls.DataGrid;
import mx.controls.dataGridClasses.DataGridColumn;
import mx.core.ClassFactory;
import mx.events.DataGridEvent;
import mx.formatters.DateFormatter;
import mx.utils.ObjectUtil;

[Event(name="itemsFiltered", type="components.SearchableDataGridFilterEvent")]
public class SearchableDataGrid extends DataGrid{
    private var _searchableDataProvider:ArrayCollection;
    private var _filterStrings:Object = new Object();
    private var _dataTypes:Object = new Object();
    private var _fieldFocus:String = "";
    private var _totalItems:int = 0;
    private var _dataFormatter:DateFormatter = null;
    public var _reloadQuery:Boolean = false;

    public function SearchableDataGrid(){
        super();
        init();
    }

    private function init():void{
        //initialise a standart DateFormatter
        var ft:DateFormatter = new DateFormatter();
        ft.formatString = "YYYY-MM-DD";
        this.dateFormatter = ft;    
    }

    private function numericSortByField(fieldName:String):Function { 
        return function(obj1:Object, obj2:Object):int  {  
            var testFlag1:Boolean = isNaN( Number( obj1[fieldName] ) ); 
            var testFlag2:Boolean = isNaN( Number( obj2[fieldName] ) ); 
            // if one value is not a number => string compare
            if (testFlag1 || testFlag2){
                var value1:String = (obj1[fieldName] == '' || obj1[fieldName] == null) ? null : new String(obj1[fieldName]);
               var value2:String = (obj2[fieldName] == '' || obj2[fieldName]== null) ? null : new String(obj2[fieldName]);
               return ObjectUtil.stringCompare( value1, value2, true );
            }else{
                var value1Number:Number = (obj1[fieldName] == '' || obj1[fieldName] == null) ? null : new Number(obj1[fieldName]);       
                var value2Number:Number = (obj2[fieldName] == '' || obj2[fieldName] == null) ? null : new Number(obj2[fieldName]);      
                return ObjectUtil.numericCompare(value1Number, value2Number);
            } 
        } 
    }

    private function setHeaderRenderer():void{
        var hr:FilterHeaderRendererFactory = new FilterHeaderRendererFactory(this);
        for(var i:int = 0; i < super.columns.length; i++){
            super.columns[i].showDataTips = true;
            super.columns[i].headerRenderer = hr;
            super.columns[i].itemRenderer = new ClassFactory(components.SearchableDGItemRenderer);;
            super.columns[i].sortCompareFunction = this.numericSortByField(super.columns[i].dataField);
        }
    }

    private function dataBindingChanged():void{
        if (_reloadQuery == false){
            _filterStrings = new Object();
        }else{
            Alert.show("You are reloading the same query, your filter strings are reapplied", "Information");
        }
        _dataTypes = new Object();
        _fieldFocus = "";
        setHeaderRenderer();
        prepareDataFilter();
    }


    public function set searchableDataProvider(val:ArrayCollection):void{
        this._searchableDataProvider = val;
        _totalItems = val.length;

        this.dataProvider = this._searchableDataProvider;
        dataBindingChanged();
    }


    private function prepareDataFilter():void{
        this._searchableDataProvider.filterFunction = dataFilter;
        this._searchableDataProvider.refresh();
        var ev:SearchableDataGridFilterEvent = new SearchableDataGridFilterEvent(this._searchableDataProvider.length, this._totalItems, this.filterStrings);
        this.dispatchEvent(ev);
    }

    private function dataFilter(item:Object):Boolean{
            var isMatch:Boolean = true;

            for (var field:String in _filterStrings){
                var sP:String = _filterStrings[field]; //searchpattern
                if(sP == null){
                    continue;
                }

                if(item[field] == null){
                    isMatch = false;
                    return isMatch;
                }

                var pattern:RegExp = new RegExp("^" + sP.toLowerCase().replace(new RegExp(/%/g), ".*"));
                if(item[field] is Date){
                    //special check for date columns
                    if(!pattern.test(this.dateFormatter.format(item[field] as Date)))
                    {
                        isMatch = false;
                        return isMatch;
                    }
                }else{
                    //its not a date column
                    if(!pattern.test((item[field].toString()).toLowerCase()))
                    {
                        isMatch = false;
                        return isMatch;
                    }
                }
            }
            return isMatch;
        }

    public function get filterStrings():Object{
        return this._filterStrings;
    }

    public function setFilterString(fieldName:String, value:String):void{
        this._filterStrings[fieldName] = value;

        if(value == ""){
            delete this._filterStrings[fieldName];
        }
        _fieldFocus = fieldName;
        prepareDataFilter();
    }

    public function getFilterString(fieldName:String):String{
        if(this._filterStrings[fieldName] == null){
            return "";
        }
        return this._filterStrings[fieldName];
    }

    public function setDataType(fieldName:String, value:String):void{
        this._dataTypes[fieldName] = value;
    }

    public function getDataType(fieldName:String):String{
        if(this._filterStrings[fieldName] == null){
            return "";
        }
        return this._filterStrings[fieldName];
    }

    public function getFieldFocus():String{
        return this._fieldFocus;
    }

    public function set dateFormatter(val:DateFormatter):void{
        this._dataFormatter = val;
    }

    public function get dateFormatter():DateFormatter{
        return this._dataFormatter;
    }
}

}

Full Code, FilterColumnHeaderRenderer:

package components{
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.TextEvent;
import flash.text.TextField;
import flash.ui.Keyboard;

import mx.containers.HBox;
import mx.containers.Tile;
import mx.containers.VBox;
import mx.controls.Button;
import mx.controls.Label;
import mx.controls.LinkButton;
import mx.controls.TextInput;
import mx.controls.dataGridClasses.DataGridColumn;
import mx.events.DataGridEvent;
import mx.events.FlexEvent;

public class FilterColumnHeaderRenderer extends VBox{
    private var title:Label;
    private var filter:TextInput;
    private var clearFilter:Label;
    private var filterHBox:HBox;
    private var tfHasFocus:Boolean = false;
    private var dataFieldName:String;
    private var dataObj:DataGridColumn;
    private var _dataGrid:SearchableDataGrid;

    public function FilterColumnHeaderRenderer(){
        super();
        this.verticalScrollPolicy = "off";
        this.horizontalScrollPolicy = "off";

        this.addEventListener(FlexEvent.CREATION_COMPLETE, creationComplete);

        title = new Label();
        filter = new TextInput();
        clearFilter  = new Label();
        filterHBox  = new HBox();

        title.percentWidth = 100;
        filterHBox.percentWidth = 100;

        clearFilter.width = 10;
        clearFilter.toolTip = "Clear the filter for this column"
        clearFilter.htmlText = "x";     

        this.addChildAt(title, 0);
        filterHBox.addChildAt(clearFilter, 0);
        filterHBox.addChildAt(filter, 1);
        this.addChildAt(filterHBox, 1);

        clearFilter.addEventListener(MouseEvent.CLICK, resetFilter);

        filter.addEventListener(FocusEvent.FOCUS_IN, hasFocus);
        filter.addEventListener(FocusEvent.FOCUS_OUT, lostFocus);
        filter.addEventListener(TextEvent.TEXT_INPUT, textChange);
        filter.addEventListener(KeyboardEvent.KEY_DOWN, textKeyDown);

        title.addEventListener(DataGridEvent.HEADER_RELEASE, onHeaderRelease);

    }

    private function onHeaderRelease( event:DataGridEvent ): void {
        var rdr:FilterColumnHeaderRenderer = event.itemRenderer as FilterColumnHeaderRenderer;
        var dataGrid:SearchableDataGrid = SearchableDataGrid(event.target);
        var dataField:String = event.dataField;
        var columnIndex:int = event.columnIndex;
    }

    private function resetFilter(event:MouseEvent):void{
        var tmpFieldName:String = event.currentTarget.parent.parent.fieldName;
        this._dataGrid.setFilterString(dataFieldName, "");
        this.filter.text = "";
    }

    private function textKeyDown(event:KeyboardEvent):void{
        if(this._dataGrid){
            if(event.keyCode == flash.ui.Keyboard.BACKSPACE){
                var actString:String = this.filter.text;
                this._dataGrid.setFilterString(dataFieldName, actString.substr(0, (actString.length > 0 ? actString.length - 1 : 0)));
            }
        }
    }

    private function creationComplete(event:FlexEvent):void{
        if(this._dataGrid != null && this._dataGrid.getFieldFocus() == dataFieldName){
            this.filter.setFocus();
        }
    }

    public function textChange(event:TextEvent):void{
        if(this._dataGrid)
        {
            this._dataGrid.setFilterString(dataFieldName, this._dataGrid.getFilterString(dataFieldName) + event.text);
        }
    }

    private function hasFocus(event:FocusEvent):void{
        tfHasFocus = true;
    }

    private function lostFocus(event:FocusEvent):void{
        tfHasFocus = false;
    }

    override public function set data(value:Object):void{
        dataObj = (value as DataGridColumn);

        dataFieldName = dataObj.dataField;

        this.title.text = dataObj.headerText;
        this.title.toolTip = dataObj.headerText;

        if(this._dataGrid)
        {
            var fs:String = this._dataGrid.filterStrings[dataFieldName];
          开发者_如何学C  if(fs != null){
                this.filter.text = fs;
            }
        }

        this.dispatchEvent(new FilterColumnHeaderRendererCreatedEvent(this, true));
    }


    public function get hasTextFocus():Boolean{
        return this.tfHasFocus;
    }

    public function get fieldName():String{
        return this.dataFieldName;
    }

    public function setText(newText:String):void{
        this.filter.text = newText;
    }

    public function set dataGrid(value:SearchableDataGrid):void{
        this._dataGrid = value;
    }

    public function get dataGrid():SearchableDataGrid{
        return this._dataGrid;
    }   
}

}

Full Code, FilterHeaderRendererFactory:

package components{
import mx.controls.DataGrid;
import mx.core.ClassFactory;
import mx.core.IFactory;

public class FilterHeaderRendererFactory implements IFactory{
    private var _base_factory:ClassFactory;
    private var _grid:SearchableDataGrid;


    public function FilterHeaderRendererFactory(grid:SearchableDataGrid){
        _base_factory = new ClassFactory(FilterColumnHeaderRenderer);
        _grid = grid;
    }

    public function newInstance():*{
        var o:FilterColumnHeaderRenderer = _base_factory.newInstance() as FilterColumnHeaderRenderer;
        o.dataGrid = _grid;
        return o;
    }
}

}


Conceptually; I'd do it this way:

Dispatch a custom event from your custom header. Make sure it bubbles. Listen for that event on the DataGrid and then sort your dataProvider directly without using the built in DataGrid functionality.


Finally I've implemented this solution:

  • within the HeaderRenderer i've changed the type of the component from label to button.
  • therefore i was able to add event listeners for focus in and focus out on this button.
  • as the focus in events happens before the header release event, i can set a boolean which checks if the header release event (sorting) should happen or not

please feel free to ask for more precise information. here the relevant source code:

Full Code, FilterColumnHeaderRenderer:

                package components
{
    import flash.events.Event;
    import flash.events.FocusEvent;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.events.TextEvent;
    import flash.text.TextField;
    import flash.ui.Keyboard;

    import mx.containers.HBox;
    import mx.containers.Tile;
    import mx.containers.VBox;
    import mx.controls.Button;
    import mx.controls.Label;
    import mx.controls.LinkButton;
    import mx.controls.TextInput;
    import mx.controls.dataGridClasses.DataGridColumn;
    import mx.events.DataGridEvent;
    import mx.events.FlexEvent;


    public class FilterColumnHeaderRenderer extends VBox{
        private var title:Label;
        private var filter:TextInput;
        private var clearFilter:Button;
        private var filterHBox:HBox;
        private var tfHasFocus:Boolean = false;
        private var dataFieldName:String;
        private var dataObj:DataGridColumn;
        private var _dataGrid:SearchableDataGrid;

        [ Embed ( source = "/icons/icon-x.gif" ) ]
        public var iconDelete:Class; 


        public function FilterColumnHeaderRenderer(){
            super();
            this.verticalScrollPolicy = "off";
            this.horizontalScrollPolicy = "off";

            this.addEventListener(FlexEvent.CREATION_COMPLETE, creationComplete);

            title = new Label();
            filter = new TextInput();
            clearFilter  = new Button();
            filterHBox  = new HBox();

            title.percentWidth = 100;
            filterHBox.percentWidth = 100;
            filterHBox.setStyle( "horizontalGap", 5 );

            clearFilter.toolTip = "Clear the filter for this column"
            clearFilter.width = 15;
            clearFilter.height = 20;
            clearFilter.label = "x";    
            clearFilter.setStyle ( "fontWeight", "normal" ) ;
            clearFilter.setStyle ( "textPadding", 0 ) ;
            clearFilter.setStyle ( "paddingLeft", 0 ) ;
            clearFilter.setStyle ( "paddingRight", 0 ) ;
            clearFilter.setStyle ( "paddingTop", 0 ) ;
            clearFilter.setStyle ( "paddingBottom", 0 ) ;
            clearFilter.addEventListener(FocusEvent.FOCUS_IN, hasFocus);
            clearFilter.addEventListener(FocusEvent.FOCUS_OUT, lostFocus);


            filter.addEventListener(FocusEvent.FOCUS_IN, hasFocus);
            filter.addEventListener(FocusEvent.FOCUS_OUT, lostFocus);
            filter.addEventListener(TextEvent.TEXT_INPUT, textChange);
            filter.addEventListener(KeyboardEvent.KEY_DOWN, textKeyDown);

            clearFilter.addEventListener(MouseEvent.CLICK, resetFilter);

            this.addChildAt(title, 0);
            filterHBox.addChildAt(clearFilter, 0);
            filterHBox.addChildAt(filter, 1);
            this.addChildAt(filterHBox, 1);
        }

        private function resetFilter(event:MouseEvent):void{
            var tmpFieldName:String = event.currentTarget.parent.parent.fieldName;
            this._dataGrid.setFilterString(dataFieldName, "");
            this.filter.text = "";
        }

        private function textKeyDown(event:KeyboardEvent):void{
            if(this._dataGrid){
                if(event.keyCode == flash.ui.Keyboard.BACKSPACE){
                    var actString:String = this.filter.text;
                    this._dataGrid.setFilterString(dataFieldName, actString.substr(0, (actString.length > 0 ? actString.length - 1 : 0)));
                }
            }
        }

        private function creationComplete(event:FlexEvent):void{
            if(this._dataGrid != null && this._dataGrid.getFieldFocus() == dataFieldName){
                this.filter.setFocus();
            }
        }

        public function textChange(event:TextEvent):void{
            if(this._dataGrid)
            {
                this._dataGrid.setFilterString(dataFieldName, this._dataGrid.getFilterString(dataFieldName) + event.text);
            }
        }

        private function hasFocus(event:FocusEvent):void{
            tfHasFocus = true;
        }

        private function lostFocus(event:FocusEvent):void{
            tfHasFocus = false;
        }

        override public function set data(value:Object):void{
            dataObj = (value as DataGridColumn);

            dataFieldName = dataObj.dataField;

            this.title.text = dataObj.headerText;
            this.title.toolTip = dataObj.headerText;

            if(this._dataGrid)
            {
                var fs:String = this._dataGrid.filterStrings[dataFieldName];
                if(fs != null){
                    this.filter.text = fs;
                }
            }
            this.dispatchEvent(new FilterColumnHeaderRendererCreatedEvent(this, true));
        }


        public function get hasTextFocus():Boolean{
            return this.tfHasFocus;
        }

        public function get fieldName():String{
            return this.dataFieldName;
        }

        public function setText(newText:String):void{
            this.filter.text = newText;
        }

        public function set dataGrid(value:SearchableDataGrid):void{
            this._dataGrid = value;
        }

        public function get dataGrid():SearchableDataGrid{
            return this._dataGrid;
        }   }}

Relevant code of the searchableDataGrid:

package components{
import flash.events.TextEvent;

import mx.collections.*;
import mx.controls.Alert;
import mx.controls.DataGrid;
import mx.controls.advancedDataGridClasses.AdvancedDataGridColumn;
import mx.controls.dataGridClasses.DataGridColumn;
import mx.core.ClassFactory;
import mx.events.DataGridEvent;
import mx.formatters.DateFormatter;
import mx.utils.ObjectUtil;

[Event(name="itemsFiltered", type="components.SearchableDataGridFilterEvent")]
public class SearchableDataGrid extends DataGrid{
    private var _searchableDataProvider:ArrayCollection;
    private var _filterStrings:Object = new Object();
    private var _dataTypes:Object = new Object();
    private var _fieldFocus:String = "";
    private var _totalItems:int = 0;
    private var _dataFormatter:DateFormatter = null;
    public var _reloadQuery:Boolean = false;

    public function SearchableDataGrid(){
        super();
        init();
    }

    private function init():void{
        //initialise a standart DateFormatter
        var ft:DateFormatter = new DateFormatter();
        ft.formatString = "YYYY-MM-DD";
        this.dateFormatter = ft;
        this.addEventListener(DataGridEvent.HEADER_RELEASE, onHeaderRelease);
    }
    private function onHeaderRelease( event:DataGridEvent ): void {
        var dataGrid:SearchableDataGrid = SearchableDataGrid(event.target);
        var dataField:String = event.dataField;
        var columnIndex:int = event.columnIndex;

        var rdr:FilterColumnHeaderRenderer = event.itemRenderer as FilterColumnHeaderRenderer;
        if(rdr.hasTextFocus){
            event.preventDefault();
        }
    }
(...)


I had a more elegant solution thanks to all the other comments here.

  1. I attached an headerRelease Event Listener to the datagrid

    <mx:Datagrid headerRelease="onHeaderRelease(event)">

  2. In the event listener I checked which InteractiveObject had focus

  3. If the focus object was NOT the datagrid, I halted the event

    protected function onHeaderRelease(e:DataGridEvent):void {
        var interactive:InteractiveObject = focusManager.getFocus() as InteractiveObject
        var hasFocus:String = flash.utils.getQualifiedClassName(interactive).toString()
    
         if (hasFocus != "mx.controls::DataGrid") { // Can also be AdvancedDataGrid
             e.stopImmediatePropagation();  // Use this as needed
             e.preventDefault(); // Mandatory
        }
    }
    

I hope this helps the next poor soul who finds himself here.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜