SDK
support
cases

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.

  1. Create virtual object for entities.
  2. Create service which will prepare data collection.
  3. Create virtual detail.
  4. 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*/
    };
});
  1. You should change entitySchemaName (schema name of your virtual object).
  2. Change parameters of web service calling (service name, method name, timeout and input parameters in data object).
  3. Inside callback of callService function you should change entitySchemaName in ESQ (write schema name of your virtual object).
  4. 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*/
        };
    });

 

Like 0

Like

Share

7 comments

Could you please send us the screen shot of the data that will be displayed once this function will be called ? 

Hi Tatiana Rogova,

          I followed your guide step by step and receive these error and don't know what going on. How can I find what problem with this? Thanks

Hi Tatiana Rogova,

            I don't know why file js of virtual detail wasn't downloaded after I add it to pre-configuration page. Please help.

Toàn Mai

Hi Toan Mai,

 

Could you please provide a bit more detailed information on your issue? Please send us your code so we could check the request precisely. Look forward to hearing from you!  

 

Regards, 

Anastasiia

 

Hi Anastasiia Markina,

       Because my code locates on many package, I will create a sample package to reproduce my problem. Please wait.

Toàn

Anastasiia Markina,

Hi Anastasiia, Had a follow up question on the virtual detail - 



The sort, sort by and filter features are not available by default on a virtual detail. UI notice that the sort & search on a real detail (One with an object in the database) happens on the server side and the detail just loads the fresh data it receives from the server.



Given this, Is there a way to enable/implement the sort & filter functionality for virtual details as well?



One way would be to override those functions on the detail schema and implement a custom sort & search purely on the client side using JS. But this is practically re-inventing the wheel. Is there a better way to implement the sort & filter? Thanks in advance!



 

M Shrikanth,

 

If you want your custom detail to have sorting, searching, and filtering functions, it's better to use the detail inheritance from the base "BaseGridDetailV2" schema instead. 

 

Thus, you will be able to customize it according to your needs, inherit necessary functionality (like filtering and sorting) and remove unnecessary ones. 

 

Regards,

Anastasiia

Show all comments
SDK
support
cases

1) In object you need set ON access by operations and publish object

2) On “Objects permissions” tab in configuration add roles “All portal users” and “All employees” for this object and manage access level for them

3) In Lookups section find lookup “List of objects available for portal users” and add your objects

4) If your object has edit page and you want see lookup value related to this object like a link you need paste this code to your page (change “UsrEntity1” to your object name and “UsrEntity1Page” to object’s edit page):

Enable lookup field link

methods: {
    init: function() {
        this.callParent(arguments);
        Terrasoft.configuration.ModuleStructure.UsrEntity1 = {
            cardSchema: "UsrEntity1Page"
            entitySchemaName: "UsrEntity1",
            pages: [{
                UId: "",
                cardSchema: "UsrEntity1Page"
            }]
        };
        Terrasoft.configuration.EntityStructure.UsrEntity1 = {
            entitySchemaName: "UsrEntity1",
            pages: [{
                UId: "",
                typeColumnName: "",
                cardSchema: "UsrEntity1Page"
            }]
        };
    }
}

5) In order to view EntityStructure and ModuleStructure of your object, execute SQL script below:

DECLARE @TableName NVARCHAR(100) = 'Activity' -- Change to your object name
SELECT
  ss.Name 'entitySchemaName',
  sh.Name 'cardSchema',
  sr.ColumnName 'typeColumnName',
  ed.TypeColumnValue 'UId'
FROM SysModuleEdit ed
  INNER JOIN SysModuleEntity en ON ed.SysModuleEntityId = en.Id
  INNER JOIN SysSchema ss ON en.SysEntitySchemaUId = ss.UId
  INNER JOIN SysSchema sh ON ed.CardSchemaUId = sh.UId
  LEFT OUTER JOIN SysEntitySchemaReference sr ON sr.ColumnUId = en.TypeColumnUId AND sr.SysSchemaId = ss.Id
WHERE ss.ExtendParent = 0 AND ss.Name = @TableName
ORDER BY ed.Position

 

Like 0

Like

Share

0 comments
Show all comments
knowledge base
SDK
Q&A
support

Question

How can I create an arbitrary html widget for Dashboards?

