AngularJS : ui-grid: dynamic expandableRowHeight - angularjs

ui-grid's expandable row feature works great if every parent row has the same number of subrows in its subGrid, and therefore the same height. Not so great if the number of subrows in the subGrid varies from row-to-row of the parent.
I've been able to get the subGrid ITSELF to have a dynamic height by passing in the array length and using that to generate a subGrid-height:
lengthOfList: data[i].friends.length
style="height:{{row.entity.subGridOptions.lengthOfList * 30}}px;
But since that subGrid has to live WITHIN the parent grid, the parent grid needs to push its rows down to make room. And it needs to do so by a different amonut for each row. Unfortunately the parent grid has a single, fixed expandableRowHeight for ALL rows. I can't specify the expandableRowHeight for EACH row.
Best I can hope for is to alter the expandableRowHeight on-the-fly, as I click on a row to open it.
Trying to figure out how to listen for the open expandable row event.
ui-grid is a shrink-wrapped plugin that writes it own DOM elements, so I have no way of adding anything to the elements that might help me capture events on them.
How can I configure the expandableRowHeight on-the-fly?
Plunker:
http://plnkr.co/edit/1IeEsXZAf9pRgKmy5jfO?p=preview
(The plunker is misbehaving. 1] A font that ui-grid is using is now blocked due to cross-domain issues, 2] sometimes the html template goes AWOL for no discernible reason.)

Ah. I found the gridApi. So this solution works!
$scope.gridOptions = {
onRegisterApi: function(gridApi) {
gridApi.expandable.on.rowExpandedStateChanged($scope,function(row){
$scope.gridOptions.expandableRowHeight = row.entity.mySubRowItems.length * 30;
});
}
}
I'm sure there's a way of referring to the row's parent grid directly. Instead of $scope.gridOptions, there's surely some row $parent reference, I just haven't found it yet.

As an update for 3.0.0 stable there is a rowExpandedBeforeStateChanged event. If you use rowExpandedStateChanged then your grid won't update w/ the new expandableRowHeight you're trying to set.
$scope.gridOptions.onRegisterApi = function(gridApi) {
// dynamic expandable rows
gridApi.expandable.on.rowExpandedBeforeStateChanged($scope, function(row) {
$scope.gridOptions.expandableRowHeight = 30 + row.entity.valueArray.length * 30;
});
};

Finally got this sorted, the important thing to know is that the expandable row has a row height, and a sub (expandable) grid size height inside that row, so if you need to display the grid to fit exactly inside the row, you need to set both heights to the same dynamic size.
Inside your top level grid api you need to specify :
$scope.gridOptions = {
onRegisterApi: function(gridApi) {
gridApi.expandable.on.rowExpandedStateChanged($scope, function (row) {
row.expandedRowHeight = (row.entity.subGridOptions.data.length * rowHeight) + headerHeight;
});
});
}
}
but now u use the expandedRowHeight variable from ui-grid version 3.0.0+, not available in 2+, the expandableRowHeight does not seem to have any affect, but might be used to set the global value for expandedRowHeight. (Note the different naming of these 2 variables on the grid)
Now this will size your top Grid row to display your sub grid correctly.
In the expandableRowTemplate.html for the sub grid u need to specify the height dynamically of the sub grid using the css style:
<div ui-grid="row.entity.subGridOptions" style="height:{{(row.entity.subGridOptions.data.length * rowHeight) + headerHeight}}px" ui-grid-auto-resize></div>
this works very well for me currently, and might help someone out there.
Note that it could also help to investigate the ui-grid-autoresize directive now in ui-grid 3+, apparently this helps in accomplishing this, not 100% sure if I used it correctly, but it works

