Jun 01, 2023, 5:24 PM
Custom state provider and grid with dynamic colums
Hello
This is the continuation of the https://forums.ext.net/showthread.ph...ovider-problem thread
(I think question in the thread was answered more then fully, so I'm starting new one)
So the idea is
- Stored procedure is executed, which return pretty big datatable(and it changes little time to time)
- once loaded, grid columns are rendered accordingly and the state will restore to previous state of the grid
Now my problem is
a) (minor one) Grid take its state from the "empty grid" - ie without columns.
I solved it by skipping statesave event, till the grid columns will not come from server. Of course any nice and elegant solution welcomed
b) my main problem is that reordering of columnns works fine however if I change sorting of columns I fell into infinite loop of loading. I spent some time here too, and I understand that its due to the sorters end update method - which basically load the data again ( sounds logical - when sorters are applied, grid require resorting, and due to the remote sort reload) but not sure why after load state is reapplied again. Any hint welcome here
sample below has both problems - simply click grid refresh button, change column order and refresh the page, state will be lost then ( problem a))
the probem b) is reproducible when you refrehs grid and change soprting of any column, on next refresh of grid , infinite loop of refreshs will be started
The sample is made as simple as possible( so for exaplestate manager is same for all users), data are comming from the array and so...
Page
additionally state handler:
This is the continuation of the https://forums.ext.net/showthread.ph...ovider-problem thread
(I think question in the thread was answered more then fully, so I'm starting new one)
So the idea is
- Stored procedure is executed, which return pretty big datatable(and it changes little time to time)
- once loaded, grid columns are rendered accordingly and the state will restore to previous state of the grid
Now my problem is
a) (minor one) Grid take its state from the "empty grid" - ie without columns.
I solved it by skipping statesave event, till the grid columns will not come from server. Of course any nice and elegant solution welcomed
b) my main problem is that reordering of columnns works fine however if I change sorting of columns I fell into infinite loop of loading. I spent some time here too, and I understand that its due to the sorters end update method - which basically load the data again ( sounds logical - when sorters are applied, grid require resorting, and due to the remote sort reload) but not sure why after load state is reapplied again. Any hint welcome here
sample below has both problems - simply click grid refresh button, change column order and refresh the page, state will be lost then ( problem a))
the probem b) is reproducible when you refrehs grid and change soprting of any column, on next refresh of grid , infinite loop of refreshs will be started
The sample is made as simple as possible( so for exaplestate manager is same for all users), data are comming from the array and so...
Page
<%@ Page Language="C#" %>
<script runat="server">
public class DTO
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
private static DTO[] Data
{
get
{
var ret = new List<DTO>();
for (var i = 0; i < 300; i++)
{
ret.Add(new DTO
{
Id = i,
Name = "Company"+i,
Description = "Description"+i
});
}
return ret.ToArray();
}
}
private object[] GetData(StoreReadDataEventArgs prms)
{
IEnumerable<DTO> ret = Data.AsQueryable();
if (prms.Sort.Length > 0)
{
if (prms.Sort[0].Property == "Name")
{
if (prms.Sort[0].Direction == Ext.Net.SortDirection.ASC)
{
ret = ret.OrderBy(item => item.Name).ToList();
}
else
{
ret = ret.OrderByDescending(item => item.Name).ToList();
}
}
if (prms.Sort[0].Property == "Id")
{
if (prms.Sort[0].Direction == Ext.Net.SortDirection.ASC)
{
ret = ret.OrderBy(item => item.Id).ToList();
}
else
{
ret = ret.OrderByDescending(item => item.Id).ToList();
}
}
if (prms.Sort[0].Property == "Description")
{
if (prms.Sort[0].Direction == Ext.Net.SortDirection.ASC)
{
ret = ret.OrderBy(item => item.Description).ToList();
}
else
{
ret = ret.OrderByDescending(item => item.Description).ToList();
}
}
}
return ret.Skip(prms.Start).Take(prms.Limit).ToArray();
}
private void PrepareColumns(object[] data)
{
ColumnBase extColumn = null;
ColumnBase lastStringColumn = null;
ModelField field = null;
int index = 0;
foreach (var propertyInfo in data[0].GetType().GetProperties())
{
if (propertyInfo.Name == "PK_CustomsExportFeed")
continue;
var dataIndex = propertyInfo.Name;
if (propertyInfo.PropertyType == typeof(DateTime))
{
extColumn = new DateColumn
{
Format = "yyyy-MM-dd"
};
field = new ModelField(dataIndex, ModelFieldType.Date, "yyyy-MM-ddTHH:mm:ss");
}
else
{
extColumn = new Column
{
Flex = 1,
MinWidth = 80
};
field = new ModelField(dataIndex);
}
AddField(field);
extColumn.DataIndex = dataIndex;
extColumn.Text = FixHeader(dataIndex);
extColumn.ItemID = dataIndex;
extColumn.ID = dataIndex;
extColumn.Width = Unit.Pixel((int)Math.Max(90, 6.5 * extColumn.Text.Length));
dgvExportData.ColumnModel.Columns.Insert(index++, extColumn);
}
}
private string FixHeader(string columnName)
{
columnName = columnName.Replace("_", "");
var ret = Regex.Replace(columnName, @"([a-z])([A-Z])", "$1 $2");
return char.ToUpper(ret[0]) + ret.Substring(1);
}
private void AddField(ModelField field)
{
if (X.IsAjaxRequest)
{
storeRequest.AddField(field);
}
else
{
storeRequest.Model[0].Fields.Add(field);
}
}
private bool ColumsCreated
{
get
{
if (ViewState["ColumsCreated"] == null)
return false;
return (bool)ViewState["ColumsCreated"];
}
set
{
ViewState["ColumsCreated"] = value;
}
}
private void storeRequest_OnRefreshData(object sender, StoreReadDataEventArgs e)
{
// main difference to previous samples on forums
var totalCount = Data.Length;
var data = GetData(e);
((PageProxy)storeRequest.Proxy[0]).Total = totalCount;
if (ColumsCreated == false)
{
PrepareColumns(data);
storeRequest.RebuildMeta();
dgvExportData.Reconfigure();
ColumsCreated = true;
}
storeRequest.DataSource = data;
storeRequest.DataBind();
}
</script>
<!DOCTYPE html>
<html>
<head runat="server">
<title>Simple Array Grid With Paging and Remote Reloading - Ext.NET Examples</title>
<script>
Ext.define('App.util.HttpStateProvider', {
extend: 'Ext.state.Provider',
requires: ['Ext.state.Provider', 'Ext.Ajax'],
alias: 'util.HttpProvider',
processStateSave: false,
config: {
userId: null,
url: null,
stateRestoredCallback: null
},
constructor: function (config) {
if (!config.userId) {
throw 'App.util.HttpStateProvider: Missing userId';
}
if (!config.url) {
throw 'App.util.HttpStateProvider: Missing url';
}
this.initConfig(config);
var me = this;
me.callParent(arguments);
},
get: function () {
return this.callParent(arguments);
},
set: function (name, value) {
//if (this.processStateSave == false)
//return;
var me = this;
if (typeof value == 'undefined' || value === null) {
me.clear(name);
return;
}
me.saveStateForKey(name, value);
me.callParent(arguments);
},
initStateFromData: function (result) {
for (var property in result) {
if (result.hasOwnProperty(property)) {
//this.state[property] = this.decodeValue(result[property]);
this.state[property] = this.decodeValue(result[property].Value);
}
}
},
// private
restoreState: function () {
var me = this,
callback = me.getStateRestoredCallback();
Ext.Ajax.request({
url: me.getUrl(),
method: 'GET',
params: {
userId: me.getUserId()
},
success: function (response, options) {
var result = JSON.parse(response.responseText.trim());
//localStorage["alaState"] = result;
me.initStateFromData(data);
if (callback) {
callback();
}
},
failure: function () {
console.log('App.util.HttpStateProvider: restoreState failed', arguments);
if (callback) { callback(); }
}
});
},
// private
clear: function (name) {
var me = this;
me.clearStateForKey(name);
me.callParent(arguments);
},
// private
saveStateForKey: function (key, value) {
var me = this;
Ext.Ajax.request({
url: me.getUrl(),
method: 'POST',
params: {
userId: me.getUserId(),
key: key,
value: me.encodeValue(value)
},
failure: function () {
console.log('App.util.HttpStateProvider: saveStateForKey failed', arguments);
}
});
},
// private
clearStateForKey: function (key) {
var me = this;
Ext.Ajax.request({
url: me.getUrl(),
method: 'DELETE',
params: {
userId: me.getUserId(),
key: key
},
failure: function () {
console.log('App.util.HttpStateProvider: clearStateForKey failed', arguments);
}
});
}
});
var alaStateProvider = new App.util.HttpStateProvider({
userId: 1,
url: 'HttpStateProviderHandler.ashx',
stateRestoredCallback: function () {
}
});
alaStateProvider.initStateFromData(<%= SampleStateHandler.GetSerialized() %>);
Ext.state.Manager.setProvider(alaStateProvider);
</script>
</head>
<body>
<form runat="server">
<ext:ResourceManager runat="server" Namespace="" />
<ext:GridPanel
ID="dgvExportData"
runat="server"
Stateful="True"
StateID="dgvExportData"
Title="Array Grid"
Width="800">
<Store>
<ext:Store ID="storeRequest" runat="server" AutoLoad="False" PageSize="10" RemoteSort="true" RemotePaging="True" OnReadData="storeRequest_OnRefreshData">
<Model>
<ext:Model runat="server" IDProperty="Id">
<%-- these are now supposed to be generated from csharp code on binding <Fields>
<ext:ModelField Name="Id" />
<ext:ModelField Name="Name" />
<ext:ModelField Name="Description" />
</Fields>--%>
</ext:Model>
</Model>
<Proxy>
<ext:PageProxy />
</Proxy>
</ext:Store>
</Store>
<ColumnModel runat="server">
<Columns>
<%--<ext:Column runat="server" Text="Id" DataIndex="Id" Flex="1" />
<ext:Column runat="server" Text="Name" DataIndex="Name" Flex="1" />
<ext:Column runat="server" Text="Description" DataIndex="Description" Flex="1" />--%>
</Columns>
</ColumnModel>
<View>
<ext:GridView runat="server" StripeRows="true" />
</View>
<BottomBar>
<ext:PagingToolbar runat="server">
</ext:PagingToolbar>
</BottomBar>
</ext:GridPanel>
</form>
</body>
</html>
- additionally state handler:
public class HttpStateProviderHandler : SampleStateHandler {
}
using Ext.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.SessionState;
/// <summary>
/// Summary description for SampleStateHandler
/// </summary>
public class SampleStateHandler: IHttpHandler, IReadOnlySessionState
{
public class StateData
{
public string Key { get; set; }
public string Value { get; set; }
}
private static Dictionary<string, StateData> _dctState = new Dictionary<string, StateData>();
public static string GetSerialized()
{
return JSON.Serialize(_dctState.Select(item => new { item.Key, item.Value.Value }).ToDictionary(item => item.Key));
}
public void ProcessRequest(HttpContext context)
{
if (context.Request["clear"] == "1")
{
_dctState.Clear();
return;
}
if (context.Request.HttpMethod == "POST")
{
StateData data = new StateData()
{
Key = context.Request["Key"],
Value = context.Request["Value"]
};
if (_dctState.ContainsKey(data.Key))
{
_dctState[data.Key] = data;
}
else
{
_dctState.Add(data.Key, data);
}
}
else if (context.Request.HttpMethod == "GET")
{
var ret = JSON.Serialize(_dctState.Select(item => new { item.Key, item.Value.Value }).ToDictionary(item => item.Key));
context.Response.Write(ret);
}
else if (context.Request.HttpMethod == "DELETE")
{
var userId = int.Parse(context.Request["userId"]);
var key = context.Request["key"];
_dctState.Remove(key);
var result = new Dictionary<string, string>();
result.Add("success", "true");
JSON.Serialize(result);
}
context.Session["StateValue"] = _dctState;
}
public bool IsReusable
{
get
{
return false;
}
}
}