How to use third party components in HTMLTreeGrid

clock July 5, 2016 07:06 by author htmltreegrid

This is a question that has come up before, and its a hard one to answer. As you are aware, we provide API hooks to give you complete control over the contents of each cell. There are some easy way to do this - for example, you can simply return some custom HTML in a labelFunction, and even make it interactive, like we show in this blog post: http://blog.htmltreegrid.com/post/How-to-add-custom-interactive-html-from-a-labelFunction.aspx 

However, the question becomes, how about when you have to use custom third party components. While we cannot guarantee the product will work with every single third party component, nor can we offer support for components that we have not authored, we certainly can show you how this can be done with a sample component. For this example, we are going to use the powerful and very popular select2 jquery auto complete component. 

Let's dive into the code:


/**
* Flexicious
* Copyright 2011, Flexicious LLC
*/
(function(window)
{
"use strict";
var AutoCompleteRenderer, uiUtil = flexiciousNmsp.UIUtils, flxConstants = flexiciousNmsp.Constants;
/**
* A CheckBoxRenderer is a custom item renderer, that defines how to use custom cells with logic that you can control
* @constructor
* @namespace flexiciousNmsp
* @extends UIComponent
*/
AutoCompleteRenderer=function(){
//make sure to call constructor
flexiciousNmsp.UIComponent.apply(this,["div"]);//second parameter is the tag name for the dom element.



var html='<select class="js-example-basic-single"> <option value="AL">Alabama</option><option value="AL">Alaska</option><option value="CO">Colorado</option></select>';
this.domElement.innerHTML=html;
this.select = this.domElement.firstChild;
var $eventSelect =$(this.select);
$eventSelect.select2();
$eventSelect.on("change", this.onChange.bind(this));

/**
* This is a getter/setter for the data property. When the cell is created, it belongs to a row
* The data property points to the item in the grids dataprovider that is being rendered by this cell.
* @type {*}
*/
this.data=null;

};
flexiciousNmsp.ItemRenderers_AutoCompleteRenderer = AutoCompleteRenderer; //add to name space
AutoCompleteRenderer.prototype = new flexiciousNmsp.UIComponent(); //setup hierarchy
AutoCompleteRenderer.prototype.typeName = AutoCompleteRenderer.typeName = 'AutoCompleteRenderer';//for quick inspection
AutoCompleteRenderer.prototype.getClassNames=function(){
return ["AutoCompleteRenderer","UIComponent"]; //this is a mechanism to replicate the "is" and "as" keywords of most other OO programming languages
};

/**
* This is important, because the grid looks for a "setData" method on the renderer.
* In here, we intercept the call to setData, and inject our logic to populate the text input.
* @param val
*/
AutoCompleteRenderer.prototype.setData=function(val){
flexiciousNmsp.UIComponent.prototype.setData.apply(this,[val]);
var cell = this.parent; //this is an instance of FlexDataGridDataCell (For data rows)
var column = cell.getColumn();//this is an instance of FlexDataGridColumn.
if(this.data[column.getDataField()])
this.select.value=this.data[column.getDataField()];
};
/**
* This event is dispatched when the user clicks on the icon. The event is actually a flexicious event, and has a trigger event
* property that points back to the original domEvent.
* @param evt
*/
AutoCompleteRenderer.prototype.onChange=function(evt){

//in the renderer, you have the handle to the cell that the renderer belongs to, via the this.parent property that you inherit from flexiciousNmsp.UIComponent.

var cell = this.parent; //this is an instance of FlexDataGridDataCell (For data rows)
var column = cell.getColumn();//this is an instance of FlexDataGridColumn.
console.log('setting value to' + this.select.value)
this.data[column.getDataField()]=this.select.value;//we use the dom element to wire back the value to the data object.
};
//This sets the inner html, and grid will try to set it. Since we are an input field, IE 8 will complain. So we ignore it since we dont need it anyway.
AutoCompleteRenderer.prototype.setText=function(val){

};
}(window));

