Extend data types in StructPIM
StructPIM implements infrastructure to support custom data type implementations. This article discusses how to implement custom data types and the different levels they can be managed on
DataItem vs. SubDataArea vs. DataArea
When working with Product Configurations in StructPIM, you can set up tabs at the root of a configuration, headlines underneath tabs and properties underneath headlines. These 3 levels are expressed as DataArea's (tab), SubDataArea's (headline) and DataItem's (property).
StructPIM supports implementing your own tabs, headlines and properies, by implementing a C# interface and building a AngularJS directives.
Once you have implemented a custom DataArea, SubDataArea or DataItem, it is available in your product configuration setup, so you are able to use your custom implementations on the configurations you want.
To create a custom tab, headline or property, you need to create an App_plugin for Umbraco containing your custom code. To do so, open your Umbraco project in which StructPIM is installed and create a sub folder to the App_Plugins folder in Umbraco. Name this folder something suitable to your custom implementations.
In your newly created folder, you will need the following files
- A .NET class implementing Struct.PIM.DataConfiguration.Entity.ISystemDataFactory
- A Javascript file for your Angular Directive
- An Html file for the HTML your Angular Directive shall render
- A package.manifest file, telling Umbraco to load your Javascript file.
The following chapters will show you examples of how to create custom Tabs, headline sections and properties.
Implement custom DataArea (tab)
To create a custom tab you need to create the files shown below in your App_Plugin folder of your Umbraco project:
Implementing ISystemDataFactory
The code example below shows an implementation of ISystemDataFactory. The implementation must return a list of DataAreas, SubDataAreas and DataItems. In this case, we are only interested in implementing a custom DataArea (tab), so we return an empty list in the two other methods. Furthermore, we implement a method to tell the StructPIM infrastructure that this tab shall be available on products (could also be variants or product hierarhies).
//MyCustomTab.cs
public class MyCustomTabFactory : ISystemDataFactory
{
public ConfigurationType GetConfigurationType()
{
//Return whether this tab is available for
//categories, products or variants
return ConfigurationType.Product;
}
public List<SystemDataArea> GetSystemDataAreas()
{
var publishingScheme = new PublishingScheme
{
PublishingSchemeType = PublishingSchemeType.DirectPublishing,
PublishingSchemeSetup = new PublishingSchemeSetup
{
ViewingRequiresPermission = false,
SavingRequiresPermission = false
}
};
return new List<SystemDataArea>
{
new SystemDataArea(
new Guid("390c0f74-7ebb-4d00-98f6-691099b85460"),
"MyCustomTab",
publishingScheme,
"Custom.MyCustomTab",
false)
};
}
public List<SystemDataItem> GetSystemDataItems()
{
return new List<SystemDataItem>();
}
public List<SystemSubDataArea> GetSystemSubDataAreas()
{
return new List<SystemSubDataArea>();
}
}
The SystemDataArea returned, contains the following:
- A unique Guid which is the same all the time. This is StructPIM's reference to this data area.
- A display name for the custom tab
- A publishing scheme telling which publishing scheme this DataArea uses
- StructPIM supports 3 different publishing schemes (DirectPublishing, DraftPublish and DraftApprovePublish). DraftPublish and DraftApprovePublish allows for users to work with draft, approvoed and published versions of your data. Usually, DirectPublishing is used
- An identifier for the Angular directive to render
- An indication whether this tab must be added to all configurations in StructPIM, or is optional
Creating your angular directive
Below is shown an example of an angular directive which is rendered, when your custom tab is added to a configuration.
Note, that the name of your directive must be [dataAreaType]DataareaRenderer, where dataAreaType is the string (in lower case) you set for your SystemDataArea in your .NET class (custom.mycustomtab) in this example.
//custom.mycustomtabDataareaRenderer.js
angular.module("custom.mycustomtabDataareaRenderer", [])
.directive("custom.mycustomtabDataareaRenderer", [
"$route", function ($route) {
return {
restrict: "E",
scope: {
config: "="
},
templateUrl: "/App_Plugins/CustomPlugin/custom.mycustomtabDataareaRenderer.html",
link: function (scope, element, attrs) {
scope.reload = function () {
console.log("reloading...")
$route.reload();
}
}
}
}
]);
var app = angular.module("umbraco");
app.requires.push("custom.mycustomtabDataareaRenderer");
<!--custom.mycustomtabDataareaRenderer.html-->
<div class="text-center">
<h1>My custom tab here...</h1>
<button class="btn btn-default" ng-click="reload()">Reload page...</button>
</div>
Package.manifest
Finally, all you need is a package.manifest file to tell Umbraco to load your directive. It looks like this:
//Package.manifest
{
javascript: [
"~/App_Plugins/CustomPlugin/custom.mycustomtabDataareaRenderer.js"
],
css: [
]
}
Set up your custom tab in StructPIM
Compile your project and start the Umbraco site with StructPIM. Go to one of your product configurations and choose Add Tab. In the Tab type dropdown in the top of the dialog, you can now select your custom tab and add it to your configuration.
Once you have added your custom tab to a configuration, go to a product, using this configuration and verify that your tab is available and works as intended.
Implement custom SubDataArea (headline)
To create a custom headline section you need to create the files shown below in your App_Plugin folder of your Umbraco project:
Implementing ISystemDataFactory
The code example below shows an implementation of ISystemDataFactory. The implementation must return a list of DataAreas, SubDataAreas and DataItems. In this case, we are only interested in implementing a custom SubDataArea (headline section), so we return an empty list in the two other methods. Furthermore, we implement a method to tell the StructPIM infrastructure that this tab shall be available on products (could also be variants or product hierarhies).
//MyCustomFactory.cs
public class MyCustomFactory : ISystemDataFactory
{
public ConfigurationType GetConfigurationType()
{
//Return whether this tab is available for
//categories, products or variants
return ConfigurationType.Product;
}
public List<SystemSubDataArea> GetSystemSubDataAreas()
{
return new List<SystemSubDataArea>
{
new SystemSubDataArea(
new Guid("6dbf31db-2be7-4eba-b942-dd3ca43d2a36"),
"Custom Headline Section",
"Custom.MyCustom",
false,
CustomService.GetCustomData,
CustomService.UpdateProductCustomData,
CustomService.ApproveProductCustomData,
CustomService.PublishProductCustomData,
CustomService.GetCustomDataItemHash)
};
}
public List<SystemDataArea> GetSystemDataAreas()
{
return new List<SystemDataArea>();
}
public List<SystemDataItem> GetSystemDataItems()
{
return new List<SystemDataItem>();
}
}
The SystemSubDataArea returned, contains the following:
- A unique Guid which is the same all the time. This is StructPIM's reference to this data area.
- A display name for the custom headline section
- An identifier for the Angular directive to render
- An indication whether this headline section must be added to all configurations in StructPIM, or is optional
- A method for getting your data
- A method for updating your data
- A method for approving your data
- A method for publishing your data
- A method for getting a hash of your data
If your sub data area is only used to show some external data in StructPIM, the methods for updating, approving and publishing may just do nothing and the method for getting a hash should just return the same value always.
In our example, these methods are implemented using CustomService.cs, CustomRepository.cs and CustomDataModel.cs. How these are implemented is shown below
//CustomService.cs
public class CustomService
{
private static CustomRepository _repo = new CustomRepository();
internal static object GetCustomData(ref dynamic data, int productId, Status status)
{
data.CustomDataItemData = _repo.GetProductCustomData(productId);
return data;
}
internal static string GetCustomDataItemHash(int productId, Status status)
{
return SharedService.GetMd5Hash(_repo.GetProductCustomData(productId));
}
internal static void UpdateProductCustomData(JObject data, StatusWrite status)
{
var obj = data.ToObject<dynamic>();
var productId = (int)obj.Id;
var model = obj.CustomDataItemData.ToObject<CustomDataModel>();
_repo.SaveCustomData(productId, model);
}
internal static void ApproveProductCustomData(int productId)
{
_repo.ApproveCustomData(productId);
}
internal static void PublishProductCustomData(int productId)
{
_repo.PublishCustomData(productId);
}
}
//CustomRepository.cs
internal CustomDataModel GetProductCustomData(int productId)
{
return new CustomDataModel { MyPropertyValue = "Custom data" };
}
internal void SaveCustomData(int productId, CustomDataModel model)
{
//Save in your repository
}
internal void ApproveCustomData(int productId)
{
//Set status of data to be approved
}
internal void PublishCustomData(int productId)
{
//Set status of data to be published
}
//CustomDataModel.cs
public class CustomDataModel
{
public string MyPropertyValue { get; set; }
}
Creating your angular directive
Below is shown an example of an angular directive which is rendered, when your custom headline section is added to a configuration.
Note, that the name of your directive must be [dataAreaType]SubdataareaRenderer, where dataAreaType is the string (in lower case) you set for your SystemSubDataArea in your .NET class (custom.mycustom) in this example.
//custom.mycustomSubDataareaRenderer.js
angular.module("custom.mycustomSubdataareaRenderer", [])
.directive("custom.mycustomSubdataareaRenderer", [
"$route", function ($route) {
return {
restrict: "E",
scope: {
config: "=",
mode: "=",
ngModel: "="
},
templateUrl: "/App_Plugins/CustomPlugin/custom.mycustomSubDataareaRenderer.html",
link: function (scope, element, attrs) {
scope.generateRandom = function () {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 8; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
scope.ngModel.SystemData.CustomDataItemData.MyPropertyValue = text;
}
}
}
}
]);
var app = angular.module("umbraco");
app.requires.push("custom.mycustomSubdataareaRenderer");
<!-- custom.mycustomSubDataareaRenderer.html -->
<div class="control-group umb-control-group">
<h4>My custom headline section</h4>
<div class="umb-el-wrap">
<label class="control-label">
My custom data
<small class="umb-detail">Edit your custom data here</small>
</label>
<div class="controls controls-row">
<input type="text" ng-model="ngModel.SystemData.CustomDataItemData.MyPropertyValue" />
<button class="btn btn-default" ng-click="generateRandom()">Generate random string</button>
</div>
</div>
</div>
Package.manifest
Finally, all you need is a package.manifest file to tell Umbraco to load your directive. It looks like this:
//package.manifest
{
javascript: [
"~/App_Plugins/CustomPlugin/custom.mycustomSubDataareaRenderer.js"
],
css: [
]
}
Set up your custom headline section in StructPIM
Compile your project and start the Umbraco site with StructPIM. Go to one of your product configurations. Open one of your tabs and click Add Headline. In the Headline type dropdown in the top of the dialog, you can now select your custom tab and add it to your configuration.
Once you have added your custom headline section to a configuration, go to a product, using this configuration and verify that your headline is available and works as intended.
Implement custom DataItem (property)
To create a custom data item you need to create the files shown below in your App_Plugin folder of your Umbraco project:
Implementing ISystemDataFactory
The code example below shows an implementation of ISystemDataFactory. The implementation must return a list of DataAreas, SubDataAreas and DataItems. In this case, we are only interested in implementing a custom DataItem (property), so we return an empty list in the two other methods. Furthermore, we implement a method to tell the StructPIM infrastructure that this tab shall be available on products (could also be variants or product hierarhies).
//MyCustomFactory.cs
public class MyCustomFactory : ISystemDataFactory
{
public ConfigurationType GetConfigurationType()
{
//Return whether this tab is available for
//categories, products or variants
return ConfigurationType.Product;
}
public List<SystemDataItem> GetSystemDataItems()
{
return new List<SystemDataItem>
{
new SystemDataItem(
new Guid("ac6a26ea-8ff9-45de-8d90-0278957b1259"),
"Custom data item",
"Custom.MyCustom",
false,
CustomService.GetCustomData,
CustomService.UpdateProductCustomData,
CustomService.ApproveProductCustomData,
CustomService.PublishProductCustomData,
CustomService.GetCustomDataItemHash
)
};
}
public List<SystemDataArea> GetSystemDataAreas()
{
return new List<SystemDataArea>();
}
public List<SystemSubDataArea> GetSystemSubDataAreas()
{
return new List<SystemSubDataArea>();
}
}
The SystemDataItem returned, contains the following:
- A unique Guid which is the same all the time. This is StructPIM's reference to this data area.
- A display name for the custom data item
- An identifier for the Angular directive to render
- An indication whether this dataitem must be added to all configurations in StructPIM, or is optional
- A method for getting your data
- A method for updating your data
- A method for approving your data
- A method for publishing your data
- A method for getting a hash of your data
If your data item is only used to show some external data in StructPIM, the methods for updating, approving and publishing may just do nothing and the method for getting a hash should just return the same value always.
In our example, these methods are implemented using CustomService.cs, CustomRepository.cs and CustomDataModel.cs. How these are implemented is shown below
//CustomService.cs
public class CustomService
{
private static CustomRepository _repo = new CustomRepository();
internal static object GetCustomData(ref dynamic data, int productId, Status status)
{
data.CustomDataItemData = _repo.GetProductCustomData(productId);
return data;
}
internal static string GetCustomDataItemHash(int productId, Status status)
{
return SharedService.GetMd5Hash(_repo.GetProductCustomData(productId));
}
internal static void UpdateProductCustomData(JObject data, StatusWrite status)
{
var obj = data.ToObject<dynamic>();
var productId = (int)obj.Id;
var model = obj.CustomDataItemData.ToObject<CustomDataModel>();
_repo.SaveCustomData(productId, model);
}
internal static void ApproveProductCustomData(int productId)
{
_repo.ApproveCustomData(productId);
}
internal static void PublishProductCustomData(int productId)
{
_repo.PublishCustomData(productId);
}
}
//CustomRepository.cs
internal CustomDataModel GetProductCustomData(int productId)
{
return new CustomDataModel { MyPropertyValue = "Custom data" };
}
internal void SaveCustomData(int productId, CustomDataModel model)
{
//Save in your repository
}
internal void ApproveCustomData(int productId)
{
//Set status of data to be approved
}
internal void PublishCustomData(int productId)
{
//Set status of data to be published
}
//CustomDataModel.cs
public class CustomDataModel
{
public string MyPropertyValue { get; set; }
}
Creating your angular directive
Below is shown an example of an angular directive which is rendered, when your custom headline section is added to a configuration.
Note, that the name of your directive must be [dataAreaType]DataitemRenderer, where dataAreaType is the string (in lower case) you set for your SystemDataItem in your .NET class (custom.mycustom) in this example.
//custom.mycustomDataitemRenderer.js
angular.module("custom.mycustomDataitemRenderer", [])
.directive("custom.mycustomDataitemRenderer", [
"$log", function ($log) {
return {
restrict: "E",
scope: {
mode: "=",
ngModel: "="
},
templateUrl: "/App_Plugins/CustomPlugin/custom.mycustomdataitemrenderer.html",
link: function (scope, element, attrs) {
}
}
}
]);
var app = angular.module("umbraco");
app.requires.push("custom.mycustomDataitemRenderer");
<!-- custom.mycustomDataitemRenderer.html -->
<div class="umb-el-wrap">
<label class="control-label">
My custom data
<small class="umb-detail">Edit your custom data here</small>
</label>
<div class="controls controls-row">
<input type="text" ng-model="ngModel.SystemData.CustomDataItemData.MyPropertyValue" />
</div>
</div>
Package.manifest
Finally, all you need is a package.manifest file to tell Umbraco to load your directive. It looks like this:
//package.manifest
{
javascript: [
"~/App_Plugins/CustomPlugin/custom.mycustomDataitemRenderer.js"
],
css: [
]
}
Set up your custom data item in StructPIM
Compile your project and start the Umbraco site with StructPIM. Go to one of your product configurations. Open a headline on one of your tabs and click Add Property. In the Headline type dropdown in the top of the dialog, you can now select your custom tab and add it to your configuration.
Once you have added your custom data item to a configuration, go to a product, using this configuration and verify that your data item is available and works as intended.