This is how it works, I checked other people's solution, they all not working
Let's assume that the headerRowHeight is 39px.
The template should be something like this :
'<div ui-grid="row.entity.subGridOptions" ng-style="{height: (row.entity.youList.length * row.entity.subGridOptions.rowHeight) + 39 + \'px\'}" ui-grid-selection ui-grid-edit ui-grid-cellnav ui-grid-validate ui-grid-auto-resize ui-grid-resize-columns></div>'
The rowExpandedStateChanged should be this:
gridApi.expandable.on.rowExpandedStateChanged($scope, function (row) {
$timeout(function() {
row.expandedRowHeight = row.entity.youList.length * row.entity.subGridOptions.rowHeight + 39;
}, 150);
});

I tried a different approach to the solution using CSS alone instead of leveraging the "expandableRowHeight" property. And this worked for me.
The parent grid data is rendered inside a div with class name "ui-grid-canvas". Add the following style -
.ui-grid-canvas{
height: auto !important;
overflow: auto !important;
}
For expanded child table, add a style to div with class name "expandableRow"
.expandableRow{
height: auto !important;
}
Also, do not set the "expandableRowHeight" property in gridOptions.

Related

Angular 1 ng-class doesn't work as expected

I have a simple ng-class that switches two classes based on the condition. When the class is switched, the order of the classes is messed up not sure why. Has anyone a solution for this?
<div class="ui" ng-class="{'two column grid' : submitNow, 'one column grid' : defaultState}"></div>
Rendered HTML when submitNow is true. This works as expected
<!-- submitNow is true -->
<div class="ui ng-scope two column grid"></div>
Rendered HTML when defaultState is true. This messes up the order of classes added by ng-class
<!-- defaultState is true -->
<div class="ui ng-scope column grid one"></div>
*** Edit ****
Quite strange because it works on jsfiddle. But here's the screenshot of my rendered html code
Here is a demo
https://codepen.io/vibwaj/pen/KKPBdNp
OK...looking at the style rules in elements inspector, semantic ui uses selectors like .ui[class*="two column"].grid > .row > .column
Not sure why they do it that way which is unusual and does make the order important.
Also not sure if it is angular or the browser that sorts the order of those classes. I suspect it is the browser, but that is a guess.
Rather than try to figure out what causes the sort you can add the following rule to fix layout for non specific class order.
.ui.two.column.grid > .row > .column,
.ui.two.column.grid > .column:not(.row){
width:50%!important;
}
Working codepen
Update:
I didn't notice the semantic UI framework that is using this approach.
If you still need the same approach, you can check the forked Codepen which I created a custom directive to be alternative than the original NgClass directive.
app.directive("myNgClass", function() {
return {
link: function(scope, element, attrs) {
scope.$watch(
function() {
return attrs.isExpand;
},
function(isExpand) {
element.removeAttr("class");
if (JSON.parse(isExpand)) {
element.addClass("two column grid");
} else {
element.addClass("one column grid");
}
}
);
}
};
});
Original Answer
So diving deep into the NgClass directive implementation in Angularjs source code and checking how they update the classes, there is a function called updateClasses.
In this function, it finds which classes should be removed and added.
Instead of replacing all the classes when the Boolean flag gets inverted, NgClass keeps the overlapping classes and checks which classes should be added / removed.
So in your case,one column grid (the default case) and two column grid have the column grid classes in common, so it will keep them, but remove the one from the start and add two at the end. So the result will be column grid one.
I really don't suggest to use the order of the classes as CSS selectors. This will make it more harder to select elements and make things more complex.
I also have a comment regarding the CSS selectors that you are using. I really suggest you to read Keep your CSS selectors short article so you can have a better practice of using shorter selector and why keeping the CSS selectors short can help with many things.
For example, If you don't need the one, column and grid classes seprately, you can just use .one-column-grid as a class name / CSS Selector instead of .one.column.grid.

Bootstrap dropdown in column header