We are defining an itemRenderer here, that simply wraps a select2 component. In its constructor, we initalize it.
In the set data method, we populate it. And we listen for its change event, where we write the data back to the 
data provider.
Below is the complete running example:

third-party-item-renderer.html (7.22 kb)

 

And this is what this will look like:

 



Selected Index Question

clock June 9, 2016 20:49 by author htmltreegrid

Recently got a question about the selected index property.


the question read something like this


The function

grid.getSelectedIndex()

returns the actual index of the selected row, which is correct when no filter is set.

But if I set a filter (which, of course, reduces the amount of actually displayed data) this function returns still the same number, which is not correct.

Is there a function that returns the index of the selected row while considering any active filters?

For example: I have 10 rows in my grid. When I click on the 5th row then getSelectedIndex() returns 4 (because it’s a zero-based index).

But if I set a filter causing that just this one row is visible and the others rows are not visible anymore (grid.getTotalRecords returns 1), then by clicking on this row the function getSelectedIndex() still returns 4, but should now return 0.


===

The short answer to this question is that  it is possible to do it if you really want the selectedIndex, do grid.getFilteredPagedSortedData() and then do an indexOf grid.getSelectedItem() on that array.


 var filterCnt = grid.getFilteredPagedSortedData({},true,false,true);

  And then

 filterCnt.indexOf(grid.getSelectedItem())

 

The longer answer is a little more involved.  and the primary idea is if  in reality what you need is the item that is selected just use the getSelectedItem method.  we made a conscious decision to deviate from the way some other products store  the selected state of the grid.  depending on what you set as the value for your selectedKeyField,  we will either store the actual object that is selected or a key property on the object that is selected.  internally we never store the index of the object that selected.  this is always calculated.  this allows us to maintain selection regardless of the state of the data provider if it's filters sorted, or paged in any manner.

 

The selectedIndex is not a reliable method of identifying the selected object, because that index changes everytime you do a sort or a filter or page. In other words. If the selected index is on page 0, and you move to page 2, the selectedIndex means nothing because the object is no longer on the page. This gets further complicated when server paging is invovled. So our recommendation is always use grid.getSelectedItem instead of grid.getSelectedIndex, because majority of the time, you are using the index to get to the actual item anyway.



Calling methods on Angular Scope

clock May 31, 2016 02:03 by author htmltreegrid

Recently a few customers have asked the same question:

We are using HTMLTreegrid with angularJS. We are expecting code snippet with angularJS.

In angularJS controller we are unable to call scope function which mentioned in previous mail.

 

At Angular contoller

 

$scope.saveStatus= function(rowData, column){

                var data='<div ><a href="javascript:void(0);" class="launchEditor" ng-click= "updateStatus('+rowData+')"><i class="fa fa-check"></i></a></div>';

         return data;

       

 }

 

 

 

Answer is to get scope from the item being clicked.  

For example:

myCompanyNameSpace.ItemRenderers_getNextHTML=function(item,column){

                       

            return "<img src='resources/app/assets/next.png' class='custom-hover' onclick='itemClickEvent(this)' title='Go to Item Info'>";

 

};

 

itemClickEvent =function(item){

        var scope = angular.element(document.getElementById("MainWrap")).scope();

                

        scope.$apply(function () {

              var itemObj = item.parentNode.component.data;

              console.log("itmeObj "+ itmeObj);

              scope.gotoItemInfo(itmeObj);

            });

};

 

 

 



A couple of customer questions

clock May 12, 2016 03:54 by author htmltreegrid

Recently we had a couple of questions that we have had before, so publishing them for your reference:

 

We are using the new HTMLTreeGrid with AngularJS and are able to set the grid style as such as:

$scope.gridOptions = {
    styles: flexGrid.UIUtils.getThemeById('blue').styles,
    delegate: $scope };

Our user's need the ability to change their theme and want the grid to reflect their new selected them.  We hope to do this without rebuilding the grid by simply changing the bound style such as:

   $scope.gridOptions.styles = flexGrid.UIUtils.getThemebyId("new theme here").styles;

