PDA

View Full Version : [CLOSED] Master/Detail with GridPanel - JavaScript crashes in the SelectionMemory onSelectChange method in certain circumstances



bogc
Sep 29, 2014, 11:05 PM
Hi:

I came across another issue that worked fine before in Ext.Net 2.1.1 but not in Ext.Net 2.5.2.

So, basically, what happens is that the app crashes in the Store indexOf method, the this.data runtime object doesn't have an indexOf method.


/**
* Get the index of the record within the store.
*
* When store is filtered, records outside of filter will not be found.
*
* @param {Ext.data.Model} record The Ext.data.Model object to find.
* @return {Number} The index of the passed Record. Returns -1 if not found.
*/
indexOf: function(record) {
return this.data.indexOf(record);
},


This is the next function in the calling stack and it belongs to the Ext.grid.plugin.SelectionMemory plugin:

onSelectChange : function (record, isSelected, suppressEvent, commitFn) {
if (suppressEvent) {
if (isSelected) {
this.onMemorySelect(this.selModel, record, this.store.indexOf(record), null);
}
else {
this.onMemoryDeselect(this.selModel, record, this.store.indexOf(record));
}
}
},

This is the stack trace in Chrome:

http://forums.ext.net/attachment.php?attachmentid=15351&stc=1

To reproduce, follow these steps:

1. After the page is rendered (I used Chrome), click on the Detail tab. The page will show the details rows for the master record
2. Click on the Master tab and click on the second row.
3. Navigate back to the Detail tab. The details are not refreshed because of the error I mentioned.

If you remove the IDProperty from the Detail model (in Index.cshtml) , it's going to work without an issue. Also, if I add the .SelectionMemory(false) on the detailGridPanel it works properly as well.

What is the SelectionMemory plugin used for? Why is it enabled by default? Historically, I came across another issue related to this: http://forums.ext.net/showthread.php?23496-CLOSED-Supress-the-select-event-triggered-by-SelectionMemory. Should I turn it off for all the grids in my app?

Here is the code that I used:

Controller:

ExtNetController.cs


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 = "en-ES", Description = "Master 2 Spanish Description"},
}
},
};

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);
}
}
}

Models:

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; }
}
}


MasterDTO.cs:


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; }
}
}


ExtNetModel.cs:


using System.Collections.Generic;

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

}
}


View:

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>


</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}.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();")
.Items(
Html.X().GridPanel().Title("Master")
.ID("masterGridPanel")
.Region(Region.North)
.Split(true)
.Height(210)
.Store(Html.X().Store()
.ModelName("Master")
.Data(Model.MasterData)
.AutoLoad(false)
.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;
})
,
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")
)
.ColumnModel(
Html.X().Column().Text("CultureCode").Flex(1).DataIndex("CultureCode"),
Html.X().Column().Text("Description").Flex(1).DataIndex("Description")
)

)

)
))

</body>
</html>




For your convenience, you can download the VS2013 project from here (https://dl.dropboxusercontent.com/u/35370420/TestSelectionMemoryPlugin.7z)(7-zip format).

Thanks!

Daniil
Sep 30, 2014, 1:23 PM
Hi @bogc,

The exactly change between the versions that caused the problem is a Store's AutoDestroy which is now true by default instead of false.

A SelectionMemory plugin is created with the initial Store of the details GridPanel and that Store is being auto destroyed after executing this line:

#{detailsGridPanel}.bindStore(rec.details());

Unfortunately, it is not documented anywhere, but as far as I can understand the SelectionMemory plugin doesn't support re-binding Stores to the GridPanel. So, if it is supposed to re-bind Stores, please turn SelectionMemory to false for sure.


What is the SelectionMemory plugin used for?

It is for maintaining selection across pages. If you select some rows on one page, then go to another page, then come back to the first, you will see the rows selected still. It is due to the SelectionMemory plugin. Also the SelectionSubmit plugin uses the SelectionMemory plugin to submit the selection of all pages to server.


Why is it enabled by default?

It goes from Ext.NET v1, at least. Maybe, earlier, from v0.8. I don't remember the moment when this behavior has been introduced or, maybe, it didn't work in Ext.NET yet:) I guess the people wanted the selection to be maintained across the pages. So, it was the behavior by default. I agree it might be considered as controversial, but, I guess, we cannot change it at this point to avoid a breaking change.

So, please disable it if needed in your application. You can do it via script:

Ext.grid.Panel.override({
selectionMemory: false
});

Sorry for the inconvenience.