I've been trying to use the ng-grid 3.0 (ui-grid). I have managed to populate my grid and it's been very responsive and it's features are really amazing. But I'm trying to customize my column headers, as I need more info there.
I can create a custom header cell template, as indicated in the docs, but I don't seem able to use a Bootstrap Dropdown there, it gets cropped and I can't use it at all. Some googling got me thinking it is probably some issue with the overflow attributes, but still I can't solve it. My grid options is as follows:
$scope.columnDefs = [
{ name:'name', displayName: 'Vdd', headerCellTemplate: 'headerTemplate.html' },
{ name:'gender', headerCellTemplate: 'headerTemplate.html' },
{ name:'company' }
]
$scope.gridOptions = {
columnDefs: $scope.columnDefs,
rowTemplate: 'rowTemplate.html',
data: 'data'
};
I have forked an example in plunkr and managed to reproduce my issue:
http://plnkr.co/edit/qdrFiifiz18fxB8w6Aja?p=preview
I want to replace the built-in dropdown menu (since it doesn't seem to allow nesting and sub-menus) and add another one (so in the end, I'd have two dropdown menus in each header cell)
Any help is appreciated =)
I am proud to say I think I've figured it out. I've been digging through ui-grid's source code and narrowed it down to this block (lines: 2847 - 2852).
function syncHorizontalHeader(scrollEvent){
var newScrollLeft = scrollEvent.getNewScrollLeft(colContainer, containerCtrl.viewport);
if (containerCtrl.headerViewport) {
containerCtrl.headerViewport.scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid);
}
}
I noticed that containerCtrl.headerViewport.scrollLeft was never getting set to newScrollLeft. A quick google search led me to this StackOverflow thread which says that you can't set the scrollLeft property of an element if it's overflow is set to visible.
My solution was to replace containerCtrl.headerViewport.scrollLeft = gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid); with containerCtrl.headerViewport.style.marginLeft = -gridUtil.denormalizeScrollLeft(containerCtrl.viewport,newScrollLeft, grid) + 'px'; which just sets a negative margin on the header. Then add an overflow:hidden; style to .ui-grid-render-container-body to hide headers that extend beyond the main grid viewport.
Doing this messed up the placement of column menus, but there is an easy fix. On line 514 replace var containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].scrollLeft; with var containerScrollLeft = renderContainerElm.querySelectorAll('.ui-grid-viewport')[0].style.marginLeft; to use the margin instead of the scroll value in the menu placement calculation.

How to scroll to a particular record in grid?

Previously, I used persistent grid plugin, until I found out that it caused some terrible slowdown - about 3-4 extra seconds of grid rendering (~20 columns and 300 rows). So, I do not want all this plugin functionality, the only thing I want to have is scrolling to a selected record in grid (there may be a lot of records selected, so scrolling to the first one is enough). I try to do it like this:
.... a lot of code ...
rowIndex=grid.store.indexOf(grid.getSelectionModel().getSelection()[0]);
record=grid.getSelectionModel().getSelection()[0];
grid.store.remove(record); // <- I remove it, because its content has changed
grid.store.insert(rowIndex,Ext.create('GridModel',json.items[0])); // <- I insert
// it back with new values
grid.getSelectionModel().select( rowIndex ); // <- This works, I see a checkmark
grid.getView().focusRow( record ); // <- This is not working
....
Instead of what I expect to see, I see scrolling to the top of the grid.
EDIT
This is not working:
Ext.fly(grid.getView().getNode(rowIndex)).scrollIntoView(); // instead of focus
Also not working:
var rowEl=grid.getView().getNode(rowIndex);
rowEl.scrollIntoView(grid.el, false);
So, what to use instead of focus?
EDIT
Setting deferRowRender to false in grid config also has no effect. Still, grid scroll to the very top of its view.
EDIT
Well, as it turned out, focusRow had no effect because of the grid.store.sync call. So, I put this routine inside sync callback function and now it is working.
In extjs 4 the solution might be use method scrollBy() http://docs.sencha.com/extjs/4.2.6/#!/api/Ext.Component-method-scrollBy
In Extjs 5: try the methods getScrollable().scrollTo(...);
http://docs.sencha.com/extjs/5.1.3/api/Ext.grid.Panel.html#placeholder-accessor-getScrollable
http://docs.sencha.com/extjs/5.1.3/api/Ext.grid.Panel.html#method-scrollTo
In Extjs 6: try the methods getScrollable().scrollToRecord(...);
http://docs.sencha.com/extjs/6.2.1/modern/Ext.grid.Grid.html#method-getScrollable
http://docs.sencha.com/extjs/6.2.1/modern/Ext.grid.Grid.html#method-scrollToRecord
Although on a different issue, this post may be useful: How to scroll to the end of the form in ExtJS
try below code
grid.getView().focusRow( record );
Try this:
var numIndex=** //this is record index
var x=grid.getView().getScrollX();
grid.getView().scrollTo(x,numIndex*21); //x,y --> unit is pixel