But this does not work.  The grid stays the same.  Can you tell me how to apply a new style after the grid is already rendered?  An example would be great.

Answer:

 

Unfortunately, there is no quick way to re paint the grid with new theme. 

 

If you look at our demo: http://www.htmltreegrid.com/demo/prod_jq_treegrid.html?example=Simple 

 

And click on the themes tab, you can switch themes. Below is the function we call once you switch themes. But this function actually destroys and re-creates the grid. This is the only certain way to definitively apply all style values, destroy internal cached information etc. 

 

 

myCompanyNameSpace.loadTheme=function(index){

    var theme=flexiciousNmsp.themes[index];

    if(!flexiciousNmsp.StyleDefaults._defaults){

        flexiciousNmsp.StyleDefaults._defaults={};

        flexiciousNmsp.UIUtils.mergeObjects(flexiciousNmsp.StyleDefaults._defaults,flexiciousNmsp.StyleDefaults.defaults);

    }

    var newStyles = {};

    flexiciousNmsp.UIUtils.mergeObjects(newStyles,flexiciousNmsp.StyleDefaults._defaults);

    flexiciousNmsp.UIUtils.mergeObjects(newStyles,theme.styles);

    flexiciousNmsp.UIUtils.mergeObjects(flexiciousNmsp.StyleDefaults.defaults,newStyles);

    myCompanyNameSpace.gotoTab(0);

};

 

 

Goto tab eventually runs the following code, but you might be able call grid.rebuild():

 

if (grid && grid.domElement && grid.domElement.parentNode){

        var domParent = grid.domElement.parentNode;

        var domElement = grid.domElement;

        grid.kill();

        domElement.innerHTML="";

        domElement.style.width="99%";

        domElement.style.height="99%";

        flexiciousNmsp.UIUtils.addChild(domParent,domElement);//because kill removes from dom.

    }

    grid = new flexiciousNmsp.FlexDataGrid(document.getElementById("gridContainer"),

        {

            dataProvider:dp,

            configuration:myCompanyNameSpace.SAMPLE_CONFIGS[config],

            styles:styles

        });

 

==============================

 

We have a grid with row background color:

myCompanyNameSpace.getCellBackground = function (cell) {

            if (cell.level.isItemSelected(cell.rowInfo.data)){

                  return cell.getStyleValue("selectionColor");

            }

 

            if (cell.rowInfo.getData().SLS_MON_STAT == "R") {

return 0xFF0000;

}

 

return null;  // keep default background color for all "normal" rows

      }

 

 

What the above code should do is, every row where the attribute SLS_MON_STAT equals to “R” should have a red background color. All other rows should have standard background color.

This works as expected so far, but the problem is that all red rows stay now in red forever. The “normal” rows (those that are not red) get a lightblue background as a temporary highlighting when the mouse hovers over them, and when a row is selected by clicking on it, it gets a darker blue background highlighting. But this highlighting does not work on the red rows.

 

How can I achieve that the highlighting works on all rows, even on those rows that are not in “default” background color?

 

 

 Answer:

In your getBackgroundFunction

 

Just add this condition:

 

 

if( grid.currentCell.rowInfo == cell.rowInfo){
         //this means cell is the same row that the mouse is over.
          return cell.getRolloverColor();

 

}


How to add custom interactive html from a labelFunction

clock March 16, 2016 18:47 by author htmltreegrid

Recently, we had a customer asking for a example of how to invoke custom logic from the code generated by a labelFunction.

for those of you who do not know what labelFunction is, it is a excellent mechanism to generate custom HTML for each cell. 

For more details, please refer:http://htmltreegrid.com/newdocs/html/Flexicious%20HTMLTreeGrid.html?ConfigurationOptionsInteractiveH.html 

 

In this example, we will see how you can get the labelFunction to return HTML with a call back function that gets you information about 

