PDA

View Full Version : [CLOSED] Difference in Paging Toolbar behaviour between Ext.NET 1.x and 2 for Grid with remote (ashx) paging and local filtering



anup
May 27, 2012, 10:45 PM
Hi,

In Ext.NET 1.x if I had a grid that loaded data remotely and pages remotely (e.g. via a JsonReader calling an ASHX), but filters were local, then as you paged through the data, those filters would apply on the client side. I believe this is the way the GridPanel is architected; the presentation is separate from the data so if you get more pages, the filters are then applied once the store is reloaded. And that works fine.

However, in Ext.NET 2, I am finding that the Paging toolbar gets disabled when you go to the next page of data (remote loaded) while you have local filters on and those local filters return no data for that page.

If, however, paging and filtering are both local or both remote, then it seems okay.

Below are code samples for version 1.x and then version 2. Then there are steps to follow to reproduce the difference in behavior.

Here is a version 1.x sample:

ASPX:



<%@ Page Language="C#" %>
<!DOCTYPE html>
<html>
<head runat="server">
<title>Simple Grid - Ext.NET Examples</title>
</head>
<body>
<ext:ResourceManager runat="server" />


<ext:Viewport runat="server" Layout="fit">
<Items>
<ext:GridPanel runat="server" Title="Simple Grid">
<Store>
<ext:Store Id="Store1" runat="server" PageSize="10">
<Reader>
<ext:JsonReader Root="Data" TotalProperty="TotalRecords">
<Fields>
<ext:RecordField Name="Company" Mapping="Name" />
<ext:RecordField Name="Price" Type="Float" />
<ext:RecordField Name="Change" Type="Float" />
<ext:RecordField Name="PctChange" Mapping="PercentChange" Type="Float" />
<ext:RecordField Name="LastChange" Type="Date" DateFormat="yyyy-MM-ddTHH:mm:ss" />
</Fields>
</ext:JsonReader>
</Reader>
<BaseParams>
<ext:Parameter Name="start" Value="0" Mode="Raw" />
<ext:Parameter Name="limit" Value="10" Mode="Raw" />
</BaseParams>
<Proxy>
<ext:HttpProxy Url="FinancialData.ashx" />
</Proxy>
</ext:Store>
</Store>
<ColumnModel>
<Columns>
<ext:Column Header="Company" DataIndex="Company" Width="200" />
<ext:Column Header="Price" DataIndex="Price" Width="50" />
<ext:Column Header="Change" DataIndex="Change" Width="50" />
<ext:Column Header="Change" DataIndex="PctChange" Width="50" />
<ext:DateColumn Header="Last Updated" DataIndex="LastChange" Format="yyyy-MM-dd hh:mmtt" Width="130" />
</Columns>
</ColumnModel>
<Plugins>
<ext:GridFilters runat="server" ID="GridFilters1" Local="true">
<Filters>
<ext:StringFilter DataIndex="Company" />
<ext:NumericFilter DataIndex="Price" />
<ext:NumericFilter DataIndex="Change" />
<ext:NumericFilter DataIndex="PctChange" />
<ext:DateFilter DataIndex="LastChange" />
</Filters>
</ext:GridFilters>
</Plugins>
<BottomBar>
<ext:PagingToolbar runat="server" StoreID="Store1" PageSize="10" />
</BottomBar>
</ext:GridPanel>
</Items>
</ext:Viewport>
</body>
</html>


And the ASHX (in same directory)



public class FinancialData : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "application/json";


int start = Convert.ToInt32(context.Request["start"]);
int limit = Convert.ToInt32(context.Request["limit"]);


Paging<CompanyData> data = GetData(start, limit);


context.Response.Write(JSON.Serialize(data));
}


private Paging<CompanyData> GetData(int start, int limit)
{
var data = CompanyData.Data;


int numberToGet = start + limit > data.Count ? data.Count - start : limit;


return new Paging<CompanyData>(data.GetRange(start, numberToGet), data.Count);
}