Create an overlay for Titanium Mobile TableView

I'm trying to create a DatePicker that slides in over a TableView in order to edit a date-field. The problem is that the DatePicker appears behind the TableView - you can see a part of it because the TableView is transparent.
I tried giving one a higher zIndex value than the other, both ways, but that didn't help. The datepicker is a picker control in a view, which I slide up with an animation.
How does one overlay a TableView with a different view?
Edit: the table is quite complicated because of the various types of data in it, so copy&paste would be overkill. But here's the relevant part:
var win = Ti.UI.createWindow();
var table = Ti.UI.createTableView({zIndex: 1});
// some table sections are added here
// create picker layer
var row = Ti.UI.createTableViewRow({zIndex: 2});
var picker_view = Titanium.UI.createView({
height: 251,
bottom: -251,
zIndex: 3,
visible: false
});
var picker = Ti.UI.createPicker({
type: Ti.UI.PICKER_TYPE_DATE,
selectionIndicator: true
});
picker_view.add(picker);
row.addEventListener('click', function()
{
picker_view.visible = true;
var slide_in = Titanium.UI.createAnimation({bottom:0});
picker_view.animate(slide_in);
});
row.add(picker_view);
some_section.add(row);
win.add(table);
Thanks!
Try to Add the picker in the window not in table.
dont add the view to the row, add it to the window.
dont set visibility on the view, it is irrelevant since you are sliding it up and down.
also you really are confusing things with the zIndexes, they are not really needed in this use case.

How to hide rows in ExtJS GridPanel?

Suppose I know which row index to target (with this.rowToBeDeleted having a value of 2, say), how can I hide this row only from the grid but not the store (I have a flag in the store, which signifies what rows should be deleted from the db later in my PHP webservice code).
You can either use one of the store.filter() methods or you can hide the row element.
grid.getView().getRow(rowIndex).style.display = 'none';
I think it's much better though to just remove the record from the store and let the store update the view since you are deleting the record and not just hiding it. With the store in batch mode (the default: batch: true, restful: false), it will remember which rows you've removed and won't fire a request to the server until you call store.save().
I suggest using store.FilterBy() and pass a function to test the value of the value in rowToBedeleted:
store.filterBy(function(record) {
return record.get("rowToBeDeleted") != 2;
});
I wrote a basic blogpost about gridfiltering a while ago, you can read it here: http://aboutfrontend.com/extjs/extjs-grid-filter/
In ExtJS 4.1, there is no view.getRow(..). Instead you can use:
this.view.addRowCls(index, 'hidden');
to hide the row at the specified index, and
this.view.removeRowCls(index, 'hidden');
to show it (where 'this' is the grid).
CSS class hidden is defined as
.hidden,
{
display: none;
}
This is useful for peculiar scenarious where store.filterBy() is not appropriate.
In the grid js file write following code to apply a CSS to those rows which you want to hide.
<pre><code>
Ext.define('MyGrid',{
extend : 'Ext.grid.Panel',
xtype : ''mygrid',
viewConfig : {
getRowClass : function(record,id){
if(record.get('rowToBeDeleted') == 2){
return 'hide-row';
}
}
},
.................
.................
});
</code></pre>
Now define a custom CSS in custom.css file:
.hide-row{display:none}
This will hide rows in grid without removing or filtering from store.
You can use the store.filter() or store.filterBy() methods for that.
Set a "hidden" property on your records and the filter all records that have hidden set to true for example. This way they'll still be present in the store but not visible in the grid.

Resources