which cell the user clicked on. Note that the key is to pass in 'this' from the label functions output html, and navigate to the container cell.

The container cell can then give you a hook into the row, the data, and the cell.

 

custom-logic-labelfunction.html (3.90 kb)



How to add a custom tooltip to HTML TreeGrid DataGrid

clock February 28, 2016 06:25 by author htmltreegrid

Recently, we had a customer asking for some help implementing a tooltip. Out of the box, we provide an API that you can listen to. Using this API, you can plug in any custom tooltip implementation. 

In this example, we are going to use the jQuery tooltip component. Once implemented, this will look like the below screenshot:

 

And below is the sample code:

 

grid-cell-tooltip.html (20.50 kb)



How to highlight just a particular column in bold on selection

clock February 22, 2016 03:52 by author htmltreegrid

Recently had a question for a customer:

Would you have an example in your demo samples on your site to show how to set a specific row cell data to bold when the row is selected. I have a column called Product Id.

On row selection, the row has been highlighted but the user says they still want the product id set to bold so they can see that it is selected. I know the row is highlighted but it seems

 

 

It is not enough for them.  They want the product id in that selected row set to bold. 

 

What we currently do, is we attach the "Selected" style to each cell that is selected in the grid. This highlights the row in bold. But in this case we just want to highlight a single column in bold. 

So what we need to do, is to remove that style that highlights everything in bold.

We do this by

/* grid selected column styles reset. */
   
  .flexiciousGrid .selectedCell label{
  font-weight: normal !important;
  }

And then we assocaite a style with that column:

.flexiciousGrid .selectedCell .nameColumn{
  font-weight: bold !important;
  }
And finally, the column needs the nameColumn stytle
var customBoldStyleFunction = function(data, col){
  return "<span class='nameColumn'>"+data[col.getDataField()]+"</span>";
  };
   

Full example below:

 



grid-selected-column-bold-text.html (18.87 kb)



How to refresh only a node within the HTML TreeGrid DataGrid

clock February 18, 2016 18:37 by author htmltreegrid

 

 

Question : Currently, I am using Treegrid in full lazy load, when I clicked a parent node it displays all the child nodes. Now my question is there any way with which I can refresh the opened level ? I want to reload the child again from server.. but for only one parent.

Answer:

Basically, the approach is very similar to what you would do when you loaded the children the first time. 

Lets look at some sample code:

First, lets add the button:

+  <div class="toolbar">  <button onclick="onRefreshClick()">Refresh Children of First Parent</button>

+</div>

On Refresh, we will just reload the first child:

+         function refreshChildren(parent, level){   if(!parent || !level)

+                return;

+            $.ajax({

+                url : GridConFig.ApiCallBaseUrl,

+                data : {name : "child_data", parent_id : parent.id},

+                type : "GET",

+                success : function(res){

+                    var response = JSON.parse(res);

+                    if(response.success){

+                        level.grid.setChildData(parent, response.data, level);

+                    } else {

+                        alert(response.message);

+                    }

+                }

+            });

+        }

+

+        function onRefreshClick(){

+            var grid = document.getElementById("grid-container").component;

+            if(!grid || !grid.getDataProvider())

+                return;

+            refreshChildren(grid.getDataProvider()[0], grid.getColumnLevel())

         }

 

 

 

 

 



Focus in on TextInput on edit

clock February 4, 2016 07:44 by author htmltreegrid

Recently a customer asked a question that helps demonsrate a couple of concepts

Basically what they wanted to do was to highlight the entire cell when starting an edit session. The default behavior of this functionality is that when you start an edit session, it will focus at the end of the text box. Assumption is that you want to continue to add to the textbox, But in this case, since the client wanted customizaiton of this functionality, there are multiple ways to do it. Although ideally you would do something like this in itemEditBegin event handler, we wanted to demonstrate another nifty way of customizing the stock behavior. Lets look at the code:

