You can create your own field types to meet customer specific requirements.
For the Products area this is done in with AngularJS and in the Customers and Media areas this is done with Angular. The first four steps are the same for both AngularJS and Angular, but then the procedure differs.
Common steps
- Define field type metadata.
- Implement JSON converter.
- Implement Excel converter.
- Implement edit field type converter.
Then proceed with the steps under AngularJS or Angular depending on which area you are creating the custom field type for.
AngularJS (Products)
- Implement settings panel controller and template.
- Implement edit panel controller and template.
Angular (Customers and Media)
- Create the Settings component.
- Create the Edit component.
- Register the components in the module.
- Build.
Common steps
The field type metadata defines the behavior of the field type. For example if the field is an array. The field type metadata is responsible for creating the actual field type from the field definition. The field definition contains information and settings for the instance of the field, and the field type contains information about operations that can be used for filtering and sorting.
namespace Litium.Studio.Accelerator.FieldFramework
{
using System.Collections.Generic;
using System.Linq;
using Litium.FieldFramework;
public class FilterFieldTypeMetadata : FieldTypeMetadataBase
{
public override string Id => "FilterFields";
public override bool IsArray => true;
public override IFieldType CreateInstance(IFieldDefinition fieldDefinition)
{
var item = new FilterFieldType();
item.Init(fieldDefinition);
return item;
}
public class FilterFieldType : FieldTypeBase
{
private Option _option;
public override object GetValue(ICollection<FieldData> fieldDatas)
{
if (fieldDatas.Count == 1 && fieldDatas.First().BooleanValue.HasValue)
{
return null;
}
return fieldDatas.Where(x => !string.IsNullOrEmpty(x.TextValue)).Select(x => x.TextValue).ToList();
}
public override void Init(IFieldDefinition fieldDefinition)
{
base.Init(fieldDefinition);
_option = fieldDefinition.Option as Option ?? new Option();
}
protected override ICollection<FieldData> PersistFieldDataInternal(object item)
{
var data = item as IEnumerable<string>;
if (data == null)
{
return new[] {new FieldData {BooleanValue = true}};
}
return data.Select(x => new FieldData {TextValue = x}).ToList();
}
}
public class Option
{
public virtual List<string> Items { get; set; } = new List<string>();
}
}
}
The JSON field type converter is used to convert the field between entity and JSON.
using System.Collections.Generic;
using Litium.FieldFramework.Converters;
using Litium.Runtime.DependencyInjection;
using Newtonsoft.Json.Linq;
namespace Litium.Accelerator.FieldFramework
{
[Service(Name = "FilterFields")]
internal class FilterJsonFieldTypeConverter : IJsonFieldTypeConverter
{
public object ConvertFromJsonValue(JsonFieldTypeConverterArgs args, JToken item)
{
var array = item as JArray;
List<string> items = null;
if (array != null)
{
items = array.ToObject<List<string>>();
}
var value = item as JValue;
if (value != null)
{
items = new List<string>(new[] { value.ToObject<string>() });
}
return items;
}
public JToken ConvertToJsonValue(JsonFieldTypeConverterArgs args, object item)
{
var items = item as IList<string> ?? new List<string>();
return new JArray(items);
}
}
}
The Excel field type converter is used to convert the field between entity and Excel columns data.
using System;
using System.Collections.Generic;
using System.Linq;
using Litium.Runtime.DependencyInjection;
using Litium.Web.Administration.FieldFramework;
[Service(Name = "FilterFields")]
internal class FilterExcelFieldTypeConverter : IExcelFieldTypeConverter
{
private const string _delimiter = ",";
public object ConvertFromExcelValue(ExcelFieldTypeConverterArgs args, object item)
{
var options = (FilterFieldTypeMetadata.Option)args.FieldDefinition.Option;
var items = GetOptionValue(item as string, options, args.CultureInfo.Name);
return items;
}
public object ConvertToExcelValue(ExcelFieldTypeConverterArgs args, object item)
{
var options = (FilterFieldTypeMetadata.Option)args.FieldDefinition.Option;
var items = item as List<string> ?? new List<string>();
return string.Join(_delimiter, items);
}
private List<string> GetOptionValue(string value, FilterFieldTypeMetadata.Option option, string cultureName)
{
var newValues = new List<string>();
var importValues = value.Split(new[] {_delimiter}, StringSplitOptions.None).ToList();
foreach (var importValue in importValues)
{
string fieldValue;
if (string.IsNullOrEmpty(cultureName))
{
fieldValue = option.Items.FirstOrDefault(i => i == importValue);
}
else
{
fieldValue = option.Items.FirstOrDefault(i => i == importValue);
}
if (fieldValue != null)
{
newValues.Add(importValue);
}
else
{
//value doesn't exist
newValues = null;
break;
}
}
return newValues;
}
}
To be able to use an instance of the field in back office, the edit field type converter needs to be implemented. The edit field type converter is responsible for providing back office with information about which controller and template to use for the edit or the settings view. The settings controller and template are used when setting up the field definition in the system settings, and the edit controller and template are used when an administrator edits the entity.
using System.Collections.Generic;
using Litium.Runtime.DependencyInjection;
using Litium.Web.Administration.FieldFramework;
using Newtonsoft.Json.Linq;
namespace Litium.Studio.Accelerator.FieldFramework
{
[Service(Name = "FilterFields")]
internal class FilterEditFieldTypeConverter : IEditFieldTypeConverter
{
public object ConvertFromEditValue(EditFieldTypeConverterArgs args, JToken item)
{
var array = item as JArray;
List<string> items = null;
if (array != null)
{
items = array.ToObject<List<string>>();
}
var value = item as JValue;
if (value != null)
{
items = new List<string>(new[] { value.ToObject<string>() });
}
return items;
}
public JToken ConvertToEditValue(EditFieldTypeConverterArgs args, object item)
{
var items = item as IList<string> ?? new List<string>();
return new JArray(items);
}
public object CreateOptionsModel() => new FilterFieldTypeMetadata.Option();
public string EditControllerName { get; } = "accelerator_filterFieldEdit";
public string EditControllerTemplate { get; } = "~/Site/Administration/fieldFramework/FilterFieldEdit.html";
public string SettingsControllerName { get; } = "accelerator_filterFieldSettings";
public string SettingsControllerTemplate { get; } = "~/Site/Administration/fieldFramework/FilterFieldSettings.html";
/// <remark>
/// The extension module should have the module name as prefix to be able to find the correct component on the client side.
/// </remark>
public string EditComponentName => "Accelerator#FieldEditorFilterFields";
/// <remark>
/// The extension module should have the module name as prefix to be able to find the correct component on the client side.
/// </remark>
public string SettingsComponentName => "Accelerator#FieldEditorFilterFieldsSetting";
}
}
The two last properties in the code example above define which edit and settings components will be used in the UI. As mentioned in the remark, the value should have the module name as prefix, followed by the # character and the component name.
Module prefix: In this case Accelerator, which is the name of the extension module. The extension module is the module developed in the solution that should be injected into the back office UI. You can see how the module is defined in the file extension.ts in Litium.Accelerator.FieldTypes\src\Accelerator. The module name must be the same in the path "\src\Accelerator", in the file extension.ts and in the attributes EditorComponentName/SettingsComponentName. If you want to change the module name to something else, make sure you change it in all places.
Component name: In this case FieldEditorFilterFields and FieldEditorFilterFieldsSetting, which were declared as components in the Angular project and registered in the file extension.ts for the module. Check the files in Litium.Accelerator.FieldTypes\src\Accelerator\Components. You have to declare the components in the Angular module to be able to use them. If you create a new component, make sure you add it in the declarations arrays of the module.
To extend and load extra script files and/or add extra modules to the application, Litium.Web.Administration.IAppExtension needs to be implemented.
using System.Collections.Generic;
using Litium.Web.Administration;
namespace Litium.Studio.Accelerator.FieldFramework
{
internal class FilterAdministrationExtension : IAppExtension
{
public IEnumerable<string> ScriptFiles { get; } = new[]
{
"~/Site/Administration/fieldFramework/accelerator_filterFieldEdit.js",
"~/Site/Administration/fieldFramework/accelerator_filterFieldSettings.js"
};
public IEnumerable<string> AngularModules { get; } = null;
public IEnumerable<string> StylesheetFiles { get; } = null;
}
}
AngularJS
The scope parameter “model” is added to the controller and contains information about the options for the field definition based on the field type.
app.controller("accelerator_filterFieldSettings",
[
"$scope", "$q", "languagesService", "pimFieldsService", "$translate", "$filter",
function($scope, $q, languagesService, pimFieldsService, $translate, $filter) {
"use strict";
// scope methods
$scope.add = function(fieldDefinition) {
if (!Array.isArray($scope.model.items))
$scope.model.items = [];
if (fieldDefinition === undefined || fieldDefinition == null) return;
if ($scope.model.items.indexOf(fieldDefinition.id) === -1) {
$scope.model.items.push(fieldDefinition.id);
}
};
$scope.remove = function(index) {
$scope.model.items.splice(index, 1);
};
// scope fields
if ($scope.model.items === undefined)
$scope.model.items = [];
$scope.modelServer = {}
angular.copy($scope.model, $scope.modelServer);
$scope.getFieldName = function(fieldId) {
var item = $filter("filter")($scope.fields, { id: fieldId }, true);
if (item && item.length) {
return item[0].title;
} else {
$scope.model.items.splice($scope.model.items.indexOf(fieldId), 1);
}
}
// scope init
var translations = {};
$translate([
"pim.template.fieldgroup.systemdefined",
"pim.template.fieldgroup.userdefined",
"accelerator.filterfield.filternews",
"accelerator.filterfield.filterprice",
"accelerator.filterfield.predefined"
])
.then(function(data) {
angular.extend(translations, data);
$q.all([
languagesService.getCultures()
.then(function(data) {
$scope.cultures = data;
}),
pimFieldsService.query(true)
.then(function(response) {
$scope.fields = response.data;
angular.forEach($scope.fields,
function(x) {
x.groupName = x.systemDefined
? translations["pim.template.fieldgroup.systemdefined"]
: translations["pim.template.fieldgroup.userdefined"];
});
$scope.fields.push({
"id": "#Price",
"title": translations["accelerator.filterfield.filterprice"],
"systemDefined": false,
"groupName": translations["accelerator.filterfield.predefined"]
});
$scope.fields.push({
"id": "#News",
"title": translations["accelerator.filterfield.filternews"],
"systemDefined": false,
"groupName": translations["accelerator.filterfield.predefined"]
});
})
])
.then(function() {
$scope.loaded = true;
});
});
}
]);
<div class="form__group" ng-form="optionsForm" ng-if="loaded">
<div class="form__group__header" translate>accelerator.filterfield.options</div>
<div class="form__group__item" form-field model="model.items" model-server="modelServer.items" hide-edit="true" field="formFieldDictionary['items']">
<p class="base__align__right">
<select ng-model="$scope.fieldDefinitionSelections" ng-options="o.title group by o.groupName for o in (fields | orderBy:['-systemDefined', 'title'])"></select>
<a href="#" ng-click="add($scope.fieldDefinitionSelections)" translate>general.add</a>
</p>
<table class="form__table">
<thead>
<tr>
<td colspan="2" translate>accelerator.filterfield.field</td>
</tr>
</thead>
<tbody ui-sortable="{connectWith: '.fields-container', axis: 'y', forceHelperSize : true}" ng-model="model.items" class="fields-container">
<tr ng-repeat="field in model.items">
<td>{{getFieldName(field)}}</td>
<td class="base__align__right"><i ng-click="remove($index)" class="fa fa-2x fa-close base__cursor__pointer"></i></td>
</tr>
<tr ng-if="group.fields.length == 0">
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
</div>
<div ng-debug="model"></div>
<div ng-debug="fields"></div>
</div>
The scope parameter model is added to the controller and contains information about the field edit view.
The model contains:
- formField: the current form field that also contains the options that are defined in the field definition.
- fields: the collection of fields in the entity.
- culture: the culture editor selected to use.
app.controller("accelerator_filterFieldEdit", [
"$scope", "$q", "languagesService", "$filter", "pimFieldsService","$translate",
function ($scope, $q, languagesService, $filter, pimFieldsService, $translate) {
"use strict";
$scope.filter = {
open: false,
search: ""
};
$scope.toggleFilter = function (state) {
$scope.filter.open = state;
$scope.filter.search = state ? "" : $scope.filter.search;
};
$scope.select = function (value, culture) {
if (!culture) {
culture = "*";
}
if ($scope.model.fields[$scope.model.formField.id] == null) {
$scope.model.fields[$scope.model.formField.id] = {};
$scope.model.fields[$scope.model.formField.id][culture] = {};
}
$scope.model.fields[$scope.model.formField.id][culture] = [value];
}
$scope.toggleSelected = function (option, culture) {
culture = culture || '*';
if (!($scope.model.formField.id in $scope.model.fields) || !$scope.model.fields[$scope.model.formField.id]) {
$scope.model.fields[$scope.model.formField.id] = {};
}
if (!(culture in $scope.model.fields[$scope.model.formField.id])) {
$scope.model.fields[$scope.model.formField.id][culture] = [];
}
var selectedIndex = $scope.model.fields[$scope.model.formField.id][culture].indexOf(option);
if (selectedIndex < 0) {
$scope.model.fields[$scope.model.formField.id][culture].push(option);
}
else {
$scope.model.fields[$scope.model.formField.id][culture].splice(selectedIndex, 1);
}
}
$scope.getOptionName = function (option, culture) {
if (angular.isArray(option)) {
var optionList = [];
option.forEach(function (opt) {
optionList.push($scope.getOptionName(opt, culture));
});
return optionList;
} else if (option) {
var item = $filter("filter")($scope.fields, { id: option }, true);
if (item && item.length) {
return item[0].title;
}
return option;
} else {
return null;
}
}
$scope.setEditLanguage = function (language) {
$scope.editLanguage = language;
}
$scope.setViewLanguage = function (language) {
$scope.viewLanguage = language;
}
$scope.add = function () {
$scope.formData.items[$scope.formData.items.length] = {};
}
$scope.remove = function (index) {
$scope.formData.items.splice(index, 1);
};
var isInherited = function () {
var field = $scope.model.fields[$scope.model.formField.id];
var languageSupport = $scope.model.formField.languageSupport;
return field == undefined || (languageSupport && field[$scope.editLanguage.id] == undefined) || (!languageSupport && field["*"] == undefined);
}
$scope.inheritChanged = function(v) {
console.log("inheritChanged", v);
if (v === true) {
if ($scope.model.formField.languageSupport) {
$scope.model.fields[$scope.model.formField.id][$scope.editLanguage.id] = undefined;
} else {
$scope.model.fields[$scope.model.formField.id]["*"] = undefined;
}
} else {
$scope.model.fields[$scope.model.formField.id] = {}
if ($scope.model.formField.languageSupport) {
$scope.model.fields[$scope.model.formField.id][$scope.editLanguage.id] = [];
} else {
$scope.model.fields[$scope.model.formField.id]["*"] = [];
}
}
}
$scope.dummy = { inherited: false };
$scope.$watch(function() { return isInherited(); },
function (v) {
console.log("isInheritedIsChanged", v, $scope.dummy.inherited);
$scope.dummy.inherited = v;
console.log("isInheritedIsChanged", v, $scope.dummy.inherited);
});
// scope init
$scope.$watch("model.formField.editable", function (v) {
if (v === true) {
if ($scope.model.culture) {
var parentLanguage = $filter("filter")($scope.languageList, { id: $scope.model.culture }, true);
if (parentLanguage && parentLanguage.length > 0) {
$scope.editLanguage = parentLanguage[0];
var notParentLanguage = $filter("filter")($scope.languageList, { id: !$scope.model.culture }, true);
if (notParentLanguage && notParentLanguage.length > 0) {
$scope.viewLanguage = notParentLanguage[0];
}
}
}
}
});
languagesService.getCultures().then(function (data) {
$scope.languageList = data.map(function (item) {
return { id: item.id, value: item.title };
});
if ($scope.model.culture) {
var parentLanguage = $filter("filter")($scope.languageList, { id: $scope.model.culture }, true);
if (parentLanguage && parentLanguage.length > 0) {
$scope.editLanguage = parentLanguage[0];
var notParentLanguage = $filter("filter")($scope.languageList, { id: !$scope.model.culture }, true);
if (notParentLanguage && notParentLanguage.length > 0) {
$scope.viewLanguage = notParentLanguage[0];
}
}
}
if (!$scope.editLanguage || $scope.languageList.indexOf($scope.editLanguage) === -1) {
$scope.editLanguage = $scope.languageList[0];
}
if (!$scope.viewLanguage) {
$scope.viewLanguage = $scope.languageList[$scope.languageList.length > 1 ? 1 : 0];
}
});
var translations = {};
$translate([
"pim.template.fieldgroup.systemdefined", "pim.template.fieldgroup.userdefined",
"accelerator.filterfield.filternews", "accelerator.filterfield.filterprice",
"accelerator.filterfield.predefined"
])
.then(function(data) {
angular.extend(translations, data);
pimFieldsService.query(true)
.then(function(response) {
$scope.fields = response.data;
$scope.fields.push({
"id": "#Price",
"title": translations["accelerator.filterfield.filterprice"],
"systemDefined": false,
"groupName": translations["accelerator.filterfield.predefined"]
});
$scope.fields.push({
"id": "#News",
"title": translations["accelerator.filterfield.filternews"],
"systemDefined": false,
"groupName": translations["accelerator.filterfield.predefined"]
});
});
});
}
]);
<div class="form__group__item__container">
<div ng-if="!model.formField.editable">
<div ng-if="!model.formField.languageSupport">
<span ng-if="model.fields[model.formField.id]['*'] != undefined">{{ getOptionName(model.fields[model.formField.id]['*'], model.culture).join(', ') }}</span>
<span ng-if="model.fields[model.formField.id]['*'] == undefined" translate>accelerator.filterfield.inherited</span>
</div>
<div ng-if="model.formField.languageSupport">
<span ng-if="model.fields[model.formField.id][model.culture] != undefined">{{ getOptionName(model.fields[model.formField.id][model.culture], model.culture).join(', ') }}</span>
<span ng-if="model.fields[model.formField.id][model.culture] == undefined" translate>accelerator.filterfield.inherited</span>
</div>
</div>
<div ng-if="model.formField.editable">
<div ng-class="{'row': model.formField.languageSupport, 'row__col__xs__6': model.formField.languageSupport}">
<div ng-show="model.formField.languageSupport">
<span><translate>general.edit</translate>: </span>
<select name="{{model.formField.id}}_culture" ng-model="editLanguage" ng-change="setEditLanguage(editLanguage)" ng-options="culture.value for culture in languageList track by culture.id"></select>
</div>
<div>
<div>
<input class="form__checkbox" type="checkbox" ng-click="inheritChanged(dummy.inherited)" ng-model="dummy.inherited" id="{{model.formField.id}}_inherited"/> <label for="{{model.formField.id}}_inherited" translate> accelerator.filterfield.inherited</label>
</div>
<div ng-if="!dummy.inherited" class="pim__filter">
<div class="filter" ng-cloak ng-mouseleave="toggleFilter(false)">
<div class="filter__button" ng-click="toggleFilter(!filter.open)">
<span translate>general.select</span>
<i class="fa filter__button__icon" ng-class="{'fa-chevron-down': !filter.open, 'fa-chevron-up': filter.open}"></i>
</div>
<div class="filter__dropdown" ng-show="filter.open">
<div class="filter__dropdown__search">
<input placeholder="Search" ng-model="filter.search"><i class="fa fa-search filter__button__icon"></i>
</div>
<ul class="filter__dropdown__list">
<li ng-repeat="item in model.formField.options.items | filter:filter.search" ng-class="{ 'highlight': $index == focusIndex }">
<div ng-if="!model.formField.languageSupport">
<input type="checkbox" name="{{model.formField.id}}" class="form__checkbox" id="option_{{model.formField.id + '_' + $index}}" ng-checked="model.fields[model.formField.id]['*'].indexOf(item) > -1" ng-click="toggleSelected(item)"/> <label for="option_{{model.formField.id + '_' + $index}}">{{ getOptionName(item, model.culture) }}</label>
</div>
<div ng-if="model.formField.languageSupport">
<input type="checkbox" name="{{model.formField.id}}" class="form__checkbox" id="option_{{model.formField.id + '_' + $index}}" ng-checked="model.fields[model.formField.id][editLanguage.id].indexOf(item) > -1" ng-click="toggleSelected(item, editLanguage.id)"/> <label for="option_{{model.formField.id + '_' + $index}}">{{ getOptionName(item, editLanguage.id) }}</label>
</div>
</li>
</ul>
<div class="filter__add__button__container">
<button ng-click="filter.open = false">OK</button>
</div>
</div>
</div>
<br ng-if="model.formField.languageSupport"/>
<div class="filter__added">
<ul>
<li class="base__cursor" ng-if="!model.formField.languageSupport" ng-repeat="item in model.fields[model.formField.id]['*']">
<span>{{ getOptionName(item, model.culture) }}</span>
<i ng-click="toggleSelected(item)" class="fa fa-close base__cursor__pointer"></i>
</li>
<li class="base__cursor" ng-if="model.formField.languageSupport" ng-repeat="item in model.fields[model.formField.id][editLanguage.id]">
<span>{{ getOptionName(item, editLanguage.id) }}</span>
<i ng-click="toggleSelected(item,[editLanguage.id])" class="fa fa-close base__cursor__pointer"></i>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="row__col__xs__6" ng-if="model.formField.languageSupport">
<span><translate>general.view</translate>: </span>
<select ng-model="viewLanguage" ng-change="setViewLanguage(viewLanguage)" ng-options="culture.value for culture in languageList"></select>
<br/>
<span ng-if="model.fields[model.formField.id][viewLanguage.id] == undefined" translate>accelerator.filterfield.inherited</span>
<span ng-if="model.fields[model.formField.id][viewLanguage.id] != undefined">{{ getOptionName(model.fields[model.formField.id][viewLanguage.id], viewLanguage.id).join(', ') }}</span>
</div>
</div>
</div>
Angular
Since we configured the SettingsComponentName as “Accelerator#FieldEditorFilterFieldsSettings” in the FilterEditFieldTypeConverter, we need to create that component in Angular.
In the Accelerator, the Settings component is located in the folder Litium.Accelerator.FieldTypes\src\Accelerator\components\field-editor-filter-fields-setting. Notice the “field-editor” prefix. All components used for edit fields have this prefix to distinguish between them and normal components. Components for edit fields should have the option to change between preview and edit mode. It should also support multiple languages.
A component in Angular consists of two parts, the template and the code. Usually they are placed in two different files, one .html and one .ts.
Template file
You can read more about the template syntax in Angular here.
It is now much easier than before to create a field edit component, and by using the built-in component in Litium, it will be easier to maintain. This is what the code looks like:
<field-editor [field]="field">
<div preview>
<tags *ngIf="viewItems?.length" [value]="viewFields" [titleSelector]="titlePreviewSelector" [readonly]="true"></tags>
</div>
<div edit>
<field-selector #control [areaName]="areaName" (onFieldSelect)="onFieldSelect($event.option, $event.checked)" [value]="fields" [visibleIds]="visibleIds"></field-selector>
<a class="base__fake__link" (click)="removeAll()">{{ 'setting.fieldSelector.removeAll' | translate }}</a>
<a *ngIf="sortable" class="base__fake__link" (click)="displaySortDialog()">{{ 'setting.fieldSelector.sort' | translate }}</a>
<div *ngIf="items?.length">
<tags [value]="fields" [titleSelector]="titleEditSelector" (onTagRemove)="remove($event)"></tags>
</div>
</div>
</field-editor>
"Field-editor" is the component that should be used when you want to create a field edit component. By using field-editor, and setting its field property like in the code above, you don't have to manage the language change or the preview/edit mode.
Inside the <field-editor> tag, you need to define what should be displayed in preview and edit mode. Note the preview and edit attributes in the two <div> tags. Those are the slot names which are used to project custom content to the pre-defined component field-editor. <div preview> defines what will be displayed in preview mode, and <div edit> defines what will be displayed in edit mode.
Two built-in components are used in this component, tags and field-selector. The tags component displays its value as a different tag item, with the possibility of removing existing items by clicking the X icon. With the FieldSelector component you can display a multi-select option, which enables the user to select several fields.
Code file
The Setting component is inherited from the built-in FieldEditorFieldSelector, and it only overrides the functions to get and set the underline data. Note that common class components can be imported from the "litium-ui" package. The template is configured in the component annotation, by setting the templateUrl.
import { Component, ChangeDetectorRef, ChangeDetectionStrategy, Input, OnInit, ViewChild } from '@angular/core';
import { NgRedux } from '@angular-redux/store';
import { TranslateService } from '@ngx-translate/core';
import { IAppState, FieldEditorFieldSelector, FormFieldActions } from 'litium-ui';
@Component({
selector: 'field-editor-filter-fields-setting',
templateUrl: '../field-editor-filter-fields-setting/field-editor-filter-fields-setting.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FieldEditorFilterFieldsSetting extends FieldEditorFieldSelector {
constructor(ngRedux: NgRedux<IAppState>,
formFieldActions: FormFieldActions,
changeDetection: ChangeDetectorRef,
translate: TranslateService) {
super(ngRedux, formFieldActions, changeDetection, translate);
}
public get items(): any[] {
const value = this.getValue(this.editLanguage);
if (!value || !value.option) {
return [];
}
return value.option.items;
}
public set items(value: any[]) {
if (this.valueAsDictionary) {
this.value[this.editLanguage].option.items = value;
} else {
this.value.option.items = value;
}
}
}
The Edit component is also inherited from the FieldEditorFieldSelector component, and only overrides those functions it needs to override. It uses the same template as the Settings component, so it is configured in the same templateUrl.
import { Component, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
import { NgRedux } from '@angular-redux/store';
import { TranslateService } from '@ngx-translate/core';
import { IAppState, FieldEditorFieldSelector, FormFieldActions } from 'litium-ui';
@Component({
selector: 'field-editor-filter-fields',
templateUrl: '../field-editor-filter-fields-setting/field-editor-filter-fields-setting.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FieldEditorFilterFields extends FieldEditorFieldSelector {
sortable = false;
constructor(ngRedux: NgRedux<IAppState>,
formFieldActions: FormFieldActions,
changeDetection: ChangeDetectorRef,
translate: TranslateService) {
super(ngRedux, formFieldActions, changeDetection, translate);
}
public get viewItems(): any[] {
return this.getValue(this.viewLanguage);
}
public set viewItems(value: any[]) {
if (this.valueAsDictionary) {
this.value[this.viewLanguage] = value;
} else {
this.value = value;
}
}
get visibleIds() {
return this.field.options ? this.field.options.items : null;
}
}
The next step is to register the components in the module. Note that the components have been imported and registered in declarations in the Accelerator module below:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { FieldEditorFilterFields, FieldEditorFilterFieldsSetting } from './components';
import { UiModule } from 'litium-ui';
@NgModule({
declarations: [
FieldEditorFilterFields,
FieldEditorFilterFieldsSetting,
],
imports: [
CommonModule,
UiModule,
TranslateModule,
]
})
export class Accelerator {
}
If you have modified existing components, or created new ones, you have to build them.
In the Accelerator, there should be a file named "litium-ui.tgz" in a folder named "src\Web\Obj". If the file is not there, please build the solution. The "litium-ui.tgz" file is the library package of Litium, which contains the base, common classes and components. This helps you build the component for the custom field type. As you can see in the code snippets above, you import and use classes from the "litium-ui" package.
After verifying that "litium-ui.tgz" is placed in the correct folder, open a command prompt and change the base path to "src\Litium.Accelerator.FieldTypes", then execute the following two commands:
- npm install
- npm run build
Finally, build the Accelerator solution.