public bool IsReusable
{
get { return false; }
}
}


And CompanyData:



public class CompanyData
{
public string Name { get; set; }
public double Price { get; set; }
public double Change { get; set; }
public double PercentChange { get; set; }
public DateTime LastChange { get; set; }


public CompanyData(string name, double price, double change, double percentChange, string lastChanged)
{
Name = name;
Price = price;
Change = change;
PercentChange = percentChange;
LastChange = DateTime.ParseExact(lastChanged, "M/d hh:mmtt", CultureInfo.InvariantCulture);
}


public static List<CompanyData> Data
{
get
{
return new List<CompanyData>
{
new CompanyData("3m Co", 71.72, 0.02, 0.03, "9/1 12:00am"),
new CompanyData("Alcoa Inc", 29.01, 0.42, 1.47, "9/1 12:00am"),
new CompanyData("Altria Group Inc", 83.81, 0.28, 0.34, "9/1 12:00am"),
new CompanyData("American Express Company", 52.55, 0.01, 0.02, "9/1 12:00am"),
new CompanyData("American International Group, Inc.", 64.13, 0.31, 0.49, "9/1 12:00am"),
new CompanyData("AT&T Inc.", 31.61, -0.48, -1.54, "9/1 12:00am"),
new CompanyData("Boeing Co.", 75.43, 0.53, 0.71, "9/1 12:00am"),
new CompanyData("Caterpillar Inc.", 67.27, 0.92, 1.39, "9/1 12:00am"),
new CompanyData("Citigroup, Inc.", 49.37, 0.02, 0.04, "9/1 12:00am"),
new CompanyData("E.I. du Pont de Nemours and Company", 40.48, 0.51, 1.28, "9/1 12:00am"),
new CompanyData("Exxon Mobil Corp", 68.1, -0.43, -0.64, "9/1 12:00am"),
new CompanyData("General Electric Company", 34.14, -0.08, -0.23, "9/1 12:00am"),
new CompanyData("General Motors Corporation", 30.27, 1.09, 3.74, "9/1 12:00am"),
new CompanyData("Hewlett-Packard Co.", 36.53, -0.03, -0.08, "9/1 12:00am"),
new CompanyData("Honeywell Intl Inc", 38.77, 0.05, 0.13, "9/1 12:00am"),
new CompanyData("Intel Corporation", 19.88, 0.31, 1.58, "9/1 12:00am"),
new CompanyData("International Business Machines", 81.41, 0.44, 0.54, "9/1 12:00am"),
new CompanyData("Johnson & Johnson", 64.72, 0.06, 0.09, "9/1 12:00am"),
new CompanyData("JP Morgan & Chase & Co", 45.73, 0.07, 0.15, "9/1 12:00am"),
new CompanyData("McDonald\"s Corporation", 36.76, 0.86, 2.40, "9/1 12:00am"),
new CompanyData("Merck & Co., Inc.", 40.96, 0.41, 1.01, "9/1 12:00am"),
new CompanyData("Microsoft Corporation", 25.84, 0.14, 0.54, "9/1 12:00am"),
new CompanyData("Pfizer Inc", 27.96, 0.4, 1.45, "9/1 12:00am"),
new CompanyData("The Coca-Cola Company", 45.07, 0.26, 0.58, "9/1 12:00am"),
new CompanyData("The Home Depot, Inc.", 34.64, 0.35, 1.02, "9/1 12:00am"),
new CompanyData("The Procter & Gamble Company", 61.91, 0.01, 0.02, "9/1 12:00am"),
new CompanyData("United Technologies Corporation", 63.26, 0.55, 0.88, "9/1 12:00am"),
new CompanyData("Verizon Communications", 35.57, 0.39, 1.11, "9/1 12:00am"),
new CompanyData("Wal-Mart Stores, Inc.", 45.45, 0.73, 1.63, "9/1 12:00am")
};
}
}
}