flexiciousNmsp.FlexDataGridContainerBase.prototype.stockBeginEdit = flexiciousNmsp.FlexDataGridContainerBase.prototype.beginEdit;
flexiciousNmsp.FlexDataGridContainerBase.prototype.beginEdit = function(cell){
    this.stockBeginEdit(cell);
    if(this.getEditor().implementsOrExtends("TextInput")){
        var txt=this.getEditor().getText();
        if(txt.length>0)
            this.getEditor().setSelection(0,txt.length);
    }

};

So what we did here is interesting:
WE took the stock beginEdit function, and essentiall renamed it stockBeginEdit. 
Then we made a new beginEdit function, where we dutifully call the stockBeginEdit
And then finally, we added our logic to beginEdit to highlight the entire text of the cell.

What we did here was nothing extra ordinary - we just leveraged the 
tremendous power of JavaScript - the ability redefine functional behavior at runtime. 


React DataGrid - initializing HTMLTreeGrid in componentDidMount of a React Component

clock February 1, 2016 04:31 by author htmltreegrid

So of late, a lot of you have been asking for integration with the excellent React Framework. As most of you are aware, we already integrate with AngularJS as we talked about in this blog post : http://blog.htmltreegrid.com/post/Angular-JS-Support.aspx

Well, React is now gaining momentum, and some would argue is on track to bypass angular in terms of popularity. We spent some time trying to understand and look at how best to integrate with react, and there are basicaly 2 options:

1) Just drop a div in a react component, and in componentDidMount, call the constructor of the FlexDataGrid. Here is the crux of this code:

var FlexDataGridBasic = React.createClass({
componentWillMount: function() {
this.grid = new flexiciousNmsp.FlexDataGrid();
},
componentDidMount: function() {
this.grid.setDomElement(this.refs.gridContainer);
},
componentDidUnMount: function() {
this.grid.kill();
},
render: function() {
return (
<div ref="gridContainer" className="flexiciousGrid" style={this.props.divStyle}></div>
);
}
});
And then in your main app
var MyScreen = React.createClass({
onCreationComplete : function(evt){
var grid = evt.currentTarget;
console.log('This message is sent from a callback')
},
onItemClick : function(evt){
var grid = evt.currentTarget;
alert('You clicked' + grid.getSelectedIndex());
},
render: function() {
var divStyle={width:'100%',height:'100%'};

return (

<FlexDataGridBasic ref="secondGrid" divStyle={divStyle}>
</FlexDataGridBasic>

);
},
componentDidMount : function() {

this.refs.secondGrid.grid.delegate=this;
this.refs.secondGrid.grid.buildFromXml('<grid forcePagerRow="true" enableFilters="true"  creationComplete="onCreationComplete" itemClick="onItemClick" enableEagerDraw="true" ><columns>' + '<column headerText="Col1" dataField="col1"/><column headerText="Col2" dataField="col2"/></columns></grid>');
    }
});
ReactDOM.render(
<MyScreen/>,
document.getElementById('content')
);

The advantage of this approach is that you can reuse all your XML configuration as is 
- and the delegate is your screen for all the callbacks. 

Now, the other approach is more React centric. Here, we define the markup of the grid as "React Markup"
<FlexDataGrid ref="firstGrid" forcePagerRow="true" enableFilters="true" creationComplete={this.onCreationComplete} 
     itemClick={this.onItemClick} enableEagerDraw="true" divStyle={divStyle}>
    <FlexDataGridColumnLevel>
<FlexDataGridColumn headerText="Col1" dataField="col1"/>
<FlexDataGridColumn headerText="Col2" dataField="col2"/>
</FlexDataGridColumnLevel>
</FlexDataGrid>
What this does is that it makes the entire markup bindable. It has the added advantage of being more "Reacty". 
Which one you choose is upto you - As always, this is a fairly newly added feature, and 
we are looking for feedback on it, so please help us and submit your feedback!
One more thing - you will need the latest build to make this work, so please request one!
Below is the file that demonsrates both these approaches:

react.html (13.85 kb)