Answer

1. Create a module with the following code:

define("UsrMySimpleWidget", ["ext-base", "terrasoft", "sandbox", "BaseFiltersGenerateModule",
"UsrMySimpleWidgetResources"], function(Ext, Terrasoft, sandbox, BaseFiltersGenerateModule, resources) {
 
           function getViewModel() {
                return Ext.create("Terrasoft.BaseViewModel", {
                     entitySchema: "Activity",
                     methods: {
                           getChart: function(key) {
                                sandbox.publish("GenerateChart", key);
                           },
                           load: function() {
                           }
                     }
                });
           }
 
           function generateMainView(renderTo) {
 
                var resultConfig = Ext.create("Terrasoft.Container", {
                     id: "tableOtchetMetkiParamContainer",
                     selectors: {
                           wrapEl: "#tableOtchetMetkiParamContainer"
                     },
                     renderTo: renderTo
                });
                return resultConfig;
           }
 
           function fillDom() {
                var htmlAdded = "<div>hello world!</div>";
                Ext.get("tableOtchetMetkiParamContainer").setHTML(htmlAdded);
           }
 
           var render = function(renderTo) {
 
                var viewConfig = generateMainView(renderTo);
                var viewModel = getViewModel();
 
                fillDom();
 
                viewConfig.bind(viewModel);
                viewConfig.render(renderTo);
           };
 
           return {
                schema: "Activity",
                methods: {
 
                },
                userCode: function() {
 
                },
                init: function() {
 
                },
                filterChanged: function(filter, eOpts) {
 
                },
                render: render
           };
     }
);

2. Add the widget to the Dashboard panel and select the schema created above.

Like 0

Like

Share

0 comments
Show all comments
SDK
mobile
Q&A
support

Question

How do views work in a mobile application? When importing data in offline mode, views are saved to the database as regular tables. It is necessary to filter the field for which the lookup is a view. The view uses records from two sections (Accounts and a custom Construction Objects section). It turns out that when a new account is added in the desktop version, it shows up in the view and can be filtered by it. When creating a new account in the offline mode of the mobile application in the view, it shows up only after synchronization, which complicates the user's experience. As far as I understand, SQL-Lite has the ability to work with views. Is it possible to implement the functionality of views?

Bpm'online application version 7.9.2 2410. Mobile application version 7.11.7.

Answer

SQLite has the ability to create a view. To do this, you need to run the script to create a view in the configuration module. Create the module itself and add it to the manifest in the CustomSchemas block.

Code example:

var sqls = [“CREATE VIEW IF NOT EXISTS AccountView (Id, Name) AS  SELECT Id, Name FROM Account”];
Terrasoft.Sql.DBExecutor.executeSql({
   isCancelable: false,
   sqls: sqls,
   success: function() {},
   failure: function() {}
});

It will be quite difficult to implement filtering by view in the card, so you will still have to write a custom business rule that will make a request for the view.

By default, we do not work with SQLite views. It makes no sense, because the representation in MSSQL or Oracle may not be the same as the SQLite implementation. A view in a mobile application is a regular table, therefore you need to work with it accordingly.

This means that if you want a value to appear there, you should add it. To do this, you can implement business rules for the “Account” and “Construction Objects” objects, so that when adding or updating a record, a copy of this record will be made in the desired view.

Like 0

Like

Share

0 comments
Show all comments
knowledge base
Q&A
SDK
support

Question

Is there any way to make a page available for editing only?

Answer

The updateButtonsVisibility() method is defined in BasePageV2.

Method 1: Override the updateButtonsVisibility() method so that ShowSaveButton had a false value: 

this.set("ShowSaveButton", false);

In this case, the fields will not be locked, but the "Save" button will not be displayed.

Method 2: Perform verification upon saving. In case of matching the necessary conditions, execute the parent method or display a message that editing is not permitted.

Like 0

Like

Share

0 comments
Show all comments

Question

By default, the mobile application wizard allows you to add only two fields to the section list.

Is it possible to increase the number of displayed fields?

Answer

The list can show not only the displayed columns but also display values generated based on the values of several columns. If complex formatting is used, or if it required to display different values depending on certain conditions, column values can be specified as functions (using the Terrasoft.sdk.GridPage.setPrimaryColumn() and Terrasoft.sdk.GridPage.setSecondaryColumn() methods):