The above works fine.

Now, here is the Ext.NET 2 equivalent:

ASPX:



<%@ Page Language="C#" %>
<!DOCTYPE html>
<html>
<head runat="server">
<title>Simple Grid - Ext.NET Examples</title>


<style type="text/css">
.positive { color: green; }
.negative { color: red; }
</style>


<script type="text/javascript">
var template = '<span class="{0}">{1}</span>';


var change = function(value) {
return Ext.String.format(template, (value > 0) ? "positive" : "negative", value);
};


var pctChange = function(value) {
return Ext.String.format(template, (value > 0) ? "positive" : "negative", value + "%");
};
</script>
</head>
<body>
<ext:ResourceManager runat="server" />


<ext:Viewport runat="server" Layout="fit">
<Items>
<ext:GridPanel runat="server" Title="Simple Grid">
<Store>
<ext:Store runat="server" PageSize="10">
<Model>
<ext:Model runat="server">
<Fields>
<ext:ModelField Name="Company" Mapping="Name" />
<ext:ModelField Name="Price" Type="Float" />
<ext:ModelField Name="Change" Type="Float" />
<ext:ModelField Name="PctChange" Mapping="PercentChange" Type="Float" />
<ext:ModelField Name="LastChange" Type="Date" DateFormat="yyyy-MM-ddTHH:mm:ss" />
</Fields>
</ext:Model>
</Model>
<Proxy>
<ext:AjaxProxy Url="FinancialData.ashx">
<Reader>
<ext:JsonReader Root="Data" TotalProperty="TotalRecords" />
</Reader>
</ext:AjaxProxy>
</Proxy>
</ext:Store>
</Store>
<ColumnModel>
<Columns>
<ext:Column runat="server" Text="Company" DataIndex="Company" Flex="1" />
<ext:Column runat="server" Text="Price" DataIndex="Price" Width="50">
<Renderer Format="UsMoney" />
</ext:Column>
<ext:Column runat="server" Text="Change" DataIndex="Change" Width="50">
<Renderer Fn="change" />
</ext:Column>
<ext:Column runat="server" Text="Change" DataIndex="PctChange" Width="50">
<Renderer Fn="pctChange" />
</ext:Column>
<ext:DateColumn runat="server" Text="Last Updated" DataIndex="LastChange" Format="yyyy-MM-dd hh:mmtt" Width="130" />
</Columns>
</ColumnModel>
<Features>
<ext:GridFilters runat="server" ID="GridFilters1" Local="true">
<Filters>
<ext:StringFilter DataIndex="Company" />
<ext:NumericFilter DataIndex="Price" />
<ext:NumericFilter DataIndex="Change" />
<ext:NumericFilter DataIndex="PctChange" />
<ext:DateFilter DataIndex="LastChange" />
</Filters>
</ext:GridFilters>
</Features>
<BottomBar>
<ext:PagingToolbar runat="server" />
</BottomBar>
</ext:GridPanel>
</Items>
</ext:Viewport>
</body>
</html>


ASHX:



public class FinancialData : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "application/json";


int start = Convert.ToInt32(context.Request["start"]);
int limit = Convert.ToInt32(context.Request["limit"]);


Paging<CompanyData> data = GetData(start, limit);


context.Response.Write(JSON.Serialize(data));
}


private Paging<CompanyData> GetData(int start, int limit)
{
var data = CompanyData.Data;


int numberToGet = start + limit > data.Count ? data.Count - start : limit;


return new Paging<CompanyData>(data.GetRange(start, numberToGet), data.Count);
}


public bool IsReusable
{
get { return false; }
}
}


Company data (same as above)

Steps to reproduce difference in behaviour:

For 1.x




Load the 1.x page
Filter on the company column - type in "American"
Notice that there will be 2 results on page 1.
Go to Page 2.
Notice no results (that is fine)
Notice also that pager is still enabled - you can go back and forward (this is good)


For 2.0 do the same, but notice for step 6, the pager is disabled after going to the next page.

I would expect 2.0 to work same as 1.x? Hopefully I've just missed a very simple thing to set or configure somewhere?

Daniil
May 28, 2012, 2:58 PM
Hi,

Thanks for the excellent post.

To turn back the 1.x behavior, please apply the following override. There is the "fix" comment.

Fix

<script type="text/javascript">
Ext.toolbar.Paging.override({
onLoad : function () {
var me = this,
pageData,
currPage,
pageCount,
afterText,
count,
total,
isEmpty;

if (!me.rendered) {
return;
}

pageData = me.getPageData();
currPage = pageData.currentPage;
total = pageData.total;
pageCount = pageData.pageCount;
//count = me.store.getCount();
count = me.store.isFiltered() ? me.store.snapshot.getCount() : me.store.getCount(); // fix
isEmpty = count === 0;
afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount);

if (this.hideRefresh) {
this.child("#refresh").hide();
}

if (total === 0) {
currPage = 1;
me.store.currentPage = 1;
} else if (currPage > pageCount) {
currPage = pageCount;
me.store.currentPage = 1;
}

Ext.suspendLayouts();
me.child('#afterTextItem').setText(afterText);
me.child('#inputItem').setDisabled(isEmpty).setVal ue(currPage);
me.child('#first').setDisabled(currPage === 1 || isEmpty);
me.child('#prev').setDisabled(currPage === 1 || isEmpty);
me.child('#next').setDisabled(currPage === pageCount || isEmpty);
me.child('#last').setDisabled(currPage === pageCount || isEmpty);
me.child('#refresh').enable();
me.updateInfo();
Ext.resumeLayouts(true);

if (me.rendered) {
me.fireEvent('change', me, pageData);
}
}
});
</script>

Personally, I would prefer the 1.x behavior as well, but I am not sure we should apply this fix for 2.x. I will discuss it with other team members.

Also, I'm not sure this fix will work correctly in all cases. Please report if you will face any trouble with that fix.

anup
May 28, 2012, 4:13 PM
Many thanks for that. I will look into it in more detail this evening or tomorrow evening; was hoping to show example of how nicely independent the various features like stores, paging, grid filtering, sorting etc are from each other in the Ext.NET 2 book; not sure I want to mention a fix for this particular combination so may rethink the example and just leave it as both being server side or both being client side which I also think is the more likely scenario...

That being said, our own production code (on Ext.NET 1.x) uses server side ashx paging and local filtering - admittedly this is mostly because our backend guys haven't had time to change the old APIs to support server side filtering which is actually what we'd like to do; normally this stuff is straightforward, but our custom storage (useful for other things) means more effort in this kind of area... But if we were to update to Ext.NET 2 any time soon the fix you provide could come in handy.

Thanks!

Daniil
May 28, 2012, 5:28 PM
We have discussed the problem with Vladimir and he suggested another fix and it really looks better.

The fix is replacing

isEmpty = count === 0;
with

isEmpty = pageCount === 0;

It means that PagingToolbar will disable its navigation if there is no pages. Indeed, it looks more logical comparing with disabling the navigation just if a current page has no items.

Finally, we would consider it a bug and I have reported it to Sencha. Lets wait what they will answer.
http://www.sencha.com/forum/showthread.php?210626

Thanks again for the question.

anup
May 29, 2012, 8:42 AM
Thanks for raising that; I should have realized it was an Ext JS bug not an Ext.NET one... I kept looking in the wrong places I guess. But your bug report linked to one of their examples. That is a good pointer for me; next time if I find these kind of problems I'll also look there, first.

Daniil
May 29, 2012, 12:22 PM
No problem. I think you should feel free to report these bugs to us. ExtJS bugs are our bugs:)

I have just updated the Sencha thread. There is some hitch.

Daniil
Jun 06, 2012, 11:58 AM
ExtJS persists to do not consider it a bug. If the things will stay as it is here, we will consider to fix it in the Ext.NET sources.

anup
Jun 06, 2012, 8:15 PM
Yeah, shame. Not sure how to proceed.

It is a bit unfortunate if you have to override that entire method and duplicate the functionality just to change half a line of code (or less!).

There are 3 combinations of paging and filtering:
- Both remote
- Both local
- Remote paging, local filtering (this is the combination giving us the problem)
(Oh, I guess remote filtering and local paging, though that makes even less sense!)

Of those, I would imagine that remote paging/local filtering is also the least likely use case for most people, to be fair. Although I'm currently stuck with that combination, it is something we want to move away from and do filtering remotely too. If that happens then I don't have a problem. However, I am not sure if and when we can be given business priority to do this over other priorities we have!

Maybe we can still convince Sencha that this behaviour is not right. Maybe we can suggest that they provide a fix if they agree, but if not that maybe they can refactor it a bit so we can inject some kind of code/callback, etc to help us modify the behaviour less dramatically. Admittedly if Ethan insists on his view that it is not a bug then from his point of view I can't see why they would want to refactor.

In that case, it is up to you guys I guess if you want to "fix" it in Ext.NET code instead... I think the drawback with you fixing it is the subtle change in behaviour - so if people are reading up the Ext JS docs or coming from Ext JS background, this could trip people up.

For me I am happy to wait quite a few weeks -- possibly even a few months :( -- before we can migrate our code to Ext.NET 2. I don't know if others will be impacted more sooner though.

Daniil
Jun 07, 2012, 5:39 AM
It is a bit unfortunate if you have to override that entire method and duplicate the functionality just to change half a line of code (or less!).

Actually we have already overrode it. For example, we do not update PagingToolbar if it is not rendered.

if (!me.rendered) {
return;
}

So, it won't be a problem to override this method. Though, you are right we follow the policy to override as less as we can.



Not sure how to proceed.

Maybe we can still convince Sencha that this behaviour is not right. Maybe we can suggest that they provide a fix if they agree, but if not that maybe they can refactor it a bit so we can inject some kind of code/callback, etc to help us modify the behaviour less dramatically. Admittedly if Ethan insists on his view that it is not a bug then from his point of view I can't see why they would want to refactor.

We need an ally with ExtJS premium support subscription:) To be serious, it would be best if someone more from ExtJS would look at the problem with a fresh look.


In that case, it is up to you guys I guess if you want to "fix" it in Ext.NET code instead... I think the drawback with you fixing it is the subtle change in behaviour - so if people are reading up the Ext JS docs or coming from Ext JS background, this could trip people up.

Well, I would not worry about it much. It would be a change to improve the performance (I still believe that). I think a more important thing - ensuring it won't cause any undesired backstage effects.



For me I am happy to wait quite a few weeks -- possibly even a few months :( -- before we can migrate our code to Ext.NET 2. I don't know if others will be impacted more sooner though.

Ok, thanks for the details.

Daniil
Jun 07, 2012, 12:35 PM
Fixed in SVN, revision #4075.

As always thanks for the report and please feel free to update the thread if you will have any notes regarding the fix.

I would mark the thread as closed if you do not mind.

anup
Jun 13, 2012, 12:36 PM
We need an ally with ExtJS premium support subscription:) To be serious, it would be best if someone more from ExtJS would look at the problem with a fresh look.

Agreed on both counts... Unfortunately he never replied to my question of what happened if he ran your example. Anyway, thanks for putting fix into your version. I hope it is not a major hassle for you to keep watching for changes to that method in case they make other changes there...

Yes, you can mark as closed.

Daniil
Jun 13, 2012, 4:53 PM
I hope it is not a major hassle for you to keep watching for changes to that method in case they make other changes there...

No problem, we already monitor it to support Ext.data.PagingStore. This Store class is for local paging with some additional functionality: it's possible to edit the records and do not lost changes after changing the current page; local filtering and sorting through all pages.