PDA

View Full Version : [CLOSED] deferInitialRefresh and infinite javascript loop



bogc
Oct 02, 2014, 12:02 AM
Hi:

This is one bug from hell !

I have been able to reproduce the bug outside my app, and it boils down to two things:
- the deferInitialRefresh property of a gridpanel view being set to true (and it is set to true if the gridpanel is refreshed later if the store it is hooked up didn't get its data when the page is rendered).
- the programmatic selection of a row gets into an infinite loop. This is an unfortunate combination of data and Ids and the fact that in my models I specify unique id fields. Please pay attention at how the detail data ids is set in the ExtnetController class. Basically each master row has two detail records. The even master rows have two detail records with the ids 1 and 2, but the odd master rows have two detail records with the ids 2 and 1. The ids are reversed on purpose to reflect the real app.

The all the code of test app is below.
To reproduce it:
1.Run the page. Enble the debugging tools for whatever browser you use.
2. select row 0
2. go to the details tab
3. go to the browse tab
4. select row 1
5. go to the details tab. You'll notice a lag, then the page is rendered and the data is modified. If you use IE 11 and enable the developer tools you'll see a stack overflow error in the console

To fix this I used the reconfigure method which turns the gridpanel view deferInitialRefresh to false before it renders the grid, while bindStore doesn't do that.

Code:

ExtNetController.cs:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Ext.Net;
using Ext.Net.MVC;
using TestSelectionMemoryPlugin.Models;

namespace TestSelectionMemoryPlugin.Controllers
{
public class ExtNetController : Controller
{
private static List<MasterDTO> _masterData = new List<MasterDTO>(); /*
{
new MasterDTO
{
Id = 1, Name = "Master 1", Details = new List<DetailDTO>
{
new DetailDTO {SurrogateId = 1, CultureCode = "en-CA", Description = "Master 1 English Description"},
new DetailDTO {SurrogateId = 2, CultureCode = "en-ES", Description = "Master 1 Spanish Description"},
}
},
new MasterDTO
{
Id = 2, Name = "Master 2", Details = new List<DetailDTO>
{
new DetailDTO {SurrogateId = 1, CultureCode = "en-CA", Description = "Master 2 English Description"},
new DetailDTO {SurrogateId = 2, CultureCode = "es-ES", Description = "Master 2 Spanish Description"},
}
},
}; */

static ExtNetController()
{
for (int i = 0; i < 100; i++)
{
MasterDTO masterDto = new MasterDTO
{
Id = i, Name = String.Format("Master {0}", i)
};

masterDto.Details = new List<DetailDTO>();
for (int j = 1; j < 3; j++)
{
masterDto.Details.Add(new DetailDTO
{
CultureCode = j == 1 ? "en-CA": "es-ES",
Description = String.Format("Master {0} {1} Description", i, j == 1? "English": "Spanish"),
SurrogateId = i % 2 == 0 ? (j == 1 ? 2: 1) : j
});
}
_masterData.Add(masterDto);
}
}

public ActionResult Index()
{
ExtNetModel model = new ExtNetModel()
{
MasterData = _masterData

};

return this.View(model);
}

public ActionResult SampleAction(string message)
{
X.Msg.Notify(new NotificationConfig
{
Icon = Icon.Accept,
Title = "Working",
Html = message
}).Show();

return this.Direct();
}

public StoreResult GetMasterData()
{
return this.Store(_masterData);
}

public StoreResult GetMasterRecord(int id)
{
return this.Store(_masterData.Where(m => m.Id == id)
.ToList(),
1);
}
}
}

DetailDTO.cs


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

namespace TestSelectionMemoryPlugin.Models
{
public class DetailDTO
{
/// <summary>
/// Manufactured ID
/// </summary>
public int SurrogateId { get; set; }
public String CultureCode { get; set; }
public String Description { get; set; }
public double change { get; set; }
}
}

ExtNetModel.cs:


using System.Collections.Generic;

namespace TestSelectionMemoryPlugin.Models
{
public class ExtNetModel
{
public IList<MasterDTO> MasterData { get; set; }

}
}

MasterDTO:


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

namespace TestSelectionMemoryPlugin.Models
{
public class MasterDTO
{
public int Id { get; set; }
public String Name { get; set; }
public IList<DetailDTO> Details { get; set; }
}
}

Index.cshtml:


@using Ext.Net;
@using Ext.Net.MVC;
@using ScriptMode = Ext.Net.ScriptMode

@model TestSelectionMemoryPlugin.Models.ExtNetModel

@{
Layout = null;
var X = Html.X();
}

<!DOCTYPE html>

<html>
<head>
<title>Ext.NET MVC Sample</title>
<script>
var getAdditionalData = function(data, idx, record, orig)
{
return {
rowBodyColspan: record.fields.getCount(),
rowBody: '<br>Here goes some data</br>'
};
}

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


</script>


</head>
<body>
@Html.X().ResourceManager().ScriptMode(ScriptMode. Development).SourceFormatting(true)

@(X.Model()
.Name("Detail")
.IDProperty("SurrogateId")
.Fields(
X.ModelField()
.Name("SurrogateId")
.Type(ModelFieldType.Int),
X.ModelField()
.Name("CultureCode")
.Type(ModelFieldType.String),
X.ModelField()
.Name("Description")
.Type(ModelFieldType.String)
)
)



@(X.Model()
.Name("Master")
.IDProperty("Id")
.Fields(
X.ModelField()
.Name("Id")
.Type(ModelFieldType.Int),
X.ModelField()
.Name("Name")
.Type(ModelFieldType.String)
)
.Associations(a => a.Add(X.HasManyAssociation()
.Model("Detail")
.Name("details")
.AssociationKey("Details"))
)
)

@(X.Store()
.ID("recordStore")
.IDMode(IDMode.Static)
.ModelName("Master")
.AutoLoad(false)
.Proxy(Html.X().AjaxProxy()
.Url(Url.Content("~/ExtNet/GetMasterRecord"))
.Reader(Html.X().JsonReader().TotalProperty("total").Root("data"))
)
.Parameters(ps =>
ps.Add(new StoreParameter("id", "#{masterGridPanel}.getSelectionModel().selected.ge tAt(0).get('Id')", ParameterMode.Raw))
)
.Listeners(l =>
{
l.Load.Handler = "var rec = store.getAt(0); /*debugger; */ #{recordFormPanel}.getForm().loadRecord(rec); #{detailsGridPanel}.bindStore(rec.details()); /*#{detailsGridPanel}.view.deferInitialRefresh = true; */ #{detailsGridPanel}.getSelectionModel().deselectAl l();#{detailsGridPanel}.getSelectionModel().select (0);";

}
)
)
@(Html.X().Viewport().Layout("fit")
.Items(
Html.X().TabPanel()
.ID("mainTabPanel")
.Listeners(a => a.TabChange.Handler = @" if (#{mainTabPanel}.getActiveTab() == #{detailsPanel}) #{recordStore}.load(); /* alert(App.detailsGridPanel.view.deferInitialRefres h + ' ' + App.detailsGridPanel.deferRowRender ); */")
.Items(
Html.X().GridPanel().Title("Master")
.ID("masterGridPanel")
.Region(Region.North)
.Split(true)
.Height(210)
.Store(Html.X().Store()
.ModelName("Master")
.AutoLoad(true)
.Proxy(Html.X().AjaxProxy()
.Url(Url.Content("~/ExtNet/GetMasterData"))
.Reader(Html.X().JsonReader().TotalProperty("total").Root("data").IDProperty("Id"))
)

)
.ColumnModel(
Html.X().Column().Text("Id").Width(120).DataIndex("Id"),
Html.X().Column().Text("Name").Flex(1).DataIndex("Name")
)
.Listeners(l =>
{
l.ViewReady.Handler = "#{masterGridPanel}.getSelectionModel().select(0);";
l.ViewReady.Delay = 1;
l.ItemDblClick.Handler = "#{mainTabPanel}.setActiveTab(#{detailsPanel});";
})
,
X.Panel().Title("Details").Layout("border")
.ID("detailsPanel")
.Items(
X.FormPanel()
.ID("recordFormPanel")
.Region(Region.North)
.Height(100)
.Split(true)
.Title("Record Form")
.Items(
X.NumberField()
.Name("Id")
.FieldLabel("ID"),
X.TextField()
.Name("Name")
.FieldLabel("Name")
),
X.GridPanel().Title("Details")
.ID("detailsGridPanel")
.Region(Region.Center)
.Store(Html.X().Store()
.ModelName("Detail")
.AutoLoad(false)
)
.SelectionMemory(false)
.SelectionModel(
Html.X().RowSelectionModel()
.Mode(SelectionMode.Single)
)
// .SelectionMemoryEvents(false)
.ColumnModel(
Html.X().Column().Text("CultureCode").Flex(1).DataIndex("CultureCode"),
Html.X().Column().Text("Description").Flex(1).DataIndex("Description").Editor(Html.X().TextField()),
Html.X().Column()
.Text("Change")
.Align(Alignment.Right)
.DataIndex("change")
.RightCommandAlign(false)
.Renderer("change")
.Commands(
Html.X().ImageCommand()
.CommandName("Dollar")
.Icon(Icon.MoneyDollar)
)
)
.Plugins(Html.X().CellEditing())
.Features(
Html.X().RowBody().GetAdditionalData("getAdditionalData")
)
.View(X.GridView().StripeRows(true).LoadMask(true) )

)

)
))

</body>
</html>

Daniil
Oct 02, 2014, 3:37 PM
Hi @bogc,

Thank you for the report. I've reproduced. Investigating.

Daniil
Oct 03, 2014, 11:15 AM
I would try this Load listener for the recordStore.

l.Load.Handler = @"if (records.length > 0) { // the Store might load no data
App.recordFormPanel.getForm().loadRecord(records[0]);
App.detailsGridPanel.getSelectionModel().deselectA ll();
App.detailsGridPanel.bindStore(records[0].details());
App.detailsGridPanel.getView().on('refresh', function() {
this.panel.getSelectionModel().select(0);
});
}";


Also this appears to be not working.

l.ViewReady.Handler = "#{masterGridPanel}.getSelectionModel().select(0);";
l.ViewReady.Delay = 1;

I guess the ViewReady listener's code is executed before the Store loads the data.

I would suggest to use this for the master GridPanel.

.SelectionModel(X.RowSelectionModel().SelectedInde x(0))