Terrasoft.sdk.GridPage.setPrimaryColumn('Account', {
	columns: ['Name', 'PrimaryContact'],
	convertFunction: function(values) {
		if (!Ext.isEmpty(values.PrimaryContact)) {
			return values.Name + ' (' + values.PrimaryContact + ')';
		} else {
			return values.Name;
		}
	}
});

You can also specify additional columns when selecting the value of the lookup field. This is done similarly to a grid, but only the Terrasoft.sdk.LookupGridPage class is used:

Terrasoft.sdk.LookupGridPage.setSecondaryColumn ("Account", "PrimaryContact");

The alternative option for expanding the capabilities of the grid is to change the template of the grid elements:

Terrasoft.util.writeStyles(
	".div-table {",
		"display:table;",
		"width:100%;",
	"}",
	".div-table-row {",
		"display:table-row;",
		"width:100%;",
		"clear:both;",
	"}",
	".div-table-col {",
		"float:left;",
		"display:table-column;",
		"min-width:50%;",
	"}",
	".div-table-col-button {",
		"float:right;",
		"display:table-column;",
	"}"
);
Ext.define("MyCustomList", {
	override: "Ext.Terrasoft.List",
 
	initializeItemTpl: function() {
		this.callParent(arguments);
		var store = this.getStore();
		var model = store.getModel();
		var modelName = model.getName();
		if (modelName === "Account") {
			var tpl = this.getItemTpl();
			tpl.html =
			"<div class=\"x-list-item-tpl div-table\">" +
				"<div class=\"div-table-row\">" +
					"<div class=\"div-table-col\">{[this.applyPrimaryColumn(values)]}</div>" +
					"<div class=\"div-table-col-button\">{Phone}</div>" +
				"</div>" +
				"<div class=\"div-table-row\">" +
					"<div class=\"div-table-col\">{[this.applySecondaryColumn(values)]}</div>" +
					"<div class=\"div-table-col-button\">{Web}</div>" +
				"</div>" +
			"</div>";
		}
	}
 
});

 

Like 0

Like

Share

0 comments
Show all comments
knowledge base
SDK
Q&A
support

Question

We have a task to lock editing of a Supplier field on the contract edit page (ContractPage) under certain conditions. We have tried "this.get" and "find('Supplier')" and set an "enabled" and "IsEnabled" options, it didn't help. We have also consulted Google - but it didn't work.

If the supplier field is cleared, the payment details are blocked, that is such functionality is available in bpm'online. How can we use it? I mean, we need to have it on the program level, not through connecting, etc., since the locking conditions are quite complicated and can be changed with time or require database queries, etc.

Answer

This function can be implemented as follows:

var supp = this.find('Supplier');
supp.customConfig = {
    enabled: { bindTo: 'methodName''} }

or

var supp = this.find('Supplier');
supp.customConfig = {
    enabled: false
}

The below example of a code demonstrates locking of the "Supplier" field if the "Number" field value is not equal to "2":

var supp = this.find('Supplier');
supp.customConfig = {
    enabled: { bindTo: 'test'}
}
this.methods.test = function(){ return this.get('Number') == '2' };

 

Like 0

Like

Share

0 comments
Show all comments
knowledge base
SDK
Q&A
support

Case

We need to implement the command line search available on portal user pages in the knowledge base not only by the primary field (name), but also by article contents and public tags.

Solution

The search can be developed in the "knowledge base search line" of a portal user page. It is a separate element that looks as a command line, but its logic implementation is contained in the KnowledgeBaseSearchModule schema.

This page can be debugged when logged in as a regular user via regular interface by opening it as a module using the following link:

http://your-website/0/Nui/ViewModule.aspx#PortalMainPageModule/

To implement an advanced filter instead of filtering by article title only, modify the following code by replacing the KnowledgeBaseSearchModule base schema:

/*sessionFilters.CustomFilters = { value: value, displayValue: value, primaryDisplayColumn: true };*/
 
var filters = Ext.create("Terrasoft.FilterGroup");
filters.addItem(Terrasoft.createColumnFilterWithParameter(
    Terrasoft.ComparisonType.CONTAIN, "Notes", value));
filters.addItem(Terrasoft.createColumnFilterWithParameter(
    Terrasoft.ComparisonType.CONTAIN, "Name", value));
filters.addItem(Terrasoft.createColumnFilterWithParameter(
    Terrasoft.ComparisonType.CONTAIN, "Keywords", value));
filters.logicalOperation = Terrasoft.LogicalOperatorType.OR;
 
var serializationInfo = filters.getDefSerializationInfo();
serializationInfo.serializeFilterManagerInfo = true;
 
sessionFilters.CustomFilters = {
    "null": {
        "displayValue": value,
        "filter": filters.serialize(serializationInfo)
    }
};

To record public tags into the "Keywords" column for further search by this column, implement the events of modifying, adding and deleting knowledge base tags by creating them in the KnowledgeBaseInTagV2 replacing object.

var userConnection = (UserConnection)HttpContext.Current.Session["UserConnection"];
Guid entityId = Entity.GetTypedColumnValue<Guid>("EntityId");
var esq = new EntitySchemaQuery(userConnection.EntitySchemaManager, "KnowledgeBaseInTagV2");
var publicTypeId = "D6FB4DE6-0809-41FE-A84F-6D245CBC5F32";
esq.AddColumn("Tag.Name");
var entityFilter = esq.CreateFilterWithParameters(FilterComparisonType.Equal,
    "Entity.Id", entityId);
var typeFilter = esq.CreateFilterWithParameters(FilterComparisonType.Equal,
    "Tag.Type", publicTypeId);
esq.Filters.Add(entityFilter);
esq.Filters.Add(typeFilter);
var entityCollection = esq.GetEntityCollection(userConnection);
string allTags = string.Empty;
foreach (var entity in entityCollection) {
    var tagName = entity.GetTypedColumnValue<string>("Tag_Name");
    allTags += tagName + ", ";
}
using (DBExecutor executor = userConnection.EnsureDBConnection()) {
    Update updateRelationshipQuery = new Update(userConnection, "KnowledgeBase");
    updateRelationshipQuery.Set("Keywords", Column.Parameter(allTags));
    updateRelationshipQuery.Where("Id").IsEqual(Column.Parameter(entityId));
    updateRelationshipQuery.Execute(executor);
}
return true;

The last thing is: after publishing the schemas, populate the "Keywords" column with public tags for already existing knowledge base records via the following script:

UPDATE KnowledgeBase
SET Keywords = ISNULL(res.Tags, '')
FROM (SELECT a.Id, Tags = (SELECT stuff((
                select ', ' + Name from (SELECT t.Name FROM KnowledgeBaseTagV2 t WHERE
                                t.TypeId = 'D6FB4DE6-0809-41FE-A84F-6D245CBC5F32'
                                and t.Id
                                IN (
                                                SELECT e.TagId FROM KnowledgeBaseInTagV2 e WHERE e.EntityId = a.Id
                                )
) tb FOR XML PATH('')), 1, 2, ''))
FROM KnowledgeBase a
GROUP BY a.Id) res
WHERE res.ID = KnowledgeBase.Id

Where "D6FB4DE6-0809-41FE-A84F-6D245CBC5F32" is a "public tag" type identifier.

Necessary conditions

Application version with a portal user page and the KnowledgeBaseSearchModule base schema.

Like 0

Like

Share

0 comments
Show all comments
SDK
support
cases

Case description:

We need to add virtual fields to page like OpportunityPageV2 which will display the value of the field from the Contact object.

Algorithm of realization:

  1. You should create replacing client schema for your page.
  2. You should create localizable strings which will contain captions of the fields.





    Figure 1. Adding of localizable string

     
  3. You should create attributes for your fields like following:

    "GlbSignatureDeeds": {

        "dataValueType": Terrasoft.DataValueType.DATE,

        "type": Terrasoft.ViewModelColumnType.VIRTUAL_COLUMN

    }

    Enumeration dataValueType specifies the type that will contain the virtual field.



    Figure 2. Terrasoft.DataValueType values

  4. You should add code like following to diff for inserting your field into page:

    {
        "operation": "insert",
        "name": "GlbSignatureDeeds",
        "values": {
            "layout": {
                "colSpan": 12,
                "rowSpan": 1,
                "column": 0,
                "row": 11
            },
            "caption": {
                "bindTo": "Resources.Strings.DeedsSigned"
            },
            "bindTo": "GlbSignatureDeeds",
            "enabled": {
                "bindTo": "Contact"
            },
            "contentType": 0
        },
        "parentName": "GeneralInfoTabGridLayoutef9cc299",
        "propertyName": "items",
        "index": 16
    },

     

  5. You should add an attribute for binding object like Contact. In the attributes property of the view model, you need to add the name of the column for which the dependency is set. For this column, declare the property dependencies, which is an array of configuration objects, each of which contains the following properties: columns - an array of column names, from the values of which the value of the current column depends; methodName is the name of the handler method. 

    "Contact": {
        "lookupListConfig": {
            "columns": ["UsrSignatureDeeds", "UsrContractSigned"]
        },
        "dependencies": [
            {
                "columns": ["Contact"],
                "methodName": "onContactChanged"
            }
        ]
    },

     

  6. You should add the logic to fill the virtual field to “methods”. Usually a virtual field is associated with the field of another object or it is calculated.
    onContactChanged: function() {
        if (this.get("Contact")) {
            this.set("GlbSignatureDeeds", this.get("Contact").UsrSignatureDeeds);
        } else {
            this.set("GlbSignatureDeeds", null);
        }
    },

     

  7. You should call this method from onEntityInitialized method.

    onEntityInitialized: function() {
        this.callParent(arguments);
        this.onContactChanged();
    },

     

  8. If you want to save this virtual fields, youd should add code like following into save() and onSaved() methods:

    save: function() {
        this.set("NeedUpdateDateDeeds", this.changedValues && this.get("Contact") &&
            this.changedValues.hasOwnProperty("GlbSignatureDeeds"));
        this.callParent(arguments);
    },
    onSaved: function(response, config) {
        if (this.get("NeedUpdateDateDeeds")) {
            var args = arguments;
            var update = Ext.create("Terrasoft.UpdateQuery", {
                rootSchemaName: "Contact"
            });
            var contactId = this.get("Contact").value;
            var filter = Terrasoft.createColumnFilterWithParameter(this.Terrasoft.ComparisonType.EQUAL,
                "Id", contactId);
            update.filters.addItem(filter);
            if (this.get("NeedUpdateDateDeeds")) {
                update.setParameterValue("UsrSignatureDeeds", this.get("GlbSignatureDeeds"),
                    this.Terrasoft.DataValueType.DATE);
            }
            update.execute(function() {
                this.set("NeedUpdateDateDeeds", false);
                this.superclass.onSaved.call(this, args);
            }, this);
        } else {
            this.callParent(arguments);
        }
    },

     

 

Like 0

Like

Share

1 comments

I believe with this method that the field brought in from a linked entity does not work properly when you make a modification to the lookup field, but then cancel the change - you end up with the previous value before pressing cancel. i.e. in this example, the virtual field GlbSignatureDeeds would not be updated back to its original value when cancelling the changes.

 

I suggest modifying step 5 so instead the attribute is set up in the following way:

"Contact": {
    "lookupListConfig": {
        "columns": ["UsrSignatureDeeds", "UsrContractSigned"]
    },
    "onChange": "onContactChanged"
},

That way onContactChanged gets run when cancelling the changes made in an edit page.

 

As a bonus, that means you can also remove step 7, as the onContactChanged method is called automatically when loading a record for the first time.

Show all comments

Case description:

We need to have opportunity to open edit page by double click on detail record.

Algorithm of realization:

For example, I want to open all details by double click on the appropriate detail record:

  1. Create replacing Client Module for schema "Base schema - Detail with list" (BaseGridDetailV2)
  2. Add next code to methods and diff properties:

    methods: {
            onGridDoubleClick: function() {
            this.editRecord();
        }
    },
    diff: /**SCHEMA_DIFF*/[
        {
            "operation": "merge",
            "name": "DataGrid",
            "values": {
                "openRecord": {
                    "bindTo": "onGridDoubleClick"
                }
            }
        }
    ]/**SCHEMA_DIFF*/

     

     

  3. Save schema -> Clear cache

Like 1

Like

Share

0 comments
Show all comments