Often, we want to have detail that will contain records which don't exist in DataBase, for this aim we can create "Virtual Detail".
In our development cases we usually create detail which load data from external services. If you want to create this detail you should complete three main steps.
- Create virtual object for entities.
- Create service which will prepare data collection.
- Create virtual detail.
- Insert virtual detail into page.
Create POCO object for entities.
You should create virtual object that won't store in DB, but we will display this one in our detail.

Figure 1. POCO object
Create service which will prepare data collection.
In this tutorial I will get data from external WCF service by ProxyClient, you can use another way for this purpose.
In general case you should write configuration service which will prepare data for our detail. My one looks like following:
namespace Terrasoft.Configuration.UsrLocationService
{
using System;
using System.Linq;
using System.IO;
using System.Text;
using System.Net;
using System.Web;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using Terrasoft.Common;
using Terrasoft.Core;
using Terrasoft.Core.Factories;
using Terrasoft.Core.DB;
using Terrasoft.Core.Entities;
using ErrorInfo = Terrasoft.Nui.ServiceModel.DataContract.ErrorInfo;
using SelectQueryResponse = Terrasoft.Nui.ServiceModel.DataContract.SelectQueryResponse;
using DataValueType = Terrasoft.Nui.ServiceModel.DataContract.DataValueType;
using EntityCollection = Terrasoft.Nui.ServiceModel.DataContract.EntityCollection;
using Newtonsoft.Json;
using System.Globalization;
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class UsrLocationService
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "GetLocations", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public string GetLocations(LocationRequest request) {
UsrBaseLocationServiceHelper helper = ClassFactory.Get<UsrBaseLocationServiceHelper>();
var client = helper.ProxyClient;
var result = client.GetLocations(
string.IsNullOrEmpty(request.AccountName) ? null : request.AccountName,
string.IsNullOrEmpty(request.AccountNumber) ? null :request.AccountNumber,
string.IsNullOrEmpty(request.Location) ? null : request.Location,
null,
string.IsNullOrEmpty(request.CityName) ? null : request.CityName,
string.IsNullOrEmpty(request.StateName) ? null : request.StateName,
string.IsNullOrEmpty(request.Zip) ? null : request.Zip,
string.IsNullOrEmpty(request.Phone) ? null : request.Phone,
null,
null,
Convert.ToInt32(request.ExcludeDeactivated),
Convert.ToInt32(request.ExcludeUnassigned)
);
var response = new UsrLocationResponse();
response.Message = result.ErrorMessage;
foreach(var entity in result.Locations)
{
response.Locations.Add(
new Dictionary<string, object>() {
{ "Id", Guid.NewGuid() },
{ "AccountID", entity.AccountID.ToString() },
{ "AccountName", entity.AccountName },
{ "Address", entity.Address },
{ "Country", entity.Country },
{ "City", entity.City },
{ "GeneralNbr", entity.GeneralNbr },
{ "EMail", entity.EMail }
}
);
}
var res = new SelectQueryResponse {
RowsAffected = response.Locations.Count,
Success = (response.Message == "CallCenterServices.Success." ? true : false),
Rows = response.Locations,
RowConfig = RowConfig,
ErrorInfo = new ErrorInfo {
Message = response.Message
}
};
return JsonConvert.SerializeObject(res);
}
public Dictionary<string, object> RowConfig = new Dictionary<string, object> {
{"Id", new { dataValueType = DataValueType.Guid }},
{"AccountID", new { dataValueType = DataValueType.Text }},
{"AccountName", new { dataValueType = DataValueType.Text }},
{"Address", new { dataValueType = DataValueType.Text }},
{"Country", new { dataValueType = DataValueType.Text }},
{"City", new { dataValueType = DataValueType.Text }},
{"GeneralNbr", new { dataValueType = DataValueType.Text }},
{"EMail", new { dataValueType = DataValueType.Text }}
};
}
[DataContract]
public class LocationRequest
{
[DataMember]
public string AccountName { get; set; }
[DataMember]
public string AccountNumber { get; set; }
[DataMember]
public string Location { get; set; }
[DataMember]
public string CityName { get; set; }
[DataMember]
public string StateName { get; set; }
[DataMember]
public string Zip { get; set; }
[DataMember]
public string Phone { get; set; }
[DataMember]
public bool ExcludeDeactivated { get; set; }
[DataMember]
public bool ExcludeUnassigned { get; set; }
}
[DataContract]
public class UsrLocationResponse
{
public UsrLocationResponse()
{
Locations = new EntityCollection();
}
[DataMember]
public string Message { get; set; }
[DataMember]
public EntityCollection Locations { get; set; }
}
}
This service should return object which contains EntityCollection.
Inside GetLocations methods after calling proxyсlient you can see how we prepare data:
var response = new UsrLocationResponse();
response.Message = result.ErrorMessage;
foreach(var entity in result.Locations)
{
response.Locations.Add(
new Dictionary<string, object>() {
{ "Id", Guid.NewGuid() },
{ "AccountID", entity.AccountID.ToString() },
{ "AccountName", entity.AccountName },
{ "Address", entity.Address },
{ "Country", entity.Country },
{ "City", entity.City },
{ "GeneralNbr", entity.GeneralNbr },
{ "EMail", entity.EMail }
}
);
}
var res = new SelectQueryResponse {
RowsAffected = response.Locations.Count,
Success = (response.Message == "CallCenterServices.Success." ? true : false),
Rows = response.Locations,
RowConfig = RowConfig,
ErrorInfo = new ErrorInfo {
Message = response.Message
}
};
Important! Columns should be same in virtual object from first step, foreach statment and RowConfig collection.
Create virtual detail.
Code of virtual detail is pretty simple. You should just copy it and change few things:
define("UsrLocationSearchResultDetail", ["UsrLocationSearchResultDetailResources"], function(resources) {
return {
entitySchemaName: "LocationServicePoco",
methods: {
sortColumn: this.Terrasoft.emptyFn,
loadGridData: this.Terrasoft.emptyFn,
init: function() {
this.callParent(arguments);
this.set("IsGridEmpty", true);
this.set("IsGridDataLoaded", true);
this.loadSearchItems();
},
loadSearchItems: function() {
this.set("MaskId", Terrasoft.Mask.show({timeout: 0}));
this.set("IsGridEmpty", true);
this.set("IsGridLoading", true);
this.set("IsGridDataLoaded", false);
var serviceConfig = {
serviceName: "UsrLocationService",
methodName: "GetLocations",
timeout: 120000,
data: {/*Some input params*/}
};
this.callService(serviceConfig, function(responseJson) {
this.set("IsGridLoading", false);
this.set("IsGridDataLoaded", true);
if (!this.Ext.isEmpty(responseJson)) {
var response = this.Ext.decode(responseJson);
if (response.success) {
if (response.rowsAffected > 0) {
this.set("IsGridEmpty", false);
var esq = this.Ext.create("Terrasoft.EntitySchemaQuery", {
rootSchemaName: "LocationServicePoco"
});
esq.parseResponse(response, function(result) {
if (result.success) {
var gridData = this.getGridData();
gridData.clear();
gridData.loadAll(result.collection);
Terrasoft.Mask.hide(this.get("MaskId"));
} else {
this.showInformationDialog(result.errorInfo);
Terrasoft.Mask.hide(this.get("MaskId"));
}
}, this);
}
} else {
this.showInformationDialog(response.errorInfo.message);
Terrasoft.Mask.hide(this.get("MaskId"));
}
}
}, this);
}
},
diff: /**SCHEMA_DIFF*/[
{
"operation": "merge",
"name": "DataGrid",
"values": {
"type": this.Terrasoft.GridType.LISTED,
"listedConfig": {
"name": "DataGridListedConfig",
"items": [
{
"name": "AccountNumberListedGridColumn",
"bindTo": "AccountID",
"caption": "Account number",
"type": Terrasoft.GridCellType.TEXT,
"position": {"column": 1, "colSpan": 2}
},
{
"name": "AccountListedGridColumn",
"bindTo": "AccountName",
"caption": "Account",
"type": Terrasoft.GridCellType.TEXT,
"position": {"column": 3, "colSpan": 3}
},
{
"name": "AddressListedGridColumn",
"bindTo": "Address",
"caption": "Address",
"type": Terrasoft.GridCellType.TEXT,
"position": {"column": 6, "colSpan": 4}
},
{
"name": "CountryListedGridColumn",
"bindTo": "Country",
"caption": "Country",
"type": Terrasoft.GridCellType.TEXT,
"position": {"column": 10, "colSpan": 4}
},
{
"name": "CityListedGridColumn",
"bindTo": "City",
"caption": "City",
"type": Terrasoft.GridCellType.TEXT,
"position": {"column": 14, "colSpan": 4}
},
{
"name": "PhoneListedGridColumn",
"bindTo": "GeneralNbr",
"caption": "Phone",
"type": Terrasoft.GridCellType.TEXT,
"position": {"column": 18, "colSpan": 3}
},
{
"name": "EmailListedGridColumn",
"bindTo": "EMail",
"caption": "Email",
"type": Terrasoft.GridCellType.TEXT,
"position": {"column": 21, "colSpan": 3}
}
]
},
"activeRowActions": [],
"activeRowAction": {"bindTo": "onActiveRowAction"},
"tiledConfig": {
"name": "DataGridTiledConfig",
"grid": {"columns": 24, "rows": 1},
"items": []
}
}
}/*,
{
"operation": "remove",
"name": "ToolsButton"
}*/
]/**SCHEMA_DIFF*/
};
});
- You should change entitySchemaName (schema name of your virtual object).
- Change parameters of web service calling (service name, method name, timeout and input parameters in data object).
- Inside callback of callService function you should change entitySchemaName in ESQ (write schema name of your virtual object).
- Change "listedConfig" inside "DataGrid" element of "diff" section. This column config should be same to config in service from step 2.
Insert virtual detail into page.
Just insert created detail into your page.
define("UsrLocationSearchPage", [],
function() {
return {
entitySchemaName: "BaseEntity",
details: /**SCHEMA_DETAILS*/{
"UsrLocationSearchResultDetail": {
"schemaName": "UsrLocationSearchResultDetail"
}
}/**SCHEMA_DETAILS*/,
diff: /**SCHEMA_DIFF*/[
{
"operation": "insert",
"name": "UsrLocationSearchResultDetail",
"values": {
"itemType": 2,
"markerValue": "added-detail"
},
"parentName": "SearchResultContainer",
"propertyName": "items",
"index": 0
}
]/**SCHEMA_DIFF*/
};
});