PDA

View Full Version : [CLOSED] Row selection is not cleared upon reloading the data behind a grid in certain circumstances



bogc
Sep 25, 2014, 12:05 AM
Hi:

This is a weird one but it is reproducible. Essentially the row selection in a gridpanel is not removed or cleared upon reloading the data in the grid panel in certain circumstances.

To reproduce this I changed the MVC GridPanel_ArrayGrid/ArrayWithPaging page.

Here are the changes I made:

1. I added a new class Company in the Areas/GridPanel_ArrayGrid/Models folder:

Company.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Ext.Net.MVC.Examples.Areas.GridPanel_ArrayGrid.Mod els
{
public class Company
{
public string company { get; set; }
public double price { get; set; }
public double change { get; set; }
public double pctChange { get; set; }
public DateTime lastChange { get; set; }
}
}

2. I modified the Areas/GridPanel_ArrayGrid/Models/ArrayWithPagingModel.cs to return object[] instead of IEnumerable.


using System;
using System.Collections;

namespace Ext.Net.MVC.Examples.Areas.GridPanel_ArrayGrid.Arr ayWithPaging
{
public class Companies
{
public static object[] GetAllCompanies()
{
DateTime now = DateTime.Now;

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


3. I modified the Areas/GridPanel_ArrayGrid/Controllers/ArrayWithPagingController to add a new function called GetData2:



using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Ext.Net.MVC.Examples.Areas.GridPanel_ArrayGrid.Mod els;

namespace Ext.Net.MVC.Examples.Areas.GridPanel_ArrayGrid.Arr ayWithPaging.Controllers
{
public class ArrayWithPagingController : Controller
{
public ActionResult Index()
{
return View(Companies.GetAllCompanies());
}

public ActionResult GetData()
{
return this.Store(Companies.GetAllCompanies());
}

public ActionResult GetData2(int start, int limit)
{
IList<Company> allCompanies = Companies.GetAllCompanies().Select(o =>{
object[] a = (object[]) o;
return new Company
{
company = a[0].ToString(),
price = Double.Parse(a[1].ToString()),
change = Double.Parse(a[3].ToString()),
pctChange = Double.Parse(a[3].ToString()),
lastChange = DateTime.Now
};
}).ToList();
return this.Store(allCompanies.Skip(start).Take(limit), allCompanies.Count);
}
}
}


4. This is the Index.cshtml page under Areas/GridPanel_ArrayGrid/Views/ArrayWithPaging:



@model System.Collections.IEnumerable
@{
ViewBag.Title = "Simple Array Grid With Local Paging and Remote Reloading - Ext.NET MVC Examples";
Layout = "~/Views/Shared/_BaseLayout.cshtml";
}

@section headtag
{
<script>
var template = '<span style="color:{0};">{1}</span>';

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

var pctChange = function (value) {
return Ext.String.format(template, (value > 0) ? "green" : "red", value + "%");
};

var onComboBoxSelect = function (combo) {
var store = combo.up("gridpanel").getStore();

store.pageSize = parseInt(combo.getValue(), 10);
// store.reload();
store.load();
};

function beforeLoad() {
alert("beforeLoad: " + App.GridPanel1.selModel.getCount());
}

function afterLoad() {
alert("afterLoad: " + App.GridPanel1.selModel.selected.getCount()); //App.GridPanel1.selModel.getCount());
}

function onSelect() {
//alert('OnSelect');
}

</script>
}

@section example
{
<h1>Array Grid with Local Paging and Remote Reloading</h1>

<p>Demonstrates how to create a grid from Array data with Local Paging and Remote Reloading.</p>

<p>Notice <b>Last Updated</b> column is revised with a new server-side DateTime stamp when the GridPanel "Refresh" button is clicked.
<br />This demonstrates that when the GridPanel is refreshed, the Data is requested again from the server via an AJAX request initiated by AjaxProxy, but the Paging and Sorting is done completely client-side in the browser.</p>

@(Html.X().GridPanel()
.Title("Array Grid")
.Width(700)
.ID("GridPanel1")
.Store(
Html.X().Store()
.ID("myStore")
.RemotePaging(true)
.PageSize(10)
//.DataSource(Model)
.AutoLoad(true)
.Model(
Html.X().Model()
.Fields(
new ModelField("company"),
new ModelField("price", ModelFieldType.Float),
new ModelField("change", ModelFieldType.Float),
new ModelField("pctChange", ModelFieldType.Float),
new ModelField("lastChange", ModelFieldType.Date)
)
)
.Proxy(
Html.X().AjaxProxy()
.Url(Url.Action("GetData2"))
.ActionMethods(a => a.Read = HttpMethod.POST)
.Reader(Html.X().JsonReader().IDProperty("company").Root("data").TotalProperty("total"))

)
.Listeners(l =>
{
l.BeforeLoad.Fn = "beforeLoad";
l.Load.Fn = "afterLoad";
})

)
.ColumnModel(
Html.X().RowNumbererColumn(),
Html.X().Column().Text("Company").DataIndex("company").Flex(1),
Html.X().Column().Text("Price").DataIndex("price").Width(75).Renderer(RendererFormat.UsMoney),
Html.X().Column().Text("Change").DataIndex("change").Width(75).Renderer("change"),
Html.X().Column().Text("Change").DataIndex("pctChange").Width(75).Renderer("pctChange"),
Html.X().DateColumn().Text("Last Updated").DataIndex("lastChange").Width(85).Format("H:mm:ss")
)
.SelectionModel(
Html.X().RowSelectionModel().Mode(SelectionMode.Si ngle)
)
.View(Html.X().GridView().StripeRows(true))
.BottomBar(
Html.X().PagingToolbar()

.Items(
Html.X().Label("Page size:"),
Html.X().ToolbarSpacer(10),
Html.X().ComboBox()
.Width(80)
.Items("1", "2", "10", "20", "100")
.SelectedItems("10")
.Listeners(l=>{
l.Select.Fn = "onComboBoxSelect";
})
)
.Plugins(Html.X().ProgressBarPager())
))
}


5. I modified _BaseLayout.cshtml to use the IDMode Static and to pretty print the javascript code that is generated.



@using ScriptMode = Ext.Net.ScriptMode
@{
Layout = null;
HttpContext.Current.Items["ext.net.mvc.example"] = true;
}

<!DOCTYPE html>

<html>
<head>
<title>@ViewBag.Title</title>
<link rel="stylesheet" href="@Url.Content("~/resources/css/examples.css")" />
@RenderSection("headtag", false)
</head>
<body>
@Html.X().ResourceManager(ViewBag.ManagerConfig as MvcResourceManagerConfig).IDMode(IDMode.Static).So urceFormatting(true).ScriptMode(ScriptMode.Develop ment)

@RenderSection("example", true)
</body>
</html>



So to reproduce this issue:
- run the page. Click the ok button whenever the alerts pop up.
- select a row in the grid
- click the refresh button in the paging toolbar and pay attention to the selection count displayed by the alerts. BeforeLoad will show 1 and afterLoad will also show 1.

ok, I am not too sure this is a bug, but I applied the same changes to the Ext.Net 2.1.1 sample and the afterLoad alert shows 0, so something has changed between that version and this version.

Why is this important to me? I have row selection model 'select' listeners that do certain actions. Because the selection is not cleared before the data is refreshed the 'select' event is not fired when the selected row is the same.

Were you aware of this change? Do you have any insight as to what has actually changed here?

thanks

bogc
Sep 25, 2014, 6:26 AM
ok, after more digging, in 4.2.1.883, they introduced this flag:

ext-4.2.1.883\src\view\Table.js (line 190):

/**
* @private
* Flag to disable refreshing SelectionModel on view refresh. Table views render rows with selected CSS class already added if necessary.
*/
refreshSelmodelOnRefresh: false,


Then, in the Ext.view.AbstractView refresh function they have this code:


if (me.hasFirstRefresh) {
// Some subclasses do not need to do this. TableView does not need to do this.
if (me.refreshSelmodelOnRefresh !== false) {
me.selModel.refresh();
} else {
// However, even if that is not needed, pruning if pruneRemoved is true (the default) still needs doing.
me.selModel.pruneIf();
}
}

Not sure I understand what they do here and why the disabled the refreshing of the SelectionModel which basically gets rid of the selection.

Daniil
Sep 25, 2014, 1:15 PM
Hi @bogc,


Not sure I understand what they do here and why the disabled the refreshing of the SelectionModel which basically gets rid of the selection

Even if call

me.selModel.refresh();
it won't clear the selection. Because there is still the selected id (IDProperty) presented in the Store after reload. In other words the selected record persists.

I am pretty sure ExtJS team has done that intentionally. We apologize that it has led to wasting of your time dealing with that scenario.

As a solution, you can remove all the records explicitly before loading or deselect all the records when needed. I would probably prefer the second option - deselecting. It is lighter.

bogc
Sep 25, 2014, 5:46 PM
Daniil, no need to apologize. The code changes come from ExtJs :-)

I wanted to understand what is going on to see if there is a way to patch the app without refactoring the existing code.

I ended up using:


<gridPanel>.selModel.deselectAll(true);


I also wanted to say that in my tests the behavior that I exposed in this thread doesn't always happen. When I started with the sample GridPanel_ArrayGrid/ArrayWithPaging I couldn't reproduce it there. Then I morphed the sample in something that follows the pattern I am using in the real application making the alterations that I documented here.

Thanks

bogc
Oct 02, 2014, 10:56 PM
Hello:

I ended up using the code below it fixes this issue across the board, for all gridpanels. However, I am not 100% sure it doesn't have any undesired side effects. Any suggestions, thoughts?



Ext.view.Table.override(
{
refresh: function()
{
var me = this;
me.getSelectionModel().deselectAll(true);
this.callParent([]);
}
}
);


Thanks

Daniil
Oct 03, 2014, 8:28 AM
Many actions can cause refreshing.

For example (off the top of my head), sorting (for example, by click on some header) causes a refresh. So, the selection goes away on sorting with your code. Is it OK for you?

bogc
Oct 03, 2014, 6:10 PM
Thanks for pointing this out. I did more testing with filters, sorting, paging and it works fine.

Daniil
Oct 04, 2014, 7:38 AM
So, is that OK for you that selection is being cleared on sorting? Or it doesn't clear for you?

I am testing with this.

Example

<%@ Page Language="C#" %>

<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
if (!X.IsAjaxRequest)
{
Store store = this.GridPanel1.GetStore();
store.DataSource = new object[]
{
new object[] { "id1", "test1" },
new object[] { "id2", "test2" },
new object[] { "id3", "test3" }
};
}
}
</script>

<!DOCTYPE html>

<html>
<head runat="server">
<title>Ext.NET v2 Example</title>

<script>
Ext.view.Table.override({
refresh: function () {
this.getSelectionModel().deselectAll(true);
this.callParent(arguments);
}
});
</script>
</head>
<body>
<form runat="server">
<ext:ResourceManager runat="server" />

<ext:GridPanel ID="GridPanel1" runat="server">
<Store>
<ext:Store runat="server">
<Model>
<ext:Model runat="server" IDProperty="id">
<Fields>
<ext:ModelField Name="id" />
<ext:ModelField Name="test" />
</Fields>
</ext:Model>
</Model>
</ext:Store>
</Store>
<ColumnModel runat="server">
<Columns>
<ext:Column runat="server" Text="ID" DataIndex="id" />
<ext:Column runat="server" Text="Test" DataIndex="test" />
</Columns>
</ColumnModel>
</ext:GridPanel>
</form>
</body>
</html>

bogc
Oct 06, 2014, 11:53 PM
Your sample clears the selection upon sorting.

Just to clarify, in my application, clearing the selection works for me because I take care of the selection myself. I keep track of the last selected row id value and when the store is loaded I select the row that was previously selected if it appears in the store, otherwise I select the first row.

If you don't take care of preserving the row selection manually, then it might be better to rely on the grid taking care of that for you, with the shortcoming that it doesn't fire the select event when same row is re-selected (unless you didn't specify an unique id in your model - in which case it might do it).

Daniil
Oct 07, 2014, 5:31 AM
Thank you for clarifying.

It looks the thread might be closed, doesn't it?

bogc
Oct 07, 2014, 7:51 AM
Yes